diff --git a/crates/emmylua_check/src/init.rs b/crates/emmylua_check/src/init.rs index 2260b1676..053f6fe82 100644 --- a/crates/emmylua_check/src/init.rs +++ b/crates/emmylua_check/src/init.rs @@ -3,10 +3,7 @@ use emmylua_code_analysis::{ }; use fern::Dispatch; use log::LevelFilter; -use std::{ - path::{Path, PathBuf}, - str::FromStr, -}; +use std::path::{Path, PathBuf}; fn root_from_configs(config_paths: &[PathBuf], fallback: &Path) -> PathBuf { if config_paths.len() != 1 { @@ -102,12 +99,12 @@ pub fn load_workspace( } for root in &emmyrc.workspace.workspace_roots { - analysis.add_main_workspace(PathBuf::from_str(root).unwrap()); + analysis.add_main_workspace(PathBuf::from(root)); } for lib in &emmyrc.workspace.library { - analysis.add_library_workspace(PathBuf::from_str(lib).unwrap()); - workspace_folders.push(PathBuf::from_str(lib).unwrap()); + analysis.add_library_workspace(PathBuf::from(lib)); + workspace_folders.push(PathBuf::from(lib)); } let file_infos = collect_files(&workspace_folders, &analysis.emmyrc, ignore); diff --git a/crates/emmylua_code_analysis/resources/schema.json b/crates/emmylua_code_analysis/resources/schema.json index 3d5164766..68148e0a8 100644 --- a/crates/emmylua_code_analysis/resources/schema.json +++ b/crates/emmylua_code_analysis/resources/schema.json @@ -151,6 +151,7 @@ "ignoreGlobs": [], "library": [], "moduleMap": [], + "packageDirs": [], "preloadFileSize": 0, "reindexDuration": 5000, "workspaceRoots": [] @@ -1100,6 +1101,14 @@ "$ref": "#/$defs/EmmyrcWorkspaceModuleMap" } }, + "packageDirs": { + "description": "Package directories. Treat the parent directory as a `library`, but only add files from the specified directory.\neg: `/usr/local/share/lua/5.1/module`", + "type": "array", + "default": [], + "items": { + "type": "string" + } + }, "preloadFileSize": { "type": "integer", "format": "int32", diff --git a/crates/emmylua_code_analysis/src/config/configs/workspace.rs b/crates/emmylua_code_analysis/src/config/configs/workspace.rs index 3ef90c6b1..fb9a1f7ae 100644 --- a/crates/emmylua_code_analysis/src/config/configs/workspace.rs +++ b/crates/emmylua_code_analysis/src/config/configs/workspace.rs @@ -14,6 +14,10 @@ pub struct EmmyrcWorkspace { /// Library paths. eg: "/usr/local/share/lua/5.1" pub library: Vec, #[serde(default)] + /// Package directories. Treat the parent directory as a `library`, but only add files from the specified directory. + /// eg: `/usr/local/share/lua/5.1/module` + pub package_dirs: Vec, + #[serde(default)] /// Workspace roots. eg: ["src", "test"] pub workspace_roots: Vec, // unused @@ -45,6 +49,7 @@ impl Default for EmmyrcWorkspace { ignore_dir: Vec::new(), ignore_globs: Vec::new(), library: Vec::new(), + package_dirs: Vec::new(), workspace_roots: Vec::new(), preload_file_size: 0, encoding: encoding_default(), diff --git a/crates/emmylua_code_analysis/src/config/mod.rs b/crates/emmylua_code_analysis/src/config/mod.rs index 092ae1095..be6d2ee2b 100644 --- a/crates/emmylua_code_analysis/src/config/mod.rs +++ b/crates/emmylua_code_analysis/src/config/mod.rs @@ -119,6 +119,9 @@ impl Emmyrc { self.workspace.library = process_and_dedup(self.workspace.library.iter(), workspace_root); + self.workspace.package_dirs = + process_and_dedup(self.workspace.package_dirs.iter(), workspace_root); + self.workspace.ignore_dir = process_and_dedup(self.workspace.ignore_dir.iter(), workspace_root); diff --git a/crates/emmylua_code_analysis/src/db_index/module/mod.rs b/crates/emmylua_code_analysis/src/db_index/module/mod.rs index 140431053..46da2a9ab 100644 --- a/crates/emmylua_code_analysis/src/db_index/module/mod.rs +++ b/crates/emmylua_code_analysis/src/db_index/module/mod.rs @@ -13,7 +13,7 @@ pub use workspace::{Workspace, WorkspaceId}; use super::traits::LuaIndex; use crate::{Emmyrc, FileId}; use std::{ - collections::HashMap, + collections::{HashMap, HashSet}, path::{Path, PathBuf}, sync::Arc, }; @@ -365,6 +365,15 @@ impl LuaModuleIndex { } } + pub fn next_library_workspace_id(&self) -> u32 { + let used: HashSet = self.workspaces.iter().map(|w| w.id.id).collect(); + let mut candidate = 2; + while used.contains(&candidate) { + candidate += 1; + } + candidate + } + #[allow(unused)] pub fn remove_workspace_root(&mut self, root: &Path) { self.workspaces.retain(|r| r.root != root); diff --git a/crates/emmylua_code_analysis/src/lib.rs b/crates/emmylua_code_analysis/src/lib.rs index 6f619960f..34cebf1d4 100644 --- a/crates/emmylua_code_analysis/src/lib.rs +++ b/crates/emmylua_code_analysis/src/lib.rs @@ -48,7 +48,6 @@ pub struct EmmyLuaAnalysis { pub compilation: LuaCompilation, pub diagnostic: LuaDiagnostic, pub emmyrc: Arc, - lib_workspace_counter: u32, } impl EmmyLuaAnalysis { @@ -58,7 +57,6 @@ impl EmmyLuaAnalysis { compilation: LuaCompilation::new(emmyrc.clone()), diagnostic: LuaDiagnostic::new(), emmyrc, - lib_workspace_counter: 2, } } @@ -99,15 +97,11 @@ impl EmmyLuaAnalysis { } pub fn add_library_workspace(&mut self, root: PathBuf) { + let module_index = self.compilation.get_db_mut().get_module_index_mut(); let id = WorkspaceId { - id: self.lib_workspace_counter, + id: module_index.next_library_workspace_id(), }; - self.lib_workspace_counter += 1; - - self.compilation - .get_db_mut() - .get_module_index_mut() - .add_workspace_root(root, id); + module_index.add_workspace_root(root, id); } pub fn update_file_by_uri(&mut self, uri: &Uri, text: Option) -> Option { diff --git a/crates/emmylua_code_analysis/src/semantic/member/find_members.rs b/crates/emmylua_code_analysis/src/semantic/member/find_members.rs index 2c6a9b515..432d47244 100644 --- a/crates/emmylua_code_analysis/src/semantic/member/find_members.rs +++ b/crates/emmylua_code_analysis/src/semantic/member/find_members.rs @@ -407,7 +407,7 @@ fn find_intersection_members( member_set.insert(key.clone()); result.push(LuaMemberInfo { - property_owner_id: None, + property_owner_id: member.property_owner_id.clone(), key: key.clone(), typ: typ.clone(), feature: None, diff --git a/crates/emmylua_doc_cli/src/init.rs b/crates/emmylua_doc_cli/src/init.rs index f3609dfe1..a0e3a42a4 100644 --- a/crates/emmylua_doc_cli/src/init.rs +++ b/crates/emmylua_doc_cli/src/init.rs @@ -5,7 +5,6 @@ use fern::Dispatch; use log::LevelFilter; use std::{ path::{Path, PathBuf}, - str::FromStr, sync::Arc, }; @@ -96,7 +95,7 @@ pub fn load_workspace( emmyrc.pre_process_emmyrc(&config_root); for lib in &emmyrc.workspace.library { - workspace_folders.push(PathBuf::from_str(lib).unwrap()); + workspace_folders.push(PathBuf::from(lib)); } let mut analysis = EmmyLuaAnalysis::new(); @@ -106,7 +105,7 @@ pub fn load_workspace( } for root in &emmyrc.workspace.workspace_roots { - analysis.add_main_workspace(PathBuf::from_str(root).unwrap()); + analysis.add_main_workspace(PathBuf::from(root)); } analysis.update_config(Arc::new(emmyrc)); diff --git a/crates/emmylua_ls/src/context/workspace_manager.rs b/crates/emmylua_ls/src/context/workspace_manager.rs index 1272785be..71f29661e 100644 --- a/crates/emmylua_ls/src/context/workspace_manager.rs +++ b/crates/emmylua_ls/src/context/workspace_manager.rs @@ -14,6 +14,34 @@ use tokio::sync::{Mutex, RwLock}; use tokio_util::sync::CancellationToken; use wax::Pattern; +#[derive(Clone, Debug)] +pub enum WorkspaceImport { + All, + SubPaths(Vec), +} + +#[derive(Clone, Debug)] +pub struct WorkspaceFolder { + pub root: PathBuf, + pub import: WorkspaceImport, +} + +impl WorkspaceFolder { + pub fn new(root: PathBuf) -> Self { + Self { + root, + import: WorkspaceImport::All, + } + } + + pub fn with_sub_paths(root: PathBuf, sub_paths: Vec) -> Self { + Self { + root, + import: WorkspaceImport::SubPaths(sub_paths), + } + } +} + pub struct WorkspaceManager { analysis: Arc>, client: Arc, @@ -22,7 +50,7 @@ pub struct WorkspaceManager { file_diagnostic: Arc, lsp_features: Arc, pub client_config: ClientConfig, - pub workspace_folders: Vec, + pub workspace_folders: Vec, pub watcher: Option, pub current_open_files: HashSet, pub match_file_pattern: WorkspaceFileMatcher, @@ -128,7 +156,7 @@ impl WorkspaceManager { } pub fn add_reload_workspace_task(&self) -> Option<()> { - let config_root: Option = self.workspace_folders.first().map(PathBuf::from); + let config_root: Option = self.workspace_folders.first().map(|wf| wf.root.clone()); let emmyrc = load_emmy_config(config_root, self.client_config.clone()); let analysis = self.analysis.clone(); @@ -235,19 +263,26 @@ impl WorkspaceManager { return true; }; - let mut file_matched = true; for workspace in &self.workspace_folders { - if let Ok(relative) = file_path.strip_prefix(workspace) { - file_matched = self.match_file_pattern.is_match(&file_path, relative); + if let Ok(relative) = file_path.strip_prefix(&workspace.root) { + let inside_import = match &workspace.import { + WorkspaceImport::All => true, + WorkspaceImport::SubPaths(paths) => { + paths.iter().any(|p| relative.starts_with(p)) + } + }; - if file_matched { - // If the file matches the include pattern, we can stop checking further. - break; + if !inside_import { + continue; + } + + if self.match_file_pattern.is_match(&file_path, relative) { + return true; } } } - file_matched + false } } diff --git a/crates/emmylua_ls/src/handlers/command/commands/emmy_add_doc_tag.rs b/crates/emmylua_ls/src/handlers/command/commands/emmy_add_doc_tag.rs index 57508802a..154ae8228 100644 --- a/crates/emmylua_ls/src/handlers/command/commands/emmy_add_doc_tag.rs +++ b/crates/emmylua_ls/src/handlers/command/commands/emmy_add_doc_tag.rs @@ -34,7 +34,7 @@ pub fn make_auto_doc_tag_command(title: &str, tag_name: &str) -> Command { async fn add_doc_tag(workspace_manager: &RwLock, tag_name: String) -> Option<()> { let workspace_manager = workspace_manager.read().await; let main_workspace = workspace_manager.workspace_folders.first()?; - let emmyrc_path = main_workspace.join(".emmyrc.json"); + let emmyrc_path = main_workspace.root.join(".emmyrc.json"); let mut emmyrc = load_configs_raw(vec![emmyrc_path.clone()], None); drop(workspace_manager); diff --git a/crates/emmylua_ls/src/handlers/command/commands/emmy_disable_code.rs b/crates/emmylua_ls/src/handlers/command/commands/emmy_disable_code.rs index faa23fe8a..d1967e7fe 100644 --- a/crates/emmylua_ls/src/handlers/command/commands/emmy_disable_code.rs +++ b/crates/emmylua_ls/src/handlers/command/commands/emmy_disable_code.rs @@ -62,7 +62,7 @@ async fn add_disable_project( ) -> Option<()> { let workspace_manager = workspace_manager.read().await; let main_workspace = workspace_manager.workspace_folders.first()?; - let emmyrc_path = main_workspace.join(".emmyrc.json"); + let emmyrc_path = main_workspace.root.join(".emmyrc.json"); let mut emmyrc = load_configs_raw(vec![emmyrc_path.clone()], None); drop(workspace_manager); diff --git a/crates/emmylua_ls/src/handlers/hover/find_origin.rs b/crates/emmylua_ls/src/handlers/hover/find_origin.rs index 3be94a54c..428244f0c 100644 --- a/crates/emmylua_ls/src/handlers/hover/find_origin.rs +++ b/crates/emmylua_ls/src/handlers/hover/find_origin.rs @@ -1,8 +1,8 @@ use std::collections::HashSet; use emmylua_code_analysis::{ - LuaCompilation, LuaDeclId, LuaMemberId, LuaSemanticDeclId, LuaType, SemanticDeclLevel, - SemanticModel, + LuaCompilation, LuaDeclId, LuaMemberId, LuaSemanticDeclId, LuaType, LuaUnionType, + SemanticDeclLevel, SemanticModel, }; use emmylua_parser::{LuaAssignStat, LuaAstNode, LuaSyntaxKind, LuaTableExpr, LuaTableField}; @@ -240,6 +240,21 @@ fn resolve_member_owner( } } +// 判断`table`是否为类 +fn table_is_class(table_type: &LuaType, depth: usize) -> bool { + if depth > 10 { + return false; + } + match table_type { + LuaType::Ref(_) | LuaType::Def(_) | LuaType::Generic(_) => true, + LuaType::Union(union) => match union.as_ref() { + LuaUnionType::Nullable(t) => table_is_class(t, depth + 1), + LuaUnionType::Multi(ts) => ts.iter().any(|t| table_is_class(t, depth + 1)), + }, + _ => false, + } +} + fn resolve_table_field_through_type_inference( semantic_model: &SemanticModel, table_field: &LuaTableField, @@ -248,10 +263,8 @@ fn resolve_table_field_through_type_inference( let table_expr = LuaTableExpr::cast(parent)?; let table_type = semantic_model.infer_table_should_be(table_expr)?; - if !matches!( - table_type, - LuaType::Ref(_) | LuaType::Def(_) | LuaType::Generic(_) - ) { + // 必须为类我们才搜索其成员 + if !table_is_class(&table_type, 0) { return None; } diff --git a/crates/emmylua_ls/src/handlers/initialized/client_config/default_config.rs b/crates/emmylua_ls/src/handlers/initialized/client_config/default_config.rs index 53732eff8..3691666c3 100644 --- a/crates/emmylua_ls/src/handlers/initialized/client_config/default_config.rs +++ b/crates/emmylua_ls/src/handlers/initialized/client_config/default_config.rs @@ -21,7 +21,7 @@ pub async fn get_client_config_default( .clone(); let main_workspace_folder = workspace_folders.first(); let client = context.client(); - let scope_uri = main_workspace_folder.map(|p| file_path_to_uri(p).unwrap()); + let scope_uri = main_workspace_folder.and_then(|p| file_path_to_uri(&p.root)); let mut configs = Vec::new(); let mut used_scope = None; diff --git a/crates/emmylua_ls/src/handlers/initialized/codestyle.rs b/crates/emmylua_ls/src/handlers/initialized/codestyle.rs index 31f7afa7b..81f12cdfa 100644 --- a/crates/emmylua_ls/src/handlers/initialized/codestyle.rs +++ b/crates/emmylua_ls/src/handlers/initialized/codestyle.rs @@ -2,22 +2,20 @@ use emmylua_code_analysis::update_code_style; use std::path::PathBuf; use walkdir::{DirEntry, WalkDir}; +use crate::context::{WorkspaceFolder, WorkspaceImport}; + const VCS_DIRS: [&str; 3] = [".git", ".hg", ".svn"]; -pub fn load_editorconfig(workspace_folders: Vec) -> Option<()> { +pub fn load_editorconfig(workspace_folders: Vec) -> Option<()> { let mut editorconfig_files = Vec::new(); for workspace in workspace_folders { - // 构建 WalkDir 迭代器,递归遍历工作区目录 - let walker = WalkDir::new(&workspace) - .into_iter() - .filter_entry(|e| !is_vcs_dir(e, &VCS_DIRS)) - .filter_map(|e| e.ok()) - .filter(|e| e.file_type().is_file()); - for entry in walker { - let path = entry.path(); - if path.ends_with(".editorconfig") { - editorconfig_files.push(path.to_path_buf()); + match &workspace.import { + WorkspaceImport::All => collect_editorconfigs(&workspace.root, &mut editorconfig_files), + WorkspaceImport::SubPaths(subs) => { + for sub in subs { + collect_editorconfigs(&workspace.root.join(sub), &mut editorconfig_files); + } } } } @@ -43,7 +41,21 @@ pub fn load_editorconfig(workspace_folders: Vec) -> Option<()> { Some(()) } -/// 判断目录条目是否应该被包含在遍历中(不被过滤) +fn collect_editorconfigs(root: &PathBuf, results: &mut Vec) { + let walker = WalkDir::new(root) + .into_iter() + .filter_entry(|e| !is_vcs_dir(e, &VCS_DIRS)) + .filter_map(|e| e.ok()) + .filter(|e| e.file_type().is_file()); + for entry in walker { + let path = entry.path(); + if path.ends_with(".editorconfig") { + results.push(path.to_path_buf()); + } + } +} + +/// 判断目录/文件是否应被包含在遍历中(不被过滤) fn is_vcs_dir(entry: &DirEntry, vcs_dirs: &[&str]) -> bool { if entry.file_type().is_dir() { let name = entry.file_name().to_string_lossy(); diff --git a/crates/emmylua_ls/src/handlers/initialized/collect_files.rs b/crates/emmylua_ls/src/handlers/initialized/collect_files.rs index e1f274a7b..8aba4b353 100644 --- a/crates/emmylua_ls/src/handlers/initialized/collect_files.rs +++ b/crates/emmylua_ls/src/handlers/initialized/collect_files.rs @@ -3,7 +3,9 @@ use std::path::PathBuf; use emmylua_code_analysis::{Emmyrc, LuaFileInfo, load_workspace_files}; use log::{debug, info}; -pub fn collect_files(workspaces: &Vec, emmyrc: &Emmyrc) -> Vec { +use crate::context::{WorkspaceFolder, WorkspaceImport}; + +pub fn collect_files(workspaces: &Vec, emmyrc: &Emmyrc) -> Vec { let mut files = Vec::new(); let (match_pattern, exclude, exclude_dir) = calculate_include_and_exclude(emmyrc); @@ -14,16 +16,36 @@ pub fn collect_files(workspaces: &Vec, emmyrc: &Emmyrc) -> Vec { + let loaded = load_workspace_files( + &workspace.root, + &match_pattern, + &exclude, + &exclude_dir, + Some(encoding), + ) + .ok(); + if let Some(loaded) = loaded { + files.extend(loaded); + } + } + WorkspaceImport::SubPaths(paths) => { + for sub in paths { + let target = workspace.root.join(sub); + let loaded = load_workspace_files( + &target, + &match_pattern, + &exclude, + &exclude_dir, + Some(encoding), + ) + .ok(); + if let Some(loaded) = loaded { + files.extend(loaded); + } + } + } } } diff --git a/crates/emmylua_ls/src/handlers/initialized/mod.rs b/crates/emmylua_ls/src/handlers/initialized/mod.rs index a84869079..a26ae0eca 100644 --- a/crates/emmylua_ls/src/handlers/initialized/mod.rs +++ b/crates/emmylua_ls/src/handlers/initialized/mod.rs @@ -3,13 +3,13 @@ mod codestyle; mod collect_files; mod locale; -use std::{path::PathBuf, str::FromStr, sync::Arc}; +use std::{path::PathBuf, sync::Arc}; use crate::{ cmd_args::CmdArgs, context::{ FileDiagnostic, LspFeatures, ProgressTask, ServerContextSnapshot, StatusBar, - WorkspaceFileMatcher, get_client_id, load_emmy_config, + WorkspaceFileMatcher, WorkspaceFolder, get_client_id, load_emmy_config, }, handlers::{ initialized::collect_files::calculate_include_and_exclude, @@ -33,7 +33,7 @@ pub async fn initialized_handler( locale::set_ls_locale(¶ms); let workspace_folders = get_workspace_folders(¶ms); let main_root: Option<&str> = match workspace_folders.first() { - Some(path) => path.to_str(), + Some(path) => path.root.to_str(), None => None, }; @@ -105,7 +105,7 @@ pub async fn init_analysis( status_bar: &StatusBar, file_diagnostic: &FileDiagnostic, lsp_features: &LspFeatures, - workspace_folders: Vec, + workspace_folders: Vec, emmyrc: Arc, ) { let mut mut_analysis = analysis.write().await; @@ -128,19 +128,45 @@ pub async fn init_analysis( let mut workspace_folders = workspace_folders; for workspace_root in &workspace_folders { - log::info!("add workspace root: {:?}", workspace_root); - mut_analysis.add_main_workspace(workspace_root.clone()); + log::info!("add workspace root: {:?}", workspace_root.root); + mut_analysis.add_main_workspace(workspace_root.root.clone()); } for workspace_root in &emmyrc.workspace.workspace_roots { log::info!("add workspace root: {:?}", workspace_root); - mut_analysis.add_main_workspace(PathBuf::from_str(workspace_root).unwrap()); + let root_path = PathBuf::from(workspace_root); + mut_analysis.add_main_workspace(root_path.clone()); + workspace_folders.push(WorkspaceFolder::new(root_path)); } for lib in &emmyrc.workspace.library { log::info!("add library: {:?}", lib); - mut_analysis.add_library_workspace(PathBuf::from_str(lib).unwrap()); - workspace_folders.push(PathBuf::from_str(lib).unwrap()); + let lib_path = PathBuf::from(lib); + mut_analysis.add_library_workspace(lib_path.clone()); + workspace_folders.push(WorkspaceFolder::new(lib_path)); + } + + for package_dir in &emmyrc.workspace.package_dirs { + let package_path = PathBuf::from(package_dir); + if let Some(parent) = package_path.parent() { + if let Some(name) = package_path.file_name() { + let parent_path = parent.to_path_buf(); + log::info!( + "add package dir {:?} with parent workspace {:?}", + package_path, + parent_path + ); + mut_analysis.add_library_workspace(parent_path.clone()); + workspace_folders.push(WorkspaceFolder::with_sub_paths( + parent_path, + vec![PathBuf::from(name)], + )); + } else { + log::warn!("package dir {:?} has no file name", package_path); + } + } else { + log::warn!("package dir {:?} has no parent", package_path); + } } status_bar.update_progress_task( @@ -183,12 +209,12 @@ pub async fn init_analysis( } } -pub fn get_workspace_folders(params: &InitializeParams) -> Vec { +pub fn get_workspace_folders(params: &InitializeParams) -> Vec { let mut workspace_folders = Vec::new(); if let Some(workspaces) = ¶ms.workspace_folders { for workspace in workspaces { if let Some(path) = uri_to_file_path(&workspace.uri) { - workspace_folders.push(path); + workspace_folders.push(WorkspaceFolder::new(path)); } } } @@ -199,7 +225,7 @@ pub fn get_workspace_folders(params: &InitializeParams) -> Vec { if let Some(uri) = ¶ms.root_uri { let root_workspace = uri_to_file_path(uri); if let Some(path) = root_workspace { - workspace_folders.push(path); + workspace_folders.push(WorkspaceFolder::new(path)); } } } diff --git a/crates/emmylua_ls/src/handlers/test/completion_resolve_test.rs b/crates/emmylua_ls/src/handlers/test/completion_resolve_test.rs index 71dfb9432..8869338a1 100644 --- a/crates/emmylua_ls/src/handlers/test/completion_resolve_test.rs +++ b/crates/emmylua_ls/src/handlers/test/completion_resolve_test.rs @@ -27,6 +27,7 @@ mod tests { detail: "local function test(event: string, callback: fun(trg: string, data: number)) -> number (+2 overloads)" .to_string(), + documentation: None, }, )); Ok(()) @@ -46,6 +47,7 @@ mod tests { "#, VirtualCompletionResolveItem { detail: "(field) Test2.event(event: \"游戏-初始化\") (+1 overloads)".to_string(), + documentation: None, }, )); Ok(()) @@ -66,6 +68,7 @@ mod tests { "#, VirtualCompletionResolveItem { detail: "(field) T.func(self: string)".to_string(), + documentation: Some("\n注释注释".to_string()), }, )); Ok(()) @@ -86,6 +89,36 @@ mod tests { "#, VirtualCompletionResolveItem { detail: "(method) T:func()".to_string(), + documentation: Some("\n注释注释".to_string()), + }, + )); + Ok(()) + } + + #[gtest] + fn test_intersection() -> Result<()> { + let mut ws = ProviderVirtualWorkspace::new(); + ws.def( + r#" + ---@class Matchers + ---@field toBe fun(self: Assertion, expected: any) -- 测试 + + ---@class Inverse + ---@field not_ T + + ---@class Assertion: Matchers + "#, + ); + check!(ws.check_completion_resolve( + r#" + + ---@type Assertion + local expect + expect: + "#, + VirtualCompletionResolveItem { + detail: "(method) Matchers:toBe(expected: any)".to_string(), + documentation: Some("\n测试".to_string()), }, )); Ok(()) diff --git a/crates/emmylua_ls/src/handlers/test/hover_test.rs b/crates/emmylua_ls/src/handlers/test/hover_test.rs index fb5a769b6..de4e0ceff 100644 --- a/crates/emmylua_ls/src/handlers/test/hover_test.rs +++ b/crates/emmylua_ls/src/handlers/test/hover_test.rs @@ -413,4 +413,32 @@ mod tests { Ok(()) } + + #[gtest] + fn test_class_with_nil() -> Result<()> { + let mut ws = ProviderVirtualWorkspace::new(); + ws.def( + r#" + ---@class A + ---@field aAnnotation? string a标签 + + ---@class B + ---@field bAnnotation? string b标签 + "#, + ); + check!(ws.check_hover( + r#" + ---@type A|B|nil + local defaultOpt = { + aAnnotation = "a", + } + "#, + VirtualHoverResult { + value: + "```lua\n(field) aAnnotation: string = \"a\"\n```\n\n---\n\na标签".to_string(), + }, + )); + + Ok(()) + } } diff --git a/crates/emmylua_ls/src/handlers/test_lib/mod.rs b/crates/emmylua_ls/src/handlers/test_lib/mod.rs index a28c9b9a7..4022bbba0 100644 --- a/crates/emmylua_ls/src/handlers/test_lib/mod.rs +++ b/crates/emmylua_ls/src/handlers/test_lib/mod.rs @@ -3,9 +3,10 @@ use googletest::prelude::*; use itertools::Itertools; use lsp_types::{ CodeActionOrCommand, CompletionItem, CompletionItemKind, CompletionResponse, - CompletionTriggerKind, GotoDefinitionResponse, Hover, HoverContents, InlayHintLabel, Location, - MarkupContent, Position, SemanticTokenModifier, SemanticTokenType, SemanticTokensResult, - SignatureHelpContext, SignatureHelpTriggerKind, SignatureInformation, TextEdit, + CompletionTriggerKind, Documentation, GotoDefinitionResponse, Hover, HoverContents, + InlayHintLabel, Location, MarkupContent, Position, SemanticTokenModifier, SemanticTokenType, + SemanticTokensResult, SignatureHelpContext, SignatureHelpTriggerKind, SignatureInformation, + TextEdit, }; use std::collections::HashSet; use std::{ops::Deref, sync::Arc}; @@ -72,6 +73,7 @@ impl Default for VirtualCompletionItem { #[derive(Debug)] pub struct VirtualCompletionResolveItem { pub detail: String, + pub documentation: Option, } #[derive(Debug)] @@ -279,7 +281,18 @@ impl ProviderVirtualWorkspace { .or_fail()?; let item = completion_resolve(&self.analysis, param.clone(), ClientId::VSCode); let item_detail = item.detail.ok_or("item detail is empty").or_fail()?; - verify_eq!(item_detail, expected.detail) + verify_eq!(item_detail, expected.detail)?; + match (item.documentation.as_ref(), expected.documentation.as_ref()) { + (None, None) => Ok(()), + (Some(doc), Some(expected_doc)) => match doc { + Documentation::String(s) => verify_eq!(s, expected_doc), + Documentation::MarkupContent(MarkupContent { value, .. }) => { + verify_eq!(value, expected_doc) + } + }, + (Some(_), None) => fail!("unexpected documentation in completion resolve result"), + (None, Some(_)) => fail!("expected documentation missing in completion resolve result"), + } } pub fn check_implementation( diff --git a/crates/emmylua_ls/src/handlers/text_document/register_file_watch.rs b/crates/emmylua_ls/src/handlers/text_document/register_file_watch.rs index 9d4d58213..8f3e7581b 100644 --- a/crates/emmylua_ls/src/handlers/text_document/register_file_watch.rs +++ b/crates/emmylua_ls/src/handlers/text_document/register_file_watch.rs @@ -94,8 +94,8 @@ async fn register_files_watch_use_fsnotify(context: ServerContextSnapshot) -> Op let mut workspace_manager = context.workspace_manager().write().await; for workspace in &workspace_manager.workspace_folders { - if let Err(e) = watcher.watch(workspace, RecursiveMode::Recursive) { - warn!("can not watch {:?}: {:?}", workspace, e); + if let Err(e) = watcher.watch(&workspace.root, RecursiveMode::Recursive) { + warn!("can not watch {:?}: {:?}", workspace.root, e); } } workspace_manager.watcher = Some(watcher);