@@ -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);
@@ -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())
{