Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -934,7 +934,7 @@ async fn test_ssh_restarting_language_server_replaces_remote_status(
});

project_a.update(cx_a, |project, cx| {
project.restart_language_servers_for_buffers(vec![buffer], HashSet::default(), cx);
project.restart_language_servers_for_buffers(vec![buffer], HashSet::default(), true, cx);
});

let restarted_server = fake_language_servers.next().await.unwrap();
Expand Down
1 change: 1 addition & 0 deletions crates/editor/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8005,6 +8005,7 @@ impl Editor {
project.restart_language_servers_for_buffers(
multi_buffer.all_buffers().into_iter().collect(),
HashSet::default(),
true,
cx,
);
});
Expand Down
14 changes: 12 additions & 2 deletions crates/extension_host/src/extension_store_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -952,7 +952,12 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {

// Start a new instance of the language server.
project.update(cx, |project, cx| {
project.restart_language_servers_for_buffers(vec![buffer.clone()], HashSet::default(), cx)
project.restart_language_servers_for_buffers(
vec![buffer.clone()],
HashSet::default(),
true,
cx,
)
});
cx.executor().run_until_parked();

Expand Down Expand Up @@ -993,7 +998,12 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
.await;
cx.executor().run_until_parked();
project.update(cx, |project, cx| {
project.restart_language_servers_for_buffers(vec![buffer.clone()], HashSet::default(), cx)
project.restart_language_servers_for_buffers(
vec![buffer.clone()],
HashSet::default(),
true,
cx,
)
});

// The extension re-fetches the latest version of the language server.
Expand Down
1 change: 1 addition & 0 deletions crates/language_tools/src/lsp_button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,7 @@ impl LanguageServerState {
HashSet::from_iter([LanguageServerSelector::Name(
server_name_for_restart.clone(),
)]),
true,
cx,
);
})
Expand Down
90 changes: 83 additions & 7 deletions crates/project/src/lsp_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,8 @@ pub struct LocalLspStore {
HashMap<Option<SharedString>, HashMap<PathBuf, Option<SharedString>>>,
>,
restricted_worktrees_tasks: HashMap<WorktreeId, (Subscription, watch::Receiver<bool>)>,
all_language_servers_stopped: bool,
stopped_language_servers: HashSet<LanguageServerName>,

buffers_to_refresh_hash_set: HashSet<BufferId>,
buffers_to_refresh_queue: VecDeque<BufferId>,
Expand Down Expand Up @@ -2829,6 +2831,9 @@ impl LocalLspStore {
only_register_servers: HashSet<LanguageServerSelector>,
cx: &mut Context<LspStore>,
) {
if self.all_language_servers_stopped {
return;
}
let buffer = buffer_handle.read(cx);
let buffer_id = buffer.remote_id();

Expand Down Expand Up @@ -2888,6 +2893,11 @@ impl LocalLspStore {
if reused && server_node.server_id().is_none() {
return None;
}
if let Some(name) = server_node.name()
&& self.stopped_language_servers.contains(&name)
{
return None;
}
if !only_register_servers.is_empty() {
if let Some(server_id) = server_node.server_id()
&& !only_register_servers.contains(&LanguageServerSelector::Id(server_id))
Expand Down Expand Up @@ -4373,6 +4383,8 @@ impl LspStore {
buffer_pull_diagnostics_result_ids: HashMap::default(),
workspace_pull_diagnostics_result_ids: HashMap::default(),
restricted_worktrees_tasks: HashMap::default(),
all_language_servers_stopped: false,
stopped_language_servers: HashSet::default(),
watched_manifest_filenames: ManifestProvidersStore::global(cx)
.manifest_file_names(),
}),
Expand Down Expand Up @@ -5430,7 +5442,16 @@ impl LspStore {
.semantic_token_config
.update_global_mode(new_global_semantic_tokens_mode)
{
self.restart_all_language_servers(cx);
let all_stopped = self
.as_local()
.is_some_and(|local| local.all_language_servers_stopped);
if !all_stopped {
// Restart servers without clearing per-server stopped status.
// Individually-stopped servers will be skipped by the guard in
// register_buffer_with_language_servers.
let buffers = self.buffer_store.read(cx).buffers().collect();
self.restart_language_servers_for_buffers(buffers, HashSet::default(), false, cx);
}
}

cx.notify();
Expand All @@ -5441,6 +5462,10 @@ impl LspStore {
let Some(local) = self.as_local_mut() else {
return;
};
if local.all_language_servers_stopped {
return;
}
let stopped_language_servers = local.stopped_language_servers.clone();
let mut adapters = BTreeMap::default();
let get_adapter = {
let languages = local.languages.clone();
Expand Down Expand Up @@ -5523,6 +5548,11 @@ impl LspStore {
)
.collect::<Vec<_>>();
for node in nodes {
if let Some(name) = node.name()
&& stopped_language_servers.contains(&name)
{
continue;
}
let server_id = node.server_id_or_init(|disposition| {
let path = &disposition.path;
let uri = Uri::from_file_path(worktree.read(cx).absolutize(&path.path));
Expand Down Expand Up @@ -10831,6 +10861,7 @@ impl LspStore {
})
})
.collect(),
true,
cx,
);
});
Expand Down Expand Up @@ -11373,6 +11404,9 @@ impl LspStore {
}

pub fn stop_all_language_servers(&mut self, cx: &mut Context<Self>) {
if let Some(local) = self.as_local_mut() {
local.all_language_servers_stopped = true;
}
self.shutdown_all_language_servers(cx).detach();
}

Expand Down Expand Up @@ -11408,14 +11442,20 @@ impl LspStore {
}

pub fn restart_all_language_servers(&mut self, cx: &mut Context<Self>) {
if let Some(local) = self.as_local_mut() {
local.all_language_servers_stopped = false;
}
// `restart_language_servers_for_buffers` with empty selectors and `clear_stopped`
// clears `stopped_language_servers` for us.
let buffers = self.buffer_store.read(cx).buffers().collect();
self.restart_language_servers_for_buffers(buffers, HashSet::default(), cx);
self.restart_language_servers_for_buffers(buffers, HashSet::default(), true, cx);
}

pub fn restart_language_servers_for_buffers(
&mut self,
buffers: Vec<Entity<Buffer>>,
only_restart_servers: HashSet<LanguageServerSelector>,
clear_stopped: bool,
cx: &mut Context<Self>,
) {
if let Some((client, project_id)) = self.upstream_client() {
Expand Down Expand Up @@ -11449,14 +11489,33 @@ impl LspStore {
});
cx.background_spawn(request).detach_and_log_err(cx);
} else {
let stop_task = if only_restart_servers.is_empty() {
let (stopped_names, stop_task) = if only_restart_servers.is_empty() {
self.stop_local_language_servers_for_buffers(&buffers, HashSet::default(), cx)
} else {
self.stop_local_language_servers_for_buffers(&[], only_restart_servers.clone(), cx)
};
cx.spawn(async move |lsp_store, cx| {
stop_task.await;
lsp_store.update(cx, |lsp_store, cx| {
if clear_stopped {
if let Some(local) = lsp_store.as_local_mut() {
if only_restart_servers.is_empty() {
// A full restart of these buffers un-suppresses every
// manually-stopped server, even ones that are no longer
// running (and so weren't returned in `stopped_names`).
local.stopped_language_servers.clear();
} else {
for name in &stopped_names {
local.stopped_language_servers.remove(name);
}
for selector in &only_restart_servers {
if let LanguageServerSelector::Name(name) = selector {
local.stopped_language_servers.remove(name);
}
}
}
}
}
for buffer in buffers {
lsp_store.register_buffer_with_language_servers(
&buffer,
Expand Down Expand Up @@ -11511,8 +11570,11 @@ impl LspStore {
Ok(())
})
} else {
let task =
let (stopped_names, task) =
self.stop_local_language_servers_for_buffers(&buffers, also_stop_servers, cx);
if let Some(local) = self.as_local_mut() {
local.stopped_language_servers.extend(stopped_names);
}
cx.background_spawn(async move {
task.await;
Ok(())
Expand All @@ -11525,9 +11587,9 @@ impl LspStore {
buffers: &[Entity<Buffer>],
also_stop_servers: HashSet<LanguageServerSelector>,
cx: &mut Context<Self>,
) -> Task<()> {
) -> (HashSet<LanguageServerName>, Task<()>) {
let Some(local) = self.as_local_mut() else {
return Task::ready(());
return (HashSet::default(), Task::ready(()));
};
let mut language_server_names_to_stop = BTreeSet::default();
let mut language_servers_to_stop = also_stop_servers
Expand Down Expand Up @@ -11569,13 +11631,27 @@ impl LspStore {
);
}

let stopped_names: HashSet<LanguageServerName> = language_servers_to_stop
.iter()
.filter_map(|id| {
local
.language_server_ids
.iter()
.find(|(_, state)| state.id == *id)
.map(|(seed, _)| seed.name.clone())
})
.collect();

local.lsp_tree.remove_nodes(&language_servers_to_stop);
let tasks = language_servers_to_stop
.into_iter()
.map(|server| self.stop_local_language_server(server, cx))
.collect::<Vec<_>>();

cx.background_spawn(futures::future::join_all(tasks).map(|_| ()))
(
stopped_names,
cx.background_spawn(futures::future::join_all(tasks).map(|_| ())),
)
}

#[cfg(any(test, feature = "test-support"))]
Expand Down
8 changes: 7 additions & 1 deletion crates/project/src/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3951,10 +3951,16 @@ impl Project {
&mut self,
buffers: Vec<Entity<Buffer>>,
only_restart_servers: HashSet<LanguageServerSelector>,
clear_stopped: bool,
cx: &mut Context<Self>,
) {
self.lsp_store.update(cx, |lsp_store, cx| {
lsp_store.restart_language_servers_for_buffers(buffers, only_restart_servers, cx)
lsp_store.restart_language_servers_for_buffers(
buffers,
only_restart_servers,
clear_stopped,
cx,
)
})
}

Expand Down
24 changes: 20 additions & 4 deletions crates/project/tests/integration/project_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1864,6 +1864,7 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
project.restart_language_servers_for_buffers(
vec![rust_buffer.clone(), json_buffer.clone()],
HashSet::default(),
true,
cx,
);
});
Expand Down Expand Up @@ -2981,7 +2982,7 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC

// Restart the server before the diagnostics finish updating.
project.update(cx, |project, cx| {
project.restart_language_servers_for_buffers(vec![buffer], HashSet::default(), cx);
project.restart_language_servers_for_buffers(vec![buffer], HashSet::default(), true, cx);
});
let mut events = cx.events(&project);

Expand Down Expand Up @@ -3099,7 +3100,12 @@ async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAp
});

project.update(cx, |project, cx| {
project.restart_language_servers_for_buffers(vec![buffer.clone()], HashSet::default(), cx);
project.restart_language_servers_for_buffers(
vec![buffer.clone()],
HashSet::default(),
true,
cx,
);
});

// The diagnostics are cleared.
Expand Down Expand Up @@ -3154,7 +3160,12 @@ async fn test_restarted_server_reporting_invalid_buffer_version(cx: &mut gpui::T
});
cx.executor().run_until_parked();
project.update(cx, |project, cx| {
project.restart_language_servers_for_buffers(vec![buffer.clone()], HashSet::default(), cx);
project.restart_language_servers_for_buffers(
vec![buffer.clone()],
HashSet::default(),
true,
cx,
);
});

let mut fake_server = fake_servers.next().await.unwrap();
Expand Down Expand Up @@ -3972,7 +3983,12 @@ async fn test_diagnostic_summaries_cleared_on_server_restart(cx: &mut gpui::Test
let mut events = cx.events(&project);

project.update(cx, |project, cx| {
project.restart_language_servers_for_buffers(vec![buffer.clone()], HashSet::default(), cx);
project.restart_language_servers_for_buffers(
vec![buffer.clone()],
HashSet::default(),
true,
cx,
);
});
cx.executor().run_until_parked();

Expand Down
Loading