diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 02c0e42c10f06006fa5b61a549684e2bb336f509..eb953cb32515ff2fa4a20785ca69d2ad857ae4ae 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -1292,6 +1292,67 @@ async fn test_language_server_relative_path(cx: &mut gpui::TestAppContext) { assert_eq!(lsp_path.to_string_lossy(), path!("lsp_on_path.exe")); } +#[gpui::test] +async fn test_language_server_tilde_path(cx: &mut gpui::TestAppContext) { + init_test(cx); + + let settings_json_contents = json!({ + "languages": { + "Rust": { + "language_servers": ["tilde_lsp"] + } + }, + "lsp": { + "tilde_lsp": { + "binary": { + "path": "~/.local/bin/rust-analyzer", + } + } + }, + }); + + let fs = FakeFs::new(cx.executor()); + fs.insert_tree( + path!("/root"), + json!({ + ".zed": { + "settings.json": settings_json_contents.to_string(), + }, + "src": { + "main.rs": "fn main() {}", + } + }), + ) + .await; + + let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await; + let language_registry = project.read_with(cx, |project, _| project.languages().clone()); + language_registry.add(rust_lang()); + + let mut tilde_lsp = language_registry.register_fake_lsp( + "Rust", + FakeLspAdapter { + name: "tilde_lsp", + ..Default::default() + }, + ); + cx.run_until_parked(); + + project + .update(cx, |project, cx| { + project.open_local_buffer_with_lsp(path!("/root/src/main.rs"), cx) + }) + .await + .unwrap(); + + let lsp_path = tilde_lsp.next().await.unwrap().binary.path; + let expected_path = paths::home_dir().join(".local/bin/rust-analyzer"); + assert_eq!( + lsp_path, expected_path, + "Tilde path should expand to home directory" + ); +} + #[gpui::test] async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppContext) { init_test(cx); diff --git a/crates/worktree/src/worktree.rs b/crates/worktree/src/worktree.rs index 7b412e187f0d2cab5c34800309525a16201a83c0..1e8c1648dca98b267146211a9b36fb78f743fb82 100644 --- a/crates/worktree/src/worktree.rs +++ b/crates/worktree/src/worktree.rs @@ -2386,16 +2386,25 @@ impl Snapshot { /// Resolves a path to an executable using the following heuristics: /// - /// 1. If the path is relative and contains more than one component, + /// 1. If the path starts with `~`, it is expanded to the user's home directory. + /// 2. If the path is relative and contains more than one component, /// it is joined to the worktree root path. - /// 2. If the path is relative and exists in the worktree + /// 3. If the path is relative and exists in the worktree /// (even if falls under an exclusion filter), /// it is joined to the worktree root path. - /// 3. Otherwise the path is returned unmodified. + /// 4. Otherwise the path is returned unmodified. /// /// Relative paths that do not exist in the worktree may /// still be found using the `PATH` environment variable. pub fn resolve_executable_path(&self, path: PathBuf) -> PathBuf { + if let Some(path_str) = path.to_str() { + if let Some(remaining_path) = path_str.strip_prefix("~/") { + return home_dir().join(remaining_path); + } else if path_str == "~" { + return home_dir().to_path_buf(); + } + } + if let Ok(rel_path) = RelPath::new(&path, self.path_style) && (path.components().count() > 1 || self.entry_for_path(&rel_path).is_some()) {