Further improve color inlay hints in multi buffers (#33642)

Kirill Bulatov created

Follow-up of https://github.com/zed-industries/zed/pull/33605

Release Notes:

- N/A

Change summary

crates/editor/src/display_map.rs           |   6 
crates/editor/src/display_map/fold_map.rs  |  19 +
crates/editor/src/display_map/inlay_map.rs |   8 
crates/editor/src/editor.rs                |   4 
crates/editor/src/element.rs               |  10 
crates/editor/src/lsp_colors.rs            | 231 ++++++++++++++---------
crates/project/src/lsp_store.rs            |  80 ++++++-
7 files changed, 230 insertions(+), 128 deletions(-)

Detailed changes

crates/editor/src/display_map.rs 🔗

@@ -37,7 +37,9 @@ pub use block_map::{
 use block_map::{BlockRow, BlockSnapshot};
 use collections::{HashMap, HashSet};
 pub use crease_map::*;
-pub use fold_map::{ChunkRenderer, ChunkRendererContext, Fold, FoldId, FoldPlaceholder, FoldPoint};
+pub use fold_map::{
+    ChunkRenderer, ChunkRendererContext, ChunkRendererId, Fold, FoldId, FoldPlaceholder, FoldPoint,
+};
 use fold_map::{FoldMap, FoldSnapshot};
 use gpui::{App, Context, Entity, Font, HighlightStyle, LineLayout, Pixels, UnderlineStyle};
 pub use inlay_map::Inlay;
@@ -538,7 +540,7 @@ impl DisplayMap {
 
     pub fn update_fold_widths(
         &mut self,
-        widths: impl IntoIterator<Item = (FoldId, Pixels)>,
+        widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
         cx: &mut Context<Self>,
     ) -> bool {
         let snapshot = self.buffer.read(cx).snapshot(cx);

crates/editor/src/display_map/fold_map.rs 🔗

@@ -1,4 +1,4 @@
-use crate::display_map::inlay_map::InlayChunk;
+use crate::{InlayId, display_map::inlay_map::InlayChunk};
 
 use super::{
     Highlights,
@@ -277,13 +277,16 @@ impl FoldMapWriter<'_> {
 
     pub(crate) fn update_fold_widths(
         &mut self,
-        new_widths: impl IntoIterator<Item = (FoldId, Pixels)>,
+        new_widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
     ) -> (FoldSnapshot, Vec<FoldEdit>) {
         let mut edits = Vec::new();
         let inlay_snapshot = self.0.snapshot.inlay_snapshot.clone();
         let buffer = &inlay_snapshot.buffer;
 
         for (id, new_width) in new_widths {
+            let ChunkRendererId::Fold(id) = id else {
+                continue;
+            };
             if let Some(metadata) = self.0.snapshot.fold_metadata_by_id.get(&id).cloned() {
                 if Some(new_width) != metadata.width {
                     let buffer_start = metadata.range.start.to_offset(buffer);
@@ -529,7 +532,7 @@ impl FoldMap {
                                 placeholder: Some(TransformPlaceholder {
                                     text: ELLIPSIS,
                                     renderer: ChunkRenderer {
-                                        id: fold.id,
+                                        id: ChunkRendererId::Fold(fold.id),
                                         render: Arc::new(move |cx| {
                                             (fold.placeholder.render)(
                                                 fold_id,
@@ -1267,11 +1270,17 @@ pub struct Chunk<'a> {
     pub renderer: Option<ChunkRenderer>,
 }
 
+#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub enum ChunkRendererId {
+    Fold(FoldId),
+    Inlay(InlayId),
+}
+
 /// A recipe for how the chunk should be presented.
 #[derive(Clone)]
 pub struct ChunkRenderer {
-    /// The id of the fold associated with this chunk.
-    pub id: FoldId,
+    /// The id of the renderer associated with this chunk.
+    pub id: ChunkRendererId,
     /// Creates a custom element to represent this chunk.
     pub render: Arc<dyn Send + Sync + Fn(&mut ChunkRendererContext) -> AnyElement>,
     /// If true, the element is constrained to the shaped width of the text.

crates/editor/src/display_map/inlay_map.rs 🔗

@@ -1,4 +1,4 @@
-use crate::{ChunkRenderer, HighlightStyles, InlayId, display_map::FoldId};
+use crate::{ChunkRenderer, HighlightStyles, InlayId};
 use collections::BTreeSet;
 use gpui::{Hsla, Rgba};
 use language::{Chunk, Edit, Point, TextSummary};
@@ -14,7 +14,7 @@ use sum_tree::{Bias, Cursor, SumTree};
 use text::{Patch, Rope};
 use ui::{ActiveTheme, IntoElement as _, ParentElement as _, Styled as _, div};
 
-use super::{Highlights, custom_highlights::CustomHighlightsChunks};
+use super::{Highlights, custom_highlights::CustomHighlightsChunks, fold_map::ChunkRendererId};
 
 /// Decides where the [`Inlay`]s should be displayed.
 ///
@@ -338,10 +338,10 @@ impl<'a> Iterator for InlayChunks<'a> {
                     }
                     InlayId::Hint(_) => self.highlight_styles.inlay_hint,
                     InlayId::DebuggerValue(_) => self.highlight_styles.inlay_hint,
-                    InlayId::Color(id) => {
+                    InlayId::Color(_) => {
                         if let Some(color) = inlay.color {
                             renderer = Some(ChunkRenderer {
-                                id: FoldId(id),
+                                id: ChunkRendererId::Inlay(inlay.id),
                                 render: Arc::new(move |cx| {
                                     div()
                                         .w_4()

crates/editor/src/editor.rs 🔗

@@ -17333,9 +17333,9 @@ impl Editor {
         self.active_indent_guides_state.dirty = true;
     }
 
-    pub fn update_fold_widths(
+    pub fn update_renderer_widths(
         &mut self,
-        widths: impl IntoIterator<Item = (FoldId, Pixels)>,
+        widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
         cx: &mut Context<Self>,
     ) -> bool {
         self.display_map

crates/editor/src/element.rs 🔗

@@ -12,8 +12,8 @@ use crate::{
     ToggleFold,
     code_context_menus::{CodeActionsMenu, MENU_ASIDE_MAX_WIDTH, MENU_ASIDE_MIN_WIDTH, MENU_GAP},
     display_map::{
-        Block, BlockContext, BlockStyle, DisplaySnapshot, EditorMargins, FoldId, HighlightKey,
-        HighlightedChunk, ToDisplayPoint,
+        Block, BlockContext, BlockStyle, ChunkRendererId, DisplaySnapshot, EditorMargins,
+        HighlightKey, HighlightedChunk, ToDisplayPoint,
     },
     editor_settings::{
         CurrentLineHighlight, DocumentColorsRenderMode, DoubleClickInMultibuffer, Minimap,
@@ -7119,7 +7119,7 @@ pub(crate) struct LineWithInvisibles {
 enum LineFragment {
     Text(ShapedLine),
     Element {
-        id: FoldId,
+        id: ChunkRendererId,
         element: Option<AnyElement>,
         size: Size<Pixels>,
         len: usize,
@@ -8297,7 +8297,7 @@ impl Element for EditorElement {
                         window,
                         cx,
                     );
-                    let new_fold_widths = line_layouts
+                    let new_renrerer_widths = line_layouts
                         .iter()
                         .flat_map(|layout| &layout.fragments)
                         .filter_map(|fragment| {
@@ -8308,7 +8308,7 @@ impl Element for EditorElement {
                             }
                         });
                     if self.editor.update(cx, |editor, cx| {
-                        editor.update_fold_widths(new_fold_widths, cx)
+                        editor.update_renderer_widths(new_renrerer_widths, cx)
                     }) {
                         // If the fold widths have changed, we need to prepaint
                         // the element again to account for any changes in

crates/editor/src/lsp_colors.rs 🔗

@@ -19,18 +19,21 @@ use crate::{
 
 #[derive(Debug)]
 pub(super) struct LspColorData {
-    cache_version_used: usize,
+    buffer_colors: HashMap<BufferId, BufferColors>,
+    render_mode: DocumentColorsRenderMode,
+}
+
+#[derive(Debug, Default)]
+struct BufferColors {
     colors: Vec<(Range<Anchor>, DocumentColor, InlayId)>,
     inlay_colors: HashMap<InlayId, usize>,
-    render_mode: DocumentColorsRenderMode,
+    cache_version_used: usize,
 }
 
 impl LspColorData {
     pub fn new(cx: &App) -> Self {
         Self {
-            cache_version_used: 0,
-            colors: Vec::new(),
-            inlay_colors: HashMap::default(),
+            buffer_colors: HashMap::default(),
             render_mode: EditorSettings::get_global(cx).lsp_document_colors,
         }
     }
@@ -47,8 +50,9 @@ impl LspColorData {
             DocumentColorsRenderMode::Inlay => Some(InlaySplice {
                 to_remove: Vec::new(),
                 to_insert: self
-                    .colors
+                    .buffer_colors
                     .iter()
+                    .flat_map(|(_, buffer_colors)| buffer_colors.colors.iter())
                     .map(|(range, color, id)| {
                         Inlay::color(
                             id.id(),
@@ -63,33 +67,49 @@ impl LspColorData {
                     })
                     .collect(),
             }),
-            DocumentColorsRenderMode::None => {
-                self.colors.clear();
-                Some(InlaySplice {
-                    to_remove: self.inlay_colors.drain().map(|(id, _)| id).collect(),
-                    to_insert: Vec::new(),
-                })
-            }
+            DocumentColorsRenderMode::None => Some(InlaySplice {
+                to_remove: self
+                    .buffer_colors
+                    .drain()
+                    .flat_map(|(_, buffer_colors)| buffer_colors.inlay_colors)
+                    .map(|(id, _)| id)
+                    .collect(),
+                to_insert: Vec::new(),
+            }),
             DocumentColorsRenderMode::Border | DocumentColorsRenderMode::Background => {
                 Some(InlaySplice {
-                    to_remove: self.inlay_colors.drain().map(|(id, _)| id).collect(),
+                    to_remove: self
+                        .buffer_colors
+                        .iter_mut()
+                        .flat_map(|(_, buffer_colors)| buffer_colors.inlay_colors.drain())
+                        .map(|(id, _)| id)
+                        .collect(),
                     to_insert: Vec::new(),
                 })
             }
         }
     }
 
-    fn set_colors(&mut self, colors: Vec<(Range<Anchor>, DocumentColor, InlayId)>) -> bool {
-        if self.colors == colors {
+    fn set_colors(
+        &mut self,
+        buffer_id: BufferId,
+        colors: Vec<(Range<Anchor>, DocumentColor, InlayId)>,
+        cache_version: Option<usize>,
+    ) -> bool {
+        let buffer_colors = self.buffer_colors.entry(buffer_id).or_default();
+        if let Some(cache_version) = cache_version {
+            buffer_colors.cache_version_used = cache_version;
+        }
+        if buffer_colors.colors == colors {
             return false;
         }
 
-        self.inlay_colors = colors
+        buffer_colors.inlay_colors = colors
             .iter()
             .enumerate()
             .map(|(i, (_, _, id))| (*id, i))
             .collect();
-        self.colors = colors;
+        buffer_colors.colors = colors;
         true
     }
 
@@ -103,8 +123,9 @@ impl LspColorData {
         {
             Vec::new()
         } else {
-            self.colors
+            self.buffer_colors
                 .iter()
+                .flat_map(|(_, buffer_colors)| &buffer_colors.colors)
                 .map(|(range, color, _)| {
                     let display_range = range.clone().to_display_points(snapshot);
                     let color = Hsla::from(Rgba {
@@ -162,10 +183,9 @@ impl Editor {
                         ColorFetchStrategy::IgnoreCache
                     } else {
                         ColorFetchStrategy::UseCache {
-                            known_cache_version: self
-                                .colors
-                                .as_ref()
-                                .map(|colors| colors.cache_version_used),
+                            known_cache_version: self.colors.as_ref().and_then(|colors| {
+                                Some(colors.buffer_colors.get(&buffer_id)?.cache_version_used)
+                            }),
                         }
                     };
                     let colors_task = lsp_store.document_colors(fetch_strategy, buffer, cx)?;
@@ -201,15 +221,13 @@ impl Editor {
                 return;
             };
 
-            let mut cache_version = None;
-            let mut new_editor_colors = Vec::<(Range<Anchor>, DocumentColor)>::new();
+            let mut new_editor_colors = HashMap::default();
             for (buffer_id, colors) in all_colors {
                 let Some(excerpts) = editor_excerpts.get(&buffer_id) else {
                     continue;
                 };
                 match colors {
                     Ok(colors) => {
-                        cache_version = colors.cache_version;
                         for color in colors.colors {
                             let color_start = point_from_lsp(color.lsp_range.start);
                             let color_end = point_from_lsp(color.lsp_range.end);
@@ -243,8 +261,15 @@ impl Editor {
                                     continue;
                                 };
 
+                                let new_entry =
+                                    new_editor_colors.entry(buffer_id).or_insert_with(|| {
+                                        (Vec::<(Range<Anchor>, DocumentColor)>::new(), None)
+                                    });
+                                new_entry.1 = colors.cache_version;
+                                let new_buffer_colors = &mut new_entry.0;
+
                                 let (Ok(i) | Err(i)) =
-                                    new_editor_colors.binary_search_by(|(probe, _)| {
+                                    new_buffer_colors.binary_search_by(|(probe, _)| {
                                         probe
                                             .start
                                             .cmp(&color_start_anchor, &multi_buffer_snapshot)
@@ -254,7 +279,7 @@ impl Editor {
                                                     .cmp(&color_end_anchor, &multi_buffer_snapshot)
                                             })
                                     });
-                                new_editor_colors
+                                new_buffer_colors
                                     .insert(i, (color_start_anchor..color_end_anchor, color));
                                 break;
                             }
@@ -267,45 +292,70 @@ impl Editor {
             editor
                 .update(cx, |editor, cx| {
                     let mut colors_splice = InlaySplice::default();
-                    let mut new_color_inlays = Vec::with_capacity(new_editor_colors.len());
                     let Some(colors) = &mut editor.colors else {
                         return;
                     };
-                    let mut existing_colors = colors.colors.iter().peekable();
-                    for (new_range, new_color) in new_editor_colors {
-                        let rgba_color = Rgba {
-                            r: new_color.color.red,
-                            g: new_color.color.green,
-                            b: new_color.color.blue,
-                            a: new_color.color.alpha,
-                        };
+                    let mut updated = false;
+                    for (buffer_id, (new_buffer_colors, new_cache_version)) in new_editor_colors {
+                        let mut new_buffer_color_inlays =
+                            Vec::with_capacity(new_buffer_colors.len());
+                        let mut existing_buffer_colors = colors
+                            .buffer_colors
+                            .entry(buffer_id)
+                            .or_default()
+                            .colors
+                            .iter()
+                            .peekable();
+                        for (new_range, new_color) in new_buffer_colors {
+                            let rgba_color = Rgba {
+                                r: new_color.color.red,
+                                g: new_color.color.green,
+                                b: new_color.color.blue,
+                                a: new_color.color.alpha,
+                            };
 
-                        loop {
-                            match existing_colors.peek() {
-                                Some((existing_range, existing_color, existing_inlay_id)) => {
-                                    match existing_range
-                                        .start
-                                        .cmp(&new_range.start, &multi_buffer_snapshot)
-                                        .then_with(|| {
-                                            existing_range
-                                                .end
-                                                .cmp(&new_range.end, &multi_buffer_snapshot)
-                                        }) {
-                                        cmp::Ordering::Less => {
-                                            colors_splice.to_remove.push(*existing_inlay_id);
-                                            existing_colors.next();
-                                            continue;
-                                        }
-                                        cmp::Ordering::Equal => {
-                                            if existing_color == &new_color {
-                                                new_color_inlays.push((
-                                                    new_range,
-                                                    new_color,
-                                                    *existing_inlay_id,
-                                                ));
-                                            } else {
+                            loop {
+                                match existing_buffer_colors.peek() {
+                                    Some((existing_range, existing_color, existing_inlay_id)) => {
+                                        match existing_range
+                                            .start
+                                            .cmp(&new_range.start, &multi_buffer_snapshot)
+                                            .then_with(|| {
+                                                existing_range
+                                                    .end
+                                                    .cmp(&new_range.end, &multi_buffer_snapshot)
+                                            }) {
+                                            cmp::Ordering::Less => {
                                                 colors_splice.to_remove.push(*existing_inlay_id);
+                                                existing_buffer_colors.next();
+                                                continue;
+                                            }
+                                            cmp::Ordering::Equal => {
+                                                if existing_color == &new_color {
+                                                    new_buffer_color_inlays.push((
+                                                        new_range,
+                                                        new_color,
+                                                        *existing_inlay_id,
+                                                    ));
+                                                } else {
+                                                    colors_splice
+                                                        .to_remove
+                                                        .push(*existing_inlay_id);
 
+                                                    let inlay = Inlay::color(
+                                                        post_inc(&mut editor.next_color_inlay_id),
+                                                        new_range.start,
+                                                        rgba_color,
+                                                    );
+                                                    let inlay_id = inlay.id;
+                                                    colors_splice.to_insert.push(inlay);
+                                                    new_buffer_color_inlays
+                                                        .push((new_range, new_color, inlay_id));
+                                                }
+                                                existing_buffer_colors.next();
+                                                break;
+                                            }
+                                            cmp::Ordering::Greater => {
                                                 let inlay = Inlay::color(
                                                     post_inc(&mut editor.next_color_inlay_id),
                                                     new_range.start,
@@ -313,49 +363,40 @@ impl Editor {
                                                 );
                                                 let inlay_id = inlay.id;
                                                 colors_splice.to_insert.push(inlay);
-                                                new_color_inlays
+                                                new_buffer_color_inlays
                                                     .push((new_range, new_color, inlay_id));
+                                                break;
                                             }
-                                            existing_colors.next();
-                                            break;
-                                        }
-                                        cmp::Ordering::Greater => {
-                                            let inlay = Inlay::color(
-                                                post_inc(&mut editor.next_color_inlay_id),
-                                                new_range.start,
-                                                rgba_color,
-                                            );
-                                            let inlay_id = inlay.id;
-                                            colors_splice.to_insert.push(inlay);
-                                            new_color_inlays.push((new_range, new_color, inlay_id));
-                                            break;
                                         }
                                     }
-                                }
-                                None => {
-                                    let inlay = Inlay::color(
-                                        post_inc(&mut editor.next_color_inlay_id),
-                                        new_range.start,
-                                        rgba_color,
-                                    );
-                                    let inlay_id = inlay.id;
-                                    colors_splice.to_insert.push(inlay);
-                                    new_color_inlays.push((new_range, new_color, inlay_id));
-                                    break;
+                                    None => {
+                                        let inlay = Inlay::color(
+                                            post_inc(&mut editor.next_color_inlay_id),
+                                            new_range.start,
+                                            rgba_color,
+                                        );
+                                        let inlay_id = inlay.id;
+                                        colors_splice.to_insert.push(inlay);
+                                        new_buffer_color_inlays
+                                            .push((new_range, new_color, inlay_id));
+                                        break;
+                                    }
                                 }
                             }
                         }
-                    }
-                    if existing_colors.peek().is_some() {
-                        colors_splice
-                            .to_remove
-                            .extend(existing_colors.map(|(_, _, id)| *id));
-                    }
 
-                    let mut updated = colors.set_colors(new_color_inlays);
-                    if let Some(cache_version) = cache_version {
-                        colors.cache_version_used = cache_version;
+                        if existing_buffer_colors.peek().is_some() {
+                            colors_splice
+                                .to_remove
+                                .extend(existing_buffer_colors.map(|(_, _, id)| *id));
+                        }
+                        updated |= colors.set_colors(
+                            buffer_id,
+                            new_buffer_color_inlays,
+                            new_cache_version,
+                        );
                     }
+
                     if colors.render_mode == DocumentColorsRenderMode::Inlay
                         && (!colors_splice.to_insert.is_empty()
                             || !colors_splice.to_remove.is_empty())

crates/project/src/lsp_store.rs 🔗

@@ -170,6 +170,7 @@ pub struct LocalLspStore {
     _subscription: gpui::Subscription,
     lsp_tree: Entity<LanguageServerTree>,
     registered_buffers: HashMap<BufferId, usize>,
+    buffers_opened_in_servers: HashMap<BufferId, HashSet<LanguageServerId>>,
     buffer_pull_diagnostics_result_ids: HashMap<LanguageServerId, HashMap<PathBuf, Option<String>>>,
 }
 
@@ -2546,6 +2547,10 @@ impl LocalLspStore {
                     vec![snapshot]
                 });
 
+            self.buffers_opened_in_servers
+                .entry(buffer_id)
+                .or_default()
+                .insert(server.server_id());
             cx.emit(LspStoreEvent::LanguageServerUpdate {
                 language_server_id: server.server_id(),
                 name: None,
@@ -3208,6 +3213,9 @@ impl LocalLspStore {
             self.language_servers.remove(server_id_to_remove);
             self.buffer_pull_diagnostics_result_ids
                 .remove(server_id_to_remove);
+            for buffer_servers in self.buffers_opened_in_servers.values_mut() {
+                buffer_servers.remove(server_id_to_remove);
+            }
             cx.emit(LspStoreEvent::LanguageServerRemoved(*server_id_to_remove));
         }
         servers_to_remove.into_keys().collect()
@@ -3787,6 +3795,7 @@ impl LspStore {
                 }),
                 lsp_tree: LanguageServerTree::new(manifest_tree, languages.clone(), cx),
                 registered_buffers: HashMap::default(),
+                buffers_opened_in_servers: HashMap::default(),
                 buffer_pull_diagnostics_result_ids: HashMap::default(),
             }),
             last_formatting_failure: None,
@@ -4159,6 +4168,7 @@ impl LspStore {
                         lsp_store.lsp_data.remove(&buffer_id);
                         let local = lsp_store.as_local_mut().unwrap();
                         local.registered_buffers.remove(&buffer_id);
+                        local.buffers_opened_in_servers.remove(&buffer_id);
                         if let Some(file) = File::from_dyn(buffer.read(cx).file()).cloned() {
                             local.unregister_old_buffer_from_language_servers(&buffer, &file, cx);
                         }
@@ -6235,21 +6245,31 @@ impl LspStore {
             } => {
                 if let Some(cached_data) = self.lsp_data.get(&buffer_id) {
                     if !version_queried_for.changed_since(&cached_data.colors_for_version) {
-                        if Some(cached_data.cache_version) == known_cache_version {
-                            return None;
-                        } else {
-                            return Some(
-                                Task::ready(Ok(DocumentColors {
-                                    colors: cached_data
-                                        .colors
-                                        .values()
-                                        .flatten()
-                                        .cloned()
-                                        .collect(),
-                                    cache_version: Some(cached_data.cache_version),
-                                }))
-                                .shared(),
-                            );
+                        let has_different_servers = self.as_local().is_some_and(|local| {
+                            local
+                                .buffers_opened_in_servers
+                                .get(&buffer_id)
+                                .cloned()
+                                .unwrap_or_default()
+                                != cached_data.colors.keys().copied().collect()
+                        });
+                        if !has_different_servers {
+                            if Some(cached_data.cache_version) == known_cache_version {
+                                return None;
+                            } else {
+                                return Some(
+                                    Task::ready(Ok(DocumentColors {
+                                        colors: cached_data
+                                            .colors
+                                            .values()
+                                            .flatten()
+                                            .cloned()
+                                            .collect(),
+                                        cache_version: Some(cached_data.cache_version),
+                                    }))
+                                    .shared(),
+                                );
+                            }
                         }
                     }
                 }
@@ -7522,6 +7542,14 @@ impl LspStore {
                         .unwrap_or(true)
                 })
                 .map(|(_, server)| server.server_id())
+                .filter(|server_id| {
+                    self.as_local().is_none_or(|local| {
+                        local
+                            .buffers_opened_in_servers
+                            .get(&snapshot.remote_id())
+                            .is_some_and(|servers| servers.contains(server_id))
+                    })
+                })
                 .collect::<Vec<_>>()
         });
 
@@ -10084,6 +10112,7 @@ impl LspStore {
         }
 
         // Tell the language server about every open buffer in the worktree that matches the language.
+        let mut buffer_paths_registered = Vec::new();
         self.buffer_store.clone().update(cx, |buffer_store, cx| {
             for buffer_handle in buffer_store.buffers() {
                 let buffer = buffer_handle.read(cx);
@@ -10142,6 +10171,12 @@ impl LspStore {
                         version,
                         initial_snapshot.text(),
                     );
+                    buffer_paths_registered.push(file.abs_path(cx));
+                    local
+                        .buffers_opened_in_servers
+                        .entry(buffer.remote_id())
+                        .or_default()
+                        .insert(server_id);
                 }
                 buffer_handle.update(cx, |buffer, cx| {
                     buffer.set_completion_triggers(
@@ -10163,6 +10198,18 @@ impl LspStore {
             }
         });
 
+        for abs_path in buffer_paths_registered {
+            cx.emit(LspStoreEvent::LanguageServerUpdate {
+                language_server_id: server_id,
+                name: Some(adapter.name()),
+                message: proto::update_language_server::Variant::RegisteredForBuffer(
+                    proto::RegisteredForBuffer {
+                        buffer_abs_path: abs_path.to_string_lossy().to_string(),
+                    },
+                ),
+            });
+        }
+
         cx.notify();
     }
 
@@ -10612,6 +10659,9 @@ impl LspStore {
         }
         if let Some(local) = self.as_local_mut() {
             local.buffer_pull_diagnostics_result_ids.remove(&for_server);
+            for buffer_servers in local.buffers_opened_in_servers.values_mut() {
+                buffer_servers.remove(&for_server);
+            }
         }
     }