From ee3245c9a594b951ff546d2fd4b12edb01020210 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 17 Jul 2025 13:11:23 +0300 Subject: [PATCH 1/5] Map the new interface # Conflicts: # crates/project/src/lsp_store.rs # Conflicts: # crates/project/src/lsp_store.rs # Conflicts: # crates/editor/src/editor.rs # crates/project/src/lsp_store.rs --- crates/editor/src/editor.rs | 15 ++-- crates/project/src/lsp_store.rs | 81 +++++++++++-------- .../project/src/lsp_store/inlay_hint_cache.rs | 56 +++++++++++++ crates/project/src/project.rs | 6 +- crates/project/src/project_tests.rs | 10 ++- crates/proto/proto/lsp.proto | 1 + 6 files changed, 127 insertions(+), 42 deletions(-) create mode 100644 crates/project/src/lsp_store/inlay_hint_cache.rs diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 29e009fdf8..a54bd68118 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1611,7 +1611,7 @@ enum InlayHintRefreshReason { SettingsChange(InlayHintSettings), NewLinesShown, BufferEdited(HashSet>), - RefreshRequested, + RefreshRequested(LanguageServerId), ExcerptsRemoved(Vec), } @@ -1623,7 +1623,7 @@ impl InlayHintRefreshReason { Self::SettingsChange(_) => "settings change", Self::NewLinesShown => "new lines shown", Self::BufferEdited(_) => "buffer edited", - Self::RefreshRequested => "refresh requested", + Self::RefreshRequested(_) => "refresh requested", Self::ExcerptsRemoved(_) => "excerpts removed", } } @@ -1864,8 +1864,11 @@ impl Editor { project::Event::RefreshCodeLens => { // we always query lens with actions, without storing them, always refreshing them } - project::Event::RefreshInlayHints => { - editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx); + project::Event::RefreshInlayHints(server_id) => { + editor.refresh_inlay_hints( + InlayHintRefreshReason::RefreshRequested(*server_id), + cx, + ); } project::Event::LanguageServerAdded(..) | project::Event::LanguageServerRemoved(..) => { @@ -5229,7 +5232,7 @@ impl Editor { InlayHintRefreshReason::BufferEdited(buffer_languages) => { (InvalidationStrategy::BufferEdited, Some(buffer_languages)) } - InlayHintRefreshReason::RefreshRequested => { + InlayHintRefreshReason::RefreshRequested(_) => { (InvalidationStrategy::RefreshRequested, None) } }; @@ -16751,9 +16754,9 @@ impl Editor { HashSet::default(), cx, ); - cx.emit(project::Event::RefreshInlayHints); }); }); + self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx); } } diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index deebaedd74..ce3f765d24 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -14,6 +14,9 @@ pub mod json_language_server_ext; pub mod lsp_ext_command; pub mod rust_analyzer_ext; +mod inlay_hint_cache; + +use self::inlay_hint_cache::InlayHintCache; use crate::{ CodeAction, ColorPresentation, Completion, CompletionResponse, CompletionSource, CoreCompletion, DocumentColor, Hover, InlayHint, LocationLink, LspAction, LspPullDiagnostics, @@ -564,7 +567,7 @@ impl LocalLspStore { } fn setup_lsp_messages( - this: WeakEntity, + lsp_store: WeakEntity, fs: Arc, language_server: &LanguageServer, delegate: Arc, @@ -575,7 +578,7 @@ impl LocalLspStore { language_server .on_notification::({ let adapter = adapter.clone(); - let this = this.clone(); + let this = lsp_store.clone(); move |mut params, cx| { let adapter = adapter.clone(); if let Some(this) = this.upgrade() { @@ -619,7 +622,7 @@ impl LocalLspStore { .on_request::({ let adapter = adapter.adapter.clone(); let delegate = delegate.clone(); - let this = this.clone(); + let this = lsp_store.clone(); let fs = fs.clone(); move |params, cx| { let adapter = adapter.clone(); @@ -667,7 +670,7 @@ impl LocalLspStore { language_server .on_request::({ - let this = this.clone(); + let this = lsp_store.clone(); move |_, cx| { let this = this.clone(); let cx = cx.clone(); @@ -695,7 +698,7 @@ impl LocalLspStore { // to these requests when initializing. language_server .on_request::({ - let this = this.clone(); + let this = lsp_store.clone(); move |params, cx| { let this = this.clone(); let mut cx = cx.clone(); @@ -716,7 +719,7 @@ impl LocalLspStore { language_server .on_request::({ - let lsp_store = this.clone(); + let lsp_store = lsp_store.clone(); move |params, cx| { let lsp_store = lsp_store.clone(); let mut cx = cx.clone(); @@ -745,7 +748,7 @@ impl LocalLspStore { language_server .on_request::({ - let lsp_store = this.clone(); + let lsp_store = lsp_store.clone(); move |params, cx| { let lsp_store = lsp_store.clone(); let mut cx = cx.clone(); @@ -774,7 +777,7 @@ impl LocalLspStore { language_server .on_request::({ - let this = this.clone(); + let this = lsp_store.clone(); move |params, cx| { let mut cx = cx.clone(); let this = this.clone(); @@ -793,18 +796,22 @@ impl LocalLspStore { language_server .on_request::({ - let this = this.clone(); + let lsp_store = lsp_store.clone(); move |(), cx| { - let this = this.clone(); + let this = lsp_store.clone(); let mut cx = cx.clone(); async move { - this.update(&mut cx, |this, cx| { - cx.emit(LspStoreEvent::RefreshInlayHints); - this.downstream_client.as_ref().map(|(client, project_id)| { - client.send(proto::RefreshInlayHints { - project_id: *project_id, + this.update(&mut cx, |lsp_store, cx| { + cx.emit(LspStoreEvent::RefreshInlayHints(server_id)); + lsp_store + .downstream_client + .as_ref() + .map(|(client, project_id)| { + client.send(proto::RefreshInlayHints { + project_id: *project_id, + server_id: server_id.to_proto(), + }) }) - }) })? .transpose()?; Ok(()) @@ -815,7 +822,7 @@ impl LocalLspStore { language_server .on_request::({ - let this = this.clone(); + let this = lsp_store.clone(); move |(), cx| { let this = this.clone(); let mut cx = cx.clone(); @@ -837,7 +844,7 @@ impl LocalLspStore { language_server .on_request::({ - let this = this.clone(); + let this = lsp_store.clone(); move |(), cx| { let this = this.clone(); let mut cx = cx.clone(); @@ -863,7 +870,7 @@ impl LocalLspStore { language_server .on_request::({ - let this = this.clone(); + let this = lsp_store.clone(); let name = name.to_string(); move |params, cx| { let this = this.clone(); @@ -901,7 +908,7 @@ impl LocalLspStore { .detach(); language_server .on_notification::({ - let this = this.clone(); + let this = lsp_store.clone(); let name = name.to_string(); move |params, cx| { let this = this.clone(); @@ -933,7 +940,7 @@ impl LocalLspStore { language_server .on_notification::({ - let this = this.clone(); + let this = lsp_store.clone(); move |params, cx| { if let Some(this) = this.upgrade() { this.update(cx, |this, cx| { @@ -952,7 +959,7 @@ impl LocalLspStore { language_server .on_notification::({ - let this = this.clone(); + let this = lsp_store.clone(); move |params, cx| { if let Some(this) = this.upgrade() { this.update(cx, |_, cx| { @@ -970,7 +977,7 @@ impl LocalLspStore { language_server .on_notification::({ - let this = this.clone(); + let this = lsp_store.clone(); move |params, cx| { let mut cx = cx.clone(); if let Some(this) = this.upgrade() { @@ -988,8 +995,8 @@ impl LocalLspStore { .detach(); json_language_server_ext::register_requests(this.clone(), language_server); - rust_analyzer_ext::register_notifications(this.clone(), language_server); - clangd_ext::register_notifications(this, language_server, adapter); + rust_analyzer_ext::register_notifications(lsp_store.clone(), language_server); + clangd_ext::register_notifications(lsp_store, language_server, adapter); } fn shutdown_language_servers_on_quit( @@ -3491,6 +3498,7 @@ pub struct LspStore { pub(super) lsp_server_capabilities: HashMap, lsp_document_colors: HashMap, lsp_code_lens: HashMap, + inlay_hint_data: HashMap, running_lsp_requests: HashMap>)>, } @@ -3540,7 +3548,7 @@ pub enum LspStoreEvent { new_language: Option>, }, Notification(String), - RefreshInlayHints, + RefreshInlayHints(LanguageServerId), RefreshCodeLens, DiagnosticsUpdated { server_id: LanguageServerId, @@ -3762,6 +3770,7 @@ impl LspStore { lsp_server_capabilities: HashMap::default(), lsp_document_colors: HashMap::default(), lsp_code_lens: HashMap::default(), + inlay_hint_data: HashMap::default(), running_lsp_requests: HashMap::default(), active_entry: None, _maintain_workspace_config, @@ -3824,6 +3833,7 @@ impl LspStore { lsp_server_capabilities: HashMap::default(), lsp_document_colors: HashMap::default(), lsp_code_lens: HashMap::default(), + inlay_hint_data: HashMap::default(), running_lsp_requests: HashMap::default(), active_entry: None, @@ -4125,6 +4135,7 @@ impl LspStore { if refcount == 0 { lsp_store.lsp_document_colors.remove(&buffer_id); lsp_store.lsp_code_lens.remove(&buffer_id); + lsp_store.inlay_hint_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); @@ -9524,7 +9535,7 @@ impl LspStore { if let Some(work) = status.pending_work.remove(&token) && !work.is_disk_based_diagnostics_progress { - cx.emit(LspStoreEvent::RefreshInlayHints); + cx.emit(LspStoreEvent::RefreshInlayHints(language_server_id)); } cx.notify(); } @@ -9656,12 +9667,14 @@ impl LspStore { } async fn handle_refresh_inlay_hints( - this: Entity, - _: TypedEnvelope, + lsp_store: Entity, + envelope: TypedEnvelope, mut cx: AsyncApp, ) -> Result { - this.update(&mut cx, |_, cx| { - cx.emit(LspStoreEvent::RefreshInlayHints); + lsp_store.update(&mut cx, |_, cx| { + cx.emit(LspStoreEvent::RefreshInlayHints( + LanguageServerId::from_proto(envelope.payload.server_id), + )); })?; Ok(proto::Ack {}) } @@ -10913,7 +10926,7 @@ impl LspStore { language_server.name(), Some(key.worktree_id), )); - cx.emit(LspStoreEvent::RefreshInlayHints); + cx.emit(LspStoreEvent::RefreshInlayHints(server_id)); let server_capabilities = language_server.capabilities(); if let Some((downstream_client, project_id)) = self.downstream_client.as_ref() { @@ -11517,6 +11530,10 @@ impl LspStore { buffer_servers.remove(&for_server); } } + + for inlay_hint_cache in self.inlay_hint_data.values_mut() { + inlay_hint_cache.remove_server_data(for_server); + } } pub fn result_id( diff --git a/crates/project/src/lsp_store/inlay_hint_cache.rs b/crates/project/src/lsp_store/inlay_hint_cache.rs new file mode 100644 index 0000000000..6b92e32c81 --- /dev/null +++ b/crates/project/src/lsp_store/inlay_hint_cache.rs @@ -0,0 +1,56 @@ +use std::{collections::BTreeMap, ops::Range}; + +use clock::Global; +use collections::HashMap; +use futures::future::Shared; +use gpui::{Context, Entity, Task}; +use language::BufferRow; +use lsp::LanguageServerId; +use text::BufferId; + +use crate::{InlayHint, buffer_store::BufferStore}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct InlayHintId(usize); + +#[derive(Debug)] +pub struct InlayHintCache { + buffer_store: Entity, + hints_for_version: Global, + hints: HashMap, + cache_version: usize, +} + +#[derive(Debug, Default)] +struct Hints { + hints: HashMap, + hints_by_chunks: BTreeMap, Option>>, + hint_updates: HashMap, Shared>>, +} + +pub struct InlayHints { + pub cache_version: usize, + pub hints: Vec, +} + +impl InlayHintCache { + pub fn remove_server_data(&mut self, for_server: LanguageServerId) -> bool { + let removed = self.hints.remove(&for_server).is_some(); + if removed { + self.cache_version += 1; + } + removed + } + + pub fn hints( + &self, + buffer: BufferId, + range: Range, + known_cache_version: Option, + cx: &mut Context, + ) -> Option>> { + todo!("TODO kb") + } + // we want to store the cache version outbound, so they can query with it: we can return nothing (`Option`) if the version matches + // we can get a new server up/down, so we want to re-query for them things, ignoring the cache version +} diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 9fd4eed641..8f4417774c 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -322,7 +322,7 @@ pub enum Event { HostReshared, Reshared, Rejoined, - RefreshInlayHints, + RefreshInlayHints(LanguageServerId), RefreshCodeLens, RevealInProjectPanel(ProjectEntryId), SnippetEdit(BufferId, Vec<(lsp::Range, Snippet)>), @@ -2922,7 +2922,9 @@ impl Project { return; }; } - LspStoreEvent::RefreshInlayHints => cx.emit(Event::RefreshInlayHints), + LspStoreEvent::RefreshInlayHints(server_id) => { + cx.emit(Event::RefreshInlayHints(*server_id)) + } LspStoreEvent::RefreshCodeLens => cx.emit(Event::RefreshCodeLens), LspStoreEvent::LanguageServerPrompt(prompt) => { cx.emit(Event::LanguageServerPrompt(prompt.clone())) diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 6dcd07482e..b4f87c4afa 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -1804,7 +1804,10 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) { fake_server .start_progress(format!("{}/0", progress_token)) .await; - assert_eq!(events.next().await.unwrap(), Event::RefreshInlayHints); + assert_eq!( + events.next().await.unwrap(), + Event::RefreshInlayHints(fake_server.server.server_id()) + ); assert_eq!( events.next().await.unwrap(), Event::DiskBasedDiagnosticsStarted { @@ -1943,7 +1946,10 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC Some(worktree_id) ) ); - assert_eq!(events.next().await.unwrap(), Event::RefreshInlayHints); + assert_eq!( + events.next().await.unwrap(), + Event::RefreshInlayHints(fake_server.server.server_id()) + ); fake_server.start_progress(progress_token).await; assert_eq!( events.next().await.unwrap(), diff --git a/crates/proto/proto/lsp.proto b/crates/proto/proto/lsp.proto index 473ef5c38c..97b07ecf76 100644 --- a/crates/proto/proto/lsp.proto +++ b/crates/proto/proto/lsp.proto @@ -465,6 +465,7 @@ message ResolveInlayHintResponse { message RefreshInlayHints { uint64 project_id = 1; + uint64 server_id = 2; } message CodeLens { From c06534b079a4dc1037418813314e3570d9fd9750 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 18 Jul 2025 12:37:12 +0300 Subject: [PATCH 2/5] Draft the new data structures --- crates/editor/src/editor.rs | 30 ++++++++++++++++++- .../project/src/lsp_store/inlay_hint_cache.rs | 2 +- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index a54bd68118..3b4dc3d80e 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -126,7 +126,7 @@ use language::{ Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery, language_settings::{ - self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode, + self, InlayHintKind, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode, all_language_settings, language_settings, }, point_from_lsp, point_to_lsp, text_diff_with_options, @@ -1177,9 +1177,35 @@ pub struct Editor { selection_drag_state: SelectionDragState, next_color_inlay_id: usize, colors: Option, + inlay_hints: Option, folding_newlines: Task<()>, } +#[derive(Debug)] +struct LspInlayHintData { + enabled: bool, + modifiers_override: bool, + enabled_in_settings: bool, + allowed_hint_kinds: HashSet>, + cache_version: Option, + inlays: Vec, + inlay_tasks: HashMap, Task<()>>>, +} + +impl LspInlayHintData { + pub fn new(settings: InlayHintSettings) -> Self { + Self { + modifiers_override: false, + enabled: settings.enabled, + enabled_in_settings: settings.enabled, + inlays: Vec::new(), + inlay_tasks: HashMap::default(), + allowed_hint_kinds: settings.enabled_inlay_hint_kinds(), + cache_version: None, + } + } +} + #[derive(Copy, Clone, Debug, PartialEq, Eq, Default)] enum NextScrollCursorCenterTopBottom { #[default] @@ -2233,6 +2259,7 @@ impl Editor { tasks_update_task: None, pull_diagnostics_task: Task::ready(()), colors: None, + inlay_hints: None, next_color_inlay_id: 0, linked_edit_ranges: Default::default(), in_project_search: false, @@ -2381,6 +2408,7 @@ impl Editor { editor.minimap = editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx); editor.colors = Some(LspColorData::new(cx)); + editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings)); editor.update_lsp_data(false, None, window, cx); } diff --git a/crates/project/src/lsp_store/inlay_hint_cache.rs b/crates/project/src/lsp_store/inlay_hint_cache.rs index 6b92e32c81..ab610f8640 100644 --- a/crates/project/src/lsp_store/inlay_hint_cache.rs +++ b/crates/project/src/lsp_store/inlay_hint_cache.rs @@ -48,7 +48,7 @@ impl InlayHintCache { range: Range, known_cache_version: Option, cx: &mut Context, - ) -> Option>> { + ) -> Option<(Range, Shared>)> { todo!("TODO kb") } // we want to store the cache version outbound, so they can query with it: we can return nothing (`Option`) if the version matches From 0afe7c32108b92cb69ddc42c746d326ab8b4daf8 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 21 Jul 2025 15:46:57 +0300 Subject: [PATCH 3/5] Start to connect the API in the editor --- crates/editor/src/editor.rs | 349 +++++++++++++++++- .../project/src/lsp_store/inlay_hint_cache.rs | 10 +- 2 files changed, 354 insertions(+), 5 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 3b4dc3d80e..a97af55bee 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1187,23 +1187,247 @@ struct LspInlayHintData { modifiers_override: bool, enabled_in_settings: bool, allowed_hint_kinds: HashSet>, - cache_version: Option, + invalidate_debounce: Option, + append_debounce: Option, + known_cache_version: Option, + // TODO kb use these for removing all hints in splices instead of accessing display_map entries. inlays: Vec, inlay_tasks: HashMap, Task<()>>>, } impl LspInlayHintData { - pub fn new(settings: InlayHintSettings) -> Self { + fn new(settings: InlayHintSettings) -> Self { Self { modifiers_override: false, enabled: settings.enabled, enabled_in_settings: settings.enabled, inlays: Vec::new(), inlay_tasks: HashMap::default(), + invalidate_debounce: debounce_value(settings.edit_debounce_ms), + append_debounce: debounce_value(settings.scroll_debounce_ms), allowed_hint_kinds: settings.enabled_inlay_hint_kinds(), - cache_version: None, + known_cache_version: None, } } + + fn modifiers_override(&mut self, new_override: bool) -> Option { + if self.modifiers_override == new_override { + return None; + } + self.modifiers_override = new_override; + if (self.enabled && self.modifiers_override) || (!self.enabled && !self.modifiers_override) + { + self.clear(); + Some(false) + } else { + Some(true) + } + } + + fn toggle(&mut self, enabled: bool) -> bool { + if self.enabled == enabled { + return false; + } + self.enabled = enabled; + self.modifiers_override = false; + if !enabled { + self.clear(); + } + true + } + + fn clear(&mut self) { + self.known_cache_version = None; + self.inlay_tasks.clear(); + self.inlays.clear(); + } + + /// Checks inlay hint settings for enabled hint kinds and general enabled state. + /// Generates corresponding inlay_map splice updates on settings changes. + /// Does not update inlay hint cache state on disabling or inlay hint kinds change: only reenabling forces new LSP queries. + fn update_settings( + &mut self, + multi_buffer: &Entity, + new_hint_settings: InlayHintSettings, + visible_hints: Vec, + cx: &mut App, + ) -> ControlFlow> { + let old_enabled = self.enabled; + // If the setting for inlay hints has changed, update `enabled`. This condition avoids inlay + // hint visibility changes when other settings change (such as theme). + // + // Another option might be to store whether the user has manually toggled inlay hint + // visibility, and prefer this. This could lead to confusion as it means inlay hint + // visibility would not change when updating the setting if they were ever toggled. + if new_hint_settings.enabled != self.enabled_in_settings { + self.enabled = new_hint_settings.enabled; + self.enabled_in_settings = new_hint_settings.enabled; + self.modifiers_override = false; + }; + self.invalidate_debounce = debounce_value(new_hint_settings.edit_debounce_ms); + self.append_debounce = debounce_value(new_hint_settings.scroll_debounce_ms); + let new_allowed_hint_kinds = new_hint_settings.enabled_inlay_hint_kinds(); + match (old_enabled, self.enabled) { + (false, false) => { + self.allowed_hint_kinds = new_allowed_hint_kinds; + ControlFlow::Break(None) + } + (true, true) => { + if new_allowed_hint_kinds == self.allowed_hint_kinds { + ControlFlow::Break(None) + } else { + todo!("TODO kb") + // let new_splice = self.new_allowed_hint_kinds_splice( + // multi_buffer, + // &visible_hints, + // &new_allowed_hint_kinds, + // cx, + // ); + // if new_splice.is_some() { + // self.allowed_hint_kinds = new_allowed_hint_kinds; + // } + // ControlFlow::Break(new_splice) + } + } + (true, false) => { + self.modifiers_override = false; + self.allowed_hint_kinds = new_allowed_hint_kinds; + if self.inlays.is_empty() { + ControlFlow::Break(None) + } else { + self.clear(); + ControlFlow::Break(Some(InlaySplice { + to_remove: visible_hints.iter().map(|inlay| inlay.id).collect(), + to_insert: Vec::new(), + })) + } + } + (false, true) => { + self.modifiers_override = false; + self.allowed_hint_kinds = new_allowed_hint_kinds; + ControlFlow::Continue(()) + } + } + } + + // fn new_allowed_hint_kinds_splice( + // &self, + // multi_buffer: &Entity, + // visible_hints: &[Inlay], + // new_kinds: &HashSet>, + // cx: &mut App, + // ) -> Option { + // let old_kinds = &self.allowed_hint_kinds; + // if new_kinds == old_kinds { + // return None; + // } + + // let mut to_remove = Vec::new(); + // let mut to_insert = Vec::new(); + // let mut shown_hints_to_remove = visible_hints.iter().fold( + // HashMap::>::default(), + // |mut current_hints, inlay| { + // current_hints + // .entry(inlay.position.excerpt_id) + // .or_default() + // .push((inlay.position, inlay.id)); + // current_hints + // }, + // ); + + // let multi_buffer = multi_buffer.read(cx); + // let multi_buffer_snapshot = multi_buffer.snapshot(cx); + + // for (excerpt_id, excerpt_cached_hints) in &self.hints { + // let shown_excerpt_hints_to_remove = + // shown_hints_to_remove.entry(*excerpt_id).or_default(); + // let excerpt_cached_hints = excerpt_cached_hints.read(); + // let mut excerpt_cache = excerpt_cached_hints.ordered_hints.iter().fuse().peekable(); + // shown_excerpt_hints_to_remove.retain(|(shown_anchor, shown_hint_id)| { + // let Some(buffer) = shown_anchor + // .buffer_id + // .and_then(|buffer_id| multi_buffer.buffer(buffer_id)) + // else { + // return false; + // }; + // let buffer_snapshot = buffer.read(cx).snapshot(); + // loop { + // match excerpt_cache.peek() { + // Some(&cached_hint_id) => { + // let cached_hint = &excerpt_cached_hints.hints_by_id[cached_hint_id]; + // if cached_hint_id == shown_hint_id { + // excerpt_cache.next(); + // return !new_kinds.contains(&cached_hint.kind); + // } + + // match cached_hint + // .position + // .cmp(&shown_anchor.text_anchor, &buffer_snapshot) + // { + // cmp::Ordering::Less | cmp::Ordering::Equal => { + // if !old_kinds.contains(&cached_hint.kind) + // && new_kinds.contains(&cached_hint.kind) + // { + // if let Some(anchor) = multi_buffer_snapshot + // .anchor_in_excerpt(*excerpt_id, cached_hint.position) + // { + // to_insert.push(Inlay::hint( + // cached_hint_id.id(), + // anchor, + // cached_hint, + // )); + // } + // } + // excerpt_cache.next(); + // } + // cmp::Ordering::Greater => return true, + // } + // } + // None => return true, + // } + // } + // }); + + // for cached_hint_id in excerpt_cache { + // let maybe_missed_cached_hint = &excerpt_cached_hints.hints_by_id[cached_hint_id]; + // let cached_hint_kind = maybe_missed_cached_hint.kind; + // if !old_kinds.contains(&cached_hint_kind) && new_kinds.contains(&cached_hint_kind) { + // if let Some(anchor) = multi_buffer_snapshot + // .anchor_in_excerpt(*excerpt_id, maybe_missed_cached_hint.position) + // { + // to_insert.push(Inlay::hint( + // cached_hint_id.id(), + // anchor, + // maybe_missed_cached_hint, + // )); + // } + // } + // } + // } + + // to_remove.extend( + // shown_hints_to_remove + // .into_values() + // .flatten() + // .map(|(_, hint_id)| hint_id), + // ); + // if to_remove.is_empty() && to_insert.is_empty() { + // None + // } else { + // Some(InlaySplice { + // to_remove, + // to_insert, + // }) + // } + // } +} + +fn debounce_value(debounce_ms: u64) -> Option { + if debounce_ms > 0 { + Some(Duration::from_millis(debounce_ms)) + } else { + None + } } #[derive(Copy, Clone, Debug, PartialEq, Eq, Default)] @@ -5279,6 +5503,125 @@ impl Editor { } } + fn refresh_inlay_hints_2(&mut self, reason: InlayHintRefreshReason, cx: &mut Context) { + if !self.mode.is_full() { + return; + } + let Some((inlay_hints, semantics_provider)) = self + .inlay_hints + .as_mut() + .zip(self.semantics_provider.as_ref()) + else { + return; + }; + + let reason_description = reason.description(); + let ignore_debounce = matches!( + reason, + InlayHintRefreshReason::SettingsChange(_) + | InlayHintRefreshReason::Toggle(_) + | InlayHintRefreshReason::ExcerptsRemoved(_) + | InlayHintRefreshReason::ModifiersChanged(_) + ); + let (invalidate_cache, required_languages) = match reason { + InlayHintRefreshReason::ModifiersChanged(enabled) => { + match inlay_hints.modifiers_override(enabled) { + Some(enabled) => { + if enabled { + (InvalidationStrategy::RefreshRequested, None) + } else { + self.splice_inlays( + &self + .visible_inlay_hints(cx) + .iter() + .map(|inlay| inlay.id) + .collect::>(), + Vec::new(), + cx, + ); + return; + } + } + None => return, + } + } + InlayHintRefreshReason::Toggle(enabled) => { + if inlay_hints.toggle(enabled) { + if enabled { + (InvalidationStrategy::RefreshRequested, None) + } else { + self.splice_inlays( + &self + .visible_inlay_hints(cx) + .iter() + .map(|inlay| inlay.id) + .collect::>(), + Vec::new(), + cx, + ); + return; + } + } else { + return; + } + } + InlayHintRefreshReason::SettingsChange(new_settings) => { + // TODO kb + return; + // match inlay_hints.update_settings( + // &self.buffer, + // new_settings, + // self.visible_inlay_hints(cx), + // cx, + // ) { + // ControlFlow::Break(Some(InlaySplice { + // to_remove, + // to_insert, + // })) => { + // self.splice_inlays(&to_remove, to_insert, cx); + // return; + // } + // ControlFlow::Break(None) => return, + // ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None), + // } + } + InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => { + if let Some(InlaySplice { + to_remove, + to_insert, + }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed) + { + self.splice_inlays(&to_remove, to_insert, cx); + } + self.display_map.update(cx, |display_map, _| { + display_map.remove_inlays_for_excerpts(&excerpts_removed) + }); + return; + } + InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None), + InlayHintRefreshReason::BufferEdited(buffer_languages) => { + (InvalidationStrategy::BufferEdited, Some(buffer_languages)) + } + InlayHintRefreshReason::RefreshRequested(_) => { + (InvalidationStrategy::RefreshRequested, None) + } + }; + + // TODO kb + // if let Some(InlaySplice { + // to_remove, + // to_insert, + // }) = self.inlay_hint_cache.spawn_hint_refresh( + // reason_description, + // self.visible_excerpts(required_languages.as_ref(), cx), + // invalidate_cache, + // ignore_debounce, + // cx, + // ) { + // self.splice_inlays(&to_remove, to_insert, cx); + // } + } + fn visible_inlay_hints(&self, cx: &Context) -> Vec { self.display_map .read(cx) diff --git a/crates/project/src/lsp_store/inlay_hint_cache.rs b/crates/project/src/lsp_store/inlay_hint_cache.rs index ab610f8640..ba64a4e16f 100644 --- a/crates/project/src/lsp_store/inlay_hint_cache.rs +++ b/crates/project/src/lsp_store/inlay_hint_cache.rs @@ -33,6 +33,12 @@ pub struct InlayHints { pub hints: Vec, } +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum HintFetchStrategy { + IgnoreCache, + UseCache { known_cache_version: Option }, +} + impl InlayHintCache { pub fn remove_server_data(&mut self, for_server: LanguageServerId) -> bool { let removed = self.hints.remove(&for_server).is_some(); @@ -45,8 +51,8 @@ impl InlayHintCache { pub fn hints( &self, buffer: BufferId, - range: Range, - known_cache_version: Option, + strategy: HintFetchStrategy, + range: impl text::ToOffset, cx: &mut Context, ) -> Option<(Range, Shared>)> { todo!("TODO kb") From 5cf3dcfdfd24f5a19ce5cfbf98721f57e8b1e193 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 23 Jul 2025 16:46:09 +0300 Subject: [PATCH 4/5] Reorder the hint data --- crates/project/src/lsp_store.rs | 6 +++--- .../project/src/lsp_store/inlay_hint_cache.rs | 18 +++++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index ce3f765d24..c5e0b726d3 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -16,7 +16,7 @@ pub mod rust_analyzer_ext; mod inlay_hint_cache; -use self::inlay_hint_cache::InlayHintCache; +use self::inlay_hint_cache::BufferInlayHints; use crate::{ CodeAction, ColorPresentation, Completion, CompletionResponse, CompletionSource, CoreCompletion, DocumentColor, Hover, InlayHint, LocationLink, LspAction, LspPullDiagnostics, @@ -994,7 +994,7 @@ impl LocalLspStore { }) .detach(); - json_language_server_ext::register_requests(this.clone(), language_server); + json_language_server_ext::register_requests(lsp_store.clone(), language_server); rust_analyzer_ext::register_notifications(lsp_store.clone(), language_server); clangd_ext::register_notifications(lsp_store, language_server, adapter); } @@ -3498,7 +3498,7 @@ pub struct LspStore { pub(super) lsp_server_capabilities: HashMap, lsp_document_colors: HashMap, lsp_code_lens: HashMap, - inlay_hint_data: HashMap, + inlay_hint_data: HashMap, running_lsp_requests: HashMap>)>, } diff --git a/crates/project/src/lsp_store/inlay_hint_cache.rs b/crates/project/src/lsp_store/inlay_hint_cache.rs index ba64a4e16f..e700bf7c6e 100644 --- a/crates/project/src/lsp_store/inlay_hint_cache.rs +++ b/crates/project/src/lsp_store/inlay_hint_cache.rs @@ -13,19 +13,18 @@ use crate::{InlayHint, buffer_store::BufferStore}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct InlayHintId(usize); -#[derive(Debug)] -pub struct InlayHintCache { - buffer_store: Entity, - hints_for_version: Global, - hints: HashMap, +#[derive(Debug, Default)] +pub struct BufferInlayHints { + all_hints: HashMap, + hints: HashMap, + chunks_for_version: Global, cache_version: usize, } #[derive(Debug, Default)] -struct Hints { - hints: HashMap, +struct HintChunks { hints_by_chunks: BTreeMap, Option>>, - hint_updates: HashMap, Shared>>, + chunk_updates: HashMap, Shared>>, } pub struct InlayHints { @@ -39,7 +38,7 @@ pub enum HintFetchStrategy { UseCache { known_cache_version: Option }, } -impl InlayHintCache { +impl BufferInlayHints { pub fn remove_server_data(&mut self, for_server: LanguageServerId) -> bool { let removed = self.hints.remove(&for_server).is_some(); if removed { @@ -50,6 +49,7 @@ impl InlayHintCache { pub fn hints( &self, + buffer_store: Entity, buffer: BufferId, strategy: HintFetchStrategy, range: impl text::ToOffset, From 44b1d5db2d29d9986f4ec09e605c048ad14f75b3 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 26 Aug 2025 12:42:22 +0300 Subject: [PATCH 5/5] Map the editor-frontend API Co-authored-by: Lukas Wirth --- crates/editor/src/editor.rs | 430 ++++++++---------- .../project/src/lsp_store/inlay_hint_cache.rs | 9 +- 2 files changed, 204 insertions(+), 235 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index a97af55bee..06894bf1f9 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -150,18 +150,18 @@ use project::{ BreakpointWithPosition, CodeAction, Completion, CompletionIntent, CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectPath, ProjectTransaction, TaskSourceKind, - debugger::breakpoint_store::Breakpoint, debugger::{ breakpoint_store::{ - BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore, - BreakpointStoreEvent, + Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState, + BreakpointStore, BreakpointStoreEvent, }, session::{Session, SessionEvent}, }, git_store::{GitStoreEvent, RepositoryEvent}, lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle}, - project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter}, - project_settings::{GitGutterSetting, ProjectSettings}, + project_settings::{ + DiagnosticSeverity, GitGutterSetting, GoToDiagnosticSeverityFilter, ProjectSettings, + }, }; use rand::{seq::SliceRandom, thread_rng}; use rpc::{ErrorCode, ErrorExt, proto::PeerId}; @@ -1189,10 +1189,9 @@ struct LspInlayHintData { allowed_hint_kinds: HashSet>, invalidate_debounce: Option, append_debounce: Option, - known_cache_version: Option, - // TODO kb use these for removing all hints in splices instead of accessing display_map entries. - inlays: Vec, - inlay_tasks: HashMap, Task<()>>>, + inlays_for_version: Option, + inlays: HashMap>, + inlay_tasks: HashMap, Task<()>>>, } impl LspInlayHintData { @@ -1201,12 +1200,12 @@ impl LspInlayHintData { modifiers_override: false, enabled: settings.enabled, enabled_in_settings: settings.enabled, - inlays: Vec::new(), + inlays: HashMap::default(), + inlays_for_version: None, inlay_tasks: HashMap::default(), invalidate_debounce: debounce_value(settings.edit_debounce_ms), append_debounce: debounce_value(settings.scroll_debounce_ms), allowed_hint_kinds: settings.enabled_inlay_hint_kinds(), - known_cache_version: None, } } @@ -1237,8 +1236,8 @@ impl LspInlayHintData { } fn clear(&mut self) { - self.known_cache_version = None; self.inlay_tasks.clear(); + // TODO kb splice!? self.inlays.clear(); } @@ -1277,16 +1276,6 @@ impl LspInlayHintData { ControlFlow::Break(None) } else { todo!("TODO kb") - // let new_splice = self.new_allowed_hint_kinds_splice( - // multi_buffer, - // &visible_hints, - // &new_allowed_hint_kinds, - // cx, - // ); - // if new_splice.is_some() { - // self.allowed_hint_kinds = new_allowed_hint_kinds; - // } - // ControlFlow::Break(new_splice) } } (true, false) => { @@ -1309,117 +1298,6 @@ impl LspInlayHintData { } } } - - // fn new_allowed_hint_kinds_splice( - // &self, - // multi_buffer: &Entity, - // visible_hints: &[Inlay], - // new_kinds: &HashSet>, - // cx: &mut App, - // ) -> Option { - // let old_kinds = &self.allowed_hint_kinds; - // if new_kinds == old_kinds { - // return None; - // } - - // let mut to_remove = Vec::new(); - // let mut to_insert = Vec::new(); - // let mut shown_hints_to_remove = visible_hints.iter().fold( - // HashMap::>::default(), - // |mut current_hints, inlay| { - // current_hints - // .entry(inlay.position.excerpt_id) - // .or_default() - // .push((inlay.position, inlay.id)); - // current_hints - // }, - // ); - - // let multi_buffer = multi_buffer.read(cx); - // let multi_buffer_snapshot = multi_buffer.snapshot(cx); - - // for (excerpt_id, excerpt_cached_hints) in &self.hints { - // let shown_excerpt_hints_to_remove = - // shown_hints_to_remove.entry(*excerpt_id).or_default(); - // let excerpt_cached_hints = excerpt_cached_hints.read(); - // let mut excerpt_cache = excerpt_cached_hints.ordered_hints.iter().fuse().peekable(); - // shown_excerpt_hints_to_remove.retain(|(shown_anchor, shown_hint_id)| { - // let Some(buffer) = shown_anchor - // .buffer_id - // .and_then(|buffer_id| multi_buffer.buffer(buffer_id)) - // else { - // return false; - // }; - // let buffer_snapshot = buffer.read(cx).snapshot(); - // loop { - // match excerpt_cache.peek() { - // Some(&cached_hint_id) => { - // let cached_hint = &excerpt_cached_hints.hints_by_id[cached_hint_id]; - // if cached_hint_id == shown_hint_id { - // excerpt_cache.next(); - // return !new_kinds.contains(&cached_hint.kind); - // } - - // match cached_hint - // .position - // .cmp(&shown_anchor.text_anchor, &buffer_snapshot) - // { - // cmp::Ordering::Less | cmp::Ordering::Equal => { - // if !old_kinds.contains(&cached_hint.kind) - // && new_kinds.contains(&cached_hint.kind) - // { - // if let Some(anchor) = multi_buffer_snapshot - // .anchor_in_excerpt(*excerpt_id, cached_hint.position) - // { - // to_insert.push(Inlay::hint( - // cached_hint_id.id(), - // anchor, - // cached_hint, - // )); - // } - // } - // excerpt_cache.next(); - // } - // cmp::Ordering::Greater => return true, - // } - // } - // None => return true, - // } - // } - // }); - - // for cached_hint_id in excerpt_cache { - // let maybe_missed_cached_hint = &excerpt_cached_hints.hints_by_id[cached_hint_id]; - // let cached_hint_kind = maybe_missed_cached_hint.kind; - // if !old_kinds.contains(&cached_hint_kind) && new_kinds.contains(&cached_hint_kind) { - // if let Some(anchor) = multi_buffer_snapshot - // .anchor_in_excerpt(*excerpt_id, maybe_missed_cached_hint.position) - // { - // to_insert.push(Inlay::hint( - // cached_hint_id.id(), - // anchor, - // maybe_missed_cached_hint, - // )); - // } - // } - // } - // } - - // to_remove.extend( - // shown_hints_to_remove - // .into_values() - // .flatten() - // .map(|(_, hint_id)| hint_id), - // ); - // if to_remove.is_empty() && to_insert.is_empty() { - // None - // } else { - // Some(InlaySplice { - // to_remove, - // to_insert, - // }) - // } - // } } fn debounce_value(debounce_ms: u64) -> Option { @@ -2116,9 +1994,9 @@ impl Editor { } project::Event::RefreshInlayHints(server_id) => { editor.refresh_inlay_hints( - InlayHintRefreshReason::RefreshRequested(*server_id), - cx, - ); + InlayHintRefreshReason::RefreshRequested(*server_id), + cx, + ); } project::Event::LanguageServerAdded(..) | project::Event::LanguageServerRemoved(..) => { @@ -5504,29 +5382,47 @@ impl Editor { } fn refresh_inlay_hints_2(&mut self, reason: InlayHintRefreshReason, cx: &mut Context) { - if !self.mode.is_full() { + if !self.mode.is_full() || self.semantics_provider.is_none() { return; } - let Some((inlay_hints, semantics_provider)) = self - .inlay_hints - .as_mut() - .zip(self.semantics_provider.as_ref()) - else { - return; - }; - let reason_description = reason.description(); - let ignore_debounce = matches!( - reason, - InlayHintRefreshReason::SettingsChange(_) - | InlayHintRefreshReason::Toggle(_) - | InlayHintRefreshReason::ExcerptsRemoved(_) - | InlayHintRefreshReason::ModifiersChanged(_) - ); - let (invalidate_cache, required_languages) = match reason { - InlayHintRefreshReason::ModifiersChanged(enabled) => { - match inlay_hints.modifiers_override(enabled) { - Some(enabled) => { + { + let Some(inlay_hints) = self.inlay_hints.as_mut() else { + return; + }; + + let reason_description = reason.description(); + let ignore_debounce = matches!( + reason, + InlayHintRefreshReason::SettingsChange(_) + | InlayHintRefreshReason::Toggle(_) + | InlayHintRefreshReason::ExcerptsRemoved(_) + | InlayHintRefreshReason::ModifiersChanged(_) + ); + let (invalidate_cache, required_languages) = match reason { + InlayHintRefreshReason::ModifiersChanged(enabled) => { + match inlay_hints.modifiers_override(enabled) { + Some(enabled) => { + if enabled { + (InvalidationStrategy::RefreshRequested, None) + } else { + self.splice_inlays( + &self + .visible_inlay_hints(cx) + .iter() + .map(|inlay| inlay.id) + .collect::>(), + Vec::new(), + cx, + ); + return; + } + } + None => return, + } + } + InlayHintRefreshReason::Toggle(enabled) => { + if inlay_hints.toggle(enabled) { if enabled { (InvalidationStrategy::RefreshRequested, None) } else { @@ -5541,85 +5437,151 @@ impl Editor { ); return; } - } - None => return, - } - } - InlayHintRefreshReason::Toggle(enabled) => { - if inlay_hints.toggle(enabled) { - if enabled { - (InvalidationStrategy::RefreshRequested, None) } else { - self.splice_inlays( - &self - .visible_inlay_hints(cx) - .iter() - .map(|inlay| inlay.id) - .collect::>(), - Vec::new(), - cx, - ); return; } - } else { + } + InlayHintRefreshReason::SettingsChange(new_settings) => { return; } - } - InlayHintRefreshReason::SettingsChange(new_settings) => { - // TODO kb - return; - // match inlay_hints.update_settings( - // &self.buffer, - // new_settings, - // self.visible_inlay_hints(cx), - // cx, - // ) { - // ControlFlow::Break(Some(InlaySplice { - // to_remove, - // to_insert, - // })) => { - // self.splice_inlays(&to_remove, to_insert, cx); - // return; - // } - // ControlFlow::Break(None) => return, - // ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None), - // } - } - InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => { - if let Some(InlaySplice { - to_remove, - to_insert, - }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed) - { - self.splice_inlays(&to_remove, to_insert, cx); + InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => { + if let Some(InlaySplice { + to_remove, + to_insert, + }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed) + { + self.splice_inlays(&to_remove, to_insert, cx); + } + self.display_map.update(cx, |display_map, _| { + display_map.remove_inlays_for_excerpts(&excerpts_removed) + }); + return; } - self.display_map.update(cx, |display_map, _| { - display_map.remove_inlays_for_excerpts(&excerpts_removed) - }); - return; - } - InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None), - InlayHintRefreshReason::BufferEdited(buffer_languages) => { - (InvalidationStrategy::BufferEdited, Some(buffer_languages)) - } - InlayHintRefreshReason::RefreshRequested(_) => { - (InvalidationStrategy::RefreshRequested, None) - } - }; + InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None), + InlayHintRefreshReason::BufferEdited(buffer_languages) => { + (InvalidationStrategy::BufferEdited, Some(buffer_languages)) + } + InlayHintRefreshReason::RefreshRequested(_) => { + (InvalidationStrategy::RefreshRequested, None) + } + }; + } - // TODO kb - // if let Some(InlaySplice { - // to_remove, - // to_insert, - // }) = self.inlay_hint_cache.spawn_hint_refresh( - // reason_description, - // self.visible_excerpts(required_languages.as_ref(), cx), - // invalidate_cache, - // ignore_debounce, - // cx, - // ) { - // self.splice_inlays(&to_remove, to_insert, cx); - // } + let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx); + let Some(semantics_provider) = self.semantics_provider.clone() else { + return; + }; + for (excerpt_id, (buffer, buffer_version, range)) in self.visible_excerpts(None, cx) { + let Some(excerpt_text_anchor_range) = + multi_buffer_snapshot.context_range_for_excerpt(excerpt_id) + else { + continue; + }; + let buffer_id = buffer.read(cx).remote_id(); + let buffer_snapshot = buffer.read(cx).snapshot(); + let buffer_anchor_range = + buffer_snapshot.anchor_before(range.start)..buffer_snapshot.anchor_after(range.end); + + let new_hints = + semantics_provider.inlay_hints_2(buffer, buffer_anchor_range.clone(), cx); + let Some(inlay_hints) = self.inlay_hints.as_mut() else { + return; + }; + if let Some((hints_range, new_hints)) = new_hints { + let buffer_hints = inlay_hints.inlay_tasks.entry(buffer_id).or_default(); + buffer_hints.insert( + hints_range.clone(), + cx.spawn(async move |editor, cx| { + let new_hints = new_hints.await; + editor + .update(cx, |editor, cx| { + let multi_buffer_snapshot = editor.buffer.read(cx).snapshot(cx); + let Some(buffer_snapshot) = + multi_buffer_snapshot.buffer_for_excerpt(excerpt_id) + else { + return; + }; + + let mut update_data = None; + if let Some(inlay_hints) = editor.inlay_hints.as_mut() { + let inlay_tasks = + inlay_hints.inlay_tasks.entry(buffer_id).or_default(); + match new_hints { + Ok(new_hints) => { + if inlay_hints.inlays_for_version.as_ref().is_some_and( + |inlays_for_version| { + !inlays_for_version + .changed_since(&buffer_version) + }, + ) { + let hints_to_remove = if inlay_hints + .inlays_for_version + .as_ref() + .is_some_and(|inlays_for_version| { + buffer_version + .changed_since(&inlays_for_version) + }) { + inlay_hints + .inlays + .remove(&buffer_id) + .unwrap_or_default() + } else { + Vec::new() + }; + let hints_to_insert = new_hints + .into_iter() + .filter_map(|lsp_hint| { + if buffer_anchor_range + .start + .cmp( + &lsp_hint.position, + buffer_snapshot, + ) + .is_ge() + && buffer_anchor_range + .end + .cmp( + &lsp_hint.position, + buffer_snapshot, + ) + .is_le() + { + let position = multi_buffer_snapshot + .anchor_in_excerpt( + excerpt_id, + lsp_hint.position, + )?; + return Some(Inlay::hint( + post_inc(&mut editor.next_inlay_id), + position, + &lsp_hint, + )); + } + None + }) + .collect(); + update_data = + Some((hints_to_remove, hints_to_insert)); + inlay_hints.inlays_for_version = + Some(buffer_version); + } + } + // TODO kb who should log and clean up the errored state? Could we do that with `lsp_store_cx.spawn`? + Err(_) => {} + } + + inlay_tasks.remove(&hints_range); + } + + if let Some((hints_to_remove, hints_to_insert)) = update_data { + editor.splice_inlays(&hints_to_remove, hints_to_insert, cx); + } + }) + .ok(); + }), + ); + } + } } fn visible_inlay_hints(&self, cx: &Context) -> Vec { @@ -22248,6 +22210,18 @@ pub trait SemanticsProvider { cx: &mut App, ) -> Option>>>; + fn inlay_hints_2( + &self, + buffer: Entity, + range: Range, + cx: &mut App, + ) -> Option<( + Range, + Shared, Arc>>>, + )> { + todo!("TODO kb") + } + fn resolve_inlay_hint( &self, hint: InlayHint, diff --git a/crates/project/src/lsp_store/inlay_hint_cache.rs b/crates/project/src/lsp_store/inlay_hint_cache.rs index e700bf7c6e..d69c4c3038 100644 --- a/crates/project/src/lsp_store/inlay_hint_cache.rs +++ b/crates/project/src/lsp_store/inlay_hint_cache.rs @@ -24,12 +24,7 @@ pub struct BufferInlayHints { #[derive(Debug, Default)] struct HintChunks { hints_by_chunks: BTreeMap, Option>>, - chunk_updates: HashMap, Shared>>, -} - -pub struct InlayHints { - pub cache_version: usize, - pub hints: Vec, + chunk_updates: HashMap, Shared>>>, } #[derive(Debug, PartialEq, Eq, Clone, Copy)] @@ -54,7 +49,7 @@ impl BufferInlayHints { strategy: HintFetchStrategy, range: impl text::ToOffset, cx: &mut Context, - ) -> Option<(Range, Shared>)> { + ) -> Option<(Range, Shared>>)> { todo!("TODO kb") } // we want to store the cache version outbound, so they can query with it: we can return nothing (`Option`) if the version matches