diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 6e400c4a6f..8d573574fd 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -8,7 +8,7 @@ mod tests; use anyhow::{anyhow, Context, Result}; use client::http::{self, HttpClient}; -use collections::HashSet; +use collections::{HashMap, HashSet}; use futures::{ future::{BoxFuture, Shared}, FutureExt, TryFutureExt, @@ -67,8 +67,11 @@ pub struct GitHubLspBinaryVersion { pub url: http::Url, } +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct LanguageServerName(pub Arc); + pub trait LspAdapter: 'static + Send + Sync { - fn name(&self) -> &'static str; + fn name(&self) -> LanguageServerName; fn fetch_latest_server_version( &self, http: Arc, @@ -159,7 +162,6 @@ pub struct Language { pub(crate) config: LanguageConfig, pub(crate) grammar: Option>, pub(crate) adapter: Option>, - lsp_binary_path: Mutex>>>>>, } pub struct Grammar { @@ -186,6 +188,12 @@ pub struct LanguageRegistry { lsp_binary_statuses_tx: async_broadcast::Sender<(Arc, LanguageServerBinaryStatus)>, lsp_binary_statuses_rx: async_broadcast::Receiver<(Arc, LanguageServerBinaryStatus)>, login_shell_env_loaded: Shared>, + lsp_binary_paths: Mutex< + HashMap< + LanguageServerName, + Shared>>>, + >, + >, } impl LanguageRegistry { @@ -197,6 +205,7 @@ impl LanguageRegistry { lsp_binary_statuses_tx, lsp_binary_statuses_rx, login_shell_env_loaded: login_shell_env_loaded.shared(), + lsp_binary_paths: Default::default(), } } @@ -246,7 +255,7 @@ impl LanguageRegistry { } pub fn start_language_server( - &self, + self: &Arc, language: Arc, root_path: Arc, http_client: Arc, @@ -291,16 +300,18 @@ impl LanguageRegistry { .ok_or_else(|| anyhow!("language server download directory has not been assigned")) .log_err()?; + let this = self.clone(); let adapter = language.adapter.clone()?; let background = cx.background().clone(); let lsp_binary_statuses = self.lsp_binary_statuses_tx.clone(); let login_shell_env_loaded = self.login_shell_env_loaded.clone(); Some(cx.background().spawn(async move { login_shell_env_loaded.await; - let server_binary_path = language - .lsp_binary_path + let server_binary_path = this + .lsp_binary_paths .lock() - .get_or_insert_with(|| { + .entry(adapter.name()) + .or_insert_with(|| { get_server_binary_path( adapter.clone(), language.clone(), @@ -342,7 +353,7 @@ async fn get_server_binary_path( download_dir: Arc, statuses: async_broadcast::Sender<(Arc, LanguageServerBinaryStatus)>, ) -> Result { - let container_dir = download_dir.join(adapter.name()); + let container_dir = download_dir.join(adapter.name().0.as_ref()); if !container_dir.exists() { smol::fs::create_dir_all(&container_dir) .await @@ -415,10 +426,13 @@ impl Language { }) }), adapter: None, - lsp_binary_path: Default::default(), } } + pub fn lsp_adapter(&self) -> Option> { + self.adapter.clone() + } + pub fn with_highlights_query(mut self, source: &str) -> Result { let grammar = self .grammar diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 4867ada7cb..b6f007659d 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -223,22 +223,19 @@ impl LspCommand for PerformRename { mut cx: AsyncAppContext, ) -> Result { if let Some(edit) = message { - let language_server = project + let (lsp_adapter, lsp_server) = project .read_with(&cx, |project, cx| { project .language_server_for_buffer(buffer.read(cx), cx) .cloned() }) .ok_or_else(|| anyhow!("no language server found for buffer"))?; - let language = buffer - .read_with(&cx, |buffer, _| buffer.language().cloned()) - .ok_or_else(|| anyhow!("no language for buffer"))?; Project::deserialize_workspace_edit( project, edit, self.push_to_history, - language.name(), - language_server, + lsp_adapter, + lsp_server, &mut cx, ) .await @@ -343,16 +340,13 @@ impl LspCommand for GetDefinition { mut cx: AsyncAppContext, ) -> Result> { let mut definitions = Vec::new(); - let language_server = project + let (lsp_adapter, language_server) = project .read_with(&cx, |project, cx| { project .language_server_for_buffer(buffer.read(cx), cx) .cloned() }) .ok_or_else(|| anyhow!("no language server found for buffer"))?; - let language = buffer - .read_with(&cx, |buffer, _| buffer.language().cloned()) - .ok_or_else(|| anyhow!("no language for buffer"))?; if let Some(message) = message { let mut unresolved_locations = Vec::new(); @@ -377,7 +371,7 @@ impl LspCommand for GetDefinition { .update(&mut cx, |this, cx| { this.open_local_buffer_via_lsp( target_uri, - language.name(), + lsp_adapter.clone(), language_server.clone(), cx, ) @@ -521,16 +515,13 @@ impl LspCommand for GetReferences { mut cx: AsyncAppContext, ) -> Result> { let mut references = Vec::new(); - let language_server = project + let (lsp_adapter, language_server) = project .read_with(&cx, |project, cx| { project .language_server_for_buffer(buffer.read(cx), cx) .cloned() }) .ok_or_else(|| anyhow!("no language server found for buffer"))?; - let language = buffer - .read_with(&cx, |buffer, _| buffer.language().cloned()) - .ok_or_else(|| anyhow!("no language for buffer"))?; if let Some(locations) = locations { for lsp_location in locations { @@ -538,7 +529,7 @@ impl LspCommand for GetReferences { .update(&mut cx, |this, cx| { this.open_local_buffer_via_lsp( lsp_location.uri, - language.name(), + lsp_adapter.clone(), language_server.clone(), cx, ) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index f4d5e9ee11..6f550d1020 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -18,8 +18,8 @@ use language::{ proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version}, range_from_lsp, Anchor, Bias, Buffer, CodeAction, CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Event as BufferEvent, File as _, Language, LanguageRegistry, - LocalFile, OffsetRangeExt, Operation, Patch, PointUtf16, TextBufferSnapshot, ToLspPosition, - ToOffset, ToPointUtf16, Transaction, + LanguageServerName, LocalFile, LspAdapter, OffsetRangeExt, Operation, Patch, PointUtf16, + TextBufferSnapshot, ToLspPosition, ToOffset, ToPointUtf16, Transaction, }; use lsp::{DiagnosticSeverity, DiagnosticTag, DocumentHighlightKind, LanguageServer}; use lsp_command::*; @@ -57,8 +57,10 @@ pub struct Project { worktrees: Vec, active_entry: Option, languages: Arc, - language_servers: HashMap<(WorktreeId, Arc), Arc>, - started_language_servers: HashMap<(WorktreeId, Arc), Task>>>, + language_servers: + HashMap<(WorktreeId, LanguageServerName), (Arc, Arc)>, + started_language_servers: + HashMap<(WorktreeId, LanguageServerName), Task>>>, language_server_statuses: BTreeMap, language_server_settings: Arc>, next_language_server_id: usize, @@ -185,7 +187,7 @@ pub struct DocumentHighlight { pub struct Symbol { pub source_worktree_id: WorktreeId, pub worktree_id: WorktreeId, - pub language_name: String, + pub language_server_name: LanguageServerName, pub path: PathBuf, pub label: CodeLabel, pub name: String, @@ -957,8 +959,8 @@ impl Project { fn open_local_buffer_via_lsp( &mut self, abs_path: lsp::Url, - lang_name: Arc, - lang_server: Arc, + lsp_adapter: Arc, + lsp_server: Arc, cx: &mut ModelContext, ) -> Task>> { cx.spawn(|this, mut cx| async move { @@ -976,8 +978,10 @@ impl Project { }) .await?; this.update(&mut cx, |this, cx| { - this.language_servers - .insert((worktree.read(cx).id(), lang_name), lang_server); + this.language_servers.insert( + (worktree.read(cx).id(), lsp_adapter.name()), + (lsp_adapter, lsp_server), + ); }); (worktree, PathBuf::new()) }; @@ -1120,7 +1124,7 @@ impl Project { } } - if let Some(server) = language_server { + if let Some((_, server)) = language_server { server .notify::( lsp::DidOpenTextDocumentParams { @@ -1153,7 +1157,7 @@ impl Project { if let Some(file) = File::from_dyn(buffer.file()) { if file.is_local() { let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap(); - if let Some(server) = this.language_server_for_buffer(buffer, cx) { + if let Some((_, server)) = this.language_server_for_buffer(buffer, cx) { server .notify::( lsp::DidCloseTextDocumentParams { @@ -1189,7 +1193,7 @@ impl Project { cx.background().spawn(request).detach_and_log_err(cx); } BufferEvent::Edited { .. } => { - let language_server = self + let (_, language_server) = self .language_server_for_buffer(buffer.read(cx), cx)? .clone(); let buffer = buffer.read(cx); @@ -1262,11 +1266,11 @@ impl Project { fn language_servers_for_worktree( &self, worktree_id: WorktreeId, - ) -> impl Iterator)> { + ) -> impl Iterator, Arc)> { self.language_servers.iter().filter_map( - move |((language_server_worktree_id, language_name), server)| { + move |((language_server_worktree_id, _), server)| { if *language_server_worktree_id == worktree_id { - Some((language_name.as_ref(), server)) + Some(server) } else { None } @@ -1302,7 +1306,12 @@ impl Project { language: Arc, cx: &mut ModelContext, ) { - let key = (worktree_id, language.name()); + let adapter = if let Some(adapter) = language.lsp_adapter() { + adapter + } else { + return; + }; + let key = (worktree_id, adapter.name()); self.started_language_servers .entry(key.clone()) .or_insert_with(|| { @@ -1416,7 +1425,7 @@ impl Project { let language_server = language_server.initialize().await.log_err()?; this.update(&mut cx, |this, cx| { this.language_servers - .insert(key.clone(), language_server.clone()); + .insert(key.clone(), (adapter, language_server.clone())); this.language_server_statuses.insert( server_id, LanguageServerStatus { @@ -1459,7 +1468,10 @@ impl Project { } else { continue; }; - if (file.worktree.read(cx).id(), language.name()) != key { + if file.worktree.read(cx).id() != key.0 + || language.lsp_adapter().map(|a| a.name()) + != Some(key.1.clone()) + { continue; } @@ -1675,7 +1687,7 @@ impl Project { } pub fn set_language_server_settings(&mut self, settings: serde_json::Value) { - for server in self.language_servers.values() { + for (_, server) in self.language_servers.values() { server .notify::( lsp::DidChangeConfigurationParams { @@ -1925,7 +1937,7 @@ impl Project { let buffer = buffer_handle.read(cx); if let Some(file) = File::from_dyn(buffer.file()) { if let Some(buffer_abs_path) = file.as_local().map(|f| f.abs_path(cx)) { - if let Some(server) = self.language_server_for_buffer(buffer, cx) { + if let Some((_, server)) = self.language_server_for_buffer(buffer, cx) { local_buffers.push((buffer_handle, buffer_abs_path, server.clone())); } } else { @@ -2062,25 +2074,24 @@ impl Project { pub fn symbols(&self, query: &str, cx: &mut ModelContext) -> Task>> { if self.is_local() { let mut language_servers = HashMap::default(); - for ((worktree_id, language_name), language_server) in self.language_servers.iter() { - if let Some((worktree, language)) = self + for ((worktree_id, _), (lsp_adapter, language_server)) in self.language_servers.iter() { + if let Some(worktree) = self .worktree_for_id(*worktree_id, cx) .and_then(|worktree| worktree.read(cx).as_local()) - .zip(self.languages.get_language(language_name)) { language_servers .entry(Arc::as_ptr(language_server)) .or_insert(( + lsp_adapter.clone(), language_server.clone(), *worktree_id, worktree.abs_path().clone(), - language.clone(), )); } } let mut requests = Vec::new(); - for (language_server, _, _, _) in language_servers.values() { + for (_, language_server, _, _) in language_servers.values() { requests.push(language_server.request::( lsp::WorkspaceSymbolParams { query: query.to_string(), @@ -2095,7 +2106,7 @@ impl Project { let mut symbols = Vec::new(); if let Some(this) = this.upgrade(&cx) { this.read_with(&cx, |this, cx| { - for ((_, source_worktree_id, worktree_abs_path, language), lsp_symbols) in + for ((adapter, _, source_worktree_id, worktree_abs_path), lsp_symbols) in language_servers.into_values().zip(responses) { symbols.extend(lsp_symbols.into_iter().flatten().filter_map( @@ -2112,8 +2123,13 @@ impl Project { path = relativize_path(&worktree_abs_path, &abs_path); } - let label = language - .label_for_symbol(&lsp_symbol.name, lsp_symbol.kind) + let label = this + .languages + .select_language(&path) + .and_then(|language| { + language + .label_for_symbol(&lsp_symbol.name, lsp_symbol.kind) + }) .unwrap_or_else(|| { CodeLabel::plain(lsp_symbol.name.clone(), None) }); @@ -2122,7 +2138,7 @@ impl Project { Some(Symbol { source_worktree_id, worktree_id, - language_name: language.name().to_string(), + language_server_name: adapter.name(), name: lsp_symbol.name, kind: lsp_symbol.kind, label, @@ -2169,9 +2185,9 @@ impl Project { cx: &mut ModelContext, ) -> Task>> { if self.is_local() { - let language_server = if let Some(server) = self.language_servers.get(&( + let (lsp_adapter, language_server) = if let Some(server) = self.language_servers.get(&( symbol.source_worktree_id, - Arc::from(symbol.language_name.as_str()), + symbol.language_server_name.clone(), )) { server.clone() } else { @@ -2196,12 +2212,7 @@ impl Project { return Task::ready(Err(anyhow!("invalid symbol path"))); }; - self.open_local_buffer_via_lsp( - symbol_uri, - Arc::from(symbol.language_name.as_str()), - language_server, - cx, - ) + self.open_local_buffer_via_lsp(symbol_uri, lsp_adapter, language_server, cx) } else if let Some(project_id) = self.remote_id() { let request = self.client.request(proto::OpenBufferForSymbol { project_id, @@ -2242,7 +2253,7 @@ impl Project { if worktree.read(cx).as_local().is_some() { let buffer_abs_path = buffer_abs_path.unwrap(); - let lang_server = + let (_, lang_server) = if let Some(server) = self.language_server_for_buffer(source_buffer, cx) { server.clone() } else { @@ -2356,7 +2367,8 @@ impl Project { let buffer_id = buffer.remote_id(); if self.is_local() { - let lang_server = if let Some(server) = self.language_server_for_buffer(buffer, cx) { + let (_, lang_server) = if let Some(server) = self.language_server_for_buffer(buffer, cx) + { server.clone() } else { return Task::ready(Ok(Default::default())); @@ -2447,7 +2459,8 @@ impl Project { if worktree.read(cx).as_local().is_some() { let buffer_abs_path = buffer_abs_path.unwrap(); - let lang_server = if let Some(server) = self.language_server_for_buffer(buffer, cx) { + let (_, lang_server) = if let Some(server) = self.language_server_for_buffer(buffer, cx) + { server.clone() } else { return Task::ready(Ok(Default::default())); @@ -2534,16 +2547,12 @@ impl Project { ) -> Task> { if self.is_local() { let buffer = buffer_handle.read(cx); - let lang_name = if let Some(lang) = buffer.language() { - lang.name() - } else { - return Task::ready(Ok(Default::default())); - }; - let lang_server = if let Some(server) = self.language_server_for_buffer(buffer, cx) { - server.clone() - } else { - return Task::ready(Ok(Default::default())); - }; + let (lsp_adapter, lang_server) = + if let Some(server) = self.language_server_for_buffer(buffer, cx) { + server.clone() + } else { + return Task::ready(Ok(Default::default())); + }; let range = action.range.to_point_utf16(buffer); cx.spawn(|this, mut cx| async move { @@ -2580,7 +2589,7 @@ impl Project { this, edit, push_to_history, - lang_name, + lsp_adapter, lang_server, &mut cx, ) @@ -2616,7 +2625,7 @@ impl Project { this: ModelHandle, edit: lsp::WorkspaceEdit, push_to_history: bool, - language_name: Arc, + lsp_adapter: Arc, language_server: Arc, cx: &mut AsyncAppContext, ) -> Result { @@ -2693,7 +2702,7 @@ impl Project { .update(cx, |this, cx| { this.open_local_buffer_via_lsp( op.text_document.uri, - language_name.clone(), + lsp_adapter.clone(), language_server.clone(), cx, ) @@ -2988,7 +2997,7 @@ impl Project { let buffer = buffer_handle.read(cx); if self.is_local() { let file = File::from_dyn(buffer.file()).and_then(File::as_local); - if let Some((file, language_server)) = + if let Some((file, (_, language_server))) = file.zip(self.language_server_for_buffer(buffer, cx).cloned()) { let lsp_params = request.to_lsp(&file.abs_path(cx), cx); @@ -4086,9 +4095,8 @@ impl Project { } fn deserialize_symbol(&self, serialized_symbol: proto::Symbol) -> Result { - let language = self - .languages - .get_language(&serialized_symbol.language_name); + let source_worktree_id = WorktreeId::from_proto(serialized_symbol.source_worktree_id); + let worktree_id = WorktreeId::from_proto(serialized_symbol.worktree_id); let start = serialized_symbol .start .ok_or_else(|| anyhow!("invalid start"))?; @@ -4096,15 +4104,17 @@ impl Project { .end .ok_or_else(|| anyhow!("invalid end"))?; let kind = unsafe { mem::transmute(serialized_symbol.kind) }; + let path = PathBuf::from(serialized_symbol.path); + let language = self.languages.select_language(&path); Ok(Symbol { - source_worktree_id: WorktreeId::from_proto(serialized_symbol.source_worktree_id), - worktree_id: WorktreeId::from_proto(serialized_symbol.worktree_id), - language_name: serialized_symbol.language_name.clone(), + source_worktree_id, + worktree_id, + language_server_name: LanguageServerName(serialized_symbol.language_server_name.into()), label: language .and_then(|language| language.label_for_symbol(&serialized_symbol.name, kind)) .unwrap_or_else(|| CodeLabel::plain(serialized_symbol.name.clone(), None)), name: serialized_symbol.name, - path: PathBuf::from(serialized_symbol.path), + path, range: PointUtf16::new(start.row, start.column)..PointUtf16::new(end.row, end.column), kind, signature: serialized_symbol @@ -4349,10 +4359,11 @@ impl Project { &self, buffer: &Buffer, cx: &AppContext, - ) -> Option<&Arc> { + ) -> Option<&(Arc, Arc)> { if let Some((file, language)) = File::from_dyn(buffer.file()).zip(buffer.language()) { let worktree_id = file.worktree_id(cx); - self.language_servers.get(&(worktree_id, language.name())) + self.language_servers + .get(&(worktree_id, language.lsp_adapter()?.name())) } else { None } @@ -4466,7 +4477,7 @@ impl Entity for Project { let shutdown_futures = self .language_servers .drain() - .filter_map(|(_, server)| server.shutdown()) + .filter_map(|(_, (_, server))| server.shutdown()) .collect::>(); Some( async move { @@ -4537,7 +4548,7 @@ fn serialize_symbol(symbol: &Symbol) -> proto::Symbol { proto::Symbol { source_worktree_id: symbol.source_worktree_id.to_proto(), worktree_id: symbol.worktree_id.to_proto(), - language_name: symbol.language_name.clone(), + language_server_name: symbol.language_server_name.0.to_string(), name: symbol.name.clone(), kind: unsafe { mem::transmute(symbol.kind) }, path: symbol.path.to_string_lossy().to_string(), diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 9d25e66190..cef0e60f0f 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -229,7 +229,7 @@ message GetProjectSymbolsResponse { message Symbol { uint64 source_worktree_id = 1; uint64 worktree_id = 2; - string language_name = 3; + string language_server_name = 3; string name = 4; int32 kind = 5; string path = 6; diff --git a/crates/zed/src/languages/c.rs b/crates/zed/src/languages/c.rs index cf4e219967..56994db425 100644 --- a/crates/zed/src/languages/c.rs +++ b/crates/zed/src/languages/c.rs @@ -3,7 +3,7 @@ use client::http::{self, HttpClient, Method}; use futures::{future::BoxFuture, FutureExt, StreamExt}; pub use language::*; use smol::fs::{self, File}; -use std::{any::Any, path::PathBuf, str, sync::Arc}; +use std::{any::Any, path::PathBuf, sync::Arc}; use util::{ResultExt, TryFutureExt}; use super::GithubRelease; @@ -11,8 +11,8 @@ use super::GithubRelease; pub struct CLspAdapter; impl super::LspAdapter for CLspAdapter { - fn name(&self) -> &'static str { - "clangd" + fn name(&self) -> LanguageServerName { + LanguageServerName("clangd".into()) } fn fetch_latest_server_version( diff --git a/crates/zed/src/languages/json.rs b/crates/zed/src/languages/json.rs index c1d0db2ed6..4069413f11 100644 --- a/crates/zed/src/languages/json.rs +++ b/crates/zed/src/languages/json.rs @@ -1,7 +1,7 @@ use anyhow::{anyhow, Context, Result}; use client::http::HttpClient; use futures::{future::BoxFuture, FutureExt, StreamExt}; -use language::LspAdapter; +use language::{LanguageServerName, LspAdapter}; use serde::Deserialize; use serde_json::json; use smol::fs; @@ -16,8 +16,8 @@ impl JsonLspAdapter { } impl LspAdapter for JsonLspAdapter { - fn name(&self) -> &'static str { - "vscode-json-languageserver" + fn name(&self) -> LanguageServerName { + LanguageServerName("vscode-json-languageserver".into()) } fn server_args(&self) -> &[&str] { diff --git a/crates/zed/src/languages/rust.rs b/crates/zed/src/languages/rust.rs index 8b06c5cbd7..4034e5effa 100644 --- a/crates/zed/src/languages/rust.rs +++ b/crates/zed/src/languages/rust.rs @@ -14,8 +14,8 @@ use super::GithubRelease; pub struct RustLspAdapter; impl LspAdapter for RustLspAdapter { - fn name(&self) -> &'static str { - "rust-analyzer" + fn name(&self) -> LanguageServerName { + LanguageServerName("rust-analyzer".into()) } fn fetch_latest_server_version( diff --git a/crates/zed/src/languages/typescript.rs b/crates/zed/src/languages/typescript.rs index f7547fdd4e..4d7af34c38 100644 --- a/crates/zed/src/languages/typescript.rs +++ b/crates/zed/src/languages/typescript.rs @@ -1,7 +1,7 @@ use anyhow::{anyhow, Context, Result}; use client::http::HttpClient; use futures::{future::BoxFuture, FutureExt, StreamExt}; -use language::LspAdapter; +use language::{LanguageServerName, LspAdapter}; use serde::Deserialize; use serde_json::json; use smol::fs; @@ -20,8 +20,8 @@ struct Versions { } impl LspAdapter for TypeScriptLspAdapter { - fn name(&self) -> &'static str { - "typescript-language-server" + fn name(&self) -> LanguageServerName { + LanguageServerName("typescript-language-server".into()) } fn server_args(&self) -> &[&str] {