1#![allow(unused)]
2// TODO kb
3
4use std::{
5 cmp::Reverse,
6 ops::{Add, AddAssign, Range, Sub},
7 sync::atomic::{self, AtomicUsize},
8};
9
10use crate::{Anchor, ExcerptId, InlayHintLocation, MultiBufferSnapshot, ToOffset, ToPoint};
11
12use super::{
13 suggestion_map::{
14 SuggestionBufferRows, SuggestionChunks, SuggestionEdit, SuggestionOffset, SuggestionPoint,
15 SuggestionSnapshot,
16 },
17 TextHighlights,
18};
19use collections::{BTreeMap, HashMap, HashSet};
20use gpui::fonts::HighlightStyle;
21use language::{Chunk, Edit, Point, Rope, TextSummary};
22use parking_lot::Mutex;
23use project::InlayHint;
24use rand::Rng;
25use sum_tree::{Bias, SumTree};
26use util::post_inc;
27
28#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
29pub struct InlayId(usize);
30
31pub struct InlayMap {
32 snapshot: Mutex<InlaySnapshot>,
33 next_inlay_id: usize,
34 inlays: HashMap<InlayId, (InlayHintLocation, Inlay)>,
35}
36
37#[derive(Clone)]
38pub struct InlaySnapshot {
39 // TODO kb merge these two together?
40 pub suggestion_snapshot: SuggestionSnapshot,
41 transforms: SumTree<Transform>,
42 pub version: usize,
43}
44
45#[derive(Clone)]
46enum Transform {
47 Isomorphic(TextSummary),
48 Inlay(Inlay),
49}
50
51impl sum_tree::Item for Transform {
52 type Summary = TransformSummary;
53
54 fn summary(&self) -> Self::Summary {
55 match self {
56 Transform::Isomorphic(summary) => TransformSummary {
57 input: summary.clone(),
58 output: summary.clone(),
59 },
60 Transform::Inlay(inlay) => TransformSummary {
61 input: TextSummary::default(),
62 output: inlay.properties.text.summary(),
63 },
64 }
65 }
66}
67
68#[derive(Clone, Debug, Default)]
69struct TransformSummary {
70 input: TextSummary,
71 output: TextSummary,
72}
73
74impl sum_tree::Summary for TransformSummary {
75 type Context = ();
76
77 fn add_summary(&mut self, other: &Self, _: &()) {
78 self.input += &other.input;
79 self.output += &other.output;
80 }
81}
82
83pub type InlayEdit = Edit<InlayOffset>;
84
85#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
86pub struct InlayOffset(pub usize);
87
88impl Add for InlayOffset {
89 type Output = Self;
90
91 fn add(self, rhs: Self) -> Self::Output {
92 Self(self.0 + rhs.0)
93 }
94}
95
96impl Sub for InlayOffset {
97 type Output = Self;
98
99 fn sub(self, rhs: Self) -> Self::Output {
100 Self(self.0 - rhs.0)
101 }
102}
103
104impl AddAssign for InlayOffset {
105 fn add_assign(&mut self, rhs: Self) {
106 self.0 += rhs.0;
107 }
108}
109
110#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
111pub struct InlayPoint(pub Point);
112
113impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint {
114 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
115 self.0 += &summary.output.lines;
116 }
117}
118
119impl<'a> sum_tree::Dimension<'a, TransformSummary> for SuggestionPoint {
120 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
121 self.0 += &summary.input.lines;
122 }
123}
124
125#[derive(Clone)]
126pub struct InlayBufferRows<'a> {
127 suggestion_rows: SuggestionBufferRows<'a>,
128}
129
130pub struct InlayChunks<'a> {
131 suggestion_chunks: SuggestionChunks<'a>,
132}
133
134#[derive(Debug, Clone)]
135pub struct Inlay {
136 pub(super) id: InlayId,
137 pub(super) properties: InlayProperties,
138}
139
140#[derive(Debug, Clone)]
141pub struct InlayProperties {
142 pub(super) position: Anchor,
143 pub(super) text: Rope,
144}
145
146impl<'a> Iterator for InlayChunks<'a> {
147 type Item = Chunk<'a>;
148
149 fn next(&mut self) -> Option<Self::Item> {
150 self.suggestion_chunks.next()
151 }
152}
153
154impl<'a> Iterator for InlayBufferRows<'a> {
155 type Item = Option<u32>;
156
157 fn next(&mut self) -> Option<Self::Item> {
158 self.suggestion_rows.next()
159 }
160}
161
162impl InlayPoint {
163 pub fn new(row: u32, column: u32) -> Self {
164 Self(Point::new(row, column))
165 }
166
167 pub fn row(self) -> u32 {
168 self.0.row
169 }
170
171 pub fn column(self) -> u32 {
172 self.0.column
173 }
174}
175
176impl InlayMap {
177 pub fn new(suggestion_snapshot: SuggestionSnapshot) -> (Self, InlaySnapshot) {
178 let snapshot = InlaySnapshot {
179 suggestion_snapshot: suggestion_snapshot.clone(),
180 version: 0,
181 transforms: SumTree::new(),
182 };
183
184 (
185 Self {
186 snapshot: Mutex::new(snapshot.clone()),
187 next_inlay_id: 0,
188 inlays: HashMap::default(),
189 },
190 snapshot,
191 )
192 }
193
194 pub fn sync(
195 &self,
196 suggestion_snapshot: SuggestionSnapshot,
197 suggestion_edits: Vec<SuggestionEdit>,
198 ) -> (InlaySnapshot, Vec<InlayEdit>) {
199 let mut snapshot = self.snapshot.lock();
200
201 if snapshot.suggestion_snapshot.version != suggestion_snapshot.version {
202 snapshot.version += 1;
203 }
204
205 let mut inlay_edits = Vec::new();
206
207 dbg!(self.inlays.len());
208
209 for suggestion_edit in suggestion_edits {
210 let old = suggestion_edit.old;
211 let new = suggestion_edit.new;
212 // TODO kb copied from suggestion_map
213 inlay_edits.push(InlayEdit {
214 old: InlayOffset(old.start.0)..InlayOffset(old.end.0),
215 new: InlayOffset(old.start.0)..InlayOffset(new.end.0),
216 })
217 }
218
219 snapshot.suggestion_snapshot = suggestion_snapshot;
220
221 (snapshot.clone(), inlay_edits)
222 }
223
224 pub fn splice(
225 &mut self,
226 to_remove: HashSet<InlayId>,
227 to_insert: Vec<(InlayHintLocation, InlayProperties)>,
228 ) -> (InlaySnapshot, Vec<InlayEdit>, Vec<InlayId>) {
229 let mut snapshot = self.snapshot.lock();
230
231 let mut inlays = BTreeMap::new();
232 let mut new_ids = Vec::new();
233 for (location, properties) in to_insert {
234 let inlay = Inlay {
235 id: InlayId(post_inc(&mut self.next_inlay_id)),
236 properties,
237 };
238 self.inlays.insert(inlay.id, (location, inlay.clone()));
239 new_ids.push(inlay.id);
240
241 let buffer_point = inlay
242 .properties
243 .position
244 .to_point(snapshot.buffer_snapshot());
245 let fold_point = snapshot
246 .suggestion_snapshot
247 .fold_snapshot
248 .to_fold_point(buffer_point, Bias::Left);
249 let suggestion_point = snapshot.suggestion_snapshot.to_suggestion_point(fold_point);
250 let inlay_point = snapshot.to_inlay_point(suggestion_point);
251
252 inlays.insert((inlay_point, Reverse(inlay.id)), Some(inlay));
253 }
254
255 for inlay_id in to_remove {
256 if let Some((_, inlay)) = self.inlays.remove(&inlay_id) {
257 let buffer_point = inlay
258 .properties
259 .position
260 .to_point(snapshot.buffer_snapshot());
261 let fold_point = snapshot
262 .suggestion_snapshot
263 .fold_snapshot
264 .to_fold_point(buffer_point, Bias::Left);
265 let suggestion_point = snapshot.suggestion_snapshot.to_suggestion_point(fold_point);
266 let inlay_point = snapshot.to_inlay_point(suggestion_point);
267 inlays.insert((inlay_point, Reverse(inlay.id)), None);
268 }
269 }
270
271 let mut new_transforms = SumTree::new();
272 let mut cursor = snapshot
273 .transforms
274 .cursor::<(InlayPoint, SuggestionPoint)>();
275 for ((inlay_point, inlay_id), inlay) in inlays {
276 new_transforms.push_tree(cursor.slice(&inlay_point, Bias::Right, &()), &());
277 while let Some(transform) = cursor.item() {
278 match transform {
279 Transform::Isomorphic(_) => break,
280 Transform::Inlay(inlay) => {
281 if inlay.id > inlay_id.0 {
282 new_transforms.push(transform.clone(), &());
283 cursor.next(&());
284 } else {
285 if inlay.id == inlay_id.0 {
286 cursor.next(&());
287 }
288 break;
289 }
290 }
291 }
292 }
293
294 if let Some(inlay) = inlay {
295 if let Some(Transform::Isomorphic(transform)) = cursor.item() {
296 let prefix = inlay_point.0 - cursor.start().0 .0;
297 if !prefix.is_zero() {
298 let prefix_suggestion_start = cursor.start().1;
299 let prefix_suggestion_end = SuggestionPoint(cursor.start().1 .0 + prefix);
300 new_transforms.push(
301 Transform::Isomorphic(
302 snapshot.suggestion_snapshot.text_summary_for_range(
303 prefix_suggestion_start..prefix_suggestion_end,
304 ),
305 ),
306 &(),
307 );
308 }
309
310 new_transforms.push(Transform::Inlay(inlay), &());
311
312 let suffix_suggestion_start = SuggestionPoint(cursor.start().1 .0 + prefix);
313 let suffix_suggestion_end = cursor.end(&()).1;
314 new_transforms.push(
315 Transform::Isomorphic(snapshot.suggestion_snapshot.text_summary_for_range(
316 suffix_suggestion_start..suffix_suggestion_end,
317 )),
318 &(),
319 );
320
321 cursor.next(&());
322 } else {
323 new_transforms.push(Transform::Inlay(inlay), &());
324 }
325 }
326 }
327
328 new_transforms.push_tree(cursor.suffix(&()), &());
329 drop(cursor);
330 snapshot.transforms = new_transforms;
331 snapshot.version += 1;
332
333 (snapshot.clone(), Vec::new(), new_ids)
334 }
335}
336
337impl InlaySnapshot {
338 pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot {
339 // TODO kb copied from suggestion_map
340 self.suggestion_snapshot.buffer_snapshot()
341 }
342
343 pub fn to_point(&self, offset: InlayOffset) -> InlayPoint {
344 // TODO kb copied from suggestion_map
345 self.to_inlay_point(
346 self.suggestion_snapshot
347 .to_point(super::suggestion_map::SuggestionOffset(offset.0)),
348 )
349 }
350
351 pub fn max_point(&self) -> InlayPoint {
352 // TODO kb copied from suggestion_map
353 self.to_inlay_point(self.suggestion_snapshot.max_point())
354 }
355
356 pub fn to_offset(&self, point: InlayPoint) -> InlayOffset {
357 // TODO kb copied from suggestion_map
358 InlayOffset(
359 self.suggestion_snapshot
360 .to_offset(self.to_suggestion_point(point, Bias::Left))
361 .0,
362 )
363 }
364
365 pub fn chars_at(&self, start: InlayPoint) -> impl '_ + Iterator<Item = char> {
366 self.suggestion_snapshot
367 .chars_at(self.to_suggestion_point(start, Bias::Left))
368 }
369
370 // TODO kb what to do with bias?
371 pub fn to_suggestion_point(&self, point: InlayPoint, _: Bias) -> SuggestionPoint {
372 SuggestionPoint(point.0)
373 }
374
375 pub fn to_inlay_point(&self, point: SuggestionPoint) -> InlayPoint {
376 InlayPoint(point.0)
377 }
378
379 pub fn clip_point(&self, point: InlayPoint, bias: Bias) -> InlayPoint {
380 // TODO kb copied from suggestion_map
381 self.to_inlay_point(
382 self.suggestion_snapshot
383 .clip_point(self.to_suggestion_point(point, bias), bias),
384 )
385 }
386
387 pub fn text_summary_for_range(&self, range: Range<InlayPoint>) -> TextSummary {
388 // TODO kb copied from suggestion_map
389 self.suggestion_snapshot.text_summary_for_range(
390 self.to_suggestion_point(range.start, Bias::Left)
391 ..self.to_suggestion_point(range.end, Bias::Left),
392 )
393 }
394
395 pub fn buffer_rows<'a>(&'a self, row: u32) -> InlayBufferRows<'a> {
396 InlayBufferRows {
397 suggestion_rows: self.suggestion_snapshot.buffer_rows(row),
398 }
399 }
400
401 pub fn line_len(&self, row: u32) -> u32 {
402 // TODO kb copied from suggestion_map
403 self.suggestion_snapshot.line_len(row)
404 }
405
406 pub fn chunks<'a>(
407 &'a self,
408 range: Range<InlayOffset>,
409 language_aware: bool,
410 text_highlights: Option<&'a TextHighlights>,
411 suggestion_highlight: Option<HighlightStyle>,
412 ) -> InlayChunks<'a> {
413 // TODO kb copied from suggestion_map
414 InlayChunks {
415 suggestion_chunks: self.suggestion_snapshot.chunks(
416 SuggestionOffset(range.start.0)..SuggestionOffset(range.end.0),
417 language_aware,
418 text_highlights,
419 suggestion_highlight,
420 ),
421 }
422 }
423
424 #[cfg(test)]
425 pub fn text(&self) -> String {
426 // TODO kb copied from suggestion_map
427 self.suggestion_snapshot.text()
428 }
429}
430
431#[cfg(test)]
432mod tests {
433 use super::*;
434 use crate::{
435 display_map::{fold_map::FoldMap, suggestion_map::SuggestionMap},
436 MultiBuffer,
437 };
438 use gpui::AppContext;
439
440 #[gpui::test]
441 fn test_basic_inlays(cx: &mut AppContext) {
442 let buffer = MultiBuffer::build_simple("abcdefghi", cx);
443 let buffer_edits = buffer.update(cx, |buffer, _| buffer.subscribe());
444 let (mut fold_map, fold_snapshot) = FoldMap::new(buffer.read(cx).snapshot(cx));
445 let (suggestion_map, suggestion_snapshot) = SuggestionMap::new(fold_snapshot.clone());
446 let (mut inlay_map, inlay_snapshot) = InlayMap::new(suggestion_snapshot.clone());
447 assert_eq!(inlay_snapshot.text(), "abcdefghi");
448
449 let (inlay_snapshot, _, inlay_ids) = inlay_map.splice(
450 HashSet::default(),
451 vec![(
452 InlayHintLocation {
453 buffer_id: 0,
454 excerpt_id: ExcerptId::default(),
455 },
456 InlayProperties {
457 position: buffer.read(cx).read(cx).anchor_before(3),
458 text: "|123|".into(),
459 },
460 )],
461 );
462 assert_eq!(inlay_snapshot.text(), "abc|123|defghi");
463
464 buffer.update(cx, |buffer, cx| buffer.edit([(0..0, "XYZ")], None, cx));
465 let (fold_snapshot, fold_edits) = fold_map.read(
466 buffer.read(cx).snapshot(cx),
467 buffer_edits.consume().into_inner(),
468 );
469 let (suggestion_snapshot, suggestion_edits) =
470 suggestion_map.sync(fold_snapshot.clone(), fold_edits);
471 let (inlay_snapshot, _) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits);
472 assert_eq!(inlay_snapshot.text(), "XYZabc|123|defghi");
473
474 //////// case: folding and unfolding the text should hine and then return the hint back
475 let (mut fold_map_writer, _, _) = fold_map.write(
476 buffer.read(cx).snapshot(cx),
477 buffer_edits.consume().into_inner(),
478 );
479 let (fold_snapshot, fold_edits) = fold_map_writer.fold([4..8]);
480 let (suggestion_snapshot, suggestion_edits) =
481 suggestion_map.sync(fold_snapshot.clone(), fold_edits);
482 let (inlay_snapshot, _) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits);
483 assert_eq!(inlay_snapshot.text(), "XYZa⋯fghi");
484
485 let (fold_snapshot, fold_edits) = fold_map_writer.unfold([4..8], false);
486 let (suggestion_snapshot, suggestion_edits) =
487 suggestion_map.sync(fold_snapshot.clone(), fold_edits);
488 let (inlay_snapshot, _) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits);
489 assert_eq!(inlay_snapshot.text(), "XYZabc|123|defghi");
490
491 ////////// case: replacing the anchor that got the hint: it should disappear, then undo and it should reappear again
492 buffer.update(cx, |buffer, cx| buffer.edit([(2..3, "C")], None, cx));
493 let (fold_snapshot, fold_edits) = fold_map.read(
494 buffer.read(cx).snapshot(cx),
495 buffer_edits.consume().into_inner(),
496 );
497 let (suggestion_snapshot, suggestion_edits) =
498 suggestion_map.sync(fold_snapshot.clone(), fold_edits);
499 let (inlay_snapshot, _) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits);
500 assert_eq!(inlay_snapshot.text(), "XYZabCdefghi");
501
502 buffer.update(cx, |buffer, cx| buffer.undo(cx));
503 let (fold_snapshot, fold_edits) = fold_map.read(
504 buffer.read(cx).snapshot(cx),
505 buffer_edits.consume().into_inner(),
506 );
507 let (suggestion_snapshot, suggestion_edits) =
508 suggestion_map.sync(fold_snapshot.clone(), fold_edits);
509 let (inlay_snapshot, _) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits);
510 assert_eq!(inlay_snapshot.text(), "XYZabc|123|defghi");
511 }
512}