@@ -24,7 +24,7 @@ pub mod test;
use ::git::diff::DiffHunk;
use aho_corasick::AhoCorasick;
-use anyhow::{anyhow, Context, Result};
+use anyhow::{anyhow, Result};
use blink_manager::BlinkManager;
use client::{ClickhouseEvent, TelemetrySettings};
use clock::ReplicaId;
@@ -54,7 +54,7 @@ use gpui::{
};
use highlight_matching_bracket::refresh_matching_bracket_highlights;
use hover_popover::{hide_hover, HoverState};
-use inlay_hint_cache::{InlayHintCache, InlayHintQuery, InlayRefreshReason, InlaySplice};
+use inlay_hint_cache::{InlayHintCache, InlayHintQuery};
pub use items::MAX_TAB_TITLE_LEN;
use itertools::Itertools;
pub use language::{char_kind, CharKind};
@@ -1193,6 +1193,12 @@ enum GotoDefinitionKind {
Type,
}
+#[derive(Debug, Copy, Clone)]
+enum InlayRefreshReason {
+ SettingsChange(editor_settings::InlayHints),
+ VisibleExcerptsChange,
+}
+
impl Editor {
pub fn single_line(
field_editor_style: Option<Arc<GetFieldEditorTheme>>,
@@ -1360,7 +1366,10 @@ impl Editor {
hover_state: Default::default(),
link_go_to_definition_state: Default::default(),
copilot_state: Default::default(),
- inlay_hint_cache: InlayHintCache::new(settings::get::<EditorSettings>(cx).inlay_hints),
+ inlay_hint_cache: InlayHintCache::new(
+ settings::get::<EditorSettings>(cx).inlay_hints,
+ cx,
+ ),
gutter_hovered: false,
_subscriptions: vec![
cx.observe(&buffer, Self::on_buffer_changed),
@@ -2605,40 +2614,16 @@ impl Editor {
let multi_buffer_handle = self.buffer().clone();
let multi_buffer_snapshot = multi_buffer_handle.read(cx).snapshot(cx);
- let currently_shown_inlay_hints = self.display_map.read(cx).current_inlays().fold(
- HashMap::<u64, HashMap<ExcerptId, Vec<(Anchor, InlayId)>>>::default(),
- |mut current_hints, inlay| {
- if let Some(buffer_id) = inlay.position.buffer_id {
- let excerpt_hints = current_hints
- .entry(buffer_id)
- .or_default()
- .entry(inlay.position.excerpt_id)
- .or_default();
- match excerpt_hints.binary_search_by(|probe| {
- inlay.position.cmp(&probe.0, &multi_buffer_snapshot)
- }) {
- Ok(ix) | Err(ix) => {
- excerpt_hints.insert(ix, (inlay.position, inlay.id));
- }
- }
- }
- current_hints
- },
- );
+ let current_inlays = self
+ .display_map
+ .read(cx)
+ .current_inlays()
+ .cloned()
+ .collect();
match reason {
- InlayRefreshReason::SettingsChange(new_settings) => {
- if let Some(InlaySplice {
- to_remove,
- to_insert,
- }) = self.inlay_hint_cache.apply_settings(
- &multi_buffer_handle,
- new_settings,
- currently_shown_inlay_hints,
- cx,
- ) {
- self.splice_inlay_hints(to_remove, to_insert, cx);
- }
- }
+ InlayRefreshReason::SettingsChange(new_settings) => self
+ .inlay_hint_cache
+ .spawn_settings_update(multi_buffer_handle, new_settings, current_inlays),
InlayRefreshReason::VisibleExcerptsChange => {
let replacement_queries = self
.excerpt_visible_offsets(&multi_buffer_handle, cx)
@@ -2652,28 +2637,12 @@ impl Editor {
}
})
.collect::<Vec<_>>();
- cx.spawn(|editor, mut cx| async move {
- let InlaySplice {
- to_remove,
- to_insert,
- } = editor
- .update(&mut cx, |editor, cx| {
- editor.inlay_hint_cache.update_hints(
- multi_buffer_handle,
- replacement_queries,
- currently_shown_inlay_hints,
- cx,
- )
- })?
- .await
- .context("inlay cache hint fetch")?;
-
- editor.update(&mut cx, |editor, cx| {
- editor.splice_inlay_hints(to_remove, to_insert, cx)
- })
- })
- // TODO kb needs cancellation for many excerpts cases like `project search "test"`
- .detach_and_log_err(cx);
+ self.inlay_hint_cache.spawn_hints_update(
+ multi_buffer_handle,
+ replacement_queries,
+ current_inlays,
+ cx,
+ )
}
};
}
@@ -1,26 +1,24 @@
use std::cmp;
-use crate::{editor_settings, Anchor, Editor, ExcerptId, InlayId, MultiBuffer};
+use crate::{
+ display_map::Inlay, editor_settings, Anchor, Editor, ExcerptId, InlayId, MultiBuffer,
+ MultiBufferSnapshot,
+};
use anyhow::Context;
use clock::Global;
use gpui::{ModelHandle, Task, ViewContext};
use log::error;
use project::{InlayHint, InlayHintKind};
-use collections::{HashMap, HashSet};
+use collections::{hash_map, HashMap, HashSet};
use util::post_inc;
-#[derive(Debug, Copy, Clone)]
-pub enum InlayRefreshReason {
- SettingsChange(editor_settings::InlayHints),
- VisibleExcerptsChange,
-}
-
-#[derive(Debug, Default)]
+#[derive(Debug)]
pub struct InlayHintCache {
inlay_hints: HashMap<InlayId, InlayHint>,
hints_in_buffers: HashMap<u64, BufferHints<(Anchor, InlayId)>>,
allowed_hint_kinds: HashSet<Option<InlayHintKind>>,
+ hint_updates_tx: smol::channel::Sender<HintsUpdate>,
}
#[derive(Clone, Debug)]
@@ -29,6 +27,13 @@ struct BufferHints<H> {
hints_per_excerpt: HashMap<ExcerptId, Vec<H>>,
}
+#[derive(Debug)]
+pub struct InlayHintQuery {
+ pub buffer_id: u64,
+ pub buffer_version: Global,
+ pub excerpt_id: ExcerptId,
+}
+
impl<H> BufferHints<H> {
fn new(buffer_version: Global) -> Self {
Self {
@@ -38,146 +43,67 @@ impl<H> BufferHints<H> {
}
}
-#[derive(Debug, Default)]
-pub struct InlaySplice {
- pub to_remove: Vec<InlayId>,
- pub to_insert: Vec<(InlayId, Anchor, InlayHint)>,
-}
-
-#[derive(Debug)]
-pub struct InlayHintQuery {
- pub buffer_id: u64,
- pub buffer_version: Global,
- pub excerpt_id: ExcerptId,
-}
-
impl InlayHintCache {
- pub fn new(inlay_hint_settings: editor_settings::InlayHints) -> Self {
+ pub fn new(
+ inlay_hint_settings: editor_settings::InlayHints,
+ cx: &mut ViewContext<Editor>,
+ ) -> Self {
+ let (hint_updates_tx, hint_updates_rx) = smol::channel::unbounded();
+ spawn_hints_update_loop(hint_updates_rx, cx);
Self {
allowed_hint_kinds: allowed_hint_types(inlay_hint_settings),
hints_in_buffers: HashMap::default(),
inlay_hints: HashMap::default(),
+ hint_updates_tx,
}
}
- pub fn apply_settings(
+ pub fn spawn_settings_update(
&mut self,
- multi_buffer: &ModelHandle<MultiBuffer>,
+ multi_buffer: ModelHandle<MultiBuffer>,
inlay_hint_settings: editor_settings::InlayHints,
- currently_shown_hints: HashMap<u64, HashMap<ExcerptId, Vec<(Anchor, InlayId)>>>,
- cx: &mut ViewContext<Editor>,
- ) -> Option<InlaySplice> {
+ current_inlays: Vec<Inlay>,
+ ) {
if !inlay_hint_settings.enabled {
self.allowed_hint_kinds = allowed_hint_types(inlay_hint_settings);
if self.inlay_hints.is_empty() {
- return None;
+ return;
} else {
- let to_remove = self.inlay_hints.keys().copied().collect();
- self.inlay_hints.clear();
- self.hints_in_buffers.clear();
- return Some(InlaySplice {
- to_remove,
- to_insert: Vec::new(),
- });
+ self.hint_updates_tx
+ .send_blocking(HintsUpdate {
+ multi_buffer,
+ current_inlays,
+ kind: HintsUpdateKind::Clean,
+ })
+ .ok();
+ return;
}
}
let new_allowed_hint_kinds = allowed_hint_types(inlay_hint_settings);
if new_allowed_hint_kinds == self.allowed_hint_kinds {
- None
- } else {
- let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
- let mut to_remove = Vec::new();
- let mut to_insert = Vec::new();
- let mut shown_hints_to_remove = currently_shown_hints;
-
- // TODO kb move into a background task
- for (buffer_id, cached_buffer_hints) in &self.hints_in_buffers {
- let shown_buffer_hints_to_remove =
- shown_hints_to_remove.entry(*buffer_id).or_default();
- for (excerpt_id, cached_excerpt_hints) in &cached_buffer_hints.hints_per_excerpt {
- let shown_excerpt_hints_to_remove =
- shown_buffer_hints_to_remove.entry(*excerpt_id).or_default();
- let mut cached_hints = cached_excerpt_hints.iter().fuse().peekable();
- shown_excerpt_hints_to_remove.retain(|(shown_anchor, shown_hint_id)| {
- loop {
- match cached_hints.peek() {
- Some((cached_anchor, cached_hint_id)) => {
- if cached_hint_id == shown_hint_id {
- return !new_allowed_hint_kinds.contains(
- &self.inlay_hints.get(&cached_hint_id).unwrap().kind,
- );
- }
-
- match cached_anchor.cmp(shown_anchor, &multi_buffer_snapshot) {
- cmp::Ordering::Less | cmp::Ordering::Equal => {
- let maybe_missed_cached_hint =
- self.inlay_hints.get(&cached_hint_id).unwrap();
- let cached_hint_kind = maybe_missed_cached_hint.kind;
- if !self.allowed_hint_kinds.contains(&cached_hint_kind)
- && new_allowed_hint_kinds
- .contains(&cached_hint_kind)
- {
- to_insert.push((
- *cached_hint_id,
- *cached_anchor,
- maybe_missed_cached_hint.clone(),
- ));
- }
- cached_hints.next();
- }
- cmp::Ordering::Greater => break,
- }
- }
- None => return true,
- }
- }
-
- match self.inlay_hints.get(&shown_hint_id) {
- Some(shown_hint) => !new_allowed_hint_kinds.contains(&shown_hint.kind),
- None => true,
- }
- });
-
- for (cached_anchor, cached_hint_id) in cached_hints {
- let maybe_missed_cached_hint =
- self.inlay_hints.get(&cached_hint_id).unwrap();
- let cached_hint_kind = maybe_missed_cached_hint.kind;
- if !self.allowed_hint_kinds.contains(&cached_hint_kind)
- && new_allowed_hint_kinds.contains(&cached_hint_kind)
- {
- to_insert.push((
- *cached_hint_id,
- *cached_anchor,
- maybe_missed_cached_hint.clone(),
- ));
- }
- }
- }
- }
+ return;
+ }
- to_remove.extend(
- shown_hints_to_remove
- .into_iter()
- .flat_map(|(_, hints_by_excerpt)| hints_by_excerpt)
- .flat_map(|(_, excerpt_hints)| excerpt_hints)
- .map(|(_, hint_id)| hint_id),
- );
- self.allowed_hint_kinds = new_allowed_hint_kinds;
- Some(InlaySplice {
- to_remove,
- to_insert,
+ self.hint_updates_tx
+ .send_blocking(HintsUpdate {
+ multi_buffer,
+ current_inlays,
+ kind: HintsUpdateKind::AllowedHintKindsChanged {
+ old: self.allowed_hint_kinds.clone(),
+ new: new_allowed_hint_kinds,
+ },
})
- }
+ .ok();
}
- pub fn update_hints(
+ pub fn spawn_hints_update(
&mut self,
multi_buffer: ModelHandle<MultiBuffer>,
queries: Vec<InlayHintQuery>,
- currently_shown_hints: HashMap<u64, HashMap<ExcerptId, Vec<(Anchor, InlayId)>>>,
+ current_inlays: Vec<Inlay>,
cx: &mut ViewContext<Editor>,
- ) -> Task<anyhow::Result<InlaySplice>> {
+ ) {
let conflicts_with_cache = queries.iter().any(|update_query| {
let Some(cached_buffer_hints) = self.hints_in_buffers.get(&update_query.buffer_id)
else { return false };
@@ -198,8 +124,7 @@ impl InlayHintCache {
}
});
- // TODO kb remember queries that run and do not query for these ranges if the buffer version was not changed
- let queries = queries
+ let queries_per_buffer = queries
.into_iter()
.filter_map(|query| {
let Some(cached_buffer_hints) = self.hints_in_buffers.get(&query.buffer_id)
@@ -220,167 +145,410 @@ impl InlayHintCache {
None
}
})
- .collect::<Vec<_>>();
- let task_multi_buffer = multi_buffer.clone();
- let fetch_queries_task = fetch_queries(multi_buffer, queries.into_iter(), cx);
- let mut to_remove = Vec::new();
- let mut to_insert = Vec::new();
- let mut cache_hints_to_persist: HashMap<
- u64,
- (Global, HashMap<ExcerptId, HashSet<InlayId>>),
- > = HashMap::default();
- cx.spawn(|editor, mut cx| async move {
- let new_hints = fetch_queries_task.await.context("inlay hints fetch")?;
- editor.update(&mut cx, |editor, cx| {
- let multi_buffer_snapshot = task_multi_buffer.read(cx).snapshot(cx);
- for (new_buffer_id, new_hints_per_buffer) in new_hints {
- let cached_buffer_hints = editor
- .inlay_hint_cache
- .hints_in_buffers
- .entry(new_buffer_id)
- .or_insert_with(|| {
- BufferHints::new(new_hints_per_buffer.buffer_version.clone())
- });
+ .fold(
+ HashMap::<u64, (Global, Vec<ExcerptId>)>::default(),
+ |mut queries_per_buffer, new_query| {
+ let (current_verison, excerpts_to_query) =
+ queries_per_buffer.entry(new_query.buffer_id).or_default();
- let buffer_cache_hints_to_persist =
- cache_hints_to_persist.entry(new_buffer_id).or_insert_with(|| (new_hints_per_buffer.buffer_version.clone(), HashMap::default()));
- if cached_buffer_hints
- .buffer_version
- .changed_since(&new_hints_per_buffer.buffer_version)
- {
- buffer_cache_hints_to_persist.0 = new_hints_per_buffer.buffer_version;
- buffer_cache_hints_to_persist.1.extend(
- cached_buffer_hints.hints_per_excerpt.iter().map(
- |(excerpt_id, excerpt_hints)| {
- (
- *excerpt_id,
- excerpt_hints.iter().map(|(_, id)| *id).collect(),
- )
- },
- ),
- );
- continue;
+ if new_query.buffer_version.changed_since(current_verison) {
+ *current_verison = new_query.buffer_version;
+ *excerpts_to_query = vec![new_query.excerpt_id];
+ } else if !current_verison.changed_since(&new_query.buffer_version) {
+ excerpts_to_query.push(new_query.excerpt_id);
}
- let shown_buffer_hints = currently_shown_hints.get(&new_buffer_id);
- for (new_excerpt_id, new_hints_per_excerpt) in
- new_hints_per_buffer.hints_per_excerpt
- {
- let excerpt_cache_hints_to_persist = buffer_cache_hints_to_persist.1
- .entry(new_excerpt_id)
- .or_default();
- let cached_excerpt_hints = cached_buffer_hints
- .hints_per_excerpt
- .entry(new_excerpt_id)
- .or_default();
- let empty_shown_excerpt_hints = Vec::new();
- let shown_excerpt_hints = shown_buffer_hints.and_then(|hints| hints.get(&new_excerpt_id)).unwrap_or(&empty_shown_excerpt_hints);
- for new_hint in new_hints_per_excerpt {
- let new_hint_anchor = multi_buffer_snapshot
- .anchor_in_excerpt(new_excerpt_id, new_hint.position);
- let cache_insert_ix = match cached_excerpt_hints.binary_search_by(|probe| {
- new_hint_anchor.cmp(&probe.0, &multi_buffer_snapshot)
- }) {
- Ok(ix) => {
- let (_, cached_inlay_id) = cached_excerpt_hints[ix];
- let cache_hit = editor
- .inlay_hint_cache
- .inlay_hints
- .get(&cached_inlay_id)
- .filter(|cached_hint| cached_hint == &&new_hint)
- .is_some();
- if cache_hit {
- excerpt_cache_hints_to_persist
- .insert(cached_inlay_id);
- None
- } else {
- Some(ix)
- }
- }
- Err(ix) => Some(ix),
- };
-
- let shown_inlay_id = match shown_excerpt_hints.binary_search_by(|probe| {
- probe.0.cmp(&new_hint_anchor, &multi_buffer_snapshot)
- }) {
- Ok(ix) => {{
- let (_, shown_inlay_id) = shown_excerpt_hints[ix];
- let shown_hint_found = editor.inlay_hint_cache.inlay_hints.get(&shown_inlay_id)
- .filter(|cached_hint| cached_hint == &&new_hint).is_some();
- if shown_hint_found {
- Some(shown_inlay_id)
- } else {
- None
- }
- }},
- Err(_) => None,
- };
-
- if let Some(insert_ix) = cache_insert_ix {
- let hint_id = match shown_inlay_id {
- Some(shown_inlay_id) => shown_inlay_id,
- None => {
- let new_hint_id = InlayId(post_inc(&mut editor.next_inlay_id));
- if editor.inlay_hint_cache.allowed_hint_kinds.contains(&new_hint.kind)
- {
- to_insert.push((new_hint_id, new_hint_anchor, new_hint.clone()));
- }
- new_hint_id
- }
- };
- excerpt_cache_hints_to_persist.insert(hint_id);
- cached_excerpt_hints.insert(insert_ix, (new_hint_anchor, hint_id));
- editor
- .inlay_hint_cache
- .inlay_hints
- .insert(hint_id, new_hint);
- }
+ queries_per_buffer
+ },
+ );
+
+ for (queried_buffer, (buffer_version, excerpts)) in queries_per_buffer {
+ self.hint_updates_tx
+ .send_blocking(HintsUpdate {
+ multi_buffer,
+ current_inlays,
+ kind: HintsUpdateKind::BufferUpdate {
+ invalidate_cache: conflicts_with_cache,
+ buffer_id: queried_buffer,
+ buffer_version,
+ excerpts,
+ },
+ })
+ .ok();
+ }
+ }
+}
+
+#[derive(Debug, Default)]
+struct InlaySplice {
+ to_remove: Vec<InlayId>,
+ to_insert: Vec<(InlayId, Anchor, InlayHint)>,
+}
+
+struct HintsUpdate {
+ multi_buffer: ModelHandle<MultiBuffer>,
+ current_inlays: Vec<Inlay>,
+ kind: HintsUpdateKind,
+}
+
+enum HintsUpdateKind {
+ Clean,
+ AllowedHintKindsChanged {
+ old: HashSet<Option<InlayHintKind>>,
+ new: HashSet<Option<InlayHintKind>>,
+ },
+ BufferUpdate {
+ buffer_id: u64,
+ buffer_version: Global,
+ excerpts: Vec<ExcerptId>,
+ invalidate_cache: bool,
+ },
+}
+
+struct UpdateTaskHandle {
+ multi_buffer: ModelHandle<MultiBuffer>,
+ cancellation_tx: smol::channel::Sender<()>,
+ task_finish_rx: smol::channel::Receiver<UpdateTaskResult>,
+}
+
+struct UpdateTaskResult {
+ multi_buffer: ModelHandle<MultiBuffer>,
+ splice: InlaySplice,
+ new_allowed_hint_kinds: Option<HashSet<Option<InlayHintKind>>>,
+ remove_from_cache: HashSet<InlayId>,
+ add_to_cache: HashMap<u64, BufferHints<(Anchor, InlayHint, InlayId)>>,
+}
+
+impl HintsUpdate {
+ fn merge(&mut self, mut other: Self) -> Result<(), Self> {
+ match (&mut self.kind, &mut other.kind) {
+ (HintsUpdateKind::Clean, HintsUpdateKind::Clean) => return Ok(()),
+ (
+ HintsUpdateKind::AllowedHintKindsChanged { .. },
+ HintsUpdateKind::AllowedHintKindsChanged { .. },
+ ) => {
+ *self = other;
+ return Ok(());
+ }
+ (
+ HintsUpdateKind::BufferUpdate {
+ buffer_id: old_buffer_id,
+ buffer_version: old_buffer_version,
+ excerpts: old_excerpts,
+ invalidate_cache: old_invalidate_cache,
+ },
+ HintsUpdateKind::BufferUpdate {
+ buffer_id: new_buffer_id,
+ buffer_version: new_buffer_version,
+ excerpts: new_excerpts,
+ invalidate_cache: new_invalidate_cache,
+ },
+ ) => {
+ if old_buffer_id == new_buffer_id {
+ if new_buffer_version.changed_since(old_buffer_version) {
+ *self = other;
+ return Ok(());
+ } else if old_buffer_version.changed_since(new_buffer_version) {
+ return Ok(());
+ } else if *new_invalidate_cache {
+ *self = other;
+ return Ok(());
+ } else {
+ let old_inlays = self
+ .current_inlays
+ .iter()
+ .map(|inlay| inlay.id)
+ .collect::<Vec<_>>();
+ let new_inlays = other
+ .current_inlays
+ .iter()
+ .map(|inlay| inlay.id)
+ .collect::<Vec<_>>();
+ if old_inlays == new_inlays {
+ old_excerpts.extend(new_excerpts.drain(..));
+ old_excerpts.dedup();
+ return Ok(());
}
}
}
+ }
+ _ => {}
+ }
+
+ Err(other)
+ }
+
+ fn spawn(self, cx: &mut ViewContext<'_, '_, Editor>) -> UpdateTaskHandle {
+ let (task_finish_tx, task_finish_rx) = smol::channel::unbounded();
+ let (cancellation_tx, cancellation_rx) = smol::channel::bounded(1);
+
+ match self.kind {
+ HintsUpdateKind::Clean => cx
+ .spawn(|editor, mut cx| async move {
+ if let Some(splice) = editor.update(&mut cx, |editor, cx| {
+ clean_cache(editor, self.current_inlays)
+ })? {
+ task_finish_tx
+ .send(UpdateTaskResult {
+ multi_buffer: self.multi_buffer.clone(),
+ splice,
+ new_allowed_hint_kinds: None,
+ remove_from_cache: HashSet::default(),
+ add_to_cache: HashMap::default(),
+ })
+ .await
+ .ok();
+ }
+ anyhow::Ok(())
+ })
+ .detach_and_log_err(cx),
+ HintsUpdateKind::AllowedHintKindsChanged { old, new } => cx
+ .spawn(|editor, mut cx| async move {
+ if let Some(splice) = editor.update(&mut cx, |editor, cx| {
+ update_allowed_hint_kinds(
+ &self.multi_buffer.read(cx).snapshot(cx),
+ self.current_inlays,
+ old,
+ new,
+ editor,
+ )
+ })? {
+ task_finish_tx
+ .send(UpdateTaskResult {
+ multi_buffer: self.multi_buffer.clone(),
+ splice,
+ new_allowed_hint_kinds: None,
+ remove_from_cache: HashSet::default(),
+ add_to_cache: HashMap::default(),
+ })
+ .await
+ .ok();
+ }
+ anyhow::Ok(())
+ })
+ .detach_and_log_err(cx),
+ HintsUpdateKind::BufferUpdate {
+ buffer_id,
+ buffer_version,
+ excerpts,
+ invalidate_cache,
+ } => todo!("TODO kb"),
+ }
- if conflicts_with_cache {
- for (shown_buffer_id, mut shown_hints_to_clean) in currently_shown_hints {
- match cache_hints_to_persist.get(&shown_buffer_id) {
- Some(cached_buffer_hints) => {
- for (persisted_id, cached_hints) in &cached_buffer_hints.1 {
- shown_hints_to_clean.entry(*persisted_id).or_default()
- .retain(|(_, shown_id)| !cached_hints.contains(shown_id));
+ UpdateTaskHandle {
+ multi_buffer: self.multi_buffer.clone(),
+ cancellation_tx,
+ task_finish_rx,
+ }
+ }
+}
+
+fn spawn_hints_update_loop(
+ hint_updates_rx: smol::channel::Receiver<HintsUpdate>,
+ cx: &mut ViewContext<'_, '_, Editor>,
+) {
+ cx.spawn(|editor, mut cx| async move {
+ let mut update = None::<HintsUpdate>;
+ let mut next_update = None::<HintsUpdate>;
+ loop {
+ if update.is_none() {
+ match hint_updates_rx.recv().await {
+ Ok(first_task) => update = Some(first_task),
+ Err(smol::channel::RecvError) => return,
+ }
+ }
+
+ let mut updates_limit = 10;
+ 'update_merge: loop {
+ match hint_updates_rx.try_recv() {
+ Ok(new_update) => {
+ match update.as_mut() {
+ Some(update) => match update.merge(new_update) {
+ Ok(()) => {}
+ Err(new_update) => {
+ next_update = Some(new_update);
+ break 'update_merge;
}
},
- None => {},
+ None => update = Some(new_update),
+ };
+
+ if updates_limit == 0 {
+ break 'update_merge;
}
- to_remove.extend(shown_hints_to_clean.into_iter()
- .flat_map(|(_, excerpt_hints)| excerpt_hints.into_iter().map(|(_, hint_id)| hint_id)));
+ updates_limit -= 1;
}
+ Err(smol::channel::TryRecvError::Empty) => break 'update_merge,
+ Err(smol::channel::TryRecvError::Closed) => return,
+ }
+ }
- editor.inlay_hint_cache.hints_in_buffers.retain(|buffer_id, buffer_hints| {
- let Some(mut buffer_hints_to_persist) = cache_hints_to_persist.remove(buffer_id) else { return false; };
- buffer_hints.buffer_version = buffer_hints_to_persist.0;
- buffer_hints.hints_per_excerpt.retain(|excerpt_id, excerpt_hints| {
- let Some(excerpt_hints_to_persist) = buffer_hints_to_persist.1.remove(&excerpt_id) else { return false; };
- excerpt_hints.retain(|(_, hint_id)| {
- let retain = excerpt_hints_to_persist.contains(hint_id);
- if !retain {
- editor
- .inlay_hint_cache
- .inlay_hints
- .remove(hint_id);
- }
- retain
+ if let Some(update) = update.take() {
+ let Ok(task_handle) = editor.update(&mut cx, |_, cx| update.spawn(cx)) else { return; };
+ while let Ok(update_task_result) = task_handle.task_finish_rx.recv().await {
+ let Ok(()) = editor.update(&mut cx, |editor, cx| {
+ let multi_buffer_snapshot = update_task_result.multi_buffer.read(cx).snapshot(cx);
+ let inlay_hint_cache = &mut editor.inlay_hint_cache;
+
+ if let Some(new_allowed_hint_kinds) = update_task_result.new_allowed_hint_kinds {
+ inlay_hint_cache.allowed_hint_kinds = new_allowed_hint_kinds;
+ }
+
+ inlay_hint_cache.hints_in_buffers.retain(|_, buffer_hints| {
+ buffer_hints.hints_per_excerpt.retain(|_, excerpt_hints| {
+ excerpt_hints.retain(|(_, hint_id)| !update_task_result.remove_from_cache.contains(hint_id));
+ !excerpt_hints.is_empty()
});
- !excerpt_hints.is_empty()
+ !buffer_hints.hints_per_excerpt.is_empty()
});
- !buffer_hints.hints_per_excerpt.is_empty()
- });
+ inlay_hint_cache.inlay_hints.retain(|hint_id, _| !update_task_result.remove_from_cache.contains(hint_id));
+
+ for (new_buffer_id, new_buffer_inlays) in update_task_result.add_to_cache {
+ let cached_buffer_hints = inlay_hint_cache.hints_in_buffers.entry(new_buffer_id).or_insert_with(|| BufferHints::new(new_buffer_inlays.buffer_version));
+ if cached_buffer_hints.buffer_version.changed_since(&new_buffer_inlays.buffer_version) {
+ continue;
+ }
+ for (excerpt_id, new_excerpt_inlays) in new_buffer_inlays.hints_per_excerpt {
+ let cached_excerpt_hints = cached_buffer_hints.hints_per_excerpt.entry(excerpt_id).or_default();
+ for (new_hint_position, new_hint, new_inlay_id) in new_excerpt_inlays {
+ if let hash_map::Entry::Vacant(v) = inlay_hint_cache.inlay_hints.entry(new_inlay_id) {
+ v.insert(new_hint);
+ match cached_excerpt_hints.binary_search_by(|probe| {
+ new_hint_position.cmp(&probe.0, &multi_buffer_snapshot)
+ }) {
+ Ok(ix) | Err(ix) => cached_excerpt_hints.insert(ix, (new_hint_position, new_inlay_id)),
+ }
+ }
+ }
+ }
+ }
+
+ let InlaySplice {
+ to_remove,
+ to_insert,
+ } = update_task_result.splice;
+ editor.splice_inlay_hints(to_remove, to_insert, cx)
+ }) else { return; };
}
+ }
+ update = next_update.take();
+ }
+ })
+ .detach()
+}
+
+fn update_allowed_hint_kinds(
+ multi_buffer_snapshot: &MultiBufferSnapshot,
+ current_inlays: Vec<Inlay>,
+ old_kinds: HashSet<Option<InlayHintKind>>,
+ new_kinds: HashSet<Option<InlayHintKind>>,
+ editor: &mut Editor,
+) -> Option<InlaySplice> {
+ if old_kinds == new_kinds {
+ return None;
+ }
- InlaySplice {
- to_remove,
- to_insert,
+ let mut to_remove = Vec::new();
+ let mut to_insert = Vec::new();
+ let mut shown_hints_to_remove = group_inlays(&multi_buffer_snapshot, current_inlays);
+ let hints_cache = &editor.inlay_hint_cache;
+
+ for (buffer_id, cached_buffer_hints) in &hints_cache.hints_in_buffers {
+ let shown_buffer_hints_to_remove = shown_hints_to_remove.entry(*buffer_id).or_default();
+ for (excerpt_id, cached_excerpt_hints) in &cached_buffer_hints.hints_per_excerpt {
+ let shown_excerpt_hints_to_remove =
+ shown_buffer_hints_to_remove.entry(*excerpt_id).or_default();
+ let mut cached_hints = cached_excerpt_hints.iter().fuse().peekable();
+ shown_excerpt_hints_to_remove.retain(|(shown_anchor, shown_hint_id)| {
+ loop {
+ match cached_hints.peek() {
+ Some((cached_anchor, cached_hint_id)) => {
+ if cached_hint_id == shown_hint_id {
+ return !new_kinds.contains(
+ &hints_cache.inlay_hints.get(&cached_hint_id).unwrap().kind,
+ );
+ }
+
+ match cached_anchor.cmp(shown_anchor, &multi_buffer_snapshot) {
+ cmp::Ordering::Less | cmp::Ordering::Equal => {
+ let maybe_missed_cached_hint =
+ hints_cache.inlay_hints.get(&cached_hint_id).unwrap();
+ let cached_hint_kind = maybe_missed_cached_hint.kind;
+ if !old_kinds.contains(&cached_hint_kind)
+ && new_kinds.contains(&cached_hint_kind)
+ {
+ to_insert.push((
+ *cached_hint_id,
+ *cached_anchor,
+ maybe_missed_cached_hint.clone(),
+ ));
+ }
+ cached_hints.next();
+ }
+ cmp::Ordering::Greater => break,
+ }
+ }
+ None => return true,
+ }
}
- })
- })
+
+ match hints_cache.inlay_hints.get(&shown_hint_id) {
+ Some(shown_hint) => !new_kinds.contains(&shown_hint.kind),
+ None => true,
+ }
+ });
+
+ for (cached_anchor, cached_hint_id) in cached_hints {
+ let maybe_missed_cached_hint =
+ hints_cache.inlay_hints.get(&cached_hint_id).unwrap();
+ let cached_hint_kind = maybe_missed_cached_hint.kind;
+ if !old_kinds.contains(&cached_hint_kind) && new_kinds.contains(&cached_hint_kind) {
+ to_insert.push((
+ *cached_hint_id,
+ *cached_anchor,
+ maybe_missed_cached_hint.clone(),
+ ));
+ }
+ }
+ }
+ }
+
+ to_remove.extend(
+ shown_hints_to_remove
+ .into_iter()
+ .flat_map(|(_, hints_by_excerpt)| hints_by_excerpt)
+ .flat_map(|(_, excerpt_hints)| excerpt_hints)
+ .map(|(_, hint_id)| hint_id),
+ );
+ Some(InlaySplice {
+ to_remove,
+ to_insert,
+ })
+}
+
+fn clean_cache(editor: &mut Editor, current_inlays: Vec<Inlay>) -> Option<InlaySplice> {
+ let hints_cache = &mut editor.inlay_hint_cache;
+ if hints_cache.inlay_hints.is_empty() {
+ None
+ } else {
+ let splice = InlaySplice {
+ to_remove: current_inlays
+ .iter()
+ .filter(|inlay| {
+ editor
+ .copilot_state
+ .suggestion
+ .as_ref()
+ .map(|inlay| inlay.id)
+ != Some(inlay.id)
+ })
+ .map(|inlay| inlay.id)
+ .collect(),
+ to_insert: Vec::new(),
+ };
+ hints_cache.inlay_hints.clear();
+ hints_cache.hints_in_buffers.clear();
+ Some(splice)
}
}
@@ -400,6 +568,8 @@ fn allowed_hint_types(
new_allowed_hint_types
}
+// TODO kb wrong, query and update the editor separately
+// TODO kb need to react on react on scrolling too, for multibuffer excerpts
fn fetch_queries(
multi_buffer: ModelHandle<MultiBuffer>,
queries: impl Iterator<Item = InlayHintQuery>,
@@ -477,3 +647,189 @@ fn fetch_queries(
Ok(inlay_updates)
})
}
+
+fn group_inlays(
+ multi_buffer_snapshot: &MultiBufferSnapshot,
+ inlays: Vec<Inlay>,
+) -> HashMap<u64, HashMap<ExcerptId, Vec<(Anchor, InlayId)>>> {
+ inlays.into_iter().fold(
+ HashMap::<u64, HashMap<ExcerptId, Vec<(Anchor, InlayId)>>>::default(),
+ |mut current_hints, inlay| {
+ if let Some(buffer_id) = inlay.position.buffer_id {
+ current_hints
+ .entry(buffer_id)
+ .or_default()
+ .entry(inlay.position.excerpt_id)
+ .or_default()
+ .push((inlay.position, inlay.id));
+ }
+ current_hints
+ },
+ )
+}
+
+async fn update_hints(
+ multi_buffer: ModelHandle<MultiBuffer>,
+ queries: Vec<InlayHintQuery>,
+ current_inlays: Vec<Inlay>,
+ invalidate_cache: bool,
+ cx: &mut ViewContext<'_, '_, Editor>,
+) -> Option<InlaySplice> {
+ let fetch_queries_task = fetch_queries(multi_buffer, queries.into_iter(), cx);
+ let new_hints = fetch_queries_task.await.context("inlay hints fetch")?;
+
+ let mut to_remove = Vec::new();
+ let mut to_insert = Vec::new();
+ let mut cache_hints_to_persist: HashMap<u64, (Global, HashMap<ExcerptId, HashSet<InlayId>>)> =
+ HashMap::default();
+
+ editor.update(&mut cx, |editor, cx| {
+ let multi_buffer_snapshot = task_multi_buffer.read(cx).snapshot(cx);
+ for (new_buffer_id, new_hints_per_buffer) in new_hints {
+ let cached_buffer_hints = editor
+ .inlay_hint_cache
+ .hints_in_buffers
+ .entry(new_buffer_id)
+ .or_insert_with(|| {
+ BufferHints::new(new_hints_per_buffer.buffer_version.clone())
+ });
+
+ let buffer_cache_hints_to_persist =
+ cache_hints_to_persist.entry(new_buffer_id).or_insert_with(|| (new_hints_per_buffer.buffer_version.clone(), HashMap::default()));
+ if cached_buffer_hints
+ .buffer_version
+ .changed_since(&new_hints_per_buffer.buffer_version)
+ {
+ buffer_cache_hints_to_persist.0 = new_hints_per_buffer.buffer_version;
+ buffer_cache_hints_to_persist.1.extend(
+ cached_buffer_hints.hints_per_excerpt.iter().map(
+ |(excerpt_id, excerpt_hints)| {
+ (
+ *excerpt_id,
+ excerpt_hints.iter().map(|(_, id)| *id).collect(),
+ )
+ },
+ ),
+ );
+ continue;
+ }
+
+ let shown_buffer_hints = currently_shown_hints.get(&new_buffer_id);
+ for (new_excerpt_id, new_hints_per_excerpt) in
+ new_hints_per_buffer.hints_per_excerpt
+ {
+ let excerpt_cache_hints_to_persist = buffer_cache_hints_to_persist.1
+ .entry(new_excerpt_id)
+ .or_default();
+ let cached_excerpt_hints = cached_buffer_hints
+ .hints_per_excerpt
+ .entry(new_excerpt_id)
+ .or_default();
+ let empty_shown_excerpt_hints = Vec::new();
+ let shown_excerpt_hints = shown_buffer_hints.and_then(|hints| hints.get(&new_excerpt_id)).unwrap_or(&empty_shown_excerpt_hints);
+ for new_hint in new_hints_per_excerpt {
+ let new_hint_anchor = multi_buffer_snapshot
+ .anchor_in_excerpt(new_excerpt_id, new_hint.position);
+ let cache_insert_ix = match cached_excerpt_hints.binary_search_by(|probe| {
+ new_hint_anchor.cmp(&probe.0, &multi_buffer_snapshot)
+ }) {
+ Ok(ix) => {
+ let (_, cached_inlay_id) = cached_excerpt_hints[ix];
+ let cache_hit = editor
+ .inlay_hint_cache
+ .inlay_hints
+ .get(&cached_inlay_id)
+ .filter(|cached_hint| cached_hint == &&new_hint)
+ .is_some();
+ if cache_hit {
+ excerpt_cache_hints_to_persist
+ .insert(cached_inlay_id);
+ None
+ } else {
+ Some(ix)
+ }
+ }
+ Err(ix) => Some(ix),
+ };
+
+ let shown_inlay_id = match shown_excerpt_hints.binary_search_by(|probe| {
+ probe.0.cmp(&new_hint_anchor, &multi_buffer_snapshot)
+ }) {
+ Ok(ix) => {{
+ let (_, shown_inlay_id) = shown_excerpt_hints[ix];
+ let shown_hint_found = editor.inlay_hint_cache.inlay_hints.get(&shown_inlay_id)
+ .filter(|cached_hint| cached_hint == &&new_hint).is_some();
+ if shown_hint_found {
+ Some(shown_inlay_id)
+ } else {
+ None
+ }
+ }},
+ Err(_) => None,
+ };
+
+ if let Some(insert_ix) = cache_insert_ix {
+ let hint_id = match shown_inlay_id {
+ Some(shown_inlay_id) => shown_inlay_id,
+ None => {
+ let new_hint_id = InlayId(post_inc(&mut editor.next_inlay_id));
+ if editor.inlay_hint_cache.allowed_hint_kinds.contains(&new_hint.kind)
+ {
+ to_insert.push((new_hint_id, new_hint_anchor, new_hint.clone()));
+ }
+ new_hint_id
+ }
+ };
+ excerpt_cache_hints_to_persist.insert(hint_id);
+ cached_excerpt_hints.insert(insert_ix, (new_hint_anchor, hint_id));
+ editor
+ .inlay_hint_cache
+ .inlay_hints
+ .insert(hint_id, new_hint);
+ }
+ }
+ }
+ }
+
+ if conflicts_with_cache {
+ for (shown_buffer_id, mut shown_hints_to_clean) in currently_shown_hints {
+ match cache_hints_to_persist.get(&shown_buffer_id) {
+ Some(cached_buffer_hints) => {
+ for (persisted_id, cached_hints) in &cached_buffer_hints.1 {
+ shown_hints_to_clean.entry(*persisted_id).or_default()
+ .retain(|(_, shown_id)| !cached_hints.contains(shown_id));
+ }
+ },
+ None => {},
+ }
+ to_remove.extend(shown_hints_to_clean.into_iter()
+ .flat_map(|(_, excerpt_hints)| excerpt_hints.into_iter().map(|(_, hint_id)| hint_id)));
+ }
+
+ editor.inlay_hint_cache.hints_in_buffers.retain(|buffer_id, buffer_hints| {
+ let Some(mut buffer_hints_to_persist) = cache_hints_to_persist.remove(buffer_id) else { return false; };
+ buffer_hints.buffer_version = buffer_hints_to_persist.0;
+ buffer_hints.hints_per_excerpt.retain(|excerpt_id, excerpt_hints| {
+ let Some(excerpt_hints_to_persist) = buffer_hints_to_persist.1.remove(&excerpt_id) else { return false; };
+ excerpt_hints.retain(|(_, hint_id)| {
+ let retain = excerpt_hints_to_persist.contains(hint_id);
+ if !retain {
+ editor
+ .inlay_hint_cache
+ .inlay_hints
+ .remove(hint_id);
+ }
+ retain
+ });
+ !excerpt_hints.is_empty()
+ });
+ !buffer_hints.hints_per_excerpt.is_empty()
+ });
+ }
+
+ Some(InlaySplice {
+ to_remove,
+ to_insert,
+ })
+ })
+}