diff --git a/CHANGELOG.md b/CHANGELOG.md index a61a041..4e34d96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to the Path Server will be documented in this file. +## Unrelease +Path Server is now published to Open VSX! You can now view it at https://open-vsx.org/extension/LKL/path-server +### Fixed +- **Core**: Fixed an error when generating completions in a directory without read permission. +- **Core**: Fixed tildes are not directly expanded on Windows platform. +- **Core**: Fixed `config.base_path` can only extend first placeholder. + ## [1.4.1] - 2026-07-25 ### Improved - Reduced resource consumption by limiting the number of blocking threads in the tokio runtime to 4 in default. diff --git a/Cargo.lock b/Cargo.lock index f7e2131..42b23a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -278,6 +278,27 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +[[package]] +name = "dirs" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d57d423b3c82e89b9a24ca3091fee61f456a26edbd28d26c65906f4bc1dcd8f" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys", +] + [[package]] name = "dlv-list" version = "0.5.2" @@ -572,6 +593,15 @@ version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" +[[package]] +name = "libredox" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6480ccc157a1389bb2e4891b24751b0f798ba640d22386f23143fbcc89da195a" +dependencies = [ + "libc", +] + [[package]] name = "line-index" version = "0.1.2" @@ -655,6 +685,12 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "ordered-multimap" version = "0.7.3" @@ -683,6 +719,7 @@ name = "path-server" version = "1.4.1" dependencies = [ "config", + "dirs", "futures", "globset", "hf", @@ -807,6 +844,17 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror", +] + [[package]] name = "ref-cast" version = "1.0.26" diff --git a/Cargo.toml b/Cargo.toml index 69a0793..fc34df5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ codegen-units = 1 [dependencies] config = "0.15.22" +dirs = "7.0.0" futures = "0.3.32" globset = "0.4.18" hf = "0.6.0" diff --git a/README.md b/README.md index 1af9439..e0b01df 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,7 @@ Run `zed: open settings file` from the command palette to edit user settings jso ## References - [GitHub Repository](https://github.com/kunlinglio/path-server) - [VS Code Extension](https://marketplace.visualstudio.com/items?itemName=LKL.path-server) +- [Open VSX](https://open-vsx.org/extension/LKL/path-server) - [Download VSIX](https://github.com/kunlinglio/path-server/releases/latest) - [Zed Extension](https://zed.dev/extensions/path-server-lsp) - [Path Server Icon](https://pictogrammers.com/library/mdi/icon/slash-forward-box/) diff --git a/src/config.rs b/src/config.rs index 76895f3..aa022fe 100644 --- a/src/config.rs +++ b/src/config.rs @@ -61,44 +61,55 @@ impl Config { pub fn base_paths( &self, workspace_folders: &[String], - document_parent: Option<&String>, - user_home: Option<&String>, + document_parent: Option<&str>, + user_home: Option<&str>, ) -> Vec<(PathBuf, String, usize)> { self.base_path .iter() .enumerate() - .filter_map(|(index, path)| { - if path.contains("${workspaceFolder}") { - Some( - workspace_folders - .iter() - .map(|workspace_folder| { - let expanded = path.replace("${workspaceFolder}", workspace_folder); - (PathBuf::from(expanded), path.clone(), index) - }) - .collect(), - ) - } else if path.contains("${document}") { - match document_parent { - Some(parent) => { - let expanded = path.replace("${document}", parent); - Some(vec![(PathBuf::from(expanded), path.clone(), index)]) + .flat_map(|(index, path)| { + let result = if path.contains("${workspaceFolder}") { + workspace_folders + .iter() + .map(|workspace_folder| { + let expanded = path.replace("${workspaceFolder}", workspace_folder); + (expanded, path.clone(), index) + }) + .collect() + } else { + vec![(path.clone(), path.clone(), index)] + }; + result + .into_iter() + .flat_map(|mut res| { + if res.0.contains("${document}") { + match document_parent { + Some(parent) => { + res.0 = res.0.replace("${document}", parent); + Some(res) + } + None => None, + } + } else { + Some(res) } - None => None, - } - } else if path.contains("${userHome}") { - match user_home { - Some(home) => { - let expanded = path.replace("${userHome}", home); - Some(vec![(PathBuf::from(expanded), path.clone(), index)]) + }) + .flat_map(|mut res| { + if res.0.contains("${userHome}") { + match user_home { + Some(home) => { + res.0 = res.0.replace("${userHome}", home); + Some(res) + } + None => None, + } + } else { + Some(res) } - None => None, - } - } else { - Some(vec![(PathBuf::from(path), path.clone(), index)]) - } + }) + .map(|res| (PathBuf::from(res.0), res.1, res.2)) + .collect::>() }) - .flatten() .collect() } @@ -295,7 +306,7 @@ mod tests { }; let workspace_folders = vec!["/ws1".to_string(), "/ws2".to_string()]; - let document_parent = Some(&"/ws1/project".to_string()); + let document_parent = Some("/ws1/project"); let user_home = None; let result = config.base_paths(&workspace_folders, document_parent, user_home); @@ -334,7 +345,7 @@ mod tests { let workspace_folders = vec![]; let document_parent = None; - let user_home = Some(&"/home/user".to_string()); + let user_home = Some("/home/user"); let result = config.base_paths(&workspace_folders, document_parent, user_home); @@ -403,4 +414,22 @@ mod tests { let cfg = res.unwrap(); assert_eq!(cfg, default); } + + #[test] + fn test_base_path_with_multi_placeholder() { + let mut config = Config::default(); + config.base_path = vec!["${workspaceFolder}/${document}/${userHome}".to_owned()]; + let workspace_folders = vec!["1".to_owned(), "2".to_owned(), "3".to_owned()]; + let document = "a"; + let user_home = "b"; + let resolved = config.base_paths(&workspace_folders, Some(document), Some(user_home)); + assert_eq!(resolved.len(), workspace_folders.len()); + assert_eq!( + resolved + .iter() + .map(|resolved| resolved.0.to_str().unwrap().to_string()) + .collect::>(), + vec!["1/a/b".to_owned(), "2/a/b".to_owned(), "3/a/b".to_owned()] + ) + } } diff --git a/src/fs.rs b/src/fs.rs index ee10cf2..b619d46 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -80,6 +80,19 @@ pub fn path_to_url(path: &PathBuf) -> PathServerResult { ))) } +/// Expand "~" to the user's home directory +pub fn expand_tilde(path: &str, home_dir: &str) -> String { + if path.starts_with("~/") || path.starts_with("~\\") { + format!("{}{}", home_dir, &path[1..]) + } else { + path.to_string() + } +} + +pub fn get_home_dir() -> Option { + dirs::home_dir().map(|p| p.to_string_lossy().to_string()) +} + #[cfg(test)] mod tests { use std::str::FromStr; @@ -105,4 +118,35 @@ mod tests { _ => assert!(false, "expected Unsupported error, got: {}", err), } } + + #[test] + fn test_expand_tilde() { + let mock_home_dir = tempfile::tempdir().unwrap(); + let mock_home_dir_str = mock_home_dir.path().to_string_lossy(); + + let result = expand_tilde("~/projects", &mock_home_dir_str); + assert_eq!(result, format!("{}/projects", mock_home_dir_str)); + let result = expand_tilde("/path/without/tilde", &mock_home_dir_str); + assert_eq!(result, "/path/without/tilde".to_string()); + } + + #[test] + fn test_get_home_dir() { + let home_dir = get_home_dir().unwrap(); + assert!(PathBuf::from(home_dir.clone()).exists()); + assert!(PathBuf::from(home_dir.clone()).is_absolute()); + expand_tilde("test_dir", &home_dir); + assert!(PathBuf::from(home_dir).is_absolute()); + } + + #[test] + fn test_windows_home_path() { + let mock_home_dir = tempfile::tempdir().unwrap(); + let mock_home_dir_str = mock_home_dir.path().to_string_lossy(); + + let result = expand_tilde("~\\projects", &mock_home_dir_str); + assert_eq!(result, format!("{}\\projects", mock_home_dir_str)); + let result = expand_tilde("\\path\\without\\tilde", &mock_home_dir_str); + assert_eq!(result, "\\path\\without\\tilde".to_string()); + } } diff --git a/src/providers/completion.rs b/src/providers/completion.rs index bb61838..4a6a233 100644 --- a/src/providers/completion.rs +++ b/src/providers/completion.rs @@ -22,13 +22,21 @@ pub async fn provide_completion( doc: &Document, (line_number, character): (usize, usize), workspace_roots: &[String], - current_file_parent: &Option, + current_file_parent: Option<&str>, completion_config: &config::Config, ) -> PathServerResult> { let line_prefix = doc.get_line(line_number, Some(character))?; + let home_dir = fs::get_home_dir(); + if home_dir.is_none() { + lsp_warn!("Failed to get home directory, '~' will not be expanded").await; + } for path_candidate in parser::parse_line(&line_prefix) { let (base_dir, partial_name) = parser::separate_prefix(path_candidate.clone()); - let base_dir = PathBuf::from(expand_tilde(&base_dir)?); + let base_dir = if let Some(home_dir) = home_dir.clone() { + PathBuf::from(fs::expand_tilde(&base_dir, &home_dir)) + } else { + PathBuf::from(&base_dir) + }; let completions: Vec = if base_dir.is_absolute() { // absolute path @@ -44,11 +52,10 @@ pub async fn provide_completion( .await? } else if base_dir.is_relative() { // relative path - let home = std::env::var("HOME").ok(); let base_paths = completion_config.base_paths( workspace_roots, - current_file_parent.as_ref(), - home.as_ref(), + current_file_parent, + home_dir.as_deref(), ); future::try_join_all(base_paths.iter().map(async |(base_path, schema, order)| { @@ -89,19 +96,6 @@ pub async fn provide_completion( Ok(vec![]) } -/// Expand "~" to the user's home directory -fn expand_tilde(path: &str) -> PathServerResult { - let path = if path.starts_with("~/") { - let home = std::env::var("HOME").map_err(|e| { - PathServerError::Unknown(format!("Failed to get HOME environment variable: {}", e)) - })?; - format!("{}{}", home, &path[1..]) - } else { - path.to_string() - }; - Ok(path) -} - /// Filter duplicated and ignored completions async fn filter( completions: Vec, @@ -162,71 +156,77 @@ async fn generate_completions( lsp_debug!("Base directory is not a directory: {}", dir.display()).await; return Ok(vec![]); } + let Ok(entries) = fs::read_dir(&dir).await else { + lsp_warn!( + "Failed to read dir {}, skip generating completions.", + dir.display() + ) + .await; + return Ok(vec![]); + }; - let completions = future::try_join_all(fs::read_dir(&dir).await?.into_iter().map( - |file| async move { - let filename = file.file_name().into_string().map_err(|os_str| { - PathServerError::EncodingError(format!( - "Failed to convert file name to string: {}", - os_str.to_string_lossy() - )) - })?; - if !filename.starts_with(partial_name) { - return PathServerResult::Ok(None); - } - if !show_hidden_files && fs::is_hidden_file(&file.path())? { - return Ok(None); - } - if fs::is_dir(&file.path()).await { - let completion = CompletionItemInner { - completion: ls_types::CompletionItem { - label: filename.clone(), - kind: Some(ls_types::CompletionItemKind::FOLDER), - insert_text: if trigger_next { - Some(filename.clone() + "/") - } else { - Some(filename.clone()) - }, - command: if trigger_next { - Some(ls_types::Command { - title: "triggerSuggest".to_string(), - command: "editor.action.triggerSuggest".to_string(), - arguments: None, - }) - } else { - None - }, - sort_text: Some(format!("{}-{}", root_schema_order, root_schema)), - filter_text: Some(format!("{}-{}", root_schema, filename.clone())), - label_details: Some(ls_types::CompletionItemLabelDetails { - description: Some(format!("from {}", root_schema)), - ..Default::default() - }), - ..Default::default() + let completions = future::try_join_all(entries.into_iter().map(|file| async move { + let filename = file.file_name().into_string().map_err(|os_str| { + PathServerError::EncodingError(format!( + "Failed to convert file name to string: {}", + os_str.to_string_lossy() + )) + })?; + if !filename.starts_with(partial_name) { + return PathServerResult::Ok(None); + } + if !show_hidden_files && fs::is_hidden_file(&file.path())? { + return Ok(None); + } + if fs::is_dir(&file.path()).await { + let completion = CompletionItemInner { + completion: ls_types::CompletionItem { + label: filename.clone(), + kind: Some(ls_types::CompletionItemKind::FOLDER), + insert_text: if trigger_next { + Some(filename.clone() + "/") + } else { + Some(filename.clone()) }, - full_path: file.path(), - }; - Ok(Some(completion)) - } else { - let completion = CompletionItemInner { - completion: ls_types::CompletionItem { - label: filename.clone(), - kind: Some(ls_types::CompletionItemKind::FILE), - insert_text: Some(filename.clone()), - sort_text: Some(format!("{}-{}", root_schema_order, root_schema)), - filter_text: Some(format!("{}-{}", root_schema, filename.clone())), - label_details: Some(ls_types::CompletionItemLabelDetails { - description: Some(format!("from {}", root_schema)), - ..Default::default() - }), - ..Default::default() + command: if trigger_next { + Some(ls_types::Command { + title: "triggerSuggest".to_string(), + command: "editor.action.triggerSuggest".to_string(), + arguments: None, + }) + } else { + None }, - full_path: file.path(), - }; - Ok(Some(completion)) - } - }, - )) + sort_text: Some(format!("{}-{}", root_schema_order, root_schema)), + filter_text: Some(format!("{}-{}", root_schema, filename.clone())), + label_details: Some(ls_types::CompletionItemLabelDetails { + description: Some(format!("from {}", root_schema)), + ..Default::default() + }), + ..Default::default() + }, + full_path: file.path(), + }; + Ok(Some(completion)) + } else { + let completion = CompletionItemInner { + completion: ls_types::CompletionItem { + label: filename.clone(), + kind: Some(ls_types::CompletionItemKind::FILE), + insert_text: Some(filename.clone()), + sort_text: Some(format!("{}-{}", root_schema_order, root_schema)), + filter_text: Some(format!("{}-{}", root_schema, filename.clone())), + label_details: Some(ls_types::CompletionItemLabelDetails { + description: Some(format!("from {}", root_schema)), + ..Default::default() + }), + ..Default::default() + }, + full_path: file.path(), + }; + Ok(Some(completion)) + } + })) .await? .into_iter() .flatten() @@ -237,7 +237,6 @@ async fn generate_completions( #[cfg(test)] mod tests { use super::*; - use std::env; #[tokio::test] async fn test_complete() { @@ -279,7 +278,7 @@ mod tests { &document, (0, "./data/a".to_string().len()), &roots, - &Option::Some(current_file_parent), + Option::Some(current_file_parent).as_deref(), &config, ) .await @@ -348,27 +347,6 @@ mod tests { assert_eq!(filtered[0].label, "1.txt"); } - #[tokio::test] - async fn test_expand_tilde() { - // test with HOME env - let dir = tempfile::tempdir().unwrap(); - unsafe { - env::set_var("HOME", dir.path()); - } - - let result = expand_tilde("~/projects").unwrap(); - assert_eq!(result, format!("{}/projects", dir.path().display())); - let result = expand_tilde("/path/without/tilde"); - assert_eq!(result.unwrap(), "/path/without/tilde".to_string()); - - // test without HOME env - unsafe { - env::remove_var("HOME"); - } - let result = expand_tilde("~/projects"); - assert!(result.is_err()); - } - #[tokio::test] async fn test_complete_absolute() { // prepare a temporary directory structure diff --git a/src/providers/definition.rs b/src/providers/definition.rs index e41f9a1..bb9f19a 100644 --- a/src/providers/definition.rs +++ b/src/providers/definition.rs @@ -8,7 +8,7 @@ use crate::resolver; pub async fn provide_definition( doc: &Document, - parent: &Option, + parent: Option<&str>, line: usize, character: usize, config: &Config, @@ -69,7 +69,7 @@ mod tests { let res = provide_definition( &doc, - &Option::Some(current_file.to_string_lossy().into_owned()), + Option::Some(current_file.to_str().unwrap()), line, character + 1, &Config::default(), @@ -115,13 +115,7 @@ mod tests { let res = provide_definition( &doc, - &Option::Some( - current_file - .parent() - .unwrap() - .to_string_lossy() - .into_owned(), - ), + Option::Some(current_file.parent().unwrap().to_str().unwrap()), line, character + 1, &Config::default(), diff --git a/src/providers/document_link.rs b/src/providers/document_link.rs index cca1fdc..2999777 100644 --- a/src/providers/document_link.rs +++ b/src/providers/document_link.rs @@ -8,7 +8,7 @@ use crate::resolver; pub async fn provide_document_links( doc: &Document, - doc_parent: &Option, + doc_parent: Option<&str>, config: &Config, workspace_roots: &[String], ) -> PathServerResult> { @@ -62,7 +62,7 @@ mod tests { let links = provide_document_links( &doc, - &Option::Some(current_file.to_string_lossy().into_owned()), + Option::Some(current_file.to_str().unwrap()), &Config::default(), &Vec::new(), ) @@ -96,13 +96,7 @@ mod tests { let links = provide_document_links( &doc, - &Option::Some( - current_file - .parent() - .unwrap() - .to_string_lossy() - .into_owned(), - ), + Option::Some(current_file.parent().unwrap().to_str().unwrap()), &Config::default(), &Vec::new(), ) diff --git a/src/providers/hover.rs b/src/providers/hover.rs index 14fd950..32dbd2a 100644 --- a/src/providers/hover.rs +++ b/src/providers/hover.rs @@ -8,7 +8,7 @@ use crate::resolver::resolve_at_pos; pub async fn provide_hover( doc: &Document, - parent: &Option, + parent: Option<&str>, line: usize, character: usize, config: &Config, @@ -62,7 +62,7 @@ mod tests { let res = provide_hover( &doc, - &Option::Some(current_file.to_string_lossy().into_owned()), + Option::Some(current_file.to_str().unwrap()), line, character + 1, &Config::default(), @@ -108,13 +108,7 @@ mod tests { let res = provide_hover( &doc, - &Option::Some( - current_file - .parent() - .unwrap() - .to_string_lossy() - .into_owned(), - ), + Option::Some(current_file.parent().unwrap().to_str().unwrap()), line, character + 1, &Config::default(), diff --git a/src/resolver/collect.rs b/src/resolver/collect.rs index 615a4d6..7d93479 100644 --- a/src/resolver/collect.rs +++ b/src/resolver/collect.rs @@ -7,7 +7,9 @@ use crate::config::Config; use crate::document::Document; use crate::error::*; use crate::fs; -use crate::parser::{PathCandidate, parse_document}; +use crate::lsp_warn; +use crate::parser; +use crate::parser::PathCandidate; use super::{RESOLVE_CACHE_TTL, ResolvedPath, ResolvedPathCache}; @@ -15,7 +17,7 @@ pub async fn resolve_all( document: &Document, config: &Config, workspace_roots: &[String], - doc_parent: &Option, + doc_parent: Option<&str>, ) -> PathServerResult>> { let mut cache = document.resolved_path.lock().await; let signature = config.signature()?; @@ -40,15 +42,19 @@ async fn compute_tokens( document: &Document, config: &Config, workspace_roots: &[String], - doc_parent: &Option, + doc_parent: Option<&str>, ) -> PathServerResult> { - let home = std::env::var("HOME").ok(); + let home = fs::get_home_dir(); + if home.is_none() { + lsp_warn!("Failed to get home directory, '~' will not be expanded").await; + } let path_candidates = if let Some(cache) = &*document.candidate_path.lock().await { // hit cache.clone() } else { // miss - let path_candidates: Arc>> = Arc::new(parse_document(document)?); + let path_candidates: Arc>> = + Arc::new(parser::parse_document(document)?); *document.candidate_path.lock().await = Some(path_candidates.clone()); path_candidates }; @@ -58,8 +64,8 @@ async fn compute_tokens( candidates, config, workspace_roots, - doc_parent.as_ref(), - home.as_ref(), + doc_parent, + home.as_deref(), document, ) .await @@ -79,12 +85,17 @@ async fn filter_exist_path( candidates: &[PathCandidate], config: &Config, workspace_roots: &[String], - parent: Option<&String>, - home: Option<&String>, + parent: Option<&str>, + home: Option<&str>, document: &Document, ) -> PathServerResult> { let resolved = future::try_join_all(candidates.iter().map(|candidate| async move { - let path = PathBuf::from(&candidate.content); + let path = if let Some(home) = home { + let expanded = fs::expand_tilde(&candidate.content, home); + PathBuf::from(expanded) + } else { + PathBuf::from(&candidate.content) + }; if path.is_absolute() { if fs::exists(&path).await { PathServerResult::Ok(vec![ @@ -101,7 +112,6 @@ async fn filter_exist_path( .into_iter() .map(|(base_path, _, _)| { let path = &path; - let candidate = &candidate; async move { let full_path = base_path.join(path); if fs::exists(&full_path).await { diff --git a/src/resolver/query.rs b/src/resolver/query.rs index dbc8867..a871a9b 100644 --- a/src/resolver/query.rs +++ b/src/resolver/query.rs @@ -9,7 +9,7 @@ pub async fn resolve_at_pos( document: &Document, config: &Config, workspace_roots: &[String], - parent: &Option, + parent: Option<&str>, cursor: (usize, usize), ) -> PathServerResult> { let tokens = resolve_all(document, config, workspace_roots, parent).await?; diff --git a/src/server.rs b/src/server.rs index deb99c1..24cce97 100644 --- a/src/server.rs +++ b/src/server.rs @@ -58,7 +58,7 @@ impl PathServer { *guard = Some(Arc::new(cfg)); } - pub async fn workspace_paths(&self) -> Vec { + async fn workspace_paths(&self) -> Vec { let lock_guard = self.workspace_roots.read().await; lock_guard .iter() @@ -67,7 +67,7 @@ impl PathServer { .collect::>() } - pub fn doc_parent(doc_url: &ls_types::Uri) -> Option { + fn doc_parent(doc_url: &ls_types::Uri) -> Option { fs::url_to_path(doc_url) .ok() .flatten() @@ -278,7 +278,7 @@ impl tower_lsp_server::LanguageServer for PathServer { doc, (line_number, character), &workspace_roots, - &parent, + parent.as_deref(), &config, ) .await?; @@ -333,7 +333,8 @@ impl tower_lsp_server::LanguageServer for PathServer { let workspace_roots = self.workspace_paths().await; let parent = Self::doc_parent(¶ms.text_document.uri); let links = - providers::provide_document_links(doc, &parent, &config, &workspace_roots).await?; + providers::provide_document_links(doc, parent.as_deref(), &config, &workspace_roots) + .await?; lsp_info!( "[Document Link] Generated {} document links in {:?}", links.len(), @@ -385,9 +386,15 @@ impl tower_lsp_server::LanguageServer for PathServer { let config = self.get_config().await; let workspace_roots = self.workspace_paths().await; - let definition = - providers::provide_definition(doc, &parent, line, character, &config, &workspace_roots) - .await?; + let definition = providers::provide_definition( + doc, + parent.as_deref(), + line, + character, + &config, + &workspace_roots, + ) + .await?; if let Some(definition) = &definition { let ls_types::GotoDefinitionResponse::Link(definition) = &definition else { unreachable!("Definition is not a link"); @@ -450,9 +457,15 @@ impl tower_lsp_server::LanguageServer for PathServer { )))?; let workspace_roots = self.workspace_paths().await; - let hover = - providers::provide_hover(doc, &parent, line, character, &config, &workspace_roots) - .await?; + let hover = providers::provide_hover( + doc, + parent.as_deref(), + line, + character, + &config, + &workspace_roots, + ) + .await?; if let Some(hover) = &hover { lsp_info!( "[Hover] Generated hover content: {:?} in {:?}",