diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 0bef4241040df0ea53616aec94acf1d6627a04a7..8282f1c8ff030c2c28c5313c6545493f18222547 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -11,7 +11,7 @@ use serde_json::{json, value::RawValue, Value}; use smol::{ channel, io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader}, - process, + process::{self, Child}, }; use std::{ future::Future, @@ -44,6 +44,7 @@ pub struct LanguageServer { io_tasks: Mutex>, Task>)>>, output_done_rx: Mutex>, root_path: PathBuf, + _server: Option, } pub struct Subscription { @@ -118,11 +119,20 @@ impl LanguageServer { .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::inherit()) + .kill_on_drop(true) .spawn()?; + let stdin = server.stdin.take().unwrap(); - let stdout = server.stdout.take().unwrap(); - let mut server = - Self::new_internal(server_id, stdin, stdout, root_path, cx, |notification| { + let stout = server.stdout.take().unwrap(); + + let mut server = Self::new_internal( + server_id, + stdin, + stout, + Some(server), + root_path, + cx, + |notification| { log::info!( "unhandled notification {}:\n{}", notification.method, @@ -131,7 +141,8 @@ impl LanguageServer { ) .unwrap() ); - }); + }, + ); if let Some(name) = binary_path.file_name() { server.name = name.to_string_lossy().to_string(); } @@ -142,6 +153,7 @@ impl LanguageServer { server_id: usize, stdin: Stdin, stdout: Stdout, + server: Option, root_path: &Path, cx: AsyncAppContext, mut on_unhandled_notification: F, @@ -242,6 +254,7 @@ impl LanguageServer { io_tasks: Mutex::new(Some((input_task, output_task))), output_done_rx: Mutex::new(Some(output_done_rx)), root_path: root_path.to_path_buf(), + _server: server, } } @@ -480,6 +493,10 @@ impl LanguageServer { self.server_id } + pub fn root_path(&self) -> &PathBuf { + &self.root_path + } + pub fn request( &self, params: T::Params, @@ -608,6 +625,7 @@ impl LanguageServer { 0, stdin_writer, stdout_reader, + None, Path::new("/"), cx.clone(), |_| {}, @@ -617,6 +635,7 @@ impl LanguageServer { 0, stdout_writer, stdin_reader, + None, Path::new("/"), cx.clone(), move |msg| { diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index ee2bf37aa1f3e5574e1fc2231e92b515b7ffa85e..5897b5fcb6a401e4eadf0d8338c44e818eaef0cd 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -242,7 +242,7 @@ impl LspCommand for PerformRename { .read_with(&cx, |project, cx| { project .language_server_for_buffer(buffer.read(cx), cx) - .cloned() + .map(|(adapter, server)| (adapter.clone(), server.clone())) }) .ok_or_else(|| anyhow!("no language server found for buffer"))?; Project::deserialize_workspace_edit( @@ -359,7 +359,7 @@ impl LspCommand for GetDefinition { .read_with(&cx, |project, cx| { project .language_server_for_buffer(buffer.read(cx), cx) - .cloned() + .map(|(adapter, server)| (adapter.clone(), server.clone())) }) .ok_or_else(|| anyhow!("no language server found for buffer"))?; @@ -388,8 +388,8 @@ impl LspCommand for GetDefinition { .update(&mut cx, |this, cx| { this.open_local_buffer_via_lsp( target_uri, - lsp_adapter.clone(), - language_server.clone(), + language_server.server_id(), + lsp_adapter.name(), cx, ) }) @@ -599,7 +599,7 @@ impl LspCommand for GetReferences { .read_with(&cx, |project, cx| { project .language_server_for_buffer(buffer.read(cx), cx) - .cloned() + .map(|(adapter, server)| (adapter.clone(), server.clone())) }) .ok_or_else(|| anyhow!("no language server found for buffer"))?; @@ -609,8 +609,8 @@ impl LspCommand for GetReferences { .update(&mut cx, |this, cx| { this.open_local_buffer_via_lsp( lsp_location.uri, - lsp_adapter.clone(), - language_server.clone(), + language_server.server_id(), + lsp_adapter.name(), cx, ) }) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 8ce07a6abdf2a654008dc218826ea2b4a64e7896..995b7bdb3e91ec2a486de8107f3378c1ae0b5d08 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -70,14 +70,26 @@ pub struct ProjectStore { projects: Vec>, } +// Language server state is stored across 3 collections: +// language_servers => +// a mapping from unique server id to LanguageServerState which can either be a task for a +// server in the process of starting, or a running server with adapter and language server arcs +// language_server_ids => a mapping from worktreeId and server name to the unique server id +// language_server_statuses => a mapping from unique server id to the current server status +// +// Multiple worktrees can map to the same language server for example when you jump to the definition +// of a file in the standard library. So language_server_ids is used to look up which server is active +// for a given worktree and language server name +// +// When starting a language server, first the id map is checked to make sure a server isn't already available +// for that worktree. If there is one, it finishes early. Otherwise, a new id is allocated and and +// the Starting variant of LanguageServerState is stored in the language_servers map. pub struct Project { worktrees: Vec, active_entry: Option, languages: Arc, - language_servers: - HashMap<(WorktreeId, LanguageServerName), (Arc, Arc)>, - started_language_servers: - HashMap<(WorktreeId, LanguageServerName), Task>>>, + language_servers: HashMap, + language_server_ids: HashMap<(WorktreeId, LanguageServerName), usize>, language_server_statuses: BTreeMap, language_server_settings: Arc>, last_workspace_edits_by_language_server: HashMap, @@ -174,6 +186,14 @@ pub enum Event { ContactCancelledJoinRequest(Arc), } +pub enum LanguageServerState { + Starting(Task>>), + Running { + adapter: Arc, + server: Arc, + }, +} + #[derive(Serialize)] pub struct LanguageServerStatus { pub name: String, @@ -437,7 +457,7 @@ impl Project { next_entry_id: Default::default(), next_diagnostic_group_id: Default::default(), language_servers: Default::default(), - started_language_servers: Default::default(), + language_server_ids: Default::default(), language_server_statuses: Default::default(), last_workspace_edits_by_language_server: Default::default(), language_server_settings: Default::default(), @@ -536,7 +556,7 @@ impl Project { }), }, language_servers: Default::default(), - started_language_servers: Default::default(), + language_server_ids: Default::default(), language_server_settings: Default::default(), language_server_statuses: response .language_servers @@ -691,7 +711,7 @@ impl Project { if let Some(lsp_adapter) = language.lsp_adapter() { if !settings.enable_language_server(Some(&language.name())) { let lsp_name = lsp_adapter.name(); - for (worktree_id, started_lsp_name) in self.started_language_servers.keys() { + for (worktree_id, started_lsp_name) in self.language_server_ids.keys() { if lsp_name == *started_lsp_name { language_servers_to_stop.push((*worktree_id, started_lsp_name.clone())); } @@ -1538,8 +1558,8 @@ impl Project { fn open_local_buffer_via_lsp( &mut self, abs_path: lsp::Url, - lsp_adapter: Arc, - lsp_server: Arc, + language_server_id: usize, + language_server_name: LanguageServerName, cx: &mut ModelContext, ) -> Task>> { cx.spawn(|this, mut cx| async move { @@ -1557,9 +1577,9 @@ impl Project { }) .await?; this.update(&mut cx, |this, cx| { - this.language_servers.insert( - (worktree.read(cx).id(), lsp_adapter.name()), - (lsp_adapter, lsp_server), + this.language_server_ids.insert( + (worktree.read(cx).id(), language_server_name), + language_server_id, ); }); (worktree, PathBuf::new()) @@ -1726,9 +1746,16 @@ impl Project { if let Some(adapter) = language.lsp_adapter() { language_id = adapter.id_for_language(language.name().as_ref()); language_server = self - .language_servers + .language_server_ids .get(&(worktree_id, adapter.name())) - .cloned(); + .and_then(|id| self.language_servers.get(&id)) + .and_then(|server_state| { + if let LanguageServerState::Running { server, .. } = server_state { + Some(server.clone()) + } else { + None + } + }); } } @@ -1739,7 +1766,7 @@ impl Project { } } - if let Some((_, server)) = language_server { + if let Some(server) = language_server { server .notify::( lsp::DidOpenTextDocumentParams { @@ -1816,9 +1843,9 @@ impl Project { } } BufferEvent::Edited { .. } => { - let (_, language_server) = self - .language_server_for_buffer(buffer.read(cx), cx)? - .clone(); + 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); @@ -1907,16 +1934,19 @@ impl Project { fn language_servers_for_worktree( &self, worktree_id: WorktreeId, - ) -> impl Iterator, Arc)> { - self.language_servers.iter().filter_map( - move |((language_server_worktree_id, _), server)| { + ) -> impl Iterator, &Arc)> { + self.language_server_ids + .iter() + .filter_map(move |((language_server_worktree_id, _), id)| { if *language_server_worktree_id == worktree_id { - Some(server) - } else { - None + if let Some(LanguageServerState::Running { adapter, server }) = + self.language_servers.get(&id) + { + return Some((adapter, server)); + } } - }, - ) + None + }) } fn assign_language_to_buffer( @@ -1960,7 +1990,8 @@ impl Project { return; }; let key = (worktree_id, adapter.name()); - self.started_language_servers + + self.language_server_ids .entry(key.clone()) .or_insert_with(|| { let server_id = post_inc(&mut self.next_language_server_id); @@ -1971,252 +2002,298 @@ impl Project { self.client.http_client(), cx, ); - cx.spawn_weak(|this, mut cx| async move { - let language_server = language_server?.await.log_err()?; - let language_server = language_server - .initialize(adapter.initialization_options()) - .await - .log_err()?; - let this = this.upgrade(&cx)?; - let disk_based_diagnostics_progress_token = - adapter.disk_based_diagnostics_progress_token(); + self.language_servers.insert( + server_id, + LanguageServerState::Starting(cx.spawn_weak(|this, mut cx| async move { + let language_server = language_server?.await.log_err()?; + let language_server = language_server + .initialize(adapter.initialization_options()) + .await + .log_err()?; + let this = this.upgrade(&cx)?; + let disk_based_diagnostics_progress_token = + adapter.disk_based_diagnostics_progress_token(); - language_server - .on_notification::({ - let this = this.downgrade(); - let adapter = adapter.clone(); - move |params, mut cx| { - if let Some(this) = this.upgrade(&cx) { - this.update(&mut cx, |this, cx| { - this.on_lsp_diagnostics_published( - server_id, params, &adapter, cx, - ); - }); + language_server + .on_notification::({ + let this = this.downgrade(); + let adapter = adapter.clone(); + move |params, mut cx| { + if let Some(this) = this.upgrade(&cx) { + this.update(&mut cx, |this, cx| { + this.on_lsp_diagnostics_published( + server_id, params, &adapter, cx, + ); + }); + } } - } - }) - .detach(); + }) + .detach(); - language_server - .on_request::({ - let settings = this - .read_with(&cx, |this, _| this.language_server_settings.clone()); - move |params, _| { - let settings = settings.lock().clone(); - async move { - Ok(params - .items - .into_iter() - .map(|item| { - if let Some(section) = &item.section { - settings - .get(section) - .cloned() - .unwrap_or(serde_json::Value::Null) - } else { - settings.clone() - } - }) - .collect()) + language_server + .on_request::({ + let settings = this.read_with(&cx, |this, _| { + this.language_server_settings.clone() + }); + move |params, _| { + let settings = settings.lock().clone(); + async move { + Ok(params + .items + .into_iter() + .map(|item| { + if let Some(section) = &item.section { + settings + .get(section) + .cloned() + .unwrap_or(serde_json::Value::Null) + } else { + settings.clone() + } + }) + .collect()) + } } - } - }) - .detach(); + }) + .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 + // 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) { - status.progress_tokens.insert(token); + if let lsp::NumberOrString::String(token) = + params.token + { + status.progress_tokens.insert(token); + } } - } - }); + }); + } + Ok(()) } + }) + .detach(); + language_server + .on_request::(|_, _| async { Ok(()) - } - }) - .detach(); - language_server - .on_request::(|_, _| async { - Ok(()) - }) - .detach(); + }) + .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(); + 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(); - 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, - cx, - ); - }); + 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, + cx, + ); + }); + } } + }) + .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; } - }) - .detach(); - this.update(&mut cx, |this, cx| { - this.language_servers - .insert(key.clone(), (adapter.clone(), language_server.clone())); - 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(), - }, - ); - language_server - .notify::( - lsp::DidChangeConfigurationParams { - settings: this.language_server_settings.lock().clone(), + // 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(), + server: language_server.clone(), }, - ) - .ok(); + ); + 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(), + }, + ); + language_server + .notify::( + lsp::DidChangeConfigurationParams { + settings: this.language_server_settings.lock().clone(), + }, + ) + .ok(); + + if let Some(project_id) = this.shared_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(); + } - if let Some(project_id) = this.shared_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 = 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()) + != Some(key.1.clone()) + { + 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()) - != Some(key.1.clone()) - { - continue; + 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(); + let language_id = + adapter.id_for_language(language.name().as_ref()); + language_server + .notify::( + lsp::DidOpenTextDocumentParams { + text_document: lsp::TextDocumentItem::new( + uri, + language_id.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() + }) + .unwrap_or(Vec::new()), + cx, + ) + }); } - - 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(); - let language_id = adapter.id_for_language(language.name().as_ref()); - language_server - .notify::( - lsp::DidOpenTextDocumentParams { - text_document: lsp::TextDocumentItem::new( - uri, - language_id.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() - }) - .unwrap_or(Vec::new()), - cx, - ) - }); } - } - cx.notify(); - Some(()) - }); + cx.notify(); + Some(language_server) + }) + })), + ); - Some(language_server) - }) + server_id }); } + // Returns a list of all of the worktrees which no longer have a language server and the root path + // for the stopped server fn stop_language_server( &mut self, worktree_id: WorktreeId, adapter_name: LanguageServerName, cx: &mut ModelContext, - ) -> Task<()> { + ) -> Task<(Option, Vec)> { let key = (worktree_id, adapter_name); - if let Some((_, language_server)) = self.language_servers.remove(&key) { - self.language_server_statuses - .remove(&language_server.server_id()); + if let Some(server_id) = self.language_server_ids.remove(&key) { + // Remove other entries for this language server as well + let mut orphaned_worktrees = vec![worktree_id]; + let other_keys = self.language_server_ids.keys().cloned().collect::>(); + for other_key in other_keys { + if self.language_server_ids.get(&other_key) == Some(&server_id) { + self.language_server_ids.remove(&other_key); + orphaned_worktrees.push(other_key.0); + } + } + + self.language_server_statuses.remove(&server_id); cx.notify(); - } - if let Some(started_language_server) = self.started_language_servers.remove(&key) { + let server_state = self.language_servers.remove(&server_id); cx.spawn_weak(|this, mut cx| async move { - if let Some(language_server) = started_language_server.await { - if let Some(shutdown) = language_server.shutdown() { - shutdown.await; + let mut root_path = None; + + let server = match server_state { + Some(LanguageServerState::Starting(started_language_server)) => { + started_language_server.await } + Some(LanguageServerState::Running { server, .. }) => Some(server), + None => None, + }; - if let Some(this) = this.upgrade(&cx) { - this.update(&mut cx, |this, cx| { - this.language_server_statuses - .remove(&language_server.server_id()); - cx.notify(); - }); + if let Some(server) = server { + root_path = Some(server.root_path().clone()); + if let Some(shutdown) = server.shutdown() { + shutdown.await; } } + + if let Some(this) = this.upgrade(&cx) { + this.update(&mut cx, |this, cx| { + this.language_server_statuses.remove(&server_id); + cx.notify(); + }); + } + + (root_path, orphaned_worktrees) }) } else { - Task::ready(()) + Task::ready((None, Vec::new())) } } @@ -2247,7 +2324,7 @@ impl Project { fn restart_language_server( &mut self, worktree_id: WorktreeId, - worktree_path: Arc, + fallback_path: Arc, language: Arc, cx: &mut ModelContext, ) { @@ -2257,12 +2334,33 @@ impl Project { return; }; - let stop = self.stop_language_server(worktree_id, adapter.name(), cx); + let server_name = adapter.name(); + let stop = self.stop_language_server(worktree_id, server_name.clone(), cx); cx.spawn_weak(|this, mut cx| async move { - stop.await; + let (original_root_path, orphaned_worktrees) = stop.await; if let Some(this) = this.upgrade(&cx) { this.update(&mut cx, |this, cx| { - this.start_language_server(worktree_id, worktree_path, language, 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_server(worktree_id, root_path, language, cx); + + // Lookup new server id and set it for each of the orphaned worktrees + if let Some(new_server_id) = this + .language_server_ids + .get(&(worktree_id, server_name.clone())) + .cloned() + { + for orphaned_worktree in orphaned_worktrees { + this.language_server_ids.insert( + (orphaned_worktree, server_name.clone()), + new_server_id.clone(), + ); + } + } }); } }) @@ -2498,14 +2596,16 @@ impl Project { } pub fn set_language_server_settings(&mut self, settings: serde_json::Value) { - for (_, server) in self.language_servers.values() { - server - .notify::( - lsp::DidChangeConfigurationParams { - settings: settings.clone(), - }, - ) - .ok(); + for server_state in self.language_servers.values() { + if let LanguageServerState::Running { server, .. } = server_state { + server + .notify::( + lsp::DidChangeConfigurationParams { + settings: settings.clone(), + }, + ) + .ok(); + } } *self.language_server_settings.lock() = settings; } @@ -2968,30 +3068,36 @@ impl Project { pub fn symbols(&self, query: &str, cx: &mut ModelContext) -> Task>> { if self.is_local() { let mut requests = Vec::new(); - for ((worktree_id, _), (lsp_adapter, language_server)) in self.language_servers.iter() { + for ((worktree_id, _), server_id) in self.language_server_ids.iter() { let worktree_id = *worktree_id; if let Some(worktree) = self .worktree_for_id(worktree_id, cx) .and_then(|worktree| worktree.read(cx).as_local()) { - let lsp_adapter = lsp_adapter.clone(); - let worktree_abs_path = worktree.abs_path().clone(); - requests.push( - language_server - .request::(lsp::WorkspaceSymbolParams { - query: query.to_string(), - ..Default::default() - }) - .log_err() - .map(move |response| { - ( - lsp_adapter, - worktree_id, - worktree_abs_path, - response.unwrap_or_default(), + if let Some(LanguageServerState::Running { adapter, server }) = + self.language_servers.get(server_id) + { + let adapter = adapter.clone(); + let worktree_abs_path = worktree.abs_path().clone(); + requests.push( + server + .request::( + lsp::WorkspaceSymbolParams { + query: query.to_string(), + ..Default::default() + }, ) - }), - ); + .log_err() + .map(move |response| { + ( + adapter, + worktree_id, + worktree_abs_path, + response.unwrap_or_default(), + ) + }), + ); + } } } @@ -3074,11 +3180,11 @@ impl Project { cx: &mut ModelContext, ) -> Task>> { if self.is_local() { - let (lsp_adapter, language_server) = if let Some(server) = self.language_servers.get(&( + let language_server_id = if let Some(id) = self.language_server_ids.get(&( symbol.source_worktree_id, symbol.language_server_name.clone(), )) { - server.clone() + *id } else { return Task::ready(Err(anyhow!( "language server for worktree and language not found" @@ -3101,7 +3207,12 @@ impl Project { return Task::ready(Err(anyhow!("invalid symbol path"))); }; - self.open_local_buffer_via_lsp(symbol_uri, lsp_adapter, language_server, cx) + self.open_local_buffer_via_lsp( + symbol_uri, + language_server_id, + symbol.language_server_name.clone(), + cx, + ) } else if let Some(project_id) = self.remote_id() { let request = self.client.request(proto::OpenBufferForSymbol { project_id, @@ -3152,8 +3263,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(source_buffer, cx) { + let lang_server = + if let Some((_, server)) = self.language_server_for_buffer(source_buffer, cx) { server.clone() } else { return Task::ready(Ok(Default::default())); @@ -3310,7 +3421,7 @@ 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 { @@ -3407,7 +3518,7 @@ 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 { @@ -3494,8 +3605,8 @@ impl Project { if self.is_local() { let buffer = buffer_handle.read(cx); let (lsp_adapter, lang_server) = - if let Some(server) = self.language_server_for_buffer(buffer, cx) { - server.clone() + if let Some((adapter, server)) = self.language_server_for_buffer(buffer, cx) { + (adapter.clone(), server.clone()) } else { return Task::ready(Ok(Default::default())); }; @@ -3531,8 +3642,8 @@ impl Project { this, edit, push_to_history, - lsp_adapter, - lang_server, + lsp_adapter.clone(), + lang_server.clone(), &mut cx, ) .await @@ -3661,8 +3772,8 @@ impl Project { .update(cx, |this, cx| { this.open_local_buffer_via_lsp( op.text_document.uri, - lsp_adapter.clone(), - language_server.clone(), + language_server.server_id(), + lsp_adapter.name(), cx, ) }) @@ -3956,9 +4067,10 @@ 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))) = - file.zip(self.language_server_for_buffer(buffer, cx).cloned()) - { + if let Some((file, language_server)) = file.zip( + self.language_server_for_buffer(buffer, cx) + .map(|(_, server)| server.clone()), + ) { let lsp_params = request.to_lsp(&file.abs_path(cx), cx); return cx.spawn(|this, cx| async move { if !request.check_capabilities(&language_server.capabilities()) { @@ -5574,14 +5686,21 @@ impl Project { &self, buffer: &Buffer, cx: &AppContext, - ) -> Option<&(Arc, 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.lsp_adapter()?.name())) - } else { - None + let key = (worktree_id, language.lsp_adapter()?.name()); + + if let Some(server_id) = self.language_server_ids.get(&key) { + if let Some(LanguageServerState::Running { adapter, server }) = + self.language_servers.get(&server_id) + { + return Some((adapter, server)); + } + } } + + None } } @@ -5741,8 +5860,16 @@ impl Entity for Project { let shutdown_futures = self .language_servers .drain() - .filter_map(|(_, (_, server))| server.shutdown()) + .map(|(_, server_state)| async { + match server_state { + LanguageServerState::Running { server, .. } => server.shutdown()?.await, + LanguageServerState::Starting(starting_server) => { + starting_server.await?.shutdown()?.await + } + } + }) .collect::>(); + Some( async move { futures::future::join_all(shutdown_futures).await; @@ -7657,6 +7784,10 @@ mod tests { .await .unwrap(); + // Assert no new language server started + cx.foreground().run_until_parked(); + assert!(fake_servers.try_next().is_err()); + assert_eq!(definitions.len(), 1); let definition = definitions.pop().unwrap(); cx.update(|cx| {