From 3180f4447743426cf2227ed1ebf37cf8edced5d3 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Tue, 9 Dec 2025 21:37:39 +0100 Subject: [PATCH] lsp: Do not drop lsp buffer handle from editor when a language change leads to buffer having a legit language (#44469) Fixes a bug that led to us unnecessarily restarting a language server when we were looking at a single file of a given language. Release Notes: - Fixed a bug that led to Zed sometimes starting an excessive amount of language servers --- crates/copilot/src/copilot.rs | 2 +- crates/editor/src/editor.rs | 6 ++++-- crates/git_ui/src/file_diff_view.rs | 2 +- crates/git_ui/src/text_diff_view.rs | 2 +- crates/language/src/buffer.rs | 13 ++++++++----- crates/multi_buffer/src/multi_buffer.rs | 6 ++++-- crates/project/src/buffer_store.rs | 2 +- crates/project/src/git_store.rs | 2 +- crates/project/src/lsp_store.rs | 2 +- crates/toolchain_selector/src/active_toolchain.rs | 2 +- 10 files changed, 23 insertions(+), 16 deletions(-) diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index 6fbdeff807b65d22193ba7fdcb8e990f7184f70e..4e6520906074c1384a4e500d89be43659c162718 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -807,7 +807,7 @@ impl Copilot { .ok(); } language::BufferEvent::FileHandleChanged - | language::BufferEvent::LanguageChanged => { + | language::BufferEvent::LanguageChanged(_) => { let new_language_id = id_for_language(buffer.read(cx).language()); let Ok(new_uri) = uri_for_buffer(&buffer, cx) else { return Ok(()); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 023e4931d33be86c70b90a8cd62aa5692c25c9d9..d841bf858b8a77f502b8bfb2499118f9e714572e 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -22007,8 +22007,10 @@ impl Editor { multi_buffer::Event::DiffHunksToggled => { self.tasks_update_task = Some(self.refresh_runnables(window, cx)); } - multi_buffer::Event::LanguageChanged(buffer_id) => { - self.registered_buffers.remove(&buffer_id); + multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => { + if !is_fresh_language { + self.registered_buffers.remove(&buffer_id); + } jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx); cx.emit(EditorEvent::Reparsed(*buffer_id)); cx.notify(); diff --git a/crates/git_ui/src/file_diff_view.rs b/crates/git_ui/src/file_diff_view.rs index e6ed8feb7f69493d3731d9d382cf9b955059fcc4..b020d7a9f3ac083f1a5adf15ca298b55063a3eb8 100644 --- a/crates/git_ui/src/file_diff_view.rs +++ b/crates/git_ui/src/file_diff_view.rs @@ -108,7 +108,7 @@ impl FileDiffView { for buffer in [&old_buffer, &new_buffer] { cx.subscribe(buffer, move |this, _, event, _| match event { language::BufferEvent::Edited - | language::BufferEvent::LanguageChanged + | language::BufferEvent::LanguageChanged(_) | language::BufferEvent::Reparsed => { this.buffer_changes_tx.send(()).ok(); } diff --git a/crates/git_ui/src/text_diff_view.rs b/crates/git_ui/src/text_diff_view.rs index 5a8f0f79592f9161ae9c7ed7f0dc2814eacc2e53..56d55415ba01f893453824be00b9eb8d6bd31a90 100644 --- a/crates/git_ui/src/text_diff_view.rs +++ b/crates/git_ui/src/text_diff_view.rs @@ -170,7 +170,7 @@ impl TextDiffView { cx.subscribe(&source_buffer, move |this, _, event, _| match event { language::BufferEvent::Edited - | language::BufferEvent::LanguageChanged + | language::BufferEvent::LanguageChanged(_) | language::BufferEvent::Reparsed => { this.buffer_changes_tx.send(()).ok(); } diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 7166a01ef64bff9e47c70cac47910f714ae2dc39..7bf62b5aa43c60a7ecee756dd66066682ac09077 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -1,8 +1,8 @@ pub mod row_chunk; use crate::{ - DebuggerTextObject, LanguageScope, Outline, OutlineConfig, RunnableCapture, RunnableTag, - TextObject, TreeSitterOptions, + DebuggerTextObject, LanguageScope, Outline, OutlineConfig, PLAIN_TEXT, RunnableCapture, + RunnableTag, TextObject, TreeSitterOptions, diagnostic_set::{DiagnosticEntry, DiagnosticEntryRef, DiagnosticGroup}, language_settings::{LanguageSettings, language_settings}, outline::OutlineItem, @@ -353,7 +353,8 @@ pub enum BufferEvent { /// The buffer is in need of a reload ReloadNeeded, /// The buffer's language was changed. - LanguageChanged, + /// The boolean indicates whether this buffer did not have a language before, but does now. + LanguageChanged(bool), /// The buffer's syntax trees were updated. Reparsed, /// The buffer's diagnostics were updated. @@ -1386,10 +1387,12 @@ impl Buffer { ) { self.non_text_state_update_count += 1; self.syntax_map.lock().clear(&self.text); - self.language = language; + let old_language = std::mem::replace(&mut self.language, language); self.was_changed(); self.reparse(cx, may_block); - cx.emit(BufferEvent::LanguageChanged); + let has_fresh_language = + self.language.is_some() && old_language.is_none_or(|old| old == *PLAIN_TEXT); + cx.emit(BufferEvent::LanguageChanged(has_fresh_language)); } /// Assign a language registry to the buffer. This allows the buffer to retrieve diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index bd163557c4f6239353e7cd5ad08a6120e20e4a3d..442abe78ee65ba91ccf8e03ab3c0ad26f3679cfc 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -129,7 +129,7 @@ pub enum Event { transaction_id: TransactionId, }, Reloaded, - LanguageChanged(BufferId), + LanguageChanged(BufferId, bool), Reparsed(BufferId), Saved, FileHandleChanged, @@ -2294,7 +2294,9 @@ impl MultiBuffer { BufferEvent::Saved => Event::Saved, BufferEvent::FileHandleChanged => Event::FileHandleChanged, BufferEvent::Reloaded => Event::Reloaded, - BufferEvent::LanguageChanged => Event::LanguageChanged(buffer_id), + BufferEvent::LanguageChanged(has_language) => { + Event::LanguageChanged(buffer_id, *has_language) + } BufferEvent::Reparsed => Event::Reparsed(buffer_id), BufferEvent::DiagnosticsUpdated => Event::DiagnosticsUpdated, BufferEvent::CapabilityChanged => { diff --git a/crates/project/src/buffer_store.rs b/crates/project/src/buffer_store.rs index f4a0d45bc86c39be595439bfe1aebb2533b62783..c38b898f5d79cf34563daa9bc7563f3c869d9a70 100644 --- a/crates/project/src/buffer_store.rs +++ b/crates/project/src/buffer_store.rs @@ -1141,7 +1141,7 @@ impl BufferStore { }) .log_err(); } - BufferEvent::LanguageChanged => {} + BufferEvent::LanguageChanged(_) => {} _ => {} } } diff --git a/crates/project/src/git_store.rs b/crates/project/src/git_store.rs index 0b74a04e1db5c0f2b7c8934d1bbe7d38b1d1ad1b..3efbb57e0312dc7e07d0dbed69f5e096a2e52eb3 100644 --- a/crates/project/src/git_store.rs +++ b/crates/project/src/git_store.rs @@ -1451,7 +1451,7 @@ impl GitStore { match event { BufferStoreEvent::BufferAdded(buffer) => { cx.subscribe(buffer, |this, buffer, event, cx| { - if let BufferEvent::LanguageChanged = event { + if let BufferEvent::LanguageChanged(_) = event { let buffer_id = buffer.read(cx).remote_id(); if let Some(diff_state) = this.diffs.get(&buffer_id) { diff_state.update(cx, |diff_state, cx| { diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 1ae6d1295f37df31aac03e2019cb5510c836fb1c..6856c0ba49da63888cdd81015ca7f725ca3cb81f 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -219,7 +219,7 @@ struct UnifiedLanguageServer { project_roots: HashSet>, } -#[derive(Clone, Hash, PartialEq, Eq)] +#[derive(Clone, Debug, Hash, PartialEq, Eq)] struct LanguageServerSeed { worktree_id: WorktreeId, name: LanguageServerName, diff --git a/crates/toolchain_selector/src/active_toolchain.rs b/crates/toolchain_selector/src/active_toolchain.rs index 122aa9f22b74c33dd8f148f2bf3b65f04da478a9..03c152e3fd3df0c62ab2f5c7e4a4746875ac955a 100644 --- a/crates/toolchain_selector/src/active_toolchain.rs +++ b/crates/toolchain_selector/src/active_toolchain.rs @@ -124,7 +124,7 @@ impl ActiveToolchain { &buffer, window, |this, _, event: &BufferEvent, window, cx| { - if matches!(event, BufferEvent::LanguageChanged) { + if matches!(event, BufferEvent::LanguageChanged(_)) { this._update_toolchain_task = Self::spawn_tracker_task(window, cx); } },