diff --git a/.vscode/settings.json b/.vscode/settings.json index 8361ab3..19288c5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,8 +2,6 @@ // Luau - disable Roblox features, enable Lune typedefs & requires "luau-lsp.sourcemap.enabled": false, "luau-lsp.types.roblox": false, - "luau-lsp.types.definitionFiles": ["luneTypes.d.luau"], - "luau-lsp.types.documentationFiles": ["luneDocs.json"], "luau-lsp.require.mode": "relativeToFile", // Luau - ignore type defs file in docs dir and dev scripts we use "luau-lsp.ignoreGlobs": [ diff --git a/packages/cli/src/cli.rs b/packages/cli/src/cli.rs index ff4c72e..c1f8189 100644 --- a/packages/cli/src/cli.rs +++ b/packages/cli/src/cli.rs @@ -1,12 +1,18 @@ -use std::process::ExitCode; +use std::{ + borrow::BorrowMut, + collections::HashMap, + path::{Path, PathBuf}, + process::ExitCode, +}; use anyhow::{Context, Result}; use clap::{CommandFactory, Parser}; +use serde_json::Value as JsonValue; use include_dir::{include_dir, Dir}; use lune::Lune; use tokio::{ - fs::read as read_to_vec, + fs::{self, read as read_to_vec}, io::{stdin, AsyncReadExt}, }; @@ -81,6 +87,7 @@ impl Cli { self } + #[allow(clippy::too_many_lines)] pub async fn run(self) -> Result { // List files in `lune` and `.lune` directories, if wanted // This will also exit early and not run anything else @@ -124,7 +131,43 @@ impl Cli { return Ok(ExitCode::FAILURE); } if self.setup { - generate_typedef_files_from_definitions(&TYPEDEFS_DIR).await?; + let generated_paths = + generate_typedef_files_from_definitions(&TYPEDEFS_DIR).await?; + let settings_json_path = PathBuf::from(".vscode/settings.json"); + let message = match fs::metadata(&settings_json_path).await { + Ok(meta) if meta.is_file() => { + if try_add_generated_typedefs_vscode(&settings_json_path, &generated_paths).await.is_err() { + "These files can be added to your LSP settings for autocomplete and documentation." + } else { + "These files have now been added to your workspace LSP settings for Visual Studio Code." + } + } + _ => "These files can be added to your LSP settings for autocomplete and documentation.", + }; + // HACK: We should probably just be serializing this hashmap to print it out, but + // that does not guarantee sorting and the sorted version is much easier to read + let mut sorted_names = generated_paths + .keys() + .map(ToString::to_string) + .collect::>(); + sorted_names.sort_unstable(); + println!( + "Typedefs have been generated in the following locations:\n{{\n{}\n}}\n{message}", + sorted_names + .iter() + .map(|name| { + let path = generated_paths.get(name).unwrap(); + format!( + " \"@lune/{}\": \"{}\",", + name, + path.canonicalize().unwrap().display() + ) + }) + .collect::>() + .join("\n") + .strip_suffix(',') + .unwrap() + ); } } if self.script_path.is_none() { @@ -172,3 +215,29 @@ impl Cli { }) } } + +async fn try_add_generated_typedefs_vscode( + settings_json_path: &Path, + generated_paths: &HashMap, +) -> Result<()> { + // FUTURE: Use a jsonc or json5 to read this file instead since it may contain comments and fail + let settings_json_contents = fs::read(settings_json_path).await?; + let mut settings_changed: bool = false; + let mut settings_json: JsonValue = serde_json::from_slice(&settings_json_contents)?; + if let JsonValue::Object(settings) = settings_json.borrow_mut() { + if let Some(JsonValue::Object(aliases)) = settings.get_mut("luau-lsp.require.fileAliases") { + for (name, path) in generated_paths { + settings_changed = true; + aliases.insert( + format!("@lune/{name}"), + JsonValue::String(path.canonicalize().unwrap().to_string_lossy().to_string()), + ); + } + } + } + if settings_changed { + let settings_json_new = serde_json::to_vec_pretty(&settings_json)?; + fs::write(settings_json_path, settings_json_new).await?; + } + Ok(()) +} diff --git a/packages/cli/src/gen/mod.rs b/packages/cli/src/gen/mod.rs index 64551df..a0acfa0 100644 --- a/packages/cli/src/gen/mod.rs +++ b/packages/cli/src/gen/mod.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::{collections::HashMap, path::PathBuf}; use anyhow::Result; use include_dir::Dir; @@ -15,7 +15,9 @@ pub async fn generate_gitbook_dir_from_definitions(dir: &Dir<'_>) -> Result<()> gitbook_dir::generate_from_type_definitions(definitions).await } -pub async fn generate_typedef_files_from_definitions(dir: &Dir<'_>) -> Result<()> { +pub async fn generate_typedef_files_from_definitions( + dir: &Dir<'_>, +) -> Result> { let definitions = read_typedefs_dir(dir)?; typedef_files::generate_from_type_definitions(definitions).await } diff --git a/packages/cli/src/gen/typedef_files.rs b/packages/cli/src/gen/typedef_files.rs index 03062ca..33453bf 100644 --- a/packages/cli/src/gen/typedef_files.rs +++ b/packages/cli/src/gen/typedef_files.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, fmt::Write}; +use std::{collections::HashMap, fmt::Write, path::PathBuf}; use anyhow::{Context, Result}; use directories::UserDirs; @@ -13,7 +13,7 @@ const GENERATED_COMMENT_TAG: &str = "--!strict"; #[allow(clippy::too_many_lines)] pub async fn generate_from_type_definitions( api_reference: HashMap, -) -> Result<()> { +) -> Result> { let mut dirs_to_write = Vec::new(); let mut files_to_write = Vec::new(); // Create the typedefs dir in the users cache dir @@ -21,7 +21,7 @@ pub async fn generate_from_type_definitions( .context("Failed to find user home directory")? .home_dir() .join(".lune") - .join("typedefs") + .join(".typedefs") .join(env!("CARGO_PKG_VERSION")); dirs_to_write.push(cache_dir.clone()); // Make typedef files @@ -36,8 +36,8 @@ pub async fn generate_from_type_definitions( category_name.to_lowercase(), env!("CARGO_PKG_VERSION") )?; - write_tree(&mut contents, category_name, category_tree)?; - files_to_write.push((path, contents)); + write_tree(&mut contents, category_name.to_string(), category_tree)?; + files_to_write.push((category_name.to_lowercase(), path, contents)); } // Write all dirs and files only when we know generation was successful let futs_dirs = dirs_to_write @@ -45,12 +45,15 @@ pub async fn generate_from_type_definitions( .map(create_dir_all) .collect::>(); let futs_files = files_to_write - .drain(..) - .map(|(path, contents)| write(path, contents)) + .iter() + .map(|(_, path, contents)| write(path, contents)) .collect::>(); try_join_all(futs_dirs).await?; try_join_all(futs_files).await?; - Ok(()) + Ok(files_to_write + .drain(..) + .map(|(name, path, _)| (name, path)) + .collect::>()) } fn make_return_table_item(item: &DefinitionsItem) -> Result {