From e82b4d8957080c35af4633f786f4a95a2ed3567e Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sat, 17 Jun 2023 22:26:29 +0300 Subject: [PATCH] Properly handle hint addition queries --- crates/editor/src/editor.rs | 13 +- crates/editor/src/inlay_hint_cache.rs | 591 ++++++++++++-------------- 2 files changed, 268 insertions(+), 336 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index fc3319dc56c438ac81993254912c0e3ebe79515b..03e98407db7b457424424e31f437651aeedeb0c8 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -540,7 +540,7 @@ pub struct Editor { gutter_hovered: bool, link_go_to_definition_state: LinkGoToDefinitionState, copilot_state: CopilotState, - inlay_cache: InlayHintCache, + inlay_hint_cache: InlayHintCache, _subscriptions: Vec, } @@ -1355,7 +1355,7 @@ impl Editor { hover_state: Default::default(), link_go_to_definition_state: Default::default(), copilot_state: Default::default(), - inlay_cache: InlayHintCache::new(settings::get::(cx).inlay_hints), + inlay_hint_cache: InlayHintCache::new(settings::get::(cx).inlay_hints), gutter_hovered: false, _subscriptions: vec![ cx.observe(&buffer, Self::on_buffer_changed), @@ -2598,7 +2598,7 @@ impl Editor { } if !settings::get::(cx).inlay_hints.enabled { - let to_remove = self.inlay_cache.clear(); + let to_remove = self.inlay_hint_cache.clear(); self.splice_inlay_hints(to_remove, Vec::new(), cx); return; } @@ -2631,7 +2631,7 @@ impl Editor { if let Some(InlaySplice { to_remove, to_insert, - }) = self.inlay_cache.apply_settings( + }) = self.inlay_hint_cache.apply_settings( new_settings, currently_visible_ranges, currently_shown_inlay_hints, @@ -2664,10 +2664,9 @@ impl Editor { to_insert, } = editor .update(&mut cx, |editor, cx| { - editor.inlay_cache.append_inlays( + editor.inlay_hint_cache.append_inlays( multi_buffer_handle, std::iter::once(updated_range_query), - currently_shown_inlay_hints, cx, ) })? @@ -2694,7 +2693,7 @@ impl Editor { to_insert, } = editor .update(&mut cx, |editor, cx| { - editor.inlay_cache.replace_inlays( + editor.inlay_hint_cache.replace_inlays( multi_buffer_handle, replacement_queries.into_iter(), currently_shown_inlay_hints, diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index 4754e2a186b8b28c9d9e62d479e45a2f41db1dc7..7f3124b5e83ab3d5c23a7c6a9caec1a9a626178b 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -4,9 +4,11 @@ use crate::{ display_map::InlayId, editor_settings, scroll::ScrollAnchor, Anchor, Editor, ExcerptId, MultiBuffer, }; +use anyhow::Context; use clock::Global; use gpui::{ModelHandle, Task, ViewContext}; use language::Buffer; +use log::error; use project::{InlayHint, InlayHintKind}; use collections::{hash_map, HashMap, HashSet}; @@ -28,6 +30,7 @@ pub struct InlayHintCache { #[derive(Clone, Debug, Default)] struct BufferInlays { buffer_version: Global, + cached_ranges: HashMap>>, excerpt_inlays: HashMap>, } @@ -36,6 +39,7 @@ impl BufferInlays { Self { buffer_version, excerpt_inlays: HashMap::default(), + cached_ranges: HashMap::default(), } } } @@ -177,16 +181,95 @@ impl InlayHintCache { &mut self, multi_buffer: ModelHandle, ranges_to_add: impl Iterator, - currently_shown_inlay_hints: HashMap>>, cx: &mut ViewContext, ) -> Task> { - self.fetch_inlays( - multi_buffer, - ranges_to_add, - currently_shown_inlay_hints, - false, - cx, - ) + let queries = ranges_to_add.filter_map(|additive_query| { + let Some(cached_buffer_inlays) = self.inlays_in_buffers.get(&additive_query.buffer_id) + else { return Some(vec![additive_query]) }; + if cached_buffer_inlays.buffer_version.changed_since(&additive_query.buffer_version) { + return None + } + let Some(excerpt_cached_ranges) = cached_buffer_inlays.cached_ranges.get(&additive_query.excerpt_id) + else { return Some(vec![additive_query]) }; + let non_cached_ranges = missing_subranges(&excerpt_cached_ranges, &additive_query.excerpt_offset_query_range); + if non_cached_ranges.is_empty() { + None + } else { + Some(non_cached_ranges.into_iter().map(|non_cached_range| InlayHintQuery { + buffer_id: additive_query.buffer_id, + buffer_version: additive_query.buffer_version.clone(), + excerpt_id: additive_query.excerpt_id, + excerpt_offset_query_range: non_cached_range, + }).collect()) + } + }).flatten(); + + let task_multi_buffer = multi_buffer.clone(); + let fetch_queries_task = fetch_queries(multi_buffer, queries, cx); + cx.spawn(|editor, mut cx| async move { + let new_hints = fetch_queries_task.await?; + editor.update(&mut cx, |editor, cx| { + let multi_buffer_snapshot = task_multi_buffer.read(cx).snapshot(cx); + let inlay_hint_cache = &mut editor.inlay_hint_cache; + let mut to_insert = Vec::new(); + for (new_buffer_id, new_hints_per_buffer) in new_hints { + let cached_buffer_inlays = inlay_hint_cache + .inlays_in_buffers + .entry(new_buffer_id) + .or_insert_with(|| { + BufferInlays::new(new_hints_per_buffer.buffer_version.clone()) + }); + if cached_buffer_inlays + .buffer_version + .changed_since(&new_hints_per_buffer.buffer_version) + { + continue; + } + + for (new_excerpt_id, new_ranges) in new_hints_per_buffer.cached_ranges { + let cached_ranges = cached_buffer_inlays + .cached_ranges + .entry(new_excerpt_id) + .or_default(); + for new_range in new_ranges { + insert_and_merge_ranges(cached_ranges, &new_range) + } + } + for (new_excerpt_id, new_hints) in new_hints_per_buffer.excerpt_inlays { + let cached_inlays = cached_buffer_inlays + .excerpt_inlays + .entry(new_excerpt_id) + .or_default(); + for new_inlay_hint in new_hints { + let new_inlay_id = todo!("TODO kb"); + let hint_anchor = multi_buffer_snapshot + .anchor_in_excerpt(new_excerpt_id, new_inlay_hint.position); + match cached_inlays.binary_search_by(|probe| { + hint_anchor.cmp(&probe.0, &multi_buffer_snapshot) + }) { + Ok(ix) | Err(ix) => { + cached_inlays.insert(ix, (hint_anchor, new_inlay_id)) + } + } + inlay_hint_cache + .inlay_hints + .insert(new_inlay_id, new_inlay_hint.clone()); + if inlay_hint_cache + .allowed_hint_kinds + .contains(&new_inlay_hint.kind) + { + to_insert.push((Some(new_inlay_id), hint_anchor, new_inlay_hint)); + } + } + } + } + + InlaySplice { + to_remove: Vec::new(), + to_insert, + } + }) + }) } pub fn replace_inlays( @@ -195,332 +278,35 @@ impl InlayHintCache { new_ranges: impl Iterator, currently_shown_inlay_hints: HashMap>>, cx: &mut ViewContext, - ) -> Task> { - self.fetch_inlays( - multi_buffer, - new_ranges, - currently_shown_inlay_hints, - true, - cx, - ) - } - - fn fetch_inlays( - &mut self, - multi_buffer: ModelHandle, - inlay_queries: impl Iterator, - mut currently_shown_inlay_hints: HashMap>>, - replace_old: bool, - cx: &mut ViewContext, ) -> Task> { let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx); - let inlay_queries_per_buffer = inlay_queries.fold( - HashMap::>::default(), - |mut queries, new_query| { - let mut buffer_queries = queries - .entry(new_query.buffer_id) - .or_insert_with(|| BufferInlays::new(new_query.buffer_version.clone())); - assert_eq!(buffer_queries.buffer_version, new_query.buffer_version); - let queries = buffer_queries - .excerpt_inlays - .entry(new_query.excerpt_id) - .or_default(); - // let z = multi_buffer_snapshot.anchor_in_excerpt(new_query.excerpt_id, text_anchor); - // .push(new_query); - // match queries - // .binary_search_by(|probe| inlay.position.cmp(&probe.0, &multi_buffer_snapshot)) - // { - // Ok(ix) | Err(ix) => { - // excerpt_hints.insert(ix, (inlay.position, inlay.id)); - // } - // } - // queries - todo!("TODO kb") - }, - ); + // let inlay_queries_per_buffer = inlay_queries.fold( + // HashMap::>::default(), + // |mut queries, new_query| { + // let mut buffer_queries = queries + // .entry(new_query.buffer_id) + // .or_insert_with(|| BufferInlays::new(new_query.buffer_version.clone())); + // assert_eq!(buffer_queries.buffer_version, new_query.buffer_version); + // let queries = buffer_queries + // .excerpt_inlays + // .entry(new_query.excerpt_id) + // .or_default(); + // // let z = multi_buffer_snapshot.anchor_in_excerpt(new_query.excerpt_id, text_anchor); + // // .push(new_query); + // // match queries + // // .binary_search_by(|probe| inlay.position.cmp(&probe.0, &multi_buffer_snapshot)) + // // { + // // Ok(ix) | Err(ix) => { + // // excerpt_hints.insert(ix, (inlay.position, inlay.id)); + // // } + // // } + // // queries + // todo!("TODO kb") + // }, + // ); todo!("TODO kb") } - - // fn fetch_inlays( - // &mut self, - // multi_buffer: ModelHandle, - // inlay_fetch_ranges: impl Iterator, - // replace_old: bool, - // cx: &mut ViewContext, - // ) -> Task> { - // let mut inlay_fetch_tasks = Vec::new(); - // for inlay_fetch_range in inlay_fetch_ranges { - // let inlays_up_to_date = self.inlays_up_to_date( - // &inlay_fetch_range.buffer_path, - // &inlay_fetch_range.buffer_version, - // inlay_fetch_range.excerpt_id, - // ); - // let task_multi_buffer = multi_buffer.clone(); - // let task = cx.spawn(|editor, mut cx| async move { - // if inlays_up_to_date { - // anyhow::Ok((inlay_fetch_range, None)) - // } else { - // let Some(buffer_handle) = cx.read(|cx| task_multi_buffer.read(cx).buffer(inlay_fetch_range.buffer_id)) - // else { return Ok((inlay_fetch_range, Some(Vec::new()))) }; - // let task = editor - // .update(&mut cx, |editor, cx| { - // editor.project.as_ref().map(|project| { - // project.update(cx, |project, cx| { - // project.query_inlay_hints_for_buffer( - // buffer_handle, - // inlay_fetch_range.excerpt_offset_query_range.clone(), - // cx, - // ) - // }) - // }) - // }) - // .context("inlays fecth task spawn")?; - - // Ok((inlay_fetch_range, match task { - // Some(task) => task.await.context("inlays for buffer task")?, - // None => Some(Vec::new()), - // })) - // } - // }); - // inlay_fetch_tasks.push(task); - // } - - // let final_task = cx.spawn(|editor, mut cx| async move { - // let mut inlay_updates: HashMap< - // PathBuf, - // ( - // Global, - // HashMap, OrderedByAnchorOffset)>>, - // ), - // > = HashMap::default(); - // let multi_buffer_snapshot = - // editor.read_with(&cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))?; - - // for task_result in futures::future::join_all(inlay_fetch_tasks).await { - // match task_result { - // Ok((inlay_fetch_range, response_inlays)) => { - // // TODO kb different caching now - // let inlays_per_excerpt = HashMap::from_iter([( - // inlay_fetch_range.excerpt_id, - // response_inlays - // .map(|excerpt_inlays| { - // excerpt_inlays.into_iter().fold( - // OrderedByAnchorOffset::default(), - // |mut ordered_inlays, inlay| { - // let anchor = multi_buffer_snapshot.anchor_in_excerpt( - // inlay_fetch_range.excerpt_id, - // inlay.position, - // ); - // ordered_inlays.add(anchor, inlay); - // ordered_inlays - // }, - // ) - // }) - // .map(|inlays| { - // (inlay_fetch_range.excerpt_offset_query_range, inlays) - // }), - // )]); - // match inlay_updates.entry(inlay_fetch_range.buffer_path) { - // hash_map::Entry::Occupied(mut o) => { - // o.get_mut().1.extend(inlays_per_excerpt); - // } - // hash_map::Entry::Vacant(v) => { - // v.insert((inlay_fetch_range.buffer_version, inlays_per_excerpt)); - // } - // } - // } - // Err(e) => error!("Failed to update inlays for buffer: {e:#}"), - // } - // } - - // let updates = if !inlay_updates.is_empty() { - // let inlays_update = editor.update(&mut cx, |editor, _| { - // editor.inlay_cache.apply_fetch_inlays(inlay_updates) - // })?; - // inlays_update - // } else { - // InlaySplice::default() - // }; - - // anyhow::Ok(updates) - // }); - - // final_task - // } - - // fn inlays_up_to_date( - // &self, - // buffer_path: &Path, - // buffer_version: &Global, - // excerpt_id: ExcerptId, - // ) -> bool { - // let Some(buffer_inlays) = self.inlays_per_buffer.get(buffer_path) else { return false }; - // let buffer_up_to_date = buffer_version == &buffer_inlays.buffer_version - // || buffer_inlays.buffer_version.changed_since(&buffer_version); - // buffer_up_to_date && buffer_inlays.inlays_per_excerpts.contains_key(&excerpt_id) - // } - - // fn apply_fetch_inlays( - // &mut self, - // fetched_inlays: HashMap< - // PathBuf, - // ( - // Global, - // HashMap, OrderedByAnchorOffset)>>, - // ), - // >, - // ) -> InlaySplice { - // let mut old_inlays = self.inlays_per_buffer.clone(); - // let mut to_remove = Vec::new(); - // let mut to_insert = Vec::new(); - - // for (buffer_path, (buffer_version, new_buffer_inlays)) in fetched_inlays { - // match old_inlays.remove(&buffer_path) { - // Some(mut old_buffer_inlays) => { - // for (excerpt_id, new_excerpt_inlays) in new_buffer_inlays { - // let (_, mut new_excerpt_inlays) = match new_excerpt_inlays { - // Some((excerpt_offset_range, new_inlays)) => ( - // excerpt_offset_range, - // new_inlays.into_ordered_elements().fuse().peekable(), - // ), - // None => continue, - // }; - // if self.inlays_up_to_date(&buffer_path, &buffer_version, excerpt_id) { - // continue; - // } - - // let self_inlays_per_buffer = self - // .inlays_per_buffer - // .get_mut(&buffer_path) - // .expect("element expected: `old_inlays.remove` returned `Some`"); - - // if old_buffer_inlays - // .inlays_per_excerpts - // .remove(&excerpt_id) - // .is_some() - // { - // let self_excerpt_inlays = self_inlays_per_buffer - // .inlays_per_excerpts - // .get_mut(&excerpt_id) - // .expect("element expected: `old_excerpt_inlays` is `Some`"); - // let mut hints_to_add = Vec::<(Anchor, (InlayId, InlayHint))>::new(); - // // TODO kb update inner buffer_id and version with the new data? - // self_excerpt_inlays.0.retain( - // |_, (old_anchor, (old_inlay_id, old_inlay))| { - // let mut retain = false; - - // while let Some(new_offset) = new_excerpt_inlays - // .peek() - // .map(|(new_anchor, _)| new_anchor.text_anchor.offset) - // { - // let old_offset = old_anchor.text_anchor.offset; - // match new_offset.cmp(&old_offset) { - // cmp::Ordering::Less => { - // let (new_anchor, new_inlay) = - // new_excerpt_inlays.next().expect( - // "element expected: `peek` returned `Some`", - // ); - // hints_to_add.push(( - // new_anchor, - // ( - // InlayId(post_inc(&mut self.next_inlay_id)), - // new_inlay, - // ), - // )); - // } - // cmp::Ordering::Equal => { - // let (new_anchor, new_inlay) = - // new_excerpt_inlays.next().expect( - // "element expected: `peek` returned `Some`", - // ); - // if &new_inlay == old_inlay { - // retain = true; - // } else { - // hints_to_add.push(( - // new_anchor, - // ( - // InlayId(post_inc( - // &mut self.next_inlay_id, - // )), - // new_inlay, - // ), - // )); - // } - // } - // cmp::Ordering::Greater => break, - // } - // } - - // if !retain { - // to_remove.push(*old_inlay_id); - // } - // retain - // }, - // ); - - // for (new_anchor, (id, new_inlay)) in hints_to_add { - // self_excerpt_inlays.add(new_anchor, (id, new_inlay.clone())); - // to_insert.push((id, new_anchor, new_inlay)); - // } - // } - - // for (new_anchor, new_inlay) in new_excerpt_inlays { - // let id = InlayId(post_inc(&mut self.next_inlay_id)); - // self_inlays_per_buffer - // .inlays_per_excerpts - // .entry(excerpt_id) - // .or_default() - // .add(new_anchor, (id, new_inlay.clone())); - // to_insert.push((id, new_anchor, new_inlay)); - // } - // } - // } - // None => { - // let mut inlays_per_excerpts: HashMap< - // ExcerptId, - // OrderedByAnchorOffset<(InlayId, InlayHint)>, - // > = HashMap::default(); - // for (new_excerpt_id, new_ordered_inlays) in new_buffer_inlays { - // if let Some((_, new_ordered_inlays)) = new_ordered_inlays { - // for (new_anchor, new_inlay) in - // new_ordered_inlays.into_ordered_elements() - // { - // let id = InlayId(post_inc(&mut self.next_inlay_id)); - // inlays_per_excerpts - // .entry(new_excerpt_id) - // .or_default() - // .add(new_anchor, (id, new_inlay.clone())); - // to_insert.push((id, new_anchor, new_inlay)); - // } - // } - // } - // self.inlays_per_buffer.insert( - // buffer_path, - // BufferInlays { - // buffer_version, - // inlays_per_excerpts, - // }, - // ); - // } - // } - // } - - // for (_, old_buffer_inlays) in old_inlays { - // for (_, old_excerpt_inlays) in old_buffer_inlays.inlays_per_excerpts { - // for (_, (id_to_remove, _)) in old_excerpt_inlays.into_ordered_elements() { - // to_remove.push(id_to_remove); - // } - // } - // } - - // to_insert.retain(|(_, _, new_hint)| self.allowed_hint_kinds.contains(&new_hint.kind)); - - // InlaySplice { - // to_remove, - // to_insert, - // } - // } } fn allowed_inlay_hint_types( @@ -538,3 +324,150 @@ fn allowed_inlay_hint_types( } new_allowed_inlay_hint_types } + +fn missing_subranges(cache: &[Range], input: &Range) -> Vec> { + let mut missing = Vec::new(); + + // Find where the input range would fit in the cache + let index = match cache.binary_search_by_key(&input.start, |probe| probe.start) { + Ok(pos) | Err(pos) => pos, + }; + + // Check for a gap from the start of the input range to the first range in the cache + if index == 0 { + if input.start < cache[index].start { + missing.push(input.start..cache[index].start); + } + } else { + let prev_end = cache[index - 1].end; + if input.start < prev_end { + missing.push(input.start..prev_end); + } + } + + // Iterate through the cache ranges starting from index + for i in index..cache.len() { + let start = if i > 0 { cache[i - 1].end } else { input.start }; + let end = cache[i].start; + + if start < end { + missing.push(start..end); + } + } + + // Check for a gap from the last range in the cache to the end of the input range + if let Some(last_range) = cache.last() { + if last_range.end < input.end { + missing.push(last_range.end..input.end); + } + } else { + // If cache is empty, the entire input range is missing + missing.push(input.start..input.end); + } + + missing +} + +fn insert_and_merge_ranges(cache: &mut Vec>, new_range: &Range) { + if cache.is_empty() { + cache.push(new_range.clone()); + return; + } + + // Find the index to insert the new range + let index = match cache.binary_search_by_key(&new_range.start, |probe| probe.start) { + Ok(pos) | Err(pos) => pos, + }; + + // Check if the new range overlaps with the previous range in the cache + if index > 0 && cache[index - 1].end >= new_range.start { + // Merge with the previous range + cache[index - 1].end = std::cmp::max(cache[index - 1].end, new_range.end); + } else { + // Insert the new range, as it doesn't overlap with the previous range + cache.insert(index, new_range.clone()); + } + + // Merge overlaps with subsequent ranges + let mut i = index; + while i + 1 < cache.len() && cache[i].end >= cache[i + 1].start { + cache[i].end = std::cmp::max(cache[i].end, cache[i + 1].end); + cache.remove(i + 1); + i += 1; + } +} + +fn fetch_queries<'a, 'b>( + multi_buffer: ModelHandle, + queries: impl Iterator, + cx: &mut ViewContext<'a, 'b, Editor>, +) -> Task>>> { + let mut inlay_fetch_tasks = Vec::new(); + for query in queries { + let task_multi_buffer = multi_buffer.clone(); + let task = cx.spawn(|editor, mut cx| async move { + let Some(buffer_handle) = cx.read(|cx| task_multi_buffer.read(cx).buffer(query.buffer_id)) + else { return anyhow::Ok((query, Some(Vec::new()))) }; + let task = editor + .update(&mut cx, |editor, cx| { + editor.project.as_ref().map(|project| { + project.update(cx, |project, cx| { + project.query_inlay_hints_for_buffer( + buffer_handle, + query.excerpt_offset_query_range.clone(), + cx, + ) + }) + }) + }) + .context("inlays fecth task spawn")?; + Ok(( + query, + match task { + Some(task) => task.await.context("inlays for buffer task")?, + None => Some(Vec::new()), + }, + )) + }); + + inlay_fetch_tasks.push(task); + } + + cx.spawn(|editor, cx| async move { + let mut inlay_updates: HashMap> = HashMap::default(); + for task_result in futures::future::join_all(inlay_fetch_tasks).await { + match task_result { + Ok((query, Some(response_inlays))) => { + let Some(buffer_snapshot) = editor.read_with(&cx, |editor, cx| { + editor.buffer().read(cx).buffer(query.buffer_id).map(|buffer| buffer.read(cx).snapshot()) + })? else { continue; }; + let buffer_inlays = inlay_updates + .entry(query.buffer_id) + .or_insert_with(|| BufferInlays::new(query.buffer_version.clone())); + assert_eq!(buffer_inlays.buffer_version, query.buffer_version); + { + let cached_ranges = buffer_inlays + .cached_ranges + .entry(query.excerpt_id) + .or_default(); + insert_and_merge_ranges(cached_ranges, &query.excerpt_offset_query_range); + let excerpt_inlays = buffer_inlays + .excerpt_inlays + .entry(query.excerpt_id) + .or_default(); + for inlay in response_inlays { + match excerpt_inlays.binary_search_by(|probe| { + inlay.position.cmp(&probe.position, &buffer_snapshot) + }) { + Ok(ix) | Err(ix) => excerpt_inlays.insert(ix, inlay), + } + } + } + } + Ok((_, None)) => {} + Err(e) => error!("Failed to update inlays for buffer: {e:#}"), + } + } + Ok(inlay_updates) + }) +}