Absolutize LSP and DAP paths more conservatively (#42482)
Fixes a regression caused by #42135 where LSP and DAP binaries weren't being used from `PATH` env var Now we absolutize the path if (path is relative AND (path has multiple components OR path exists in worktree)). - Relative paths with multiple components might not exist in the worktree because they are ignored. Paths with a single component will at least have an entry saying that they exist and are ignored. - Relative paths with multiple components will never use the `PATH` env var, so they can be safely absolutized Release Notes: - N/A
This commit is contained in:
@@ -291,6 +291,7 @@ pub trait LspAdapterDelegate: Send + Sync {
|
||||
fn http_client(&self) -> Arc<dyn HttpClient>;
|
||||
fn worktree_id(&self) -> WorktreeId;
|
||||
fn worktree_root_path(&self) -> &Path;
|
||||
fn resolve_executable_path(&self, path: PathBuf) -> PathBuf;
|
||||
fn update_status(&self, language: LanguageServerName, status: BinaryStatus);
|
||||
fn registered_lsp_adapters(&self) -> Vec<Arc<dyn LspAdapter>>;
|
||||
async fn language_server_download_dir(&self, name: &LanguageServerName) -> Option<Arc<Path>>;
|
||||
|
||||
@@ -262,8 +262,8 @@ impl DapStore {
|
||||
let user_installed_path = dap_settings.and_then(|s| match &s.binary {
|
||||
DapBinary::Default => None,
|
||||
DapBinary::Custom(binary) => {
|
||||
// if `binary` is absolute, `.join()` will keep it unmodified
|
||||
Some(worktree.read(cx).abs_path().join(PathBuf::from(binary)))
|
||||
let path = PathBuf::from(binary);
|
||||
Some(worktree.read(cx).resolve_executable_path(path))
|
||||
}
|
||||
});
|
||||
let user_args = dap_settings.map(|s| s.args.clone());
|
||||
|
||||
@@ -573,8 +573,7 @@ impl LocalLspStore {
|
||||
env.extend(settings.env.unwrap_or_default());
|
||||
|
||||
Ok(LanguageServerBinary {
|
||||
// if `path` is absolute, `.join()` will keep it unmodified
|
||||
path: delegate.worktree_root_path().join(path),
|
||||
path: delegate.resolve_executable_path(path),
|
||||
env: Some(env),
|
||||
arguments: settings
|
||||
.arguments
|
||||
@@ -13516,6 +13515,10 @@ impl LspAdapterDelegate for LocalLspAdapterDelegate {
|
||||
self.worktree.abs_path().as_ref()
|
||||
}
|
||||
|
||||
fn resolve_executable_path(&self, path: PathBuf) -> PathBuf {
|
||||
self.worktree.resolve_executable_path(path)
|
||||
}
|
||||
|
||||
async fn shell_env(&self) -> HashMap<String, String> {
|
||||
let task = self.load_shell_env_task.clone();
|
||||
task.await.unwrap_or_default()
|
||||
|
||||
@@ -1215,13 +1215,20 @@ async fn test_language_server_relative_path(cx: &mut gpui::TestAppContext) {
|
||||
let settings_json_contents = json!({
|
||||
"languages": {
|
||||
"Rust": {
|
||||
"language_servers": ["my_fake_lsp"]
|
||||
"language_servers": ["my_fake_lsp", "lsp_on_path"]
|
||||
}
|
||||
},
|
||||
"lsp": {
|
||||
"my_fake_lsp": {
|
||||
"binary": {
|
||||
"path": path!("relative_path/to/my_fake_lsp_binary.exe").to_string(),
|
||||
// file exists, so this is treated as a relative path
|
||||
"path": path!(".relative_path/to/my_fake_lsp_binary.exe").to_string(),
|
||||
}
|
||||
},
|
||||
"lsp_on_path": {
|
||||
"binary": {
|
||||
// file doesn't exist, so it will fall back on PATH env var
|
||||
"path": path!("lsp_on_path.exe").to_string(),
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1234,7 +1241,7 @@ async fn test_language_server_relative_path(cx: &mut gpui::TestAppContext) {
|
||||
".zed": {
|
||||
"settings.json": settings_json_contents.to_string(),
|
||||
},
|
||||
"relative_path": {
|
||||
".relative_path": {
|
||||
"to": {
|
||||
"my_fake_lsp.exe": "",
|
||||
},
|
||||
@@ -1250,13 +1257,20 @@ async fn test_language_server_relative_path(cx: &mut gpui::TestAppContext) {
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
language_registry.add(rust_lang());
|
||||
|
||||
let mut fake_rust_servers = language_registry.register_fake_lsp(
|
||||
let mut my_fake_lsp = language_registry.register_fake_lsp(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
name: "my_fake_lsp",
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
let mut lsp_on_path = language_registry.register_fake_lsp(
|
||||
"Rust",
|
||||
FakeLspAdapter {
|
||||
name: "lsp_on_path",
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
@@ -1268,11 +1282,14 @@ async fn test_language_server_relative_path(cx: &mut gpui::TestAppContext) {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let lsp_path = fake_rust_servers.next().await.unwrap().binary.path;
|
||||
let lsp_path = my_fake_lsp.next().await.unwrap().binary.path;
|
||||
assert_eq!(
|
||||
lsp_path.to_string_lossy(),
|
||||
path!("/the-root/relative_path/to/my_fake_lsp_binary.exe"),
|
||||
path!("/the-root/.relative_path/to/my_fake_lsp_binary.exe"),
|
||||
);
|
||||
|
||||
let lsp_path = lsp_on_path.next().await.unwrap().binary.path;
|
||||
assert_eq!(lsp_path.to_string_lossy(), path!("lsp_on_path.exe"));
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
|
||||
@@ -2384,6 +2384,27 @@ impl Snapshot {
|
||||
})
|
||||
}
|
||||
|
||||
/// Resolves a path to an executable using the following heuristics:
|
||||
///
|
||||
/// 1. 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
|
||||
/// (even if falls under an exclusion filter),
|
||||
/// it is joined to the worktree root path.
|
||||
/// 3. 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 Ok(rel_path) = RelPath::new(&path, self.path_style)
|
||||
&& (path.components().count() > 1 || self.entry_for_path(&rel_path).is_some())
|
||||
{
|
||||
self.abs_path().join(path)
|
||||
} else {
|
||||
path
|
||||
}
|
||||
}
|
||||
|
||||
pub fn entry_for_id(&self, id: ProjectEntryId) -> Option<&Entry> {
|
||||
let entry = self.entries_by_id.get(&id, ())?;
|
||||
self.entry_for_path(&entry.path)
|
||||
|
||||
Reference in New Issue
Block a user