Prevent creating extra language server instances if there already exists one for that workspace

This commit is contained in:
Keith Simmons 2022-06-30 16:46:26 -07:00
parent 2ee57c1512
commit db05e32389
3 changed files with 391 additions and 306 deletions

View file

@ -11,7 +11,7 @@ use serde_json::{json, value::RawValue, Value};
use smol::{ use smol::{
channel, channel,
io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader}, io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader},
process, process::{self, Child},
}; };
use std::{ use std::{
future::Future, future::Future,
@ -44,6 +44,7 @@ pub struct LanguageServer {
io_tasks: Mutex<Option<(Task<Option<()>>, Task<Option<()>>)>>, io_tasks: Mutex<Option<(Task<Option<()>>, Task<Option<()>>)>>,
output_done_rx: Mutex<Option<barrier::Receiver>>, output_done_rx: Mutex<Option<barrier::Receiver>>,
root_path: PathBuf, root_path: PathBuf,
_server: Option<Child>,
} }
pub struct Subscription { pub struct Subscription {
@ -118,11 +119,20 @@ impl LanguageServer {
.stdin(Stdio::piped()) .stdin(Stdio::piped())
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.stderr(Stdio::inherit()) .stderr(Stdio::inherit())
.kill_on_drop(true)
.spawn()?; .spawn()?;
let stdin = server.stdin.take().unwrap(); let stdin = server.stdin.take().unwrap();
let stdout = server.stdout.take().unwrap(); let stout = server.stdout.take().unwrap();
let mut server =
Self::new_internal(server_id, stdin, stdout, root_path, cx, |notification| { let mut server = Self::new_internal(
server_id,
stdin,
stout,
Some(server),
root_path,
cx,
|notification| {
log::info!( log::info!(
"unhandled notification {}:\n{}", "unhandled notification {}:\n{}",
notification.method, notification.method,
@ -131,7 +141,8 @@ impl LanguageServer {
) )
.unwrap() .unwrap()
); );
}); },
);
if let Some(name) = binary_path.file_name() { if let Some(name) = binary_path.file_name() {
server.name = name.to_string_lossy().to_string(); server.name = name.to_string_lossy().to_string();
} }
@ -142,6 +153,7 @@ impl LanguageServer {
server_id: usize, server_id: usize,
stdin: Stdin, stdin: Stdin,
stdout: Stdout, stdout: Stdout,
server: Option<Child>,
root_path: &Path, root_path: &Path,
cx: AsyncAppContext, cx: AsyncAppContext,
mut on_unhandled_notification: F, mut on_unhandled_notification: F,
@ -242,6 +254,7 @@ impl LanguageServer {
io_tasks: Mutex::new(Some((input_task, output_task))), io_tasks: Mutex::new(Some((input_task, output_task))),
output_done_rx: Mutex::new(Some(output_done_rx)), output_done_rx: Mutex::new(Some(output_done_rx)),
root_path: root_path.to_path_buf(), root_path: root_path.to_path_buf(),
_server: server,
} }
} }
@ -608,6 +621,7 @@ impl LanguageServer {
0, 0,
stdin_writer, stdin_writer,
stdout_reader, stdout_reader,
None,
Path::new("/"), Path::new("/"),
cx.clone(), cx.clone(),
|_| {}, |_| {},
@ -617,6 +631,7 @@ impl LanguageServer {
0, 0,
stdout_writer, stdout_writer,
stdin_reader, stdin_reader,
None,
Path::new("/"), Path::new("/"),
cx.clone(), cx.clone(),
move |msg| { move |msg| {

View file

@ -242,7 +242,7 @@ impl LspCommand for PerformRename {
.read_with(&cx, |project, cx| { .read_with(&cx, |project, cx| {
project project
.language_server_for_buffer(buffer.read(cx), cx) .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"))?; .ok_or_else(|| anyhow!("no language server found for buffer"))?;
Project::deserialize_workspace_edit( Project::deserialize_workspace_edit(
@ -359,7 +359,7 @@ impl LspCommand for GetDefinition {
.read_with(&cx, |project, cx| { .read_with(&cx, |project, cx| {
project project
.language_server_for_buffer(buffer.read(cx), cx) .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"))?; .ok_or_else(|| anyhow!("no language server found for buffer"))?;
@ -388,8 +388,8 @@ impl LspCommand for GetDefinition {
.update(&mut cx, |this, cx| { .update(&mut cx, |this, cx| {
this.open_local_buffer_via_lsp( this.open_local_buffer_via_lsp(
target_uri, target_uri,
lsp_adapter.clone(), language_server.server_id(),
language_server.clone(), lsp_adapter.name(),
cx, cx,
) )
}) })
@ -599,7 +599,7 @@ impl LspCommand for GetReferences {
.read_with(&cx, |project, cx| { .read_with(&cx, |project, cx| {
project project
.language_server_for_buffer(buffer.read(cx), cx) .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"))?; .ok_or_else(|| anyhow!("no language server found for buffer"))?;
@ -609,8 +609,8 @@ impl LspCommand for GetReferences {
.update(&mut cx, |this, cx| { .update(&mut cx, |this, cx| {
this.open_local_buffer_via_lsp( this.open_local_buffer_via_lsp(
lsp_location.uri, lsp_location.uri,
lsp_adapter.clone(), language_server.server_id(),
language_server.clone(), lsp_adapter.name(),
cx, cx,
) )
}) })

View file

@ -65,6 +65,14 @@ pub trait Item: Entity {
fn entry_id(&self, cx: &AppContext) -> Option<ProjectEntryId>; fn entry_id(&self, cx: &AppContext) -> Option<ProjectEntryId>;
} }
pub enum LanguageServerState {
Starting(Task<Option<Arc<LanguageServer>>>),
Running {
adapter: Arc<dyn LspAdapter>,
server: Arc<LanguageServer>,
},
}
pub struct ProjectStore { pub struct ProjectStore {
db: Arc<Db>, db: Arc<Db>,
projects: Vec<WeakModelHandle<Project>>, projects: Vec<WeakModelHandle<Project>>,
@ -74,10 +82,8 @@ pub struct Project {
worktrees: Vec<WorktreeHandle>, worktrees: Vec<WorktreeHandle>,
active_entry: Option<ProjectEntryId>, active_entry: Option<ProjectEntryId>,
languages: Arc<LanguageRegistry>, languages: Arc<LanguageRegistry>,
language_servers: language_servers: HashMap<usize, LanguageServerState>,
HashMap<(WorktreeId, LanguageServerName), (Arc<dyn LspAdapter>, Arc<LanguageServer>)>, language_server_ids: HashMap<(WorktreeId, LanguageServerName), usize>,
started_language_servers:
HashMap<(WorktreeId, LanguageServerName), Task<Option<Arc<LanguageServer>>>>,
language_server_statuses: BTreeMap<usize, LanguageServerStatus>, language_server_statuses: BTreeMap<usize, LanguageServerStatus>,
language_server_settings: Arc<Mutex<serde_json::Value>>, language_server_settings: Arc<Mutex<serde_json::Value>>,
last_workspace_edits_by_language_server: HashMap<usize, ProjectTransaction>, last_workspace_edits_by_language_server: HashMap<usize, ProjectTransaction>,
@ -179,7 +185,7 @@ pub struct LanguageServerStatus {
pub name: String, pub name: String,
pub pending_work: BTreeMap<String, LanguageServerProgress>, pub pending_work: BTreeMap<String, LanguageServerProgress>,
pub has_pending_diagnostic_updates: bool, pub has_pending_diagnostic_updates: bool,
progress_tokens: HashSet<String>, pub progress_tokens: HashSet<String>,
} }
#[derive(Clone, Debug, Serialize)] #[derive(Clone, Debug, Serialize)]
@ -437,7 +443,7 @@ impl Project {
next_entry_id: Default::default(), next_entry_id: Default::default(),
next_diagnostic_group_id: Default::default(), next_diagnostic_group_id: Default::default(),
language_servers: Default::default(), language_servers: Default::default(),
started_language_servers: Default::default(), language_server_ids: Default::default(),
language_server_statuses: Default::default(), language_server_statuses: Default::default(),
last_workspace_edits_by_language_server: Default::default(), last_workspace_edits_by_language_server: Default::default(),
language_server_settings: Default::default(), language_server_settings: Default::default(),
@ -536,7 +542,7 @@ impl Project {
}), }),
}, },
language_servers: Default::default(), language_servers: Default::default(),
started_language_servers: Default::default(), language_server_ids: Default::default(),
language_server_settings: Default::default(), language_server_settings: Default::default(),
language_server_statuses: response language_server_statuses: response
.language_servers .language_servers
@ -691,7 +697,7 @@ impl Project {
if let Some(lsp_adapter) = language.lsp_adapter() { if let Some(lsp_adapter) = language.lsp_adapter() {
if !settings.enable_language_server(Some(&language.name())) { if !settings.enable_language_server(Some(&language.name())) {
let lsp_name = lsp_adapter.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 { if lsp_name == *started_lsp_name {
language_servers_to_stop.push((*worktree_id, started_lsp_name.clone())); language_servers_to_stop.push((*worktree_id, started_lsp_name.clone()));
} }
@ -1538,8 +1544,8 @@ impl Project {
fn open_local_buffer_via_lsp( fn open_local_buffer_via_lsp(
&mut self, &mut self,
abs_path: lsp::Url, abs_path: lsp::Url,
lsp_adapter: Arc<dyn LspAdapter>, language_server_id: usize,
lsp_server: Arc<LanguageServer>, language_server_name: LanguageServerName,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) -> Task<Result<ModelHandle<Buffer>>> { ) -> Task<Result<ModelHandle<Buffer>>> {
cx.spawn(|this, mut cx| async move { cx.spawn(|this, mut cx| async move {
@ -1557,9 +1563,9 @@ impl Project {
}) })
.await?; .await?;
this.update(&mut cx, |this, cx| { this.update(&mut cx, |this, cx| {
this.language_servers.insert( this.language_server_ids.insert(
(worktree.read(cx).id(), lsp_adapter.name()), (worktree.read(cx).id(), language_server_name),
(lsp_adapter, lsp_server), language_server_id,
); );
}); });
(worktree, PathBuf::new()) (worktree, PathBuf::new())
@ -1726,9 +1732,16 @@ impl Project {
if let Some(adapter) = language.lsp_adapter() { if let Some(adapter) = language.lsp_adapter() {
language_id = adapter.id_for_language(language.name().as_ref()); language_id = adapter.id_for_language(language.name().as_ref());
language_server = self language_server = self
.language_servers .language_server_ids
.get(&(worktree_id, adapter.name())) .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 +1752,7 @@ impl Project {
} }
} }
if let Some((_, server)) = language_server { if let Some(server) = language_server {
server server
.notify::<lsp::notification::DidOpenTextDocument>( .notify::<lsp::notification::DidOpenTextDocument>(
lsp::DidOpenTextDocumentParams { lsp::DidOpenTextDocumentParams {
@ -1816,9 +1829,9 @@ impl Project {
} }
} }
BufferEvent::Edited { .. } => { BufferEvent::Edited { .. } => {
let (_, language_server) = self let language_server = self
.language_server_for_buffer(buffer.read(cx), cx)? .language_server_for_buffer(buffer.read(cx), cx)
.clone(); .map(|(_, server)| server.clone())?;
let buffer = buffer.read(cx); let buffer = buffer.read(cx);
let file = File::from_dyn(buffer.file())?; let file = File::from_dyn(buffer.file())?;
let abs_path = file.as_local()?.abs_path(cx); let abs_path = file.as_local()?.abs_path(cx);
@ -1907,16 +1920,19 @@ impl Project {
fn language_servers_for_worktree( fn language_servers_for_worktree(
&self, &self,
worktree_id: WorktreeId, worktree_id: WorktreeId,
) -> impl Iterator<Item = &(Arc<dyn LspAdapter>, Arc<LanguageServer>)> { ) -> impl Iterator<Item = (&Arc<dyn LspAdapter>, &Arc<LanguageServer>)> {
self.language_servers.iter().filter_map( self.language_server_ids
move |((language_server_worktree_id, _), server)| { .iter()
.filter_map(move |((language_server_worktree_id, _), id)| {
if *language_server_worktree_id == worktree_id { if *language_server_worktree_id == worktree_id {
Some(server) if let Some(LanguageServerState::Running { adapter, server }) =
} else { self.language_servers.get(&id)
None {
return Some((adapter, server));
}
} }
}, None
) })
} }
fn assign_language_to_buffer( fn assign_language_to_buffer(
@ -1960,7 +1976,8 @@ impl Project {
return; return;
}; };
let key = (worktree_id, adapter.name()); let key = (worktree_id, adapter.name());
self.started_language_servers
self.language_server_ids
.entry(key.clone()) .entry(key.clone())
.or_insert_with(|| { .or_insert_with(|| {
let server_id = post_inc(&mut self.next_language_server_id); let server_id = post_inc(&mut self.next_language_server_id);
@ -1971,218 +1988,240 @@ impl Project {
self.client.http_client(), self.client.http_client(),
cx, cx,
); );
cx.spawn_weak(|this, mut cx| async move { self.language_servers.insert(
let language_server = language_server?.await.log_err()?; server_id,
let language_server = language_server LanguageServerState::Starting(cx.spawn_weak(|this, mut cx| async move {
.initialize(adapter.initialization_options()) let language_server = language_server?.await.log_err()?;
.await let language_server = language_server
.log_err()?; .initialize(adapter.initialization_options())
let this = this.upgrade(&cx)?; .await
let disk_based_diagnostics_progress_token = .log_err()?;
adapter.disk_based_diagnostics_progress_token(); let this = this.upgrade(&cx)?;
let disk_based_diagnostics_progress_token =
adapter.disk_based_diagnostics_progress_token();
language_server
.on_notification::<lsp::notification::PublishDiagnostics, _>({
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();
language_server
.on_request::<lsp::request::WorkspaceConfiguration, _, _>({
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();
// 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::<lsp::request::WorkDoneProgressCreate, _, _>({
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::<lsp::request::RegisterCapability, _, _>(|_, _| async {
Ok(())
})
.detach();
language_server
.on_request::<lsp::request::ApplyWorkspaceEdit, _, _>({
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::<lsp::notification::Progress, _>({
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| {
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 language_server
.notify::<lsp::notification::DidChangeConfiguration>( .on_notification::<lsp::notification::PublishDiagnostics, _>({
lsp::DidChangeConfigurationParams { let this = this.downgrade();
settings: this.language_server_settings.lock().clone(), let adapter = adapter.clone();
}, move |params, mut cx| {
) if let Some(this) = this.upgrade(&cx) {
.ok(); this.update(&mut cx, |this, cx| {
this.on_lsp_diagnostics_published(
if let Some(project_id) = this.shared_remote_id() { server_id, params, &adapter, cx,
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;
} }
})
.detach();
let file = file.as_local()?; language_server
let versions = this .on_request::<lsp::request::WorkspaceConfiguration, _, _>({
.buffer_snapshots let settings = this.read_with(&cx, |this, _| {
.entry(buffer.remote_id()) this.language_server_settings.clone()
.or_insert_with(|| vec![(0, buffer.text_snapshot())]); });
let (version, initial_snapshot) = versions.last().unwrap(); move |params, _| {
let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap(); let settings = settings.lock().clone();
let language_id = adapter.id_for_language(language.name().as_ref()); async move {
language_server Ok(params
.notify::<lsp::notification::DidOpenTextDocument>( .items
lsp::DidOpenTextDocumentParams { .into_iter()
text_document: lsp::TextDocumentItem::new( .map(|item| {
uri, if let Some(section) = &item.section {
language_id.unwrap_or_default(), settings
*version, .get(section)
initial_snapshot.text(), .cloned()
), .unwrap_or(serde_json::Value::Null)
}, } else {
) settings.clone()
.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()), .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::<lsp::request::WorkDoneProgressCreate, _, _>({
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::<lsp::request::RegisterCapability, _, _>(|_, _| async {
Ok(())
})
.detach();
language_server
.on_request::<lsp::request::ApplyWorkspaceEdit, _, _>({
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, cx,
) )
}); }
})
.detach();
language_server
.on_notification::<lsp::notification::Progress, _>({
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.
if this
.language_server_ids
.get(&key)
.map(|id| id != &server_id)
.unwrap_or(false)
{
return None;
} }
}
cx.notify(); this.language_servers.insert(
Some(()) server_id,
}); LanguageServerState::Running {
adapter: adapter.clone(),
server: 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::notification::DidChangeConfiguration>(
lsp::DidChangeConfigurationParams {
settings: this.language_server_settings.lock().clone(),
},
)
.ok();
Some(language_server) 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;
}
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::notification::DidOpenTextDocument>(
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(language_server)
})
})),
);
server_id
}); });
} }
@ -2193,26 +2232,28 @@ impl Project {
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) -> Task<()> { ) -> Task<()> {
let key = (worktree_id, adapter_name); let key = (worktree_id, adapter_name);
if let Some((_, language_server)) = self.language_servers.remove(&key) { if let Some(server_id) = self.language_server_ids.remove(&key) {
self.language_server_statuses let server_state = self.language_servers.remove(&server_id);
.remove(&language_server.server_id());
cx.notify();
}
if let Some(started_language_server) = self.started_language_servers.remove(&key) {
cx.spawn_weak(|this, mut cx| async move { cx.spawn_weak(|this, mut cx| async move {
if let Some(language_server) = started_language_server.await { let server = match server_state {
if let Some(shutdown) = language_server.shutdown() { Some(LanguageServerState::Starting(started_language_server)) => {
started_language_server.await
}
Some(LanguageServerState::Running { server, .. }) => Some(server),
None => None,
};
if let Some(server) = server {
if let Some(shutdown) = server.shutdown() {
shutdown.await; shutdown.await;
} }
}
if let Some(this) = this.upgrade(&cx) { if let Some(this) = this.upgrade(&cx) {
this.update(&mut cx, |this, cx| { this.update(&mut cx, |this, cx| {
this.language_server_statuses this.language_server_statuses.remove(&server_id);
.remove(&language_server.server_id()); cx.notify();
cx.notify(); });
});
}
} }
}) })
} else { } else {
@ -2498,14 +2539,16 @@ impl Project {
} }
pub fn set_language_server_settings(&mut self, settings: serde_json::Value) { pub fn set_language_server_settings(&mut self, settings: serde_json::Value) {
for (_, server) in self.language_servers.values() { for server_state in self.language_servers.values() {
server if let LanguageServerState::Running { server, .. } = server_state {
.notify::<lsp::notification::DidChangeConfiguration>( server
lsp::DidChangeConfigurationParams { .notify::<lsp::notification::DidChangeConfiguration>(
settings: settings.clone(), lsp::DidChangeConfigurationParams {
}, settings: settings.clone(),
) },
.ok(); )
.ok();
}
} }
*self.language_server_settings.lock() = settings; *self.language_server_settings.lock() = settings;
} }
@ -2968,30 +3011,36 @@ impl Project {
pub fn symbols(&self, query: &str, cx: &mut ModelContext<Self>) -> Task<Result<Vec<Symbol>>> { pub fn symbols(&self, query: &str, cx: &mut ModelContext<Self>) -> Task<Result<Vec<Symbol>>> {
if self.is_local() { if self.is_local() {
let mut requests = Vec::new(); 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; let worktree_id = *worktree_id;
if let Some(worktree) = self if let Some(worktree) = self
.worktree_for_id(worktree_id, cx) .worktree_for_id(worktree_id, cx)
.and_then(|worktree| worktree.read(cx).as_local()) .and_then(|worktree| worktree.read(cx).as_local())
{ {
let lsp_adapter = lsp_adapter.clone(); if let Some(LanguageServerState::Running { adapter, server }) =
let worktree_abs_path = worktree.abs_path().clone(); self.language_servers.get(server_id)
requests.push( {
language_server let adapter = adapter.clone();
.request::<lsp::request::WorkspaceSymbol>(lsp::WorkspaceSymbolParams { let worktree_abs_path = worktree.abs_path().clone();
query: query.to_string(), requests.push(
..Default::default() server
}) .request::<lsp::request::WorkspaceSymbol>(
.log_err() lsp::WorkspaceSymbolParams {
.map(move |response| { query: query.to_string(),
( ..Default::default()
lsp_adapter, },
worktree_id,
worktree_abs_path,
response.unwrap_or_default(),
) )
}), .log_err()
); .map(move |response| {
(
adapter,
worktree_id,
worktree_abs_path,
response.unwrap_or_default(),
)
}),
);
}
} }
} }
@ -3074,11 +3123,11 @@ impl Project {
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) -> Task<Result<ModelHandle<Buffer>>> { ) -> Task<Result<ModelHandle<Buffer>>> {
if self.is_local() { 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.source_worktree_id,
symbol.language_server_name.clone(), symbol.language_server_name.clone(),
)) { )) {
server.clone() *id
} else { } else {
return Task::ready(Err(anyhow!( return Task::ready(Err(anyhow!(
"language server for worktree and language not found" "language server for worktree and language not found"
@ -3101,7 +3150,12 @@ impl Project {
return Task::ready(Err(anyhow!("invalid symbol path"))); 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() { } else if let Some(project_id) = self.remote_id() {
let request = self.client.request(proto::OpenBufferForSymbol { let request = self.client.request(proto::OpenBufferForSymbol {
project_id, project_id,
@ -3152,8 +3206,8 @@ impl Project {
if worktree.read(cx).as_local().is_some() { if worktree.read(cx).as_local().is_some() {
let buffer_abs_path = buffer_abs_path.unwrap(); 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) { if let Some((_, server)) = self.language_server_for_buffer(source_buffer, cx) {
server.clone() server.clone()
} else { } else {
return Task::ready(Ok(Default::default())); return Task::ready(Ok(Default::default()));
@ -3310,7 +3364,7 @@ impl Project {
let buffer_id = buffer.remote_id(); let buffer_id = buffer.remote_id();
if self.is_local() { 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() server.clone()
} else { } else {
@ -3407,7 +3461,7 @@ impl Project {
if worktree.read(cx).as_local().is_some() { if worktree.read(cx).as_local().is_some() {
let buffer_abs_path = buffer_abs_path.unwrap(); 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() server.clone()
} else { } else {
@ -3494,8 +3548,8 @@ impl Project {
if self.is_local() { if self.is_local() {
let buffer = buffer_handle.read(cx); let buffer = buffer_handle.read(cx);
let (lsp_adapter, lang_server) = let (lsp_adapter, lang_server) =
if let Some(server) = self.language_server_for_buffer(buffer, cx) { if let Some((adapter, server)) = self.language_server_for_buffer(buffer, cx) {
server.clone() (adapter.clone(), server.clone())
} else { } else {
return Task::ready(Ok(Default::default())); return Task::ready(Ok(Default::default()));
}; };
@ -3531,8 +3585,8 @@ impl Project {
this, this,
edit, edit,
push_to_history, push_to_history,
lsp_adapter, lsp_adapter.clone(),
lang_server, lang_server.clone(),
&mut cx, &mut cx,
) )
.await .await
@ -3661,8 +3715,8 @@ impl Project {
.update(cx, |this, cx| { .update(cx, |this, cx| {
this.open_local_buffer_via_lsp( this.open_local_buffer_via_lsp(
op.text_document.uri, op.text_document.uri,
lsp_adapter.clone(), language_server.server_id(),
language_server.clone(), lsp_adapter.name(),
cx, cx,
) )
}) })
@ -3956,9 +4010,10 @@ impl Project {
let buffer = buffer_handle.read(cx); let buffer = buffer_handle.read(cx);
if self.is_local() { if self.is_local() {
let file = File::from_dyn(buffer.file()).and_then(File::as_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(
file.zip(self.language_server_for_buffer(buffer, cx).cloned()) self.language_server_for_buffer(buffer, cx)
{ .map(|(_, server)| server.clone()),
) {
let lsp_params = request.to_lsp(&file.abs_path(cx), cx); let lsp_params = request.to_lsp(&file.abs_path(cx), cx);
return cx.spawn(|this, cx| async move { return cx.spawn(|this, cx| async move {
if !request.check_capabilities(&language_server.capabilities()) { if !request.check_capabilities(&language_server.capabilities()) {
@ -5574,14 +5629,21 @@ impl Project {
&self, &self,
buffer: &Buffer, buffer: &Buffer,
cx: &AppContext, cx: &AppContext,
) -> Option<&(Arc<dyn LspAdapter>, Arc<LanguageServer>)> { ) -> Option<(&Arc<dyn LspAdapter>, &Arc<LanguageServer>)> {
if let Some((file, language)) = File::from_dyn(buffer.file()).zip(buffer.language()) { if let Some((file, language)) = File::from_dyn(buffer.file()).zip(buffer.language()) {
let worktree_id = file.worktree_id(cx); let worktree_id = file.worktree_id(cx);
self.language_servers let key = (worktree_id, language.lsp_adapter()?.name());
.get(&(worktree_id, language.lsp_adapter()?.name()))
} else { if let Some(server_id) = self.language_server_ids.get(&key) {
None if let Some(LanguageServerState::Running { adapter, server }) =
self.language_servers.get(&server_id)
{
return Some((adapter, server));
}
}
} }
None
} }
} }
@ -5741,8 +5803,16 @@ impl Entity for Project {
let shutdown_futures = self let shutdown_futures = self
.language_servers .language_servers
.drain() .drain()
.filter_map(|(_, (_, server))| server.shutdown()) .filter_map(|(_, server_state)| {
// TODO: Handle starting servers?
if let LanguageServerState::Running { server, .. } = server_state {
server.shutdown()
} else {
None
}
})
.collect::<Vec<_>>(); .collect::<Vec<_>>();
Some( Some(
async move { async move {
futures::future::join_all(shutdown_futures).await; futures::future::join_all(shutdown_futures).await;