1use std::ops::Range;
2
3use crate::{
4 editor_settings, scroll::ScrollAnchor, Anchor, Editor, ExcerptId, InlayId, MultiBuffer,
5};
6use anyhow::Context;
7use clock::Global;
8use gpui::{ModelHandle, Task, ViewContext};
9use language::Buffer;
10use log::error;
11use project::{InlayHint, InlayHintKind};
12
13use collections::{hash_map, HashMap, HashSet};
14use util::post_inc;
15
16#[derive(Debug, Copy, Clone)]
17pub enum InlayRefreshReason {
18 SettingsChange(editor_settings::InlayHints),
19 Scroll(ScrollAnchor),
20 VisibleExcerptsChange,
21}
22
23#[derive(Debug, Default)]
24pub struct InlayHintCache {
25 inlay_hints: HashMap<InlayId, InlayHint>,
26 hints_in_buffers: HashMap<u64, BufferHints<(Anchor, InlayId)>>,
27 allowed_hint_kinds: HashSet<Option<InlayHintKind>>,
28}
29
30#[derive(Clone, Debug)]
31struct BufferHints<I> {
32 buffer_version: Global,
33 hints_per_excerpt: HashMap<ExcerptId, ExcerptHints<I>>,
34}
35
36#[derive(Clone, Debug)]
37struct ExcerptHints<I> {
38 cached_excerpt_offsets: Vec<Range<usize>>,
39 hints: Vec<I>,
40}
41
42impl<I> Default for ExcerptHints<I> {
43 fn default() -> Self {
44 Self {
45 cached_excerpt_offsets: Vec::new(),
46 hints: Vec::new(),
47 }
48 }
49}
50
51impl<I> BufferHints<I> {
52 fn new(buffer_version: Global) -> Self {
53 Self {
54 buffer_version,
55 hints_per_excerpt: HashMap::default(),
56 }
57 }
58}
59
60#[derive(Debug, Default)]
61pub struct InlaySplice {
62 pub to_remove: Vec<InlayId>,
63 pub to_insert: Vec<(InlayId, Anchor, InlayHint)>,
64}
65
66pub struct InlayHintQuery {
67 pub buffer_id: u64,
68 pub buffer_version: Global,
69 pub excerpt_id: ExcerptId,
70 pub excerpt_offset_query_range: Range<usize>,
71}
72
73impl InlayHintCache {
74 pub fn new(inlay_hint_settings: editor_settings::InlayHints) -> Self {
75 Self {
76 allowed_hint_kinds: allowed_inlay_hint_types(inlay_hint_settings),
77 hints_in_buffers: HashMap::default(),
78 inlay_hints: HashMap::default(),
79 }
80 }
81
82 pub fn apply_settings(
83 &mut self,
84 inlay_hint_settings: editor_settings::InlayHints,
85 currently_visible_ranges: Vec<(ModelHandle<Buffer>, Range<usize>, ExcerptId)>,
86 mut currently_shown_inlay_hints: HashMap<u64, HashMap<ExcerptId, Vec<(Anchor, InlayId)>>>,
87 cx: &mut ViewContext<Editor>,
88 ) -> Option<InlaySplice> {
89 let new_allowed_hint_kinds = allowed_inlay_hint_types(inlay_hint_settings);
90 if new_allowed_hint_kinds == self.allowed_hint_kinds {
91 None
92 } else {
93 self.allowed_hint_kinds = new_allowed_hint_kinds;
94 let mut to_remove = Vec::new();
95 let mut to_insert = Vec::new();
96
97 let mut considered_hints =
98 HashMap::<u64, HashMap<ExcerptId, HashSet<InlayId>>>::default();
99 for (visible_buffer, _, visible_excerpt_id) in currently_visible_ranges {
100 let visible_buffer = visible_buffer.read(cx);
101 let visible_buffer_id = visible_buffer.remote_id();
102 match currently_shown_inlay_hints.entry(visible_buffer_id) {
103 hash_map::Entry::Occupied(mut o) => {
104 let shown_hints_per_excerpt = o.get_mut();
105 for (_, shown_hint_id) in shown_hints_per_excerpt
106 .remove(&visible_excerpt_id)
107 .unwrap_or_default()
108 {
109 considered_hints
110 .entry(visible_buffer_id)
111 .or_default()
112 .entry(visible_excerpt_id)
113 .or_default()
114 .insert(shown_hint_id);
115 match self.inlay_hints.get(&shown_hint_id) {
116 Some(shown_hint) => {
117 if !self.allowed_hint_kinds.contains(&shown_hint.kind) {
118 to_remove.push(shown_hint_id);
119 }
120 }
121 None => to_remove.push(shown_hint_id),
122 }
123 }
124 if shown_hints_per_excerpt.is_empty() {
125 o.remove();
126 }
127 }
128 hash_map::Entry::Vacant(_) => {}
129 }
130 }
131
132 let reenabled_hints = self
133 .hints_in_buffers
134 .iter()
135 .filter_map(|(cached_buffer_id, cached_hints_per_excerpt)| {
136 let considered_hints_in_excerpts = considered_hints.get(cached_buffer_id)?;
137 let not_considered_cached_hints = cached_hints_per_excerpt
138 .hints_per_excerpt
139 .iter()
140 .filter_map(|(cached_excerpt_id, cached_excerpt_hints)| {
141 let considered_excerpt_hints =
142 considered_hints_in_excerpts.get(&cached_excerpt_id)?;
143 let not_considered_cached_hints = cached_excerpt_hints
144 .hints
145 .iter()
146 .filter(|(_, cached_hint_id)| {
147 !considered_excerpt_hints.contains(cached_hint_id)
148 })
149 .copied();
150 Some(not_considered_cached_hints)
151 })
152 .flatten();
153 Some(not_considered_cached_hints)
154 })
155 .flatten()
156 .filter_map(|(cached_anchor, cached_inlay_id)| {
157 Some((
158 cached_anchor,
159 cached_inlay_id,
160 self.inlay_hints.get(&cached_inlay_id)?,
161 ))
162 })
163 .filter(|(_, _, cached_inlay)| self.allowed_hint_kinds.contains(&cached_inlay.kind))
164 .map(|(cached_anchor, cached_inlay_id, reenabled_inlay)| {
165 (cached_inlay_id, cached_anchor, reenabled_inlay.clone())
166 });
167 to_insert.extend(reenabled_hints);
168
169 to_remove.extend(
170 currently_shown_inlay_hints
171 .into_iter()
172 .flat_map(|(_, hints_by_excerpt)| hints_by_excerpt)
173 .flat_map(|(_, excerpt_hints)| excerpt_hints)
174 .map(|(_, hint_id)| hint_id),
175 );
176
177 Some(InlaySplice {
178 to_remove,
179 to_insert,
180 })
181 }
182 }
183
184 pub fn clear(&mut self) -> Vec<InlayId> {
185 let ids_to_remove = self.inlay_hints.drain().map(|(id, _)| id).collect();
186 self.hints_in_buffers.clear();
187 ids_to_remove
188 }
189
190 pub fn append_hints(
191 &mut self,
192 multi_buffer: ModelHandle<MultiBuffer>,
193 ranges_to_add: impl Iterator<Item = InlayHintQuery>,
194 cx: &mut ViewContext<Editor>,
195 ) -> Task<anyhow::Result<InlaySplice>> {
196 let queries = ranges_to_add.filter_map(|additive_query| {
197 let Some(cached_buffer_hints) = self.hints_in_buffers.get(&additive_query.buffer_id)
198 else { return Some(vec![additive_query]) };
199 if cached_buffer_hints.buffer_version.changed_since(&additive_query.buffer_version) {
200 return None
201 }
202 let Some(excerpt_hints) = cached_buffer_hints.hints_per_excerpt.get(&additive_query.excerpt_id)
203 else { return Some(vec![additive_query]) };
204 let non_cached_ranges = missing_subranges(&excerpt_hints.cached_excerpt_offsets, &additive_query.excerpt_offset_query_range);
205 if non_cached_ranges.is_empty() {
206 None
207 } else {
208 Some(non_cached_ranges.into_iter().map(|non_cached_range| InlayHintQuery {
209 buffer_id: additive_query.buffer_id,
210 buffer_version: additive_query.buffer_version.clone(),
211 excerpt_id: additive_query.excerpt_id,
212 excerpt_offset_query_range: non_cached_range,
213 }).collect())
214 }
215 }).flatten();
216
217 let task_multi_buffer = multi_buffer.clone();
218 let fetch_queries_task = fetch_queries(multi_buffer, queries, cx);
219 cx.spawn(|editor, mut cx| async move {
220 let new_hints = fetch_queries_task.await?;
221 editor.update(&mut cx, |editor, cx| {
222 let multi_buffer_snapshot = task_multi_buffer.read(cx).snapshot(cx);
223 let mut to_insert = Vec::new();
224 for (new_buffer_id, new_hints_per_buffer) in new_hints {
225 let cached_buffer_hints = editor
226 .inlay_hint_cache
227 .hints_in_buffers
228 .entry(new_buffer_id)
229 .or_insert_with(|| {
230 BufferHints::new(new_hints_per_buffer.buffer_version.clone())
231 });
232 if cached_buffer_hints
233 .buffer_version
234 .changed_since(&new_hints_per_buffer.buffer_version)
235 {
236 continue;
237 }
238
239 for (new_excerpt_id, new_excerpt_hints) in
240 new_hints_per_buffer.hints_per_excerpt
241 {
242 let cached_excerpt_hints = cached_buffer_hints
243 .hints_per_excerpt
244 .entry(new_excerpt_id)
245 .or_insert_with(|| ExcerptHints::default());
246 for new_range in new_excerpt_hints.cached_excerpt_offsets {
247 insert_and_merge_ranges(
248 &mut cached_excerpt_hints.cached_excerpt_offsets,
249 &new_range,
250 )
251 }
252 for new_inlay_hint in new_excerpt_hints.hints {
253 let hint_anchor = multi_buffer_snapshot
254 .anchor_in_excerpt(new_excerpt_id, new_inlay_hint.position);
255 let insert_ix =
256 match cached_excerpt_hints.hints.binary_search_by(|probe| {
257 hint_anchor.cmp(&probe.0, &multi_buffer_snapshot)
258 }) {
259 Ok(ix) | Err(ix) => ix,
260 };
261
262 let new_inlay_id = InlayId(post_inc(&mut editor.next_inlay_id));
263 cached_excerpt_hints
264 .hints
265 .insert(insert_ix, (hint_anchor, new_inlay_id));
266 editor
267 .inlay_hint_cache
268 .inlay_hints
269 .insert(new_inlay_id, new_inlay_hint.clone());
270 if editor
271 .inlay_hint_cache
272 .allowed_hint_kinds
273 .contains(&new_inlay_hint.kind)
274 {
275 to_insert.push((new_inlay_id, hint_anchor, new_inlay_hint));
276 }
277 }
278 }
279 }
280
281 InlaySplice {
282 to_remove: Vec::new(),
283 to_insert,
284 }
285 })
286 })
287 }
288
289 pub fn replace_hints(
290 &mut self,
291 multi_buffer: ModelHandle<MultiBuffer>,
292 new_ranges: impl Iterator<Item = InlayHintQuery>,
293 currently_shown_inlay_hints: HashMap<u64, HashMap<ExcerptId, Vec<(Anchor, InlayId)>>>,
294 cx: &mut ViewContext<Editor>,
295 ) -> Task<anyhow::Result<InlaySplice>> {
296 let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
297 // let inlay_queries_per_buffer = inlay_queries.fold(
298 // HashMap::<u64, BufferInlays<InlayHintQuery>>::default(),
299 // |mut queries, new_query| {
300 // let mut buffer_queries = queries
301 // .entry(new_query.buffer_id)
302 // .or_insert_with(|| BufferInlays::new(new_query.buffer_version.clone()));
303 // assert_eq!(buffer_queries.buffer_version, new_query.buffer_version);
304 // let queries = buffer_queries
305 // .excerpt_inlays
306 // .entry(new_query.excerpt_id)
307 // .or_default();
308 // // let z = multi_buffer_snapshot.anchor_in_excerpt(new_query.excerpt_id, text_anchor);
309 // // .push(new_query);
310 // // match queries
311 // // .binary_search_by(|probe| inlay.position.cmp(&probe.0, &multi_buffer_snapshot))
312 // // {
313 // // Ok(ix) | Err(ix) => {
314 // // excerpt_hints.insert(ix, (inlay.position, inlay.id));
315 // // }
316 // // }
317 // // queries
318 // todo!("TODO kb")
319 // },
320 // );
321
322 todo!("TODO kb")
323 }
324}
325
326fn allowed_inlay_hint_types(
327 inlay_hint_settings: editor_settings::InlayHints,
328) -> HashSet<Option<InlayHintKind>> {
329 let mut new_allowed_inlay_hint_types = HashSet::default();
330 if inlay_hint_settings.show_type_hints {
331 new_allowed_inlay_hint_types.insert(Some(InlayHintKind::Type));
332 }
333 if inlay_hint_settings.show_parameter_hints {
334 new_allowed_inlay_hint_types.insert(Some(InlayHintKind::Parameter));
335 }
336 if inlay_hint_settings.show_other_hints {
337 new_allowed_inlay_hint_types.insert(None);
338 }
339 new_allowed_inlay_hint_types
340}
341
342fn missing_subranges(cache: &[Range<usize>], input: &Range<usize>) -> Vec<Range<usize>> {
343 let mut missing = Vec::new();
344
345 // Find where the input range would fit in the cache
346 let index = match cache.binary_search_by_key(&input.start, |probe| probe.start) {
347 Ok(pos) | Err(pos) => pos,
348 };
349
350 // Check for a gap from the start of the input range to the first range in the cache
351 if index == 0 {
352 if input.start < cache[index].start {
353 missing.push(input.start..cache[index].start);
354 }
355 } else {
356 let prev_end = cache[index - 1].end;
357 if input.start < prev_end {
358 missing.push(input.start..prev_end);
359 }
360 }
361
362 // Iterate through the cache ranges starting from index
363 for i in index..cache.len() {
364 let start = if i > 0 { cache[i - 1].end } else { input.start };
365 let end = cache[i].start;
366
367 if start < end {
368 missing.push(start..end);
369 }
370 }
371
372 // Check for a gap from the last range in the cache to the end of the input range
373 if let Some(last_range) = cache.last() {
374 if last_range.end < input.end {
375 missing.push(last_range.end..input.end);
376 }
377 } else {
378 // If cache is empty, the entire input range is missing
379 missing.push(input.start..input.end);
380 }
381
382 missing
383}
384
385fn insert_and_merge_ranges(cache: &mut Vec<Range<usize>>, new_range: &Range<usize>) {
386 if cache.is_empty() {
387 cache.push(new_range.clone());
388 return;
389 }
390
391 // Find the index to insert the new range
392 let index = match cache.binary_search_by_key(&new_range.start, |probe| probe.start) {
393 Ok(pos) | Err(pos) => pos,
394 };
395
396 // Check if the new range overlaps with the previous range in the cache
397 if index > 0 && cache[index - 1].end >= new_range.start {
398 // Merge with the previous range
399 cache[index - 1].end = std::cmp::max(cache[index - 1].end, new_range.end);
400 } else {
401 // Insert the new range, as it doesn't overlap with the previous range
402 cache.insert(index, new_range.clone());
403 }
404
405 // Merge overlaps with subsequent ranges
406 let mut i = index;
407 while i + 1 < cache.len() && cache[i].end >= cache[i + 1].start {
408 cache[i].end = std::cmp::max(cache[i].end, cache[i + 1].end);
409 cache.remove(i + 1);
410 i += 1;
411 }
412}
413
414fn fetch_queries<'a, 'b>(
415 multi_buffer: ModelHandle<MultiBuffer>,
416 queries: impl Iterator<Item = InlayHintQuery>,
417 cx: &mut ViewContext<'a, 'b, Editor>,
418) -> Task<anyhow::Result<HashMap<u64, BufferHints<InlayHint>>>> {
419 let mut inlay_fetch_tasks = Vec::new();
420 for query in queries {
421 let task_multi_buffer = multi_buffer.clone();
422 let task = cx.spawn(|editor, mut cx| async move {
423 let Some(buffer_handle) = cx.read(|cx| task_multi_buffer.read(cx).buffer(query.buffer_id))
424 else { return anyhow::Ok((query, Some(Vec::new()))) };
425 let task = editor
426 .update(&mut cx, |editor, cx| {
427 editor.project.as_ref().map(|project| {
428 project.update(cx, |project, cx| {
429 project.query_inlay_hints_for_buffer(
430 buffer_handle,
431 query.excerpt_offset_query_range.clone(),
432 cx,
433 )
434 })
435 })
436 })
437 .context("inlays fecth task spawn")?;
438 Ok((
439 query,
440 match task {
441 Some(task) => task.await.context("inlays for buffer task")?,
442 None => Some(Vec::new()),
443 },
444 ))
445 });
446
447 inlay_fetch_tasks.push(task);
448 }
449
450 cx.spawn(|editor, cx| async move {
451 let mut inlay_updates: HashMap<u64, BufferHints<InlayHint>> = HashMap::default();
452 for task_result in futures::future::join_all(inlay_fetch_tasks).await {
453 match task_result {
454 Ok((query, Some(response_hints))) => {
455 let Some(buffer_snapshot) = editor.read_with(&cx, |editor, cx| {
456 editor.buffer().read(cx).buffer(query.buffer_id).map(|buffer| buffer.read(cx).snapshot())
457 })? else { continue; };
458 let buffer_hints = inlay_updates
459 .entry(query.buffer_id)
460 .or_insert_with(|| BufferHints::new(query.buffer_version.clone()));
461 if buffer_snapshot.version().changed_since(&buffer_hints.buffer_version) {
462 continue;
463 }
464 let cached_excerpt_hints = buffer_hints
465 .hints_per_excerpt
466 .entry(query.excerpt_id)
467 .or_default();
468 insert_and_merge_ranges(&mut cached_excerpt_hints.cached_excerpt_offsets, &query.excerpt_offset_query_range);
469 let excerpt_hints = &mut cached_excerpt_hints.hints;
470 for inlay in response_hints {
471 match excerpt_hints.binary_search_by(|probe| {
472 inlay.position.cmp(&probe.position, &buffer_snapshot)
473 }) {
474 Ok(ix) | Err(ix) => excerpt_hints.insert(ix, inlay),
475 }
476 }
477 }
478 Ok((_, None)) => {}
479 Err(e) => error!("Failed to update inlays for buffer: {e:#}"),
480 }
481 }
482 Ok(inlay_updates)
483 })
484}