diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 824c108e46..f3a3c9b00f 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -2764,6 +2764,15 @@ impl MultiBufferSnapshot { .and_then(|(buffer, offset)| buffer.language_scope_at(offset)) } + pub fn language_indent_size_at( + &self, + position: T, + cx: &AppContext, + ) -> Option { + let (buffer_snapshot, offset) = self.point_to_buffer_offset(position)?; + Some(buffer_snapshot.language_indent_size_at(offset, cx)) + } + pub fn is_dirty(&self) -> bool { self.is_dirty } diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 7325ca9af5..d24b9f7033 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -156,6 +156,7 @@ pub struct Completion { #[derive(Clone, Debug)] pub struct CodeAction { + pub server_id: usize, pub range: Range, pub lsp_action: lsp::CodeAction, } diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 81aa1de7bd..7e65a73ffc 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -414,7 +414,7 @@ pub struct BracketPair { pub struct Language { pub(crate) config: LanguageConfig, pub(crate) grammar: Option>, - pub(crate) adapter: Option>, + pub(crate) adapters: Vec>, #[cfg(any(test, feature = "test-support"))] fake_adapter: Option<( @@ -492,7 +492,7 @@ struct AvailableLanguage { path: &'static str, config: LanguageConfig, grammar: tree_sitter::Language, - lsp_adapter: Option>, + lsp_adapters: Vec>, get_queries: fn(&str) -> LanguageQueries, } @@ -513,6 +513,7 @@ pub struct LanguageRegistry { } struct LanguageRegistryState { + next_language_server_id: usize, languages: Vec>, available_languages: Vec, next_available_language_id: AvailableLanguageId, @@ -522,11 +523,17 @@ struct LanguageRegistryState { version: usize, } +pub struct PendingLanguageServer { + pub server_id: usize, + pub task: Task>, +} + impl LanguageRegistry { pub fn new(login_shell_env_loaded: Task<()>) -> Self { let (lsp_binary_statuses_tx, lsp_binary_statuses_rx) = async_broadcast::broadcast(16); Self { state: RwLock::new(LanguageRegistryState { + next_language_server_id: 0, languages: vec![PLAIN_TEXT.clone()], available_languages: Default::default(), next_available_language_id: 0, @@ -558,7 +565,7 @@ impl LanguageRegistry { path: &'static str, config: LanguageConfig, grammar: tree_sitter::Language, - lsp_adapter: Option>, + lsp_adapters: Vec>, get_queries: fn(&str) -> LanguageQueries, ) { let state = &mut *self.state.write(); @@ -567,7 +574,7 @@ impl LanguageRegistry { path, config, grammar, - lsp_adapter, + lsp_adapters, get_queries, }); } @@ -590,12 +597,13 @@ impl LanguageRegistry { state .available_languages .iter() - .filter_map(|l| l.lsp_adapter.clone()) + .flat_map(|l| l.lsp_adapters.clone()) .chain( state .languages .iter() - .filter_map(|l| l.adapter.as_ref().map(|a| a.adapter.clone())), + .flat_map(|language| &language.adapters) + .map(|adapter| adapter.adapter.clone()), ) .collect::>() }; @@ -721,7 +729,7 @@ impl LanguageRegistry { let queries = (language.get_queries)(&language.path); let language = Language::new(language.config, Some(language.grammar)) - .with_lsp_adapter(language.lsp_adapter) + .with_lsp_adapters(language.lsp_adapters) .await; let name = language.name(); match language.with_queries(queries) { @@ -774,18 +782,16 @@ impl LanguageRegistry { self.state.read().languages.iter().cloned().collect() } - pub fn start_language_server( + pub fn start_language_servers( self: &Arc, - server_id: usize, language: Arc, root_path: Arc, http_client: Arc, cx: &mut AppContext, - ) -> Option>> { + ) -> Vec { #[cfg(any(test, feature = "test-support"))] if language.fake_adapter.is_some() { - let language = language; - return Some(cx.spawn(|cx| async move { + let task = cx.spawn(|cx| async move { let (servers_tx, fake_adapter) = language.fake_adapter.as_ref().unwrap(); let (server, mut fake_server) = lsp::LanguageServer::fake( fake_adapter.name.to_string(), @@ -810,53 +816,71 @@ impl LanguageRegistry { }) .detach(); Ok(server) - })); + }); + return vec![PendingLanguageServer { server_id: 0, task }]; } let download_dir = self .language_server_download_dir .clone() .ok_or_else(|| anyhow!("language server download directory has not been assigned")) - .log_err()?; + .log_err(); + let download_dir = match download_dir { + Some(download_dir) => download_dir, + None => return Vec::new(), + }; - let this = self.clone(); - let adapter = language.adapter.clone()?; - let lsp_binary_statuses = self.lsp_binary_statuses_tx.clone(); - let login_shell_env_loaded = self.login_shell_env_loaded.clone(); + let mut results = Vec::new(); - Some(cx.spawn(|cx| async move { - login_shell_env_loaded.await; + for adapter in &language.adapters { + let this = self.clone(); + let language = language.clone(); + let http_client = http_client.clone(); + let download_dir = download_dir.clone(); + let root_path = root_path.clone(); + let adapter = adapter.clone(); + let lsp_binary_statuses = self.lsp_binary_statuses_tx.clone(); + let login_shell_env_loaded = self.login_shell_env_loaded.clone(); + let server_id = post_inc(&mut self.state.write().next_language_server_id); - let mut lock = this.lsp_binary_paths.lock(); - let entry = lock - .entry(adapter.name.clone()) - .or_insert_with(|| { - get_binary( - adapter.clone(), - language.clone(), - http_client, - download_dir, - lsp_binary_statuses, - ) - .map_err(Arc::new) - .boxed() - .shared() - }) - .clone(); - drop(lock); - let binary = entry.clone().map_err(|e| anyhow!(e)).await?; + let task = cx.spawn(|cx| async move { + login_shell_env_loaded.await; - let server = lsp::LanguageServer::new( - server_id, - &binary.path, - &binary.arguments, - &root_path, - adapter.code_action_kinds(), - cx, - )?; + let mut lock = this.lsp_binary_paths.lock(); + let entry = lock + .entry(adapter.name.clone()) + .or_insert_with(|| { + get_binary( + adapter.clone(), + language.clone(), + http_client, + download_dir, + lsp_binary_statuses, + ) + .map_err(Arc::new) + .boxed() + .shared() + }) + .clone(); + drop(lock); + let binary = entry.clone().map_err(|e| anyhow!(e)).await?; - Ok(server) - })) + let server = lsp::LanguageServer::new( + server_id, + &binary.path, + &binary.arguments, + &root_path, + adapter.code_action_kinds(), + cx, + )?; + + Ok(server) + }); + + results.push(PendingLanguageServer { server_id, task }); + } + + results } pub fn language_server_binary_statuses( @@ -974,15 +998,15 @@ impl Language { highlight_map: Default::default(), }) }), - adapter: None, + adapters: Vec::new(), #[cfg(any(test, feature = "test-support"))] fake_adapter: None, } } - pub fn lsp_adapter(&self) -> Option> { - self.adapter.clone() + pub fn lsp_adapters(&self) -> &[Arc] { + &self.adapters } pub fn id(&self) -> Option { @@ -1209,9 +1233,9 @@ impl Language { Arc::get_mut(self.grammar.as_mut().unwrap()).unwrap() } - pub async fn with_lsp_adapter(mut self, lsp_adapter: Option>) -> Self { - if let Some(adapter) = lsp_adapter { - self.adapter = Some(CachedLspAdapter::new(adapter).await); + pub async fn with_lsp_adapters(mut self, lsp_adapters: Vec>) -> Self { + for adapter in lsp_adapters { + self.adapters.push(CachedLspAdapter::new(adapter).await); } self } @@ -1224,7 +1248,7 @@ impl Language { let (servers_tx, servers_rx) = mpsc::unbounded(); self.fake_adapter = Some((servers_tx, fake_lsp_adapter.clone())); let adapter = CachedLspAdapter::new(Arc::new(fake_lsp_adapter)).await; - self.adapter = Some(adapter); + self.adapters = vec![adapter]; servers_rx } @@ -1233,28 +1257,31 @@ impl Language { } pub async fn disk_based_diagnostic_sources(&self) -> &[String] { - match self.adapter.as_ref() { + match self.adapters.first().as_ref() { Some(adapter) => &adapter.disk_based_diagnostic_sources, None => &[], } } pub async fn disk_based_diagnostics_progress_token(&self) -> Option<&str> { - if let Some(adapter) = self.adapter.as_ref() { - adapter.disk_based_diagnostics_progress_token.as_deref() - } else { - None + for adapter in &self.adapters { + let token = adapter.disk_based_diagnostics_progress_token.as_deref(); + if token.is_some() { + return token; + } } + + None } pub async fn process_diagnostics(&self, diagnostics: &mut lsp::PublishDiagnosticsParams) { - if let Some(processor) = self.adapter.as_ref() { - processor.process_diagnostics(diagnostics).await; + for adapter in &self.adapters { + adapter.process_diagnostics(diagnostics).await; } } pub async fn process_completion(self: &Arc, completion: &mut lsp::CompletionItem) { - if let Some(adapter) = self.adapter.as_ref() { + for adapter in &self.adapters { adapter.process_completion(completion).await; } } @@ -1263,7 +1290,8 @@ impl Language { self: &Arc, completion: &lsp::CompletionItem, ) -> Option { - self.adapter + self.adapters + .first() .as_ref()? .label_for_completion(completion, self) .await @@ -1274,7 +1302,8 @@ impl Language { name: &str, kind: lsp::SymbolKind, ) -> Option { - self.adapter + self.adapters + .first() .as_ref()? .label_for_symbol(name, kind, self) .await @@ -1595,7 +1624,7 @@ mod tests { ..Default::default() }, tree_sitter_json::language(), - None, + vec![], |_| Default::default(), ); languages.register( @@ -1606,7 +1635,7 @@ mod tests { ..Default::default() }, tree_sitter_rust::language(), - None, + vec![], |_| Default::default(), ); assert_eq!( diff --git a/crates/language/src/proto.rs b/crates/language/src/proto.rs index 1f6ecd0a90..fb50f2a743 100644 --- a/crates/language/src/proto.rs +++ b/crates/language/src/proto.rs @@ -462,6 +462,7 @@ pub async fn deserialize_completion( pub fn serialize_code_action(action: &CodeAction) -> proto::CodeAction { proto::CodeAction { + server_id: action.server_id as u64, start: Some(serialize_anchor(&action.range.start)), end: Some(serialize_anchor(&action.range.end)), lsp_action: serde_json::to_vec(&action.lsp_action).unwrap(), @@ -479,6 +480,7 @@ pub fn deserialize_code_action(action: proto::CodeAction) -> Result .ok_or_else(|| anyhow!("invalid end"))?; let lsp_action = serde_json::from_slice(&action.lsp_action)?; Ok(CodeAction { + server_id: action.server_id as usize, range: start..end, lsp_action, }) diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index fb69df8766..96e44d6f84 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -33,21 +33,25 @@ pub(crate) trait LspCommand: 'static + Sized { language_server: &Arc, cx: &AppContext, ) -> ::Params; + async fn response_from_lsp( self, message: ::Result, project: ModelHandle, buffer: ModelHandle, + server_id: usize, cx: AsyncAppContext, ) -> Result; fn to_proto(&self, project_id: u64, buffer: &Buffer) -> Self::ProtoRequest; + async fn from_proto( message: Self::ProtoRequest, project: ModelHandle, buffer: ModelHandle, cx: AsyncAppContext, ) -> Result; + fn response_to_proto( response: Self::Response, project: &mut Project, @@ -55,6 +59,7 @@ pub(crate) trait LspCommand: 'static + Sized { buffer_version: &clock::Global, cx: &mut AppContext, ) -> ::Response; + async fn response_from_proto( self, message: ::Response, @@ -62,6 +67,7 @@ pub(crate) trait LspCommand: 'static + Sized { buffer: ModelHandle, cx: AsyncAppContext, ) -> Result; + fn buffer_id_from_proto(message: &Self::ProtoRequest) -> u64; } @@ -137,6 +143,7 @@ impl LspCommand for PrepareRename { message: Option, _: ModelHandle, buffer: ModelHandle, + _: usize, cx: AsyncAppContext, ) -> Result>> { buffer.read_with(&cx, |buffer, _| { @@ -263,10 +270,12 @@ impl LspCommand for PerformRename { message: Option, project: ModelHandle, buffer: ModelHandle, + server_id: usize, mut cx: AsyncAppContext, ) -> Result { if let Some(edit) = message { - let (lsp_adapter, lsp_server) = language_server_for_buffer(&project, &buffer, &mut cx)?; + let (lsp_adapter, lsp_server) = + language_server_for_buffer(&project, &buffer, server_id, &mut cx)?; Project::deserialize_workspace_edit( project, edit, @@ -380,9 +389,10 @@ impl LspCommand for GetDefinition { message: Option, project: ModelHandle, buffer: ModelHandle, + server_id: usize, cx: AsyncAppContext, ) -> Result> { - location_links_from_lsp(message, project, buffer, cx).await + location_links_from_lsp(message, project, buffer, server_id, cx).await } fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetDefinition { @@ -472,9 +482,10 @@ impl LspCommand for GetTypeDefinition { message: Option, project: ModelHandle, buffer: ModelHandle, + server_id: usize, cx: AsyncAppContext, ) -> Result> { - location_links_from_lsp(message, project, buffer, cx).await + location_links_from_lsp(message, project, buffer, server_id, cx).await } fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetTypeDefinition { @@ -537,12 +548,13 @@ impl LspCommand for GetTypeDefinition { fn language_server_for_buffer( project: &ModelHandle, buffer: &ModelHandle, + server_id: usize, cx: &mut AsyncAppContext, ) -> Result<(Arc, Arc)> { project .read_with(cx, |project, cx| { project - .language_server_for_buffer(buffer.read(cx), cx) + .language_server_for_buffer(buffer.read(cx), server_id, cx) .map(|(adapter, server)| (adapter.clone(), server.clone())) }) .ok_or_else(|| anyhow!("no language server found for buffer")) @@ -614,6 +626,7 @@ async fn location_links_from_lsp( message: Option, project: ModelHandle, buffer: ModelHandle, + server_id: usize, mut cx: AsyncAppContext, ) -> Result> { let message = match message { @@ -642,7 +655,8 @@ async fn location_links_from_lsp( } } - let (lsp_adapter, language_server) = language_server_for_buffer(&project, &buffer, &mut cx)?; + let (lsp_adapter, language_server) = + language_server_for_buffer(&project, &buffer, server_id, &mut cx)?; let mut definitions = Vec::new(); for (origin_range, target_uri, target_range) in unresolved_links { let target_buffer_handle = project @@ -756,11 +770,12 @@ impl LspCommand for GetReferences { locations: Option>, project: ModelHandle, buffer: ModelHandle, + server_id: usize, mut cx: AsyncAppContext, ) -> Result> { let mut references = Vec::new(); let (lsp_adapter, language_server) = - language_server_for_buffer(&project, &buffer, &mut cx)?; + language_server_for_buffer(&project, &buffer, server_id, &mut cx)?; if let Some(locations) = locations { for lsp_location in locations { @@ -917,6 +932,7 @@ impl LspCommand for GetDocumentHighlights { lsp_highlights: Option>, _: ModelHandle, buffer: ModelHandle, + _: usize, cx: AsyncAppContext, ) -> Result> { buffer.read_with(&cx, |buffer, _| { @@ -1062,6 +1078,7 @@ impl LspCommand for GetHover { message: Option, _: ModelHandle, buffer: ModelHandle, + _: usize, cx: AsyncAppContext, ) -> Result { Ok(message.and_then(|hover| { @@ -1283,6 +1300,7 @@ impl LspCommand for GetCompletions { completions: Option, _: ModelHandle, buffer: ModelHandle, + _: usize, cx: AsyncAppContext, ) -> Result> { let completions = if let Some(completions) = completions { @@ -1502,6 +1520,7 @@ impl LspCommand for GetCodeActions { actions: Option, _: ModelHandle, _: ModelHandle, + server_id: usize, _: AsyncAppContext, ) -> Result> { Ok(actions @@ -1510,6 +1529,7 @@ impl LspCommand for GetCodeActions { .filter_map(|entry| { if let lsp::CodeActionOrCommand::CodeAction(lsp_action) = entry { Some(CodeAction { + server_id, range: self.range.clone(), lsp_action, }) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index d5b7ac3f3f..36cd76fe3d 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -31,8 +31,8 @@ use language::{ range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, CachedLspAdapter, CodeAction, CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, Event as BufferEvent, File as _, Language, LanguageRegistry, LanguageServerName, LocalFile, OffsetRangeExt, Operation, Patch, - PointUtf16, RopeFingerprint, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, - Unclipped, + PendingLanguageServer, PointUtf16, RopeFingerprint, TextBufferSnapshot, ToOffset, ToPointUtf16, + Transaction, Unclipped, }; use lsp::{ DiagnosticSeverity, DiagnosticTag, DidChangeWatchedFilesRegistrationOptions, @@ -99,7 +99,6 @@ pub struct Project { language_server_ids: HashMap<(WorktreeId, LanguageServerName), usize>, language_server_statuses: BTreeMap, last_workspace_edits_by_language_server: HashMap, - next_language_server_id: usize, client: Arc, next_entry_id: Arc, join_project_response_message_id: u32, @@ -124,7 +123,7 @@ pub struct Project { /// A mapping from a buffer ID to None means that we've started waiting for an ID but haven't finished loading it. /// Used for re-issuing buffer requests when peers temporarily disconnect incomplete_remote_buffers: HashMap>>, - buffer_snapshots: HashMap>, + buffer_snapshots: HashMap>>, // buffer_id -> server_id -> vec of snapshots buffers_being_formatted: HashSet, nonce: u128, _maintain_buffer_languages: Task<()>, @@ -133,6 +132,11 @@ pub struct Project { copilot_enabled: bool, } +struct LspBufferSnapshot { + version: i32, + snapshot: TextBufferSnapshot, +} + enum BufferMessage { Operation { buffer_id: u64, @@ -469,7 +473,6 @@ impl Project { language_server_statuses: Default::default(), last_workspace_edits_by_language_server: Default::default(), buffers_being_formatted: Default::default(), - next_language_server_id: 0, nonce: StdRng::from_entropy().gen(), terminals: Terminals { local_handles: Vec::new(), @@ -554,7 +557,6 @@ impl Project { }) .collect(), last_workspace_edits_by_language_server: Default::default(), - next_language_server_id: 0, opened_buffers: Default::default(), buffers_being_formatted: Default::default(), buffer_snapshots: Default::default(), @@ -645,7 +647,7 @@ impl Project { let mut language_servers_to_stop = Vec::new(); for language in self.languages.to_vec() { - if let Some(lsp_adapter) = language.lsp_adapter() { + for lsp_adapter in language.lsp_adapters() { if !settings.enable_language_server(Some(&language.name())) { let lsp_name = &lsp_adapter.name; for (worktree_id, started_lsp_name) in self.language_server_ids.keys() { @@ -665,7 +667,7 @@ impl Project { // Start all the newly-enabled language servers. for (worktree_id, worktree_path, language) in language_servers_to_start { - self.start_language_server(worktree_id, worktree_path, language, cx); + self.start_language_servers(worktree_id, worktree_path, language, cx); } if !self.copilot_enabled && Copilot::global(cx).is_some() { @@ -1550,7 +1552,7 @@ impl Project { cx.spawn(|this, mut cx| async move { if let Some(old_path) = old_path { this.update(&mut cx, |this, cx| { - this.unregister_buffer_from_language_server(&buffer, old_path, cx); + this.unregister_buffer_from_language_servers(&buffer, old_path, cx); }); } let (worktree, path) = worktree_task.await?; @@ -1564,7 +1566,7 @@ impl Project { .await?; this.update(&mut cx, |this, cx| { this.detect_language_for_buffer(&buffer, cx); - this.register_buffer_with_language_server(&buffer, cx); + this.register_buffer_with_language_servers(&buffer, cx); }); Ok(()) }) @@ -1628,14 +1630,15 @@ impl Project { .detach(); self.detect_language_for_buffer(buffer, cx); - self.register_buffer_with_language_server(buffer, cx); + self.register_buffer_with_language_servers(buffer, cx); self.register_buffer_with_copilot(buffer, cx); cx.observe_release(buffer, |this, buffer, cx| { 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) { + for server in this.language_servers_for_buffer(buffer, cx) { server + .1 .notify::( lsp::DidCloseTextDocumentParams { text_document: lsp::TextDocumentIdentifier::new(uri), @@ -1652,46 +1655,50 @@ impl Project { Ok(()) } - fn register_buffer_with_language_server( + fn register_buffer_with_language_servers( &mut self, buffer_handle: &ModelHandle, cx: &mut ModelContext, ) { let buffer = buffer_handle.read(cx); let buffer_id = buffer.remote_id(); + 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(); - let initial_snapshot = buffer.text_snapshot(); + if !file.is_local() { + return; + } - let mut language_server = None; - let mut language_id = None; - if let Some(language) = buffer.language() { - let worktree_id = file.worktree_id(cx); - if let Some(adapter) = language.lsp_adapter() { - language_id = adapter.language_ids.get(language.name().as_ref()).cloned(); - language_server = self - .language_server_ids - .get(&(worktree_id, adapter.name.clone())) - .and_then(|id| self.language_servers.get(id)) - .and_then(|server_state| { - if let LanguageServerState::Running { server, .. } = server_state { - Some(server.clone()) - } else { - None - } - }); - } + let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap(); + let initial_snapshot = buffer.text_snapshot(); + + if let Some(local_worktree) = file.worktree.read(cx).as_local() { + if let Some(diagnostics) = local_worktree.diagnostics_for_path(file.path()) { + self.update_buffer_diagnostics(buffer_handle, diagnostics, None, cx) + .log_err(); } + } - if let Some(local_worktree) = file.worktree.read(cx).as_local() { - if let Some(diagnostics) = local_worktree.diagnostics_for_path(file.path()) { - self.update_buffer_diagnostics(buffer_handle, diagnostics, None, cx) - .log_err(); - } - } + if let Some(language) = buffer.language() { + let worktree_id = file.worktree_id(cx); + + for adapter in language.lsp_adapters() { + let language_id = adapter.language_ids.get(language.name().as_ref()).cloned(); + let server = self + .language_server_ids + .get(&(worktree_id, adapter.name.clone())) + .and_then(|id| self.language_servers.get(id)) + .and_then(|server_state| { + if let LanguageServerState::Running { server, .. } = server_state { + Some(server.clone()) + } else { + None + } + }); + let server = match server { + Some(server) => server, + None => continue, + }; - if let Some(server) = language_server { server .notify::( lsp::DidOpenTextDocumentParams { @@ -1704,6 +1711,7 @@ impl Project { }, ) .log_err(); + buffer_handle.update(cx, |buffer, cx| { buffer.set_completion_triggers( server @@ -1713,16 +1721,23 @@ impl Project { .and_then(|provider| provider.trigger_characters.clone()) .unwrap_or_default(), cx, - ) + ); }); + + let snapshot = LspBufferSnapshot { + version: 0, + snapshot: initial_snapshot, + }; self.buffer_snapshots - .insert(buffer_id, vec![(0, initial_snapshot)]); + .entry(buffer_id) + .or_default() + .insert(server.server_id(), vec![snapshot]); } } } } - fn unregister_buffer_from_language_server( + fn unregister_buffer_from_language_servers( &mut self, buffer: &ModelHandle, old_path: PathBuf, @@ -1731,7 +1746,7 @@ impl Project { buffer.update(cx, |buffer, cx| { buffer.update_diagnostics(Default::default(), cx); self.buffer_snapshots.remove(&buffer.remote_id()); - if let Some((_, language_server)) = self.language_server_for_buffer(buffer, cx) { + for (_, language_server) in self.language_servers_for_buffer(buffer, cx) { language_server .notify::( lsp::DidCloseTextDocumentParams { @@ -1833,52 +1848,62 @@ impl Project { }) .ok(); } + BufferEvent::Edited { .. } => { - let language_server = self - .language_server_for_buffer(buffer.read(cx), cx) - .map(|(_, server)| server.clone())?; let buffer = buffer.read(cx); let file = File::from_dyn(buffer.file())?; let abs_path = file.as_local()?.abs_path(cx); let uri = lsp::Url::from_file_path(abs_path).unwrap(); - let buffer_snapshots = self.buffer_snapshots.get_mut(&buffer.remote_id())?; - let (version, prev_snapshot) = buffer_snapshots.last()?; let next_snapshot = buffer.text_snapshot(); - let next_version = version + 1; - let content_changes = buffer - .edits_since::<(PointUtf16, usize)>(prev_snapshot.version()) - .map(|edit| { - let edit_start = edit.new.start.0; - let edit_end = edit_start + (edit.old.end.0 - edit.old.start.0); - let new_text = next_snapshot - .text_for_range(edit.new.start.1..edit.new.end.1) - .collect(); - lsp::TextDocumentContentChangeEvent { - range: Some(lsp::Range::new( - point_to_lsp(edit_start), - point_to_lsp(edit_end), - )), - range_length: None, - text: new_text, - } - }) - .collect(); + for (_, language_server) in self.language_servers_for_buffer(buffer, cx) { + let language_server = language_server.clone(); - buffer_snapshots.push((next_version, next_snapshot)); + let buffer_snapshots = self + .buffer_snapshots + .get_mut(&buffer.remote_id()) + .and_then(|m| m.get_mut(&language_server.server_id()))?; + let previous_snapshot = buffer_snapshots.last()?; + let next_version = previous_snapshot.version + 1; - language_server - .notify::( - lsp::DidChangeTextDocumentParams { - text_document: lsp::VersionedTextDocumentIdentifier::new( - uri, - next_version, - ), - content_changes, - }, - ) - .log_err(); + let content_changes = buffer + .edits_since::<(PointUtf16, usize)>(previous_snapshot.snapshot.version()) + .map(|edit| { + let edit_start = edit.new.start.0; + let edit_end = edit_start + (edit.old.end.0 - edit.old.start.0); + let new_text = next_snapshot + .text_for_range(edit.new.start.1..edit.new.end.1) + .collect(); + lsp::TextDocumentContentChangeEvent { + range: Some(lsp::Range::new( + point_to_lsp(edit_start), + point_to_lsp(edit_end), + )), + range_length: None, + text: new_text, + } + }) + .collect(); + + buffer_snapshots.push(LspBufferSnapshot { + version: next_version, + snapshot: next_snapshot, + }); + + language_server + .notify::( + lsp::DidChangeTextDocumentParams { + text_document: lsp::VersionedTextDocumentIdentifier::new( + uri, + next_version, + ), + content_changes, + }, + ) + .log_err(); + } } + BufferEvent::Saved => { let file = File::from_dyn(buffer.read(cx).file())?; let worktree_id = file.worktree_id(cx); @@ -1898,13 +1923,17 @@ impl Project { .log_err(); } - let language_server_id = self.language_server_id_for_buffer(buffer.read(cx), cx)?; - if let Some(LanguageServerState::Running { - adapter, - simulate_disk_based_diagnostics_completion, - .. - }) = self.language_servers.get_mut(&language_server_id) - { + let language_server_ids = self.language_server_ids_for_buffer(buffer.read(cx), cx); + for language_server_id in language_server_ids { + let LanguageServerState::Running { + adapter, + simulate_disk_based_diagnostics_completion, + .. + } = match self.language_servers.get_mut(&language_server_id) { + Some(state) => state, + None => continue, + }; + // After saving a buffer using a language server that doesn't provide // a disk-based progress token, kick off a timer that will reset every // time the buffer is saved. If the timer eventually fires, simulate @@ -1933,6 +1962,7 @@ impl Project { } } } + _ => {} } @@ -1987,7 +2017,7 @@ impl Project { for buffer in plain_text_buffers { project.detect_language_for_buffer(&buffer, cx); - project.register_buffer_with_language_server(&buffer, cx); + project.register_buffer_with_language_servers(&buffer, cx); } for buffer in buffers_with_unknown_injections { @@ -2071,12 +2101,12 @@ impl Project { if let Some(worktree) = file.worktree.read(cx).as_local() { let worktree_id = worktree.id(); let worktree_abs_path = worktree.abs_path().clone(); - self.start_language_server(worktree_id, worktree_abs_path, new_language, cx); + self.start_language_servers(worktree_id, worktree_abs_path, new_language, cx); } } } - fn start_language_server( + fn start_language_servers( &mut self, worktree_id: WorktreeId, worktree_path: Arc, @@ -2090,313 +2120,333 @@ impl Project { return; } - let adapter = if let Some(adapter) = language.lsp_adapter() { - adapter - } else { - return; - }; - let key = (worktree_id, adapter.name.clone()); + let adapters = language.lsp_adapters(); + let language_servers = self.languages.start_language_servers( + language.clone(), + worktree_path, + self.client.http_client(), + cx, + ); + debug_assert_eq!(adapters.len(), language_servers.len()); - let mut initialization_options = adapter.initialization_options.clone(); + for (adapter, pending_server) in adapters.into_iter().zip(language_servers.into_iter()) { + let key = (worktree_id, adapter.name.clone()); + let lsp = &cx.global::().lsp.get(&adapter.name.0); + let override_options = lsp.map(|s| s.initialization_options.clone()).flatten(); - let lsp = &cx.global::().lsp.get(&adapter.name.0); - let override_options = lsp.map(|s| s.initialization_options.clone()).flatten(); - match (&mut initialization_options, override_options) { - (Some(initialization_options), Some(override_options)) => { - merge_json_value_into(override_options, initialization_options); + let mut initialization_options = adapter.initialization_options.clone(); + match (&mut initialization_options, override_options) { + (Some(initialization_options), Some(override_options)) => { + merge_json_value_into(override_options, initialization_options); + } + (None, override_options) => initialization_options = override_options, + _ => {} } - (None, override_options) => initialization_options = override_options, - _ => {} + + self.language_server_ids + .entry(key.clone()) + .or_insert_with(|| { + self.setup_language_adapter( + worktree_path, + initialization_options, + pending_server, + adapter, + &language, + key, + cx, + ) + }); } + } - self.language_server_ids - .entry(key.clone()) - .or_insert_with(|| { - let languages = self.languages.clone(); - let server_id = post_inc(&mut self.next_language_server_id); - let language_server = self.languages.start_language_server( - server_id, - language.clone(), - worktree_path, - self.client.http_client(), - cx, - ); - self.language_servers.insert( - server_id, - LanguageServerState::Starting(cx.spawn_weak(|this, mut cx| async move { - let workspace_config = - cx.update(|cx| languages.workspace_configuration(cx)).await; - let language_server = language_server?.await.log_err()?; - let language_server = language_server - .initialize(initialization_options) - .await - .log_err()?; - let this = this.upgrade(&cx)?; + fn setup_language_adapter( + &mut self, + worktree_path: Arc, + initialization_options: Option, + pending_server: PendingLanguageServer, + adapter: &Arc, + language: &Arc, + key: (WorktreeId, LanguageServerName), + cx: &mut ModelContext, + ) -> usize { + let server_id = pending_server.server_id; + let languages = self.languages.clone(); - language_server - .on_notification::({ - let this = this.downgrade(); - let adapter = adapter.clone(); - move |mut params, cx| { - let this = this; - let adapter = adapter.clone(); - cx.spawn(|mut cx| async move { - adapter.process_diagnostics(&mut params).await; - if let Some(this) = this.upgrade(&cx) { - this.update(&mut cx, |this, cx| { - this.update_diagnostics( - server_id, - params, - &adapter.disk_based_diagnostic_sources, - cx, - ) - .log_err(); - }); + self.language_servers.insert( + server_id, + LanguageServerState::Starting(cx.spawn_weak(|this, mut cx| async move { + let workspace_config = cx.update(|cx| languages.workspace_configuration(cx)).await; + let language_server = pending_server.task.await.log_err()?; + let language_server = language_server + .initialize(initialization_options) + .await + .log_err()?; + let this = this.upgrade(&cx)?; + + language_server + .on_notification::({ + let this = this.downgrade(); + let adapter = adapter.clone(); + move |mut params, cx| { + let this = this; + let adapter = adapter.clone(); + cx.spawn(|mut cx| async move { + adapter.process_diagnostics(&mut params).await; + if let Some(this) = this.upgrade(&cx) { + this.update(&mut cx, |this, cx| { + this.update_diagnostics( + server_id, + params, + &adapter.disk_based_diagnostic_sources, + cx, + ) + .log_err(); + }); + } + }) + .detach(); + } + }) + .detach(); + + language_server + .on_request::({ + let languages = languages.clone(); + move |params, mut cx| { + let languages = languages.clone(); + async move { + let workspace_config = + cx.update(|cx| languages.workspace_configuration(cx)).await; + Ok(params + .items + .into_iter() + .map(|item| { + if let Some(section) = &item.section { + workspace_config + .get(section) + .cloned() + .unwrap_or(serde_json::Value::Null) + } else { + workspace_config.clone() } }) - .detach(); - } - }) - .detach(); + .collect()) + } + } + }) + .detach(); - language_server - .on_request::({ - let languages = languages.clone(); - move |params, mut cx| { - let languages = languages.clone(); - async move { - let workspace_config = cx - .update(|cx| languages.workspace_configuration(cx)) - .await; - Ok(params - .items - .into_iter() - .map(|item| { - if let Some(section) = &item.section { - workspace_config - .get(section) - .cloned() - .unwrap_or(serde_json::Value::Null) - } else { - workspace_config.clone() - } - }) - .collect()) - } - } - }) - .detach(); - - // Even though we don't have handling for these requests, respond to them to - // avoid stalling any language server like `gopls` which waits for a response - // to these requests when initializing. - language_server - .on_request::({ - let this = this.downgrade(); - move |params, mut cx| async move { - if let Some(this) = this.upgrade(&cx) { - this.update(&mut cx, |this, _| { - if let Some(status) = - this.language_server_statuses.get_mut(&server_id) - { - if let lsp::NumberOrString::String(token) = - params.token - { - status.progress_tokens.insert(token); - } - } - }); - } - Ok(()) - } - }) - .detach(); - language_server - .on_request::({ - let this = this.downgrade(); - move |params, mut cx| async move { - let this = this - .upgrade(&cx) - .ok_or_else(|| anyhow!("project dropped"))?; - for reg in params.registrations { - if reg.method == "workspace/didChangeWatchedFiles" { - if let Some(options) = reg.register_options { - let options = serde_json::from_value(options)?; - this.update(&mut cx, |this, cx| { - this.on_lsp_did_change_watched_files( - server_id, options, cx, - ); - }); - } + // Even though we don't have handling for these requests, respond to them to + // avoid stalling any language server like `gopls` which waits for a response + // to these requests when initializing. + language_server + .on_request::({ + let this = this.downgrade(); + move |params, mut cx| async move { + if let Some(this) = this.upgrade(&cx) { + this.update(&mut cx, |this, _| { + if let Some(status) = + this.language_server_statuses.get_mut(&server_id) + { + if let lsp::NumberOrString::String(token) = params.token { + status.progress_tokens.insert(token); } } - Ok(()) - } - }) - .detach(); - - language_server - .on_request::({ - let this = this.downgrade(); - let adapter = adapter.clone(); - let language_server = language_server.clone(); - move |params, cx| { - Self::on_lsp_workspace_edit( - this, - params, - server_id, - adapter.clone(), - language_server.clone(), - cx, - ) - } - }) - .detach(); - - let disk_based_diagnostics_progress_token = - adapter.disk_based_diagnostics_progress_token.clone(); - - language_server - .on_notification::({ - let this = this.downgrade(); - move |params, mut cx| { - if let Some(this) = this.upgrade(&cx) { + }); + } + Ok(()) + } + }) + .detach(); + language_server + .on_request::({ + let this = this.downgrade(); + move |params, mut cx| async move { + let this = this + .upgrade(&cx) + .ok_or_else(|| anyhow!("project dropped"))?; + for reg in params.registrations { + if reg.method == "workspace/didChangeWatchedFiles" { + if let Some(options) = reg.register_options { + let options = serde_json::from_value(options)?; this.update(&mut cx, |this, cx| { - this.on_lsp_progress( - params, - server_id, - disk_based_diagnostics_progress_token.clone(), - cx, + this.on_lsp_did_change_watched_files( + server_id, options, cx, ); }); } } - }) - .detach(); + } + Ok(()) + } + }) + .detach(); - language_server - .notify::( - lsp::DidChangeConfigurationParams { - settings: workspace_config, - }, + language_server + .on_request::({ + let this = this.downgrade(); + let adapter = adapter.clone(); + let language_server = language_server.clone(); + move |params, cx| { + Self::on_lsp_workspace_edit( + this, + params, + server_id, + adapter.clone(), + language_server.clone(), + cx, ) - .ok(); + } + }) + .detach(); - this.update(&mut cx, |this, cx| { - // If the language server for this key doesn't match the server id, don't store the - // server. Which will cause it to be dropped, killing the process - if this - .language_server_ids - .get(&key) - .map(|id| id != &server_id) - .unwrap_or(false) - { - return None; + let disk_based_diagnostics_progress_token = + adapter.disk_based_diagnostics_progress_token.clone(); + + language_server + .on_notification::({ + let this = this.downgrade(); + move |params, mut cx| { + if let Some(this) = this.upgrade(&cx) { + this.update(&mut cx, |this, cx| { + this.on_lsp_progress( + params, + server_id, + disk_based_diagnostics_progress_token.clone(), + cx, + ); + }); } + } + }) + .detach(); - // Update language_servers collection with Running variant of LanguageServerState - // indicating that the server is up and running and ready - this.language_servers.insert( - server_id, - LanguageServerState::Running { - adapter: adapter.clone(), - language, - watched_paths: Default::default(), - server: language_server.clone(), - simulate_disk_based_diagnostics_completion: None, - }, - ); - this.language_server_statuses.insert( - server_id, - LanguageServerStatus { + language_server + .notify::( + lsp::DidChangeConfigurationParams { + settings: workspace_config, + }, + ) + .ok(); + + this.update(&mut cx, |this, cx| { + // If the language server for this key doesn't match the server id, don't store the + // server. Which will cause it to be dropped, killing the process + if this + .language_server_ids + .get(&key) + .map(|id| id != &server_id) + .unwrap_or(false) + { + return None; + } + + // Update language_servers collection with Running variant of LanguageServerState + // indicating that the server is up and running and ready + this.language_servers.insert( + server_id, + LanguageServerState::Running { + adapter: adapter.clone(), + language: language.clone(), + watched_paths: Default::default(), + server: language_server.clone(), + simulate_disk_based_diagnostics_completion: None, + }, + ); + this.language_server_statuses.insert( + server_id, + LanguageServerStatus { + name: language_server.name().to_string(), + pending_work: Default::default(), + has_pending_diagnostic_updates: false, + progress_tokens: Default::default(), + }, + ); + + if let Some(project_id) = this.remote_id() { + this.client + .send(proto::StartLanguageServer { + project_id, + server: Some(proto::LanguageServer { + id: server_id as u64, name: language_server.name().to_string(), - pending_work: Default::default(), - has_pending_diagnostic_updates: false, - progress_tokens: Default::default(), - }, - ); + }), + }) + .log_err(); + } - if let Some(project_id) = this.remote_id() { - this.client - .send(proto::StartLanguageServer { - project_id, - server: Some(proto::LanguageServer { - id: server_id as u64, - name: language_server.name().to_string(), - }), - }) - .log_err(); + // Tell the language server about every open buffer in the worktree that matches the language. + for buffer in this.opened_buffers.values() { + if let Some(buffer_handle) = buffer.upgrade(cx) { + let buffer = buffer_handle.read(cx); + let file = match File::from_dyn(buffer.file()) { + Some(file) => file, + None => continue, + }; + let language = match buffer.language() { + Some(language) => language, + None => continue, + }; + + if file.worktree.read(cx).id() != key.0 + || !language.lsp_adapters().iter().any(|a| a.name == key.1) + { + continue; } - // Tell the language server about every open buffer in the worktree that matches the language. - for buffer in this.opened_buffers.values() { - if let Some(buffer_handle) = buffer.upgrade(cx) { - let buffer = buffer_handle.read(cx); - let file = if let Some(file) = File::from_dyn(buffer.file()) { - file - } else { - continue; - }; - let language = if let Some(language) = buffer.language() { - language - } else { - continue; - }; - if file.worktree.read(cx).id() != key.0 - || language.lsp_adapter().map(|a| a.name.clone()) - != Some(key.1.clone()) - { - continue; - } + let file = file.as_local()?; + let versions = this + .buffer_snapshots + .entry(buffer.remote_id()) + .or_default() + .entry(server_id) + .or_insert_with(|| { + vec![LspBufferSnapshot { + version: 0, + snapshot: buffer.text_snapshot(), + }] + }); - let file = file.as_local()?; - let versions = this - .buffer_snapshots - .entry(buffer.remote_id()) - .or_insert_with(|| vec![(0, buffer.text_snapshot())]); - - let (version, initial_snapshot) = versions.last().unwrap(); - let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap(); - language_server - .notify::( - lsp::DidOpenTextDocumentParams { - text_document: lsp::TextDocumentItem::new( - uri, - adapter - .language_ids - .get(language.name().as_ref()) - .cloned() - .unwrap_or_default(), - *version, - initial_snapshot.text(), - ), - }, - ) - .log_err()?; - buffer_handle.update(cx, |buffer, cx| { - buffer.set_completion_triggers( - language_server - .capabilities() - .completion_provider - .as_ref() - .and_then(|provider| { - provider.trigger_characters.clone() - }) + let snapshot = versions.last().unwrap(); + let version = snapshot.version; + let initial_snapshot = snapshot.snapshot; + let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap(); + language_server + .notify::( + lsp::DidOpenTextDocumentParams { + text_document: lsp::TextDocumentItem::new( + uri, + adapter + .language_ids + .get(language.name().as_ref()) + .cloned() .unwrap_or_default(), - cx, - ) - }); - } - } + version, + initial_snapshot.text(), + ), + }, + ) + .log_err()?; + buffer_handle.update(cx, |buffer, cx| { + buffer.set_completion_triggers( + language_server + .capabilities() + .completion_provider + .as_ref() + .and_then(|provider| provider.trigger_characters.clone()) + .unwrap_or_default(), + cx, + ) + }); + } + } - cx.notify(); - Some(language_server) - }) - })), - ); - - server_id - }); + cx.notify(); + Some(language_server) + }) + })), + ); + server_id } // Returns a list of all of the worktrees which no longer have a language server and the root path @@ -2476,52 +2526,64 @@ impl Project { }) .collect(); for (worktree_id, worktree_abs_path, language) in language_server_lookup_info { - self.restart_language_server(worktree_id, worktree_abs_path, language, cx); + self.restart_language_servers(worktree_id, worktree_abs_path, language, cx); } None } - fn restart_language_server( + fn restart_language_servers( &mut self, worktree_id: WorktreeId, fallback_path: Arc, language: Arc, cx: &mut ModelContext, ) { - let adapter = if let Some(adapter) = language.lsp_adapter() { - adapter - } else { + let mut stops = Vec::new(); + for adapter in language.lsp_adapters() { + stops.push(self.stop_language_server(worktree_id, adapter.name.clone(), cx)); + } + + if stops.is_empty() { return; - }; + } + let mut stops = stops.into_iter(); - let server_name = adapter.name.clone(); - let stop = self.stop_language_server(worktree_id, server_name.clone(), cx); cx.spawn_weak(|this, mut cx| async move { - let (original_root_path, orphaned_worktrees) = stop.await; - if let Some(this) = this.upgrade(&cx) { - this.update(&mut cx, |this, cx| { - // Attempt to restart using original server path. Fallback to passed in - // path if we could not retrieve the root path - let root_path = original_root_path - .map(|path_buf| Arc::from(path_buf.as_path())) - .unwrap_or(fallback_path); + let (original_root_path, mut orphaned_worktrees) = stops.next().unwrap().await; + for stop in stops { + let (_, worktrees) = stop.await; + orphaned_worktrees.extend_from_slice(&worktrees); + } - this.start_language_server(worktree_id, root_path, language, cx); + let this = match this.upgrade(&cx) { + Some(this) => this, + None => return, + }; - // Lookup new server id and set it for each of the orphaned worktrees + this.update(&mut cx, |this, cx| { + // Attempt to restart using original server path. Fallback to passed in + // path if we could not retrieve the root path + let root_path = original_root_path + .map(|path_buf| Arc::from(path_buf.as_path())) + .unwrap_or(fallback_path); + + this.start_language_servers(worktree_id, root_path, language, cx); + + // Lookup new server ids and set them for each of the orphaned worktrees + for adapter in language.lsp_adapters() { if let Some(new_server_id) = this .language_server_ids - .get(&(worktree_id, server_name.clone())) + .get(&(worktree_id, adapter.name.clone())) .cloned() { for orphaned_worktree in orphaned_worktrees { this.language_server_ids - .insert((orphaned_worktree, server_name.clone()), new_server_id); + .insert((orphaned_worktree, adapter.name.clone()), new_server_id); } } - }); - } + } + }); }) .detach(); } @@ -3074,7 +3136,7 @@ impl Project { let file = File::from_dyn(buffer.file())?; let buffer_abs_path = file.as_local().map(|f| f.abs_path(cx)); let server = self - .language_server_for_buffer(buffer, cx) + .primary_language_servers_for_buffer(buffer, cx) .map(|s| s.1.clone()); Some((buffer_handle, buffer_abs_path, server)) }) @@ -3323,7 +3385,7 @@ impl Project { if let Some(lsp_edits) = lsp_edits { this.update(cx, |this, cx| { - this.edits_from_lsp(buffer, lsp_edits, None, cx) + this.edits_from_lsp(buffer, lsp_edits, language_server.server_id(), None, cx) }) .await } else { @@ -3654,7 +3716,7 @@ impl Project { let buffer_id = buffer.remote_id(); if self.is_local() { - let lang_server = match self.language_server_for_buffer(buffer, cx) { + let lang_server = match self.primary_language_servers_for_buffer(buffer, cx) { Some((_, server)) => server.clone(), _ => return Task::ready(Ok(Default::default())), }; @@ -3667,7 +3729,13 @@ impl Project { if let Some(edits) = resolved_completion.additional_text_edits { let edits = this .update(&mut cx, |this, cx| { - this.edits_from_lsp(&buffer_handle, edits, None, cx) + this.edits_from_lsp( + &buffer_handle, + edits, + lang_server.server_id(), + None, + cx, + ) }) .await?; @@ -3757,12 +3825,13 @@ impl Project { ) -> Task> { if self.is_local() { let buffer = buffer_handle.read(cx); - let (lsp_adapter, lang_server) = - if let Some((adapter, server)) = self.language_server_for_buffer(buffer, cx) { - (adapter.clone(), server.clone()) - } else { - return Task::ready(Ok(Default::default())); - }; + let (lsp_adapter, lang_server) = if let Some((adapter, server)) = + self.language_server_for_buffer(buffer, action.server_id, cx) + { + (adapter.clone(), server.clone()) + } else { + return Task::ready(Ok(Default::default())); + }; let range = action.range.to_point_utf16(buffer); cx.spawn(|this, mut cx| async move { @@ -3896,6 +3965,7 @@ impl Project { .await?; } } + lsp::DocumentChangeOperation::Op(lsp::ResourceOp::Rename(op)) => { let source_abs_path = op .old_uri @@ -3912,6 +3982,7 @@ impl Project { ) .await?; } + lsp::DocumentChangeOperation::Op(lsp::ResourceOp::Delete(op)) => { let abs_path = op .uri @@ -3924,6 +3995,7 @@ impl Project { fs.remove_file(&abs_path, options).await?; } } + lsp::DocumentChangeOperation::Edit(op) => { let buffer_to_edit = this .update(cx, |this, cx| { @@ -3945,6 +4017,7 @@ impl Project { this.edits_from_lsp( &buffer_to_edit, edits, + language_server.server_id(), op.text_document.version, cx, ) @@ -4214,6 +4287,7 @@ impl Project { } } + // TODO: Wire this up to allow selecting a server? fn request_lsp( &self, buffer_handle: ModelHandle, @@ -4227,7 +4301,7 @@ impl Project { if self.is_local() { let file = File::from_dyn(buffer.file()).and_then(File::as_local); if let Some((file, language_server)) = file.zip( - self.language_server_for_buffer(buffer, cx) + self.primary_language_servers_for_buffer(buffer, cx) .map(|(_, server)| server.clone()), ) { let lsp_params = request.to_lsp(&file.abs_path(cx), buffer, &language_server, cx); @@ -4241,7 +4315,13 @@ impl Project { .await .context("lsp request failed")?; request - .response_from_lsp(response, this, buffer_handle, cx) + .response_from_lsp( + response, + this, + buffer_handle, + language_server.server_id(), + cx, + ) .await }); } @@ -4491,9 +4571,9 @@ impl Project { } for (buffer, old_path) in renamed_buffers { - self.unregister_buffer_from_language_server(&buffer, old_path, cx); + self.unregister_buffer_from_language_servers(&buffer, old_path, cx); self.detect_language_for_buffer(&buffer, cx); - self.register_buffer_with_language_server(&buffer, cx); + self.register_buffer_with_language_servers(&buffer, cx); } } @@ -6048,10 +6128,11 @@ impl Project { &mut self, buffer: &ModelHandle, lsp_edits: impl 'static + Send + IntoIterator, + server_id: usize, version: Option, cx: &mut ModelContext, ) -> Task, String)>>> { - let snapshot = self.buffer_snapshot_for_lsp_version(buffer, version, cx); + let snapshot = self.buffer_snapshot_for_lsp_version(buffer, server_id, version, cx); cx.background().spawn(async move { let snapshot = snapshot?; let mut lsp_edits = lsp_edits @@ -6150,6 +6231,7 @@ impl Project { fn buffer_snapshot_for_lsp_version( &mut self, buffer: &ModelHandle, + server_id: usize, version: Option, cx: &AppContext, ) -> Result { @@ -6160,51 +6242,85 @@ impl Project { let snapshots = self .buffer_snapshots .get_mut(&buffer_id) - .ok_or_else(|| anyhow!("no snapshot found for buffer {}", buffer_id))?; - let found_snapshot = snapshots - .binary_search_by_key(&version, |e| e.0) - .map(|ix| snapshots[ix].1.clone()) - .map_err(|_| { - anyhow!( - "snapshot not found for buffer {} at version {}", - buffer_id, - version - ) + .and_then(|m| m.get_mut(&server_id)) + .ok_or_else(|| { + anyhow!("no snapshots found for buffer {buffer_id} and server {server_id}") })?; - snapshots.retain(|(snapshot_version, _)| { - snapshot_version + OLD_VERSIONS_TO_RETAIN >= version - }); + + let found_snapshot = snapshots + .binary_search_by_key(&version, |e| e.version) + .map(|ix| snapshots[ix].snapshot.clone()) + .map_err(|_| { + anyhow!("snapshot not found for buffer {buffer_id} server {server_id} at version {version}") + })?; + + snapshots.retain(|snapshot| snapshot.version + OLD_VERSIONS_TO_RETAIN >= version); Ok(found_snapshot) } else { Ok((buffer.read(cx)).text_snapshot()) } } - fn language_server_for_buffer( + fn running_language_servers_for_buffer( + &self, + buffer: &Buffer, + cx: &AppContext, + ) -> impl Iterator, &Arc)> { + self.language_server_ids_for_buffer(buffer, cx) + .into_iter() + .filter_map(|server_id| { + let server = self.language_servers.get(&server_id)?; + if let LanguageServerState::Running { + adapter, server, .. + } = server + { + Some((adapter, server)) + } else { + None + } + }) + } + + fn language_servers_for_buffer( + &self, + buffer: &Buffer, + cx: &AppContext, + ) -> Vec<(&Arc, &Arc)> { + self.running_language_servers_for_buffer(buffer, cx) + .collect() + } + + fn primary_language_servers_for_buffer( &self, buffer: &Buffer, cx: &AppContext, ) -> Option<(&Arc, &Arc)> { - let server_id = self.language_server_id_for_buffer(buffer, cx)?; - let server = self.language_servers.get(&server_id)?; - if let LanguageServerState::Running { - adapter, server, .. - } = server - { - Some((adapter, server)) - } else { - None - } + self.running_language_servers_for_buffer(buffer, cx).next() } - fn language_server_id_for_buffer(&self, buffer: &Buffer, cx: &AppContext) -> Option { + fn language_server_for_buffer( + &self, + buffer: &Buffer, + server_id: usize, + cx: &AppContext, + ) -> Option<(&Arc, &Arc)> { + self.running_language_servers_for_buffer(buffer, cx) + .find(|(_, s)| s.server_id() == server_id) + } + + fn language_server_ids_for_buffer(&self, buffer: &Buffer, cx: &AppContext) -> Vec { if let Some((file, language)) = File::from_dyn(buffer.file()).zip(buffer.language()) { - let name = language.lsp_adapter()?.name.clone(); let worktree_id = file.worktree_id(cx); - let key = (worktree_id, name); - self.language_server_ids.get(&key).copied() + language + .lsp_adapters() + .iter() + .flat_map(|adapter| { + let key = (worktree_id, adapter.name.clone()); + self.language_server_ids.get(&key).copied() + }) + .collect() } else { - None + Vec::new() } } } diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index b4bcba24db..09c3326739 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -1573,6 +1573,7 @@ async fn test_edits_from_lsp_with_past_version(cx: &mut gpui::TestAppContext) { new_text: "".into(), }, ], + 0, Some(lsp_document_version), cx, ) @@ -1667,6 +1668,7 @@ async fn test_edits_from_lsp_with_edits_on_adjacent_lines(cx: &mut gpui::TestApp new_text: "".into(), }, ], + 0, None, cx, ) @@ -1770,6 +1772,7 @@ async fn test_invalid_edits_from_lsp(cx: &mut gpui::TestAppContext) { .unindent(), }, ], + 0, None, cx, ) @@ -2258,7 +2261,7 @@ async fn test_save_as(cx: &mut gpui::TestAppContext) { ..Default::default() }, tree_sitter_rust::language(), - None, + vec![], |_| Default::default(), ); diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index ff7a882f1a..72b66f3d78 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -684,9 +684,10 @@ message SearchProjectResponse { } message CodeAction { - Anchor start = 1; - Anchor end = 2; - bytes lsp_action = 3; + uint64 server_id = 1; + Anchor start = 2; + Anchor end = 3; + bytes lsp_action = 4; } message ProjectTransaction { diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 12e6c1b1f2..db5d5913cb 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -37,121 +37,107 @@ pub fn init( themes: Arc, node_runtime: Arc, ) { - for (name, grammar, lsp_adapter) in [ + fn adapter_arc(adapter: impl LspAdapter) -> Arc { + Arc::new(adapter) + } + + let languages_list = [ ( "c", tree_sitter_c::language(), - Some(Arc::new(c::CLspAdapter) as Arc), + vec![adapter_arc(c::CLspAdapter)], ), ( "cpp", tree_sitter_cpp::language(), - Some(Arc::new(c::CLspAdapter)), - ), - ( - "css", - tree_sitter_css::language(), - None, // + vec![adapter_arc(c::CLspAdapter)], ), + ("css", tree_sitter_css::language(), vec![]), ( "elixir", tree_sitter_elixir::language(), - Some(Arc::new(elixir::ElixirLspAdapter)), + vec![adapter_arc(elixir::ElixirLspAdapter)], ), ( "go", tree_sitter_go::language(), - Some(Arc::new(go::GoLspAdapter)), + vec![adapter_arc(go::GoLspAdapter)], ), ( "json", tree_sitter_json::language(), - Some(Arc::new(json::JsonLspAdapter::new( + vec![adapter_arc(json::JsonLspAdapter::new( node_runtime.clone(), languages.clone(), themes.clone(), - ))), - ), - ( - "markdown", - tree_sitter_markdown::language(), - None, // + ))], ), + ("markdown", tree_sitter_markdown::language(), vec![]), ( "python", tree_sitter_python::language(), - Some(Arc::new(python::PythonLspAdapter::new( + vec![adapter_arc(python::PythonLspAdapter::new( node_runtime.clone(), - ))), + ))], ), ( "rust", tree_sitter_rust::language(), - Some(Arc::new(rust::RustLspAdapter)), - ), - ( - "toml", - tree_sitter_toml::language(), - None, // + vec![adapter_arc(rust::RustLspAdapter)], ), + ("toml", tree_sitter_toml::language(), vec![]), ( "tsx", tree_sitter_typescript::language_tsx(), - Some(Arc::new(typescript::TypeScriptLspAdapter::new( + vec![adapter_arc(typescript::TypeScriptLspAdapter::new( node_runtime.clone(), - ))), + ))], ), ( "typescript", tree_sitter_typescript::language_typescript(), - Some(Arc::new(typescript::TypeScriptLspAdapter::new( + vec![adapter_arc(typescript::TypeScriptLspAdapter::new( node_runtime.clone(), - ))), + ))], ), ( "javascript", tree_sitter_typescript::language_tsx(), - Some(Arc::new(typescript::TypeScriptLspAdapter::new( + vec![adapter_arc(typescript::TypeScriptLspAdapter::new( node_runtime.clone(), - ))), + ))], ), ( "html", tree_sitter_html::language(), - Some(Arc::new(html::HtmlLspAdapter::new(node_runtime.clone()))), + vec![adapter_arc(html::HtmlLspAdapter::new(node_runtime.clone()))], ), ( "ruby", tree_sitter_ruby::language(), - Some(Arc::new(ruby::RubyLanguageServer)), + vec![adapter_arc(ruby::RubyLanguageServer)], ), ( "erb", tree_sitter_embedded_template::language(), - Some(Arc::new(ruby::RubyLanguageServer)), - ), - ( - "scheme", - tree_sitter_scheme::language(), - None, // - ), - ( - "racket", - tree_sitter_racket::language(), - None, // + vec![adapter_arc(ruby::RubyLanguageServer)], ), + ("scheme", tree_sitter_scheme::language(), vec![]), + ("racket", tree_sitter_racket::language(), vec![]), ( "lua", tree_sitter_lua::language(), - Some(Arc::new(lua::LuaLspAdapter)), + vec![adapter_arc(lua::LuaLspAdapter)], ), ( "yaml", tree_sitter_yaml::language(), - Some(Arc::new(yaml::YamlLspAdapter::new(node_runtime.clone()))), + vec![adapter_arc(yaml::YamlLspAdapter::new(node_runtime.clone()))], ), - ] { - languages.register(name, load_config(name), grammar, lsp_adapter, load_queries); + ]; + + for (name, grammar, lsp_adapters) in languages_list { + languages.register(name, load_config(name), grammar, lsp_adapters, load_queries); } } @@ -163,7 +149,7 @@ pub async fn language( ) -> Arc { Arc::new( Language::new(load_config(name), Some(grammar)) - .with_lsp_adapter(lsp_adapter) + .with_lsp_adapters(lsp_adapter) .await .with_queries(load_queries(name)) .unwrap(), diff --git a/crates/zed/src/languages/typescript.rs b/crates/zed/src/languages/typescript.rs index d6097d9b06..3121bfe81f 100644 --- a/crates/zed/src/languages/typescript.rs +++ b/crates/zed/src/languages/typescript.rs @@ -37,7 +37,7 @@ impl TypeScriptLspAdapter { } } -struct Versions { +struct TypeScriptVersions { typescript_version: String, server_version: String, } @@ -52,7 +52,8 @@ impl LspAdapter for TypeScriptLspAdapter { &self, _: Arc, ) -> Result> { - Ok(Box::new(Versions { + dbg!(); + Ok(Box::new(TypeScriptVersions { typescript_version: self.node.npm_package_latest_version("typescript").await?, server_version: self .node @@ -67,7 +68,8 @@ impl LspAdapter for TypeScriptLspAdapter { _: Arc, container_dir: PathBuf, ) -> Result { - let versions = versions.downcast::().unwrap(); + dbg!(); + let versions = versions.downcast::().unwrap(); let server_path = container_dir.join(Self::NEW_SERVER_PATH); if fs::metadata(&server_path).await.is_err() { @@ -92,18 +94,10 @@ impl LspAdapter for TypeScriptLspAdapter { } async fn cached_server_binary(&self, container_dir: PathBuf) -> Option { + dbg!(); (|| async move { - let mut last_version_dir = None; - let mut entries = fs::read_dir(&container_dir).await?; - while let Some(entry) = entries.next().await { - let entry = entry?; - if entry.file_type().await?.is_dir() { - last_version_dir = Some(entry.path()); - } - } - let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?; - let old_server_path = last_version_dir.join(Self::OLD_SERVER_PATH); - let new_server_path = last_version_dir.join(Self::NEW_SERVER_PATH); + let old_server_path = container_dir.join(Self::OLD_SERVER_PATH); + let new_server_path = container_dir.join(Self::NEW_SERVER_PATH); if new_server_path.exists() { Ok(LanguageServerBinary { path: self.node.binary_path().await?, @@ -117,7 +111,7 @@ impl LspAdapter for TypeScriptLspAdapter { } else { Err(anyhow!( "missing executable in directory {:?}", - last_version_dir + container_dir )) } })() @@ -170,6 +164,86 @@ impl LspAdapter for TypeScriptLspAdapter { } } +pub struct EsLintLspAdapter { + node: Arc, +} + +impl EsLintLspAdapter { + const SERVER_PATH: &'static str = "node_modules/typescript-language-server/lib/cli.mjs"; + + pub fn new(node: Arc) -> Self { + EsLintLspAdapter { node } + } +} + +#[async_trait] +impl LspAdapter for EsLintLspAdapter { + async fn name(&self) -> LanguageServerName { + LanguageServerName("eslint".into()) + } + + async fn fetch_latest_server_version( + &self, + _: Arc, + ) -> Result> { + Ok(Box::new( + self.node.npm_package_latest_version("eslint").await?, + )) + } + + async fn fetch_server_binary( + &self, + versions: Box, + _: Arc, + container_dir: PathBuf, + ) -> Result { + let version = versions.downcast::().unwrap(); + let server_path = container_dir.join(Self::SERVER_PATH); + + if fs::metadata(&server_path).await.is_err() { + self.node + .npm_install_packages([("eslint", version.as_str())], &container_dir) + .await?; + } + + Ok(LanguageServerBinary { + path: self.node.binary_path().await?, + arguments: server_binary_arguments(&server_path), + }) + } + + async fn cached_server_binary(&self, container_dir: PathBuf) -> Option { + (|| async move { + let server_path = container_dir.join(Self::SERVER_PATH); + if server_path.exists() { + Ok(LanguageServerBinary { + path: self.node.binary_path().await?, + arguments: server_binary_arguments(&server_path), + }) + } else { + Err(anyhow!( + "missing executable in directory {:?}", + container_dir + )) + } + })() + .await + .log_err() + } + + async fn label_for_completion( + &self, + item: &lsp::CompletionItem, + language: &Arc, + ) -> Option { + None + } + + async fn initialization_options(&self) -> Option { + None + } +} + #[cfg(test)] mod tests { use gpui::TestAppContext;