Skip to repository content2089 lines · 75.3 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T01:33:03.100Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
bracket_colorization.rs
1//! Bracket highlights, also known as "rainbow brackets".
2//! Uses tree-sitter queries from brackets.scm to capture bracket pairs,
3//! and theme accents to colorize those.
4
5use std::cmp::Ordering;
6use std::ops::Range;
7use std::sync::Arc;
8
9use crate::{Editor, HighlightKey};
10use collections::{HashMap, HashSet};
11use gpui::{AppContext as _, Context, HighlightStyle, Hsla};
12use language::{BufferRow, BufferSnapshot, language_settings::LanguageSettings};
13use multi_buffer::{Anchor, BufferOffset, ExcerptRange, MultiBufferSnapshot};
14use text::OffsetRangeExt as _;
15use theme::{Appearance, Oklab, Oklch, hsla_to_oklab, hsla_to_oklch, oklch_to_hsla};
16use ui::utils::apca_contrast;
17
18impl Editor {
19 pub(crate) fn colorize_brackets(&mut self, invalidate: bool, cx: &mut Context<Editor>) {
20 if !self.mode.is_full() {
21 return;
22 }
23
24 if invalidate {
25 self.bracket_fetched_tree_sitter_chunks.clear();
26 }
27
28 let Some(accent_data) = self.accent_data.as_ref() else {
29 return;
30 };
31 let accents = accent_data.colors.0.clone();
32 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
33
34 let visible_excerpts = self.visible_buffer_ranges(cx);
35 let excerpt_data: Vec<(
36 BufferSnapshot,
37 Range<BufferOffset>,
38 ExcerptRange<text::Anchor>,
39 )> = visible_excerpts
40 .into_iter()
41 .filter(|(buffer_snapshot, _, _)| {
42 let Some(buffer) = self.buffer().read(cx).buffer(buffer_snapshot.remote_id())
43 else {
44 return false;
45 };
46 let buffer = buffer.read(cx);
47 buffer
48 .language()
49 .is_some_and(|language| language.grammar().is_some())
50 && LanguageSettings::for_buffer(buffer, cx).colorize_brackets
51 })
52 .collect();
53
54 if !invalidate && (accents.is_empty() || excerpt_data.is_empty()) {
55 return;
56 }
57
58 let mut fetched_tree_sitter_chunks = excerpt_data
59 .iter()
60 .filter_map(|(_, _, excerpt_range)| {
61 let key = excerpt_range.context.clone();
62 Some((
63 key.clone(),
64 self.bracket_fetched_tree_sitter_chunks.get(&key).cloned()?,
65 ))
66 })
67 .collect::<HashMap<Range<text::Anchor>, HashSet<Range<BufferRow>>>>();
68
69 let accents_count = accents.len();
70 let bracket_matches_by_accent = cx.background_spawn(async move {
71 if accents_count == 0 {
72 return (HashMap::default(), fetched_tree_sitter_chunks);
73 }
74
75 let bracket_matches_by_accent: HashMap<usize, Vec<Range<Anchor>>> =
76 excerpt_data.into_iter().fold(
77 HashMap::default(),
78 |mut acc, (buffer_snapshot, buffer_range, excerpt_range)| {
79 let fetched_chunks = fetched_tree_sitter_chunks
80 .entry(excerpt_range.context.clone())
81 .or_default();
82
83 let brackets_by_accent = compute_bracket_ranges(
84 &multi_buffer_snapshot,
85 &buffer_snapshot,
86 buffer_range,
87 excerpt_range,
88 fetched_chunks,
89 accents_count,
90 );
91
92 for (accent_number, new_ranges) in brackets_by_accent {
93 let ranges = acc
94 .entry(accent_number)
95 .or_insert_with(Vec::<Range<Anchor>>::new);
96
97 for new_range in new_ranges {
98 let i = ranges
99 .binary_search_by(|probe| {
100 probe.start.cmp(&new_range.start, &multi_buffer_snapshot)
101 })
102 .unwrap_or_else(|i| i);
103 ranges.insert(i, new_range);
104 }
105 }
106
107 acc
108 },
109 );
110
111 (bracket_matches_by_accent, fetched_tree_sitter_chunks)
112 });
113
114 self.colorize_brackets_task = cx.spawn(async move |editor, cx| {
115 if invalidate {
116 editor
117 .update(cx, |editor, cx| {
118 editor.clear_highlights_with(
119 &mut |key| matches!(key, HighlightKey::ColorizeBracket(_)),
120 cx,
121 );
122 })
123 .ok();
124 }
125
126 let (bracket_matches_by_accent, updated_chunks) = bracket_matches_by_accent.await;
127
128 editor
129 .update(cx, |editor, cx| {
130 editor
131 .bracket_fetched_tree_sitter_chunks
132 .extend(updated_chunks);
133 for (accent_number, bracket_highlights) in bracket_matches_by_accent {
134 let Some(&bracket_color) = accents.get(accent_number) else {
135 continue;
136 };
137 let style = HighlightStyle {
138 color: Some(bracket_color),
139 ..HighlightStyle::default()
140 };
141
142 editor.highlight_text_key(
143 HighlightKey::ColorizeBracket(accent_number),
144 bracket_highlights,
145 style,
146 true,
147 cx,
148 );
149 }
150 })
151 .ok();
152 });
153 }
154}
155
156const BACKGROUND_APCA_LIGHT: f32 = 35.0;
157const BACKGROUND_APCA_DARK: f32 = 30.0;
158const ADJACENT_OKLAB_LIGHT: f32 = 0.10;
159const ADJACENT_OKLAB_DARK: f32 = 0.08;
160const ADJACENT_OKLAB_LIGHT_INTERVENTION: f32 = 0.095;
161const ADJACENT_OKLAB_DARK_INTERVENTION: f32 = 0.08;
162const LIGHTNESS_CLAMP_MIN: f32 = 0.18;
163const LIGHTNESS_CLAMP_MAX: f32 = 0.92;
164
165pub(crate) fn bracket_colorization_accents(
166 accents: &[Hsla],
167 appearance: Appearance,
168 background: Hsla,
169) -> Arc<[Hsla]> {
170 let (intervention_distance, comfortable_distance, min_background_contrast) = match appearance {
171 Appearance::Light => (
172 ADJACENT_OKLAB_LIGHT_INTERVENTION,
173 ADJACENT_OKLAB_LIGHT,
174 BACKGROUND_APCA_LIGHT,
175 ),
176 Appearance::Dark => (
177 ADJACENT_OKLAB_DARK_INTERVENTION,
178 ADJACENT_OKLAB_DARK,
179 BACKGROUND_APCA_DARK,
180 ),
181 };
182 let background_adjusted = accents
183 .iter()
184 .copied()
185 .map(|accent| adjust_color_for_background(accent, background, min_background_contrast))
186 .collect::<Vec<_>>();
187 let adjusted_min_adj = min_adjacent_oklab_distance(&background_adjusted, background);
188
189 if accents.len() < 3 || adjusted_min_adj >= intervention_distance {
190 return Arc::from(background_adjusted);
191 }
192
193 let reordered = maximize_adjacent_separation(&background_adjusted, background);
194 if min_adjacent_oklab_distance(&reordered, background) >= comfortable_distance {
195 Arc::from(reordered)
196 } else {
197 Arc::from(background_adjusted)
198 }
199}
200
201fn maximize_adjacent_separation(accents: &[Hsla], background: Hsla) -> Vec<Hsla> {
202 let Some((&first, rest)) = accents.split_first() else {
203 return Vec::new();
204 };
205 let mut remaining = rest.to_vec();
206 let mut order = Vec::with_capacity(accents.len());
207 order.push(first);
208 let mut last = first;
209
210 while !remaining.is_empty() {
211 let Some((position, &next)) =
212 remaining
213 .iter()
214 .enumerate()
215 .max_by(|&(_, &left), &(_, &right)| {
216 compare_candidates(background, last, first, left, right)
217 })
218 else {
219 break;
220 };
221 remaining.swap_remove(position);
222 order.push(next);
223 last = next;
224 }
225
226 order
227}
228
229fn compare_candidates(
230 background: Hsla,
231 last: Hsla,
232 first: Hsla,
233 left: Hsla,
234 right: Hsla,
235) -> Ordering {
236 adjacent_distance(last, left, background)
237 .partial_cmp(&adjacent_distance(last, right, background))
238 .unwrap_or(Ordering::Equal)
239 .then_with(|| {
240 adjacent_distance(first, left, background)
241 .partial_cmp(&adjacent_distance(first, right, background))
242 .unwrap_or(Ordering::Equal)
243 })
244}
245
246fn min_adjacent_oklab_distance(accents: &[Hsla], background: Hsla) -> f32 {
247 if accents.len() < 2 {
248 return f32::MAX;
249 }
250 accents
251 .iter()
252 .copied()
253 .zip(accents.iter().copied().cycle().skip(1))
254 .take(accents.len())
255 .map(|(left, right)| adjacent_distance(left, right, background))
256 .fold(f32::MAX, f32::min)
257}
258
259fn oklab_distance(left: Oklab, right: Oklab) -> f32 {
260 let dl = left.l - right.l;
261 let da = left.a - right.a;
262 let db = left.b - right.b;
263 (dl * dl + da * da + db * db).sqrt()
264}
265
266fn adjacent_distance(left: Hsla, right: Hsla, background: Hsla) -> f32 {
267 oklab_distance(
268 hsla_to_oklab(background.blend(left)),
269 hsla_to_oklab(background.blend(right)),
270 )
271}
272
273fn adjust_color_for_background(
274 color: Hsla,
275 background: Hsla,
276 minimum_background_contrast: f32,
277) -> Hsla {
278 if background_contrast(color, background) >= minimum_background_contrast {
279 return color;
280 }
281
282 let original = hsla_to_oklab(color);
283 let darker_candidate = adjusted_lightness_candidate(
284 color,
285 background,
286 minimum_background_contrast,
287 LIGHTNESS_CLAMP_MIN,
288 );
289 let lighter_candidate = adjusted_lightness_candidate(
290 color,
291 background,
292 minimum_background_contrast,
293 LIGHTNESS_CLAMP_MAX,
294 );
295
296 match (darker_candidate, lighter_candidate) {
297 (Some(darker_candidate), Some(lighter_candidate)) => {
298 let darker_distance = oklab_distance(original, hsla_to_oklab(darker_candidate));
299 let lighter_distance = oklab_distance(original, hsla_to_oklab(lighter_candidate));
300 if darker_distance <= lighter_distance {
301 darker_candidate
302 } else {
303 lighter_candidate
304 }
305 }
306 (Some(darker_candidate), None) => darker_candidate,
307 (None, Some(lighter_candidate)) => lighter_candidate,
308 (None, None) => color,
309 }
310}
311
312fn adjusted_lightness_candidate(
313 color: Hsla,
314 background: Hsla,
315 minimum_background_contrast: f32,
316 target_lightness: f32,
317) -> Option<Hsla> {
318 let original = hsla_to_oklch(color);
319 let lightness_delta = target_lightness - original.l;
320
321 if lightness_delta.abs() <= f32::EPSILON {
322 return None;
323 }
324
325 (1..=128).find_map(|step| {
326 let amount = step as f32 / 128.0;
327 let candidate = oklch_to_hsla(
328 Oklch {
329 l: (original.l + lightness_delta * amount).clamp(0.0, 1.0),
330 chroma: original.chroma,
331 hue: original.hue,
332 },
333 color.a,
334 );
335 (background_contrast(candidate, background) >= minimum_background_contrast)
336 .then_some(candidate)
337 })
338}
339
340fn background_contrast(foreground: Hsla, background: Hsla) -> f32 {
341 apca_contrast(background.blend(foreground), background).abs()
342}
343
344fn compute_bracket_ranges(
345 multi_buffer_snapshot: &MultiBufferSnapshot,
346 buffer_snapshot: &BufferSnapshot,
347 buffer_range: Range<BufferOffset>,
348 excerpt_range: ExcerptRange<text::Anchor>,
349 fetched_chunks: &mut HashSet<Range<BufferRow>>,
350 accents_count: usize,
351) -> Vec<(usize, Vec<Range<Anchor>>)> {
352 let context = excerpt_range.context.to_offset(buffer_snapshot);
353
354 buffer_snapshot
355 .fetch_bracket_ranges(
356 buffer_range.start.0..buffer_range.end.0,
357 Some(fetched_chunks),
358 )
359 .into_iter()
360 .flat_map(|(chunk_range, pairs)| {
361 if fetched_chunks.insert(chunk_range) {
362 pairs
363 } else {
364 Vec::new()
365 }
366 })
367 .filter_map(|pair| {
368 let color_index = pair.color_index?;
369
370 let mut ranges = Vec::new();
371
372 if context.start <= pair.open_range.start && pair.open_range.end <= context.end {
373 let anchors = buffer_snapshot.anchor_range_inside(pair.open_range);
374 ranges.push(
375 multi_buffer_snapshot.anchor_in_buffer(anchors.start)?
376 ..multi_buffer_snapshot.anchor_in_buffer(anchors.end)?,
377 );
378 };
379
380 if context.start <= pair.close_range.start && pair.close_range.end <= context.end {
381 let anchors = buffer_snapshot.anchor_range_inside(pair.close_range);
382 ranges.push(
383 multi_buffer_snapshot.anchor_in_buffer(anchors.start)?
384 ..multi_buffer_snapshot.anchor_in_buffer(anchors.end)?,
385 );
386 };
387
388 Some((color_index % accents_count, ranges))
389 })
390 .collect()
391}
392
393#[cfg(test)]
394mod tests {
395 use std::{cmp, sync::Arc, time::Duration};
396
397 use super::*;
398 use crate::{
399 DisplayPoint, EditorMode, EditorSnapshot, MoveToBeginning, MoveToEnd, MoveUp,
400 display_map::{DisplayRow, ToDisplayPoint},
401 editor_tests::init_test,
402 test::{
403 editor_lsp_test_context::EditorLspTestContext, editor_test_context::EditorTestContext,
404 },
405 };
406 use collections::HashSet;
407 use fs::FakeFs;
408 use gpui::{Rgba, UpdateGlobal as _, hsla};
409 use indoc::indoc;
410 use itertools::Itertools;
411 use language::{Capability, markdown_lang};
412 use languages::rust_lang;
413 use multi_buffer::{MultiBuffer, PathKey};
414 use pretty_assertions::assert_eq;
415 use project::Project;
416 use rope::Point;
417 use serde_json::json;
418 use settings::{AccentContent, SettingsStore};
419 use text::{Bias, OffsetRangeExt, ToOffset};
420 use theme::Appearance;
421 use theme_settings::ThemeStyleContent;
422 use ui::ActiveTheme;
423
424 use util::{path, post_inc};
425
426 fn light_editor_background() -> Hsla {
427 hsla(0.0, 0.0, 0.98, 1.0)
428 }
429
430 fn dark_editor_background() -> Hsla {
431 hsla(0.0, 0.0, 0.12, 1.0)
432 }
433
434 #[test]
435 fn test_auto_bracket_colorization_mode_reorders_weak_palette() {
436 let accents = vec![
437 hsla(0.0, 1.0, 0.68, 1.0),
438 hsla(0.02, 1.0, 0.68, 1.0),
439 hsla(0.34, 1.0, 0.68, 1.0),
440 hsla(0.36, 1.0, 0.68, 1.0),
441 ];
442
443 let original_min_adj = min_adjacent_oklab_distance(&accents, dark_editor_background());
444 let reordered = maximize_adjacent_separation(&accents, dark_editor_background());
445 let reordered_min_adj = min_adjacent_oklab_distance(&reordered, dark_editor_background());
446
447 assert_ne!(reordered.as_slice(), accents.as_slice());
448 assert!(reordered_min_adj > original_min_adj);
449 }
450
451 #[test]
452 fn test_preserves_strong_palette() {
453 let accents = vec![
454 hsla(0.0, 1.0, 0.78, 1.0),
455 hsla(0.16, 1.0, 0.78, 1.0),
456 hsla(0.33, 1.0, 0.78, 1.0),
457 hsla(0.66, 1.0, 0.78, 1.0),
458 ];
459
460 let palette =
461 bracket_colorization_accents(&accents, Appearance::Dark, dark_editor_background());
462
463 assert_eq!(palette.as_ref(), accents.as_slice());
464 }
465
466 #[test]
467 fn test_adjusts_background_failures_preserving_hue_and_chroma() {
468 let accents = vec![
469 hsla(0.58, 1.0, 0.28, 1.0),
470 hsla(0.12, 1.0, 0.28, 1.0),
471 hsla(0.22, 0.9, 0.76, 1.0),
472 ];
473
474 let palette =
475 bracket_colorization_accents(&accents, Appearance::Light, light_editor_background());
476 let original = hsla_to_oklch(accents[2]);
477 let adjusted = hsla_to_oklch(palette[2]);
478
479 assert_ne!(palette.as_ref(), accents.as_slice());
480 assert_eq!(palette.len(), accents.len());
481 assert_eq!(palette[0], accents[0]);
482 assert_eq!(palette[1], accents[1]);
483 assert_ne!(palette[2], accents[2]);
484 assert!((original.chroma - adjusted.chroma).abs() < 0.0001);
485 assert!((original.hue - adjusted.hue).abs() < 0.001);
486 assert_ne!(original.l, adjusted.l);
487 assert!(
488 background_contrast(palette[2], light_editor_background()) >= BACKGROUND_APCA_LIGHT
489 );
490 }
491
492 #[test]
493 fn test_preserves_light_near_miss_palette() {
494 let accents = vec![
495 Hsla::from(Rgba::try_from("#CC241D").expect("valid color")),
496 Hsla::from(Rgba::try_from("#98971A").expect("valid color")),
497 Hsla::from(Rgba::try_from("#D79921").expect("valid color")),
498 Hsla::from(Rgba::try_from("#458588").expect("valid color")),
499 Hsla::from(Rgba::try_from("#B16286").expect("valid color")),
500 Hsla::from(Rgba::try_from("#689D6A").expect("valid color")),
501 Hsla::from(Rgba::try_from("#D65D0E").expect("valid color")),
502 ];
503 let background = Hsla::from(Rgba::try_from("#FBF1C7").expect("valid color"));
504
505 let palette = bracket_colorization_accents(&accents, Appearance::Light, background);
506 let original_min_adj = min_adjacent_oklab_distance(&accents, background);
507
508 assert_eq!(palette.as_ref(), accents.as_slice());
509 assert!(original_min_adj < ADJACENT_OKLAB_LIGHT);
510 assert!(original_min_adj >= ADJACENT_OKLAB_LIGHT_INTERVENTION);
511 }
512
513 #[test]
514 fn test_adjust_color_for_background_prefers_closest_passing_candidate() {
515 // Verify that when both darker and lighter candidates exist,
516 // we pick the one with minimum OKLab distance from the original.
517 let background = hsla(0.0, 0.0, 0.50, 1.0);
518 let min_contrast = 30.0;
519 let color = hsla(0.33, 0.7, 0.46, 1.0);
520
521 assert!(
522 background_contrast(color, background) < min_contrast,
523 "test color must fail contrast check; got {}",
524 background_contrast(color, background)
525 );
526
527 let original = hsla_to_oklab(color);
528 let darker =
529 adjusted_lightness_candidate(color, background, min_contrast, LIGHTNESS_CLAMP_MIN)
530 .expect("fixture must produce a darker passing candidate");
531 let lighter =
532 adjusted_lightness_candidate(color, background, min_contrast, LIGHTNESS_CLAMP_MAX)
533 .expect("fixture must produce a lighter passing candidate");
534
535 let darker_dist = oklab_distance(original, hsla_to_oklab(darker));
536 let lighter_dist = oklab_distance(original, hsla_to_oklab(lighter));
537 let expected = if darker_dist <= lighter_dist {
538 darker
539 } else {
540 lighter
541 };
542 assert_eq!(
543 adjust_color_for_background(color, background, min_contrast),
544 expected
545 );
546 }
547
548 #[test]
549 fn test_background_adjustment_edge_cases() {
550 let color = hsla(0.22, 0.9, 0.76, 1.0);
551 let original_contrast = background_contrast(color, light_editor_background());
552 assert!(original_contrast < 20.0);
553 let palette =
554 bracket_colorization_accents(&[color], Appearance::Light, light_editor_background());
555 assert_ne!(palette.as_ref(), &[color][..]);
556 assert!(
557 background_contrast(palette[0], light_editor_background()) >= BACKGROUND_APCA_LIGHT
558 );
559
560 let impossible_color = hsla(0.58, 1.0, 0.47, 1.0);
561 let impossible_bg = hsla(0.0, 0.0, 0.50, 1.0);
562 assert_eq!(
563 adjust_color_for_background(impossible_color, impossible_bg, 200.0),
564 impossible_color
565 );
566 }
567
568 #[gpui::test]
569 async fn test_basic_bracket_colorization(cx: &mut gpui::TestAppContext) {
570 init_test(cx, |language_settings| {
571 language_settings.defaults.colorize_brackets = Some(true);
572 });
573 let mut cx = EditorLspTestContext::new(
574 Arc::into_inner(rust_lang()).unwrap(),
575 lsp::ServerCapabilities::default(),
576 cx,
577 )
578 .await;
579
580 cx.set_state(indoc! {r#"ˇuse std::{collections::HashMap, future::Future};
581
582fn main() {
583 let a = one((), { () }, ());
584 println!("{a}");
585 println!("{a}");
586 for i in 0..a {
587 println!("{i}");
588 }
589
590 let b = {
591 {
592 {
593 [([([([([([([([([([((), ())])])])])])])])])])]
594 }
595 }
596 };
597}
598
599#[rustfmt::skip]
600fn one(a: (), (): (), c: ()) -> usize { 1 }
601
602fn two<T>(a: HashMap<String, Vec<Option<T>>>) -> usize
603where
604 T: Future<Output = HashMap<String, Vec<Option<Box<()>>>>>,
605{
606 2
607}
608"#});
609 cx.executor().advance_clock(Duration::from_millis(100));
610 cx.executor().run_until_parked();
611
612 assert_eq!(
613 r#"use std::«1{collections::HashMap, future::Future}1»;
614
615fn main«1()1» «1{
616 let a = one«2(«3()3», «3{ «4()4» }3», «3()3»)2»;
617 println!«2("{a}")2»;
618 println!«2("{a}")2»;
619 for i in 0..a «2{
620 println!«3("{i}")3»;
621 }2»
622
623 let b = «2{
624 «3{
625 «4{
626 «5[«6(«7[«1(«2[«3(«4[«5(«6[«7(«1[«2(«3[«4(«5[«6(«7[«1(«2[«3(«4()4», «4()4»)3»]2»)1»]7»)6»]5»)4»]3»)2»]1»)7»]6»)5»]4»)3»]2»)1»]7»)6»]5»
627 }4»
628 }3»
629 }2»;
630}1»
631
632#«1[rustfmt::skip]1»
633fn one«1(a: «2()2», «2()2»: «2()2», c: «2()2»)1» -> usize «1{ 1 }1»
634
635fn two«1<T>1»«1(a: HashMap«2<String, Vec«3<Option«4<T>4»>3»>2»)1» -> usize
636where
637 T: Future«1<Output = HashMap«2<String, Vec«3<Option«4<Box«5<«6()6»>5»>4»>3»>2»>1»,
638«1{
639 2
640}1»
641
6421 hsla(207.80, 81.00%, 66.00%, 1.00)
6432 hsla(29.00, 54.00%, 61.00%, 1.00)
6443 hsla(286.00, 51.00%, 64.00%, 1.00)
6454 hsla(187.00, 47.00%, 55.00%, 1.00)
6465 hsla(355.00, 65.00%, 65.00%, 1.00)
6476 hsla(95.00, 38.00%, 62.00%, 1.00)
6487 hsla(39.00, 67.00%, 69.00%, 1.00)
649"#,
650 &bracket_colors_markup(&mut cx),
651 "All brackets should be colored based on their depth"
652 );
653 }
654
655 #[gpui::test]
656 async fn test_file_less_file_colorization(cx: &mut gpui::TestAppContext) {
657 init_test(cx, |language_settings| {
658 language_settings.defaults.colorize_brackets = Some(true);
659 });
660 let editor = cx.add_window(|window, cx| {
661 let multi_buffer = MultiBuffer::build_simple("fn main() {}", cx);
662 multi_buffer.update(cx, |multi_buffer, cx| {
663 multi_buffer
664 .as_singleton()
665 .unwrap()
666 .update(cx, |buffer, cx| {
667 buffer.set_language(Some(rust_lang()), cx);
668 });
669 });
670 Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
671 });
672
673 cx.executor().advance_clock(Duration::from_millis(100));
674 cx.executor().run_until_parked();
675
676 assert_eq!(
677 "fn main«1()1» «1{}1»
6781 hsla(207.80, 81.00%, 66.00%, 1.00)
679",
680 editor
681 .update(cx, |editor, window, cx| {
682 editor_bracket_colors_markup(&editor.snapshot(window, cx))
683 })
684 .unwrap(),
685 "File-less buffer should still have its brackets colorized"
686 );
687 }
688
689 #[gpui::test]
690 async fn test_markdown_bracket_colorization(cx: &mut gpui::TestAppContext) {
691 init_test(cx, |language_settings| {
692 language_settings.defaults.colorize_brackets = Some(true);
693 });
694 let mut cx = EditorLspTestContext::new(
695 Arc::into_inner(markdown_lang()).unwrap(),
696 lsp::ServerCapabilities::default(),
697 cx,
698 )
699 .await;
700
701 cx.set_state(indoc! {r#"ˇ[LLM-powered features](./ai/overview.md), [bring and configure your own API keys](./ai/llm-providers.md#use-your-own-keys)"#});
702 cx.executor().advance_clock(Duration::from_millis(100));
703 cx.executor().run_until_parked();
704
705 assert_eq!(
706 r#"«1[LLM-powered features]1»«1(./ai/overview.md)1», «1[bring and configure your own API keys]1»«1(./ai/llm-providers.md#use-your-own-keys)1»
7071 hsla(207.80, 81.00%, 66.00%, 1.00)
708"#,
709 &bracket_colors_markup(&mut cx),
710 "All markdown brackets should be colored based on their depth"
711 );
712
713 cx.set_state(indoc! {r#"ˇ{{}}"#});
714 cx.executor().advance_clock(Duration::from_millis(100));
715 cx.executor().run_until_parked();
716
717 assert_eq!(
718 r#"«1{«2{}2»}1»
7191 hsla(207.80, 81.00%, 66.00%, 1.00)
7202 hsla(29.00, 54.00%, 61.00%, 1.00)
721"#,
722 &bracket_colors_markup(&mut cx),
723 "All markdown brackets should be colored based on their depth, again"
724 );
725
726 cx.set_state(indoc! {r#"ˇ('')('')
727
728((''))('')
729
730('')((''))"#});
731 cx.executor().advance_clock(Duration::from_millis(100));
732 cx.executor().run_until_parked();
733
734 assert_eq!(
735 "«1('')1»«1('')1»\n\n«1(«2('')2»)1»«1('')1»\n\n«1('')1»«1(«2('')2»)1»\n1 hsla(207.80, 81.00%, 66.00%, 1.00)\n2 hsla(29.00, 54.00%, 61.00%, 1.00)\n",
736 &bracket_colors_markup(&mut cx),
737 "Markdown quote pairs should not interfere with parenthesis pairing"
738 );
739 }
740
741 #[gpui::test]
742 async fn test_markdown_brackets_in_multiple_hunks(cx: &mut gpui::TestAppContext) {
743 init_test(cx, |language_settings| {
744 language_settings.defaults.colorize_brackets = Some(true);
745 });
746 let mut cx = EditorLspTestContext::new(
747 Arc::into_inner(markdown_lang()).unwrap(),
748 lsp::ServerCapabilities::default(),
749 cx,
750 )
751 .await;
752
753 let rows = 100;
754 let footer = "1 hsla(207.80, 81.00%, 66.00%, 1.00)\n";
755
756 let simple_brackets = (0..rows).map(|_| "ˇ[]\n").collect::<String>();
757 let simple_brackets_highlights = (0..rows).map(|_| "«1[]1»\n").collect::<String>();
758 cx.set_state(&simple_brackets);
759 cx.update_editor(|editor, window, cx| {
760 editor.move_to_end(&MoveToEnd, window, cx);
761 });
762 cx.executor().advance_clock(Duration::from_millis(100));
763 cx.executor().run_until_parked();
764 assert_eq!(
765 format!("{simple_brackets_highlights}\n{footer}"),
766 bracket_colors_markup(&mut cx),
767 "Simple bracket pairs should be colored"
768 );
769
770 let paired_brackets = (0..rows).map(|_| "ˇ[]()\n").collect::<String>();
771 let paired_brackets_highlights = (0..rows).map(|_| "«1[]1»«1()1»\n").collect::<String>();
772 cx.set_state(&paired_brackets);
773 // Wait for reparse to complete after content change
774 cx.executor().advance_clock(Duration::from_millis(100));
775 cx.executor().run_until_parked();
776 cx.update_editor(|editor, _, cx| {
777 // Force invalidation of bracket cache after reparse
778 editor.colorize_brackets(true, cx);
779 });
780 // Scroll to beginning to fetch first chunks
781 cx.update_editor(|editor, window, cx| {
782 editor.move_to_beginning(&MoveToBeginning, window, cx);
783 });
784 cx.executor().advance_clock(Duration::from_millis(100));
785 cx.executor().run_until_parked();
786 // Scroll to end to fetch remaining chunks
787 cx.update_editor(|editor, window, cx| {
788 editor.move_to_end(&MoveToEnd, window, cx);
789 });
790 cx.executor().advance_clock(Duration::from_millis(100));
791 cx.executor().run_until_parked();
792 assert_eq!(
793 format!("{paired_brackets_highlights}\n{footer}"),
794 bracket_colors_markup(&mut cx),
795 "Paired bracket pairs should be colored"
796 );
797 }
798
799 #[gpui::test]
800 async fn test_markdown_code_block_brackets_across_chunks(cx: &mut gpui::TestAppContext) {
801 init_test(cx, |language_settings| {
802 language_settings.defaults.colorize_brackets = Some(true);
803 });
804
805 let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
806 language_registry.add(markdown_lang());
807 language_registry.add(rust_lang());
808
809 let mut cx = EditorTestContext::new(cx).await;
810 cx.update_buffer(|buffer, cx| {
811 buffer.set_language_registry(language_registry.clone());
812 buffer.set_language(Some(markdown_lang()), cx);
813 });
814
815 // The code block is longer than a single tree-sitter data chunk (50 rows),
816 // so the outer brackets open in the first chunk and close in the second one.
817 let filler = (0..58).map(|_| " \"one\",\n").collect::<String>();
818 let source = format!("ˇ```rs\nfn main() {{\n let a = vec![\n{filler} ];\n}}\n```\n");
819 cx.set_state(&source);
820 cx.update_editor(|editor, window, cx| {
821 editor.move_to_end(&MoveToEnd, window, cx);
822 });
823 cx.executor().advance_clock(Duration::from_millis(100));
824 cx.executor().run_until_parked();
825 cx.update_editor(|editor, window, cx| {
826 editor.move_to_beginning(&MoveToBeginning, window, cx);
827 });
828 cx.executor().advance_clock(Duration::from_millis(100));
829 cx.executor().run_until_parked();
830
831 let expected = format!(
832 "```rs\nfn main«1()1» «1{{\n let a = vec!«2[\n{filler} ]2»;\n}}1»\n```\n\n1 hsla(207.80, 81.00%, 66.00%, 1.00)\n2 hsla(29.00, 54.00%, 61.00%, 1.00)\n"
833 );
834 assert_eq!(
835 expected,
836 bracket_colors_markup(&mut cx),
837 "Brackets crossing the 50-row chunk boundary inside a markdown code block should keep consistent colors"
838 );
839 }
840
841 #[gpui::test]
842 async fn test_bracket_colorization_after_language_swap(cx: &mut gpui::TestAppContext) {
843 init_test(cx, |language_settings| {
844 language_settings.defaults.colorize_brackets = Some(true);
845 });
846
847 let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
848 language_registry.add(markdown_lang());
849 language_registry.add(rust_lang());
850
851 let mut cx = EditorTestContext::new(cx).await;
852 cx.update_buffer(|buffer, cx| {
853 buffer.set_language_registry(language_registry.clone());
854 buffer.set_language(Some(markdown_lang()), cx);
855 });
856
857 cx.set_state(indoc! {r#"
858 fn main() {
859 let v: Vec<Stringˇ> = vec![];
860 }
861 "#});
862 cx.executor().advance_clock(Duration::from_millis(100));
863 cx.executor().run_until_parked();
864
865 assert_eq!(
866 r#"fn main«1()1» «1{
867 let v: Vec<String> = vec!«2[]2»;
868}1»
869
8701 hsla(207.80, 81.00%, 66.00%, 1.00)
8712 hsla(29.00, 54.00%, 61.00%, 1.00)
872"#,
873 &bracket_colors_markup(&mut cx),
874 "Markdown does not colorize <> brackets"
875 );
876
877 cx.update_buffer(|buffer, cx| {
878 buffer.set_language(Some(rust_lang()), cx);
879 });
880 cx.executor().advance_clock(Duration::from_millis(100));
881 cx.executor().run_until_parked();
882
883 assert_eq!(
884 r#"fn main«1()1» «1{
885 let v: Vec«2<String>2» = vec!«2[]2»;
886}1»
887
8881 hsla(207.80, 81.00%, 66.00%, 1.00)
8892 hsla(29.00, 54.00%, 61.00%, 1.00)
890"#,
891 &bracket_colors_markup(&mut cx),
892 "After switching to Rust, <> brackets are now colorized"
893 );
894 }
895
896 #[gpui::test]
897 async fn test_bracket_colorization_when_editing(cx: &mut gpui::TestAppContext) {
898 init_test(cx, |language_settings| {
899 language_settings.defaults.colorize_brackets = Some(true);
900 });
901 let mut cx = EditorLspTestContext::new(
902 Arc::into_inner(rust_lang()).unwrap(),
903 lsp::ServerCapabilities::default(),
904 cx,
905 )
906 .await;
907
908 cx.set_state(indoc! {r#"
909struct Foo<'a, T> {
910 data: Vec<Option<&'a T>>,
911}
912
913fn process_data() {
914 let map:ˇ
915}
916"#});
917
918 cx.update_editor(|editor, window, cx| {
919 editor.handle_input(" Result<", window, cx);
920 });
921 cx.executor().advance_clock(Duration::from_millis(100));
922 cx.executor().run_until_parked();
923 assert_eq!(
924 indoc! {r#"
925struct Foo«1<'a, T>1» «1{
926 data: Vec«2<Option«3<&'a T>3»>2»,
927}1»
928
929fn process_data«1()1» «1{
930 let map: Result<
931}1»
932
9331 hsla(207.80, 81.00%, 66.00%, 1.00)
9342 hsla(29.00, 54.00%, 61.00%, 1.00)
9353 hsla(286.00, 51.00%, 64.00%, 1.00)
936"#},
937 &bracket_colors_markup(&mut cx),
938 "Brackets without pairs should be ignored and not colored"
939 );
940
941 cx.update_editor(|editor, window, cx| {
942 editor.handle_input("Option<Foo<'_, ()", window, cx);
943 });
944 cx.executor().advance_clock(Duration::from_millis(100));
945 cx.executor().run_until_parked();
946 assert_eq!(
947 indoc! {r#"
948struct Foo«1<'a, T>1» «1{
949 data: Vec«2<Option«3<&'a T>3»>2»,
950}1»
951
952fn process_data«1()1» «1{
953 let map: Result<Option<Foo<'_, «2()2»
954}1»
955
9561 hsla(207.80, 81.00%, 66.00%, 1.00)
9572 hsla(29.00, 54.00%, 61.00%, 1.00)
9583 hsla(286.00, 51.00%, 64.00%, 1.00)
959"#},
960 &bracket_colors_markup(&mut cx),
961 );
962
963 cx.update_editor(|editor, window, cx| {
964 editor.handle_input(">", window, cx);
965 });
966 cx.executor().advance_clock(Duration::from_millis(100));
967 cx.executor().run_until_parked();
968 assert_eq!(
969 indoc! {r#"
970struct Foo«1<'a, T>1» «1{
971 data: Vec«2<Option«3<&'a T>3»>2»,
972}1»
973
974fn process_data«1()1» «1{
975 let map: Result<Option<Foo«2<'_, «3()3»>2»
976}1»
977
9781 hsla(207.80, 81.00%, 66.00%, 1.00)
9792 hsla(29.00, 54.00%, 61.00%, 1.00)
9803 hsla(286.00, 51.00%, 64.00%, 1.00)
981"#},
982 &bracket_colors_markup(&mut cx),
983 "When brackets start to get closed, inner brackets are re-colored based on their depth"
984 );
985
986 cx.update_editor(|editor, window, cx| {
987 editor.handle_input(">", window, cx);
988 });
989 cx.executor().advance_clock(Duration::from_millis(100));
990 cx.executor().run_until_parked();
991 assert_eq!(
992 indoc! {r#"
993struct Foo«1<'a, T>1» «1{
994 data: Vec«2<Option«3<&'a T>3»>2»,
995}1»
996
997fn process_data«1()1» «1{
998 let map: Result<Option«2<Foo«3<'_, «4()4»>3»>2»
999}1»
1000
10011 hsla(207.80, 81.00%, 66.00%, 1.00)
10022 hsla(29.00, 54.00%, 61.00%, 1.00)
10033 hsla(286.00, 51.00%, 64.00%, 1.00)
10044 hsla(187.00, 47.00%, 55.00%, 1.00)
1005"#},
1006 &bracket_colors_markup(&mut cx),
1007 );
1008
1009 cx.update_editor(|editor, window, cx| {
1010 editor.handle_input(", ()> = unimplemented!();", window, cx);
1011 });
1012 cx.executor().advance_clock(Duration::from_millis(100));
1013 cx.executor().run_until_parked();
1014 assert_eq!(
1015 indoc! {r#"
1016struct Foo«1<'a, T>1» «1{
1017 data: Vec«2<Option«3<&'a T>3»>2»,
1018}1»
1019
1020fn process_data«1()1» «1{
1021 let map: Result«2<Option«3<Foo«4<'_, «5()5»>4»>3», «3()3»>2» = unimplemented!«2()2»;
1022}1»
1023
10241 hsla(207.80, 81.00%, 66.00%, 1.00)
10252 hsla(29.00, 54.00%, 61.00%, 1.00)
10263 hsla(286.00, 51.00%, 64.00%, 1.00)
10274 hsla(187.00, 47.00%, 55.00%, 1.00)
10285 hsla(355.00, 65.00%, 65.00%, 1.00)
1029"#},
1030 &bracket_colors_markup(&mut cx),
1031 );
1032 }
1033
1034 #[gpui::test]
1035 async fn test_bracket_colorization_chunks(cx: &mut gpui::TestAppContext) {
1036 let comment_lines = 100;
1037
1038 init_test(cx, |language_settings| {
1039 language_settings.defaults.colorize_brackets = Some(true);
1040 });
1041 let mut cx = EditorLspTestContext::new(
1042 Arc::into_inner(rust_lang()).unwrap(),
1043 lsp::ServerCapabilities::default(),
1044 cx,
1045 )
1046 .await;
1047
1048 cx.set_state(&separate_with_comment_lines(
1049 indoc! {r#"
1050mod foo {
1051 ˇfn process_data_1() {
1052 let map: Option<Vec<()>> = None;
1053 }
1054"#},
1055 indoc! {r#"
1056 fn process_data_2() {
1057 let map: Option<Vec<()>> = None;
1058 }
1059}
1060"#},
1061 comment_lines,
1062 ));
1063
1064 cx.executor().advance_clock(Duration::from_millis(100));
1065 cx.executor().run_until_parked();
1066 assert_eq!(
1067 &separate_with_comment_lines(
1068 indoc! {r#"
1069mod foo «1{
1070 fn process_data_1«2()2» «2{
1071 let map: Option«3<Vec«4<«5()5»>4»>3» = None;
1072 }2»
1073"#},
1074 indoc! {r#"
1075 fn process_data_2() {
1076 let map: Option<Vec<()>> = None;
1077 }
1078}1»
1079
10801 hsla(207.80, 81.00%, 66.00%, 1.00)
10812 hsla(29.00, 54.00%, 61.00%, 1.00)
10823 hsla(286.00, 51.00%, 64.00%, 1.00)
10834 hsla(187.00, 47.00%, 55.00%, 1.00)
10845 hsla(355.00, 65.00%, 65.00%, 1.00)
1085"#},
1086 comment_lines,
1087 ),
1088 &bracket_colors_markup(&mut cx),
1089 "First, the only visible chunk is getting the bracket highlights"
1090 );
1091
1092 cx.update_editor(|editor, window, cx| {
1093 editor.move_to_end(&MoveToEnd, window, cx);
1094 editor.move_up(&MoveUp, window, cx);
1095 });
1096 cx.executor().advance_clock(Duration::from_millis(100));
1097 cx.executor().run_until_parked();
1098 assert_eq!(
1099 &separate_with_comment_lines(
1100 indoc! {r#"
1101mod foo «1{
1102 fn process_data_1«2()2» «2{
1103 let map: Option«3<Vec«4<«5()5»>4»>3» = None;
1104 }2»
1105"#},
1106 indoc! {r#"
1107 fn process_data_2«2()2» «2{
1108 let map: Option«3<Vec«4<«5()5»>4»>3» = None;
1109 }2»
1110}1»
1111
11121 hsla(207.80, 81.00%, 66.00%, 1.00)
11132 hsla(29.00, 54.00%, 61.00%, 1.00)
11143 hsla(286.00, 51.00%, 64.00%, 1.00)
11154 hsla(187.00, 47.00%, 55.00%, 1.00)
11165 hsla(355.00, 65.00%, 65.00%, 1.00)
1117"#},
1118 comment_lines,
1119 ),
1120 &bracket_colors_markup(&mut cx),
1121 "After scrolling to the bottom, both chunks should have the highlights"
1122 );
1123
1124 cx.update_editor(|editor, window, cx| {
1125 editor.handle_input("{{}}}", window, cx);
1126 });
1127 cx.executor().advance_clock(Duration::from_millis(100));
1128 cx.executor().run_until_parked();
1129 assert_eq!(
1130 &separate_with_comment_lines(
1131 indoc! {r#"
1132mod foo «1{
1133 fn process_data_1() {
1134 let map: Option<Vec<()>> = None;
1135 }
1136"#},
1137 indoc! {r#"
1138 fn process_data_2«2()2» «2{
1139 let map: Option«3<Vec«4<«5()5»>4»>3» = None;
1140 }
1141 «3{«4{}4»}3»}2»}1»
1142
11431 hsla(207.80, 81.00%, 66.00%, 1.00)
11442 hsla(29.00, 54.00%, 61.00%, 1.00)
11453 hsla(286.00, 51.00%, 64.00%, 1.00)
11464 hsla(187.00, 47.00%, 55.00%, 1.00)
11475 hsla(355.00, 65.00%, 65.00%, 1.00)
1148"#},
1149 comment_lines,
1150 ),
1151 &bracket_colors_markup(&mut cx),
1152 "First chunk's brackets are invalidated after an edit, and only 2nd (visible) chunk is re-colorized"
1153 );
1154
1155 cx.update_editor(|editor, window, cx| {
1156 editor.move_to_beginning(&MoveToBeginning, window, cx);
1157 });
1158 cx.executor().advance_clock(Duration::from_millis(100));
1159 cx.executor().run_until_parked();
1160 assert_eq!(
1161 &separate_with_comment_lines(
1162 indoc! {r#"
1163mod foo «1{
1164 fn process_data_1«2()2» «2{
1165 let map: Option«3<Vec«4<«5()5»>4»>3» = None;
1166 }2»
1167"#},
1168 indoc! {r#"
1169 fn process_data_2«2()2» «2{
1170 let map: Option«3<Vec«4<«5()5»>4»>3» = None;
1171 }
1172 «3{«4{}4»}3»}2»}1»
1173
11741 hsla(207.80, 81.00%, 66.00%, 1.00)
11752 hsla(29.00, 54.00%, 61.00%, 1.00)
11763 hsla(286.00, 51.00%, 64.00%, 1.00)
11774 hsla(187.00, 47.00%, 55.00%, 1.00)
11785 hsla(355.00, 65.00%, 65.00%, 1.00)
1179"#},
1180 comment_lines,
1181 ),
1182 &bracket_colors_markup(&mut cx),
1183 "Scrolling back to top should re-colorize all chunks' brackets"
1184 );
1185
1186 cx.update(|_, cx| {
1187 SettingsStore::update_global(cx, |store, cx| {
1188 store.update_user_settings(cx, |settings| {
1189 settings.project.all_languages.defaults.colorize_brackets = Some(false);
1190 });
1191 });
1192 });
1193 cx.executor().run_until_parked();
1194 assert_eq!(
1195 &separate_with_comment_lines(
1196 indoc! {r#"
1197mod foo {
1198 fn process_data_1() {
1199 let map: Option<Vec<()>> = None;
1200 }
1201"#},
1202 r#" fn process_data_2() {
1203 let map: Option<Vec<()>> = None;
1204 }
1205 {{}}}}
1206
1207"#,
1208 comment_lines,
1209 ),
1210 &bracket_colors_markup(&mut cx),
1211 "Turning bracket colorization off should remove all bracket colors"
1212 );
1213
1214 cx.update(|_, cx| {
1215 SettingsStore::update_global(cx, |store, cx| {
1216 store.update_user_settings(cx, |settings| {
1217 settings.project.all_languages.defaults.colorize_brackets = Some(true);
1218 });
1219 });
1220 });
1221 cx.executor().run_until_parked();
1222 assert_eq!(
1223 &separate_with_comment_lines(
1224 indoc! {r#"
1225mod foo «1{
1226 fn process_data_1«2()2» «2{
1227 let map: Option«3<Vec«4<«5()5»>4»>3» = None;
1228 }2»
1229"#},
1230 r#" fn process_data_2() {
1231 let map: Option<Vec<()>> = None;
1232 }
1233 {{}}}}1»
1234
12351 hsla(207.80, 81.00%, 66.00%, 1.00)
12362 hsla(29.00, 54.00%, 61.00%, 1.00)
12373 hsla(286.00, 51.00%, 64.00%, 1.00)
12384 hsla(187.00, 47.00%, 55.00%, 1.00)
12395 hsla(355.00, 65.00%, 65.00%, 1.00)
1240"#,
1241 comment_lines,
1242 ),
1243 &bracket_colors_markup(&mut cx),
1244 "Turning bracket colorization back on refreshes the visible excerpts' bracket colors"
1245 );
1246 }
1247
1248 #[gpui::test]
1249 async fn test_rainbow_bracket_highlights(cx: &mut gpui::TestAppContext) {
1250 init_test(cx, |language_settings| {
1251 language_settings.defaults.colorize_brackets = Some(true);
1252 });
1253 let mut cx = EditorLspTestContext::new(
1254 Arc::into_inner(rust_lang()).unwrap(),
1255 lsp::ServerCapabilities::default(),
1256 cx,
1257 )
1258 .await;
1259
1260 // taken from r-a https://github.com/rust-lang/rust-analyzer/blob/d733c07552a2dc0ec0cc8f4df3f0ca969a93fd90/crates/ide/src/inlay_hints.rs#L81-L297
1261 cx.set_state(indoc! {r#"ˇ
1262 pub(crate) fn inlay_hints(
1263 db: &RootDatabase,
1264 file_id: FileId,
1265 range_limit: Option<TextRange>,
1266 config: &InlayHintsConfig,
1267 ) -> Vec<InlayHint> {
1268 let _p = tracing::info_span!("inlay_hints").entered();
1269 let sema = Semantics::new(db);
1270 let file_id = sema
1271 .attach_first_edition(file_id)
1272 .unwrap_or_else(|| EditionedFileId::current_edition(db, file_id));
1273 let file = sema.parse(file_id);
1274 let file = file.syntax();
1275
1276 let mut acc = Vec::new();
1277
1278 let Some(scope) = sema.scope(file) else {
1279 return acc;
1280 };
1281 let famous_defs = FamousDefs(&sema, scope.krate());
1282 let display_target = famous_defs.1.to_display_target(sema.db);
1283
1284 let ctx = &mut InlayHintCtx::default();
1285 let mut hints = |event| {
1286 if let Some(node) = handle_event(ctx, event) {
1287 hints(&mut acc, ctx, &famous_defs, config, file_id, display_target, node);
1288 }
1289 };
1290 let mut preorder = file.preorder();
1291 salsa::attach(sema.db, || {
1292 while let Some(event) = preorder.next() {
1293 if matches!((&event, range_limit), (WalkEvent::Enter(node), Some(range)) if range.intersect(node.text_range()).is_none())
1294 {
1295 preorder.skip_subtree();
1296 continue;
1297 }
1298 hints(event);
1299 }
1300 });
1301 if let Some(range_limit) = range_limit {
1302 acc.retain(|hint| range_limit.contains_range(hint.range));
1303 }
1304 acc
1305 }
1306
1307 #[derive(Default)]
1308 struct InlayHintCtx {
1309 lifetime_stacks: Vec<Vec<SmolStr>>,
1310 extern_block_parent: Option<ast::ExternBlock>,
1311 }
1312
1313 pub(crate) fn inlay_hints_resolve(
1314 db: &RootDatabase,
1315 file_id: FileId,
1316 resolve_range: TextRange,
1317 hash: u64,
1318 config: &InlayHintsConfig,
1319 hasher: impl Fn(&InlayHint) -> u64,
1320 ) -> Option<InlayHint> {
1321 let _p = tracing::info_span!("inlay_hints_resolve").entered();
1322 let sema = Semantics::new(db);
1323 let file_id = sema
1324 .attach_first_edition(file_id)
1325 .unwrap_or_else(|| EditionedFileId::current_edition(db, file_id));
1326 let file = sema.parse(file_id);
1327 let file = file.syntax();
1328
1329 let scope = sema.scope(file)?;
1330 let famous_defs = FamousDefs(&sema, scope.krate());
1331 let mut acc = Vec::new();
1332
1333 let display_target = famous_defs.1.to_display_target(sema.db);
1334
1335 let ctx = &mut InlayHintCtx::default();
1336 let mut hints = |event| {
1337 if let Some(node) = handle_event(ctx, event) {
1338 hints(&mut acc, ctx, &famous_defs, config, file_id, display_target, node);
1339 }
1340 };
1341
1342 let mut preorder = file.preorder();
1343 while let Some(event) = preorder.next() {
1344 // This can miss some hints that require the parent of the range to calculate
1345 if matches!(&event, WalkEvent::Enter(node) if resolve_range.intersect(node.text_range()).is_none())
1346 {
1347 preorder.skip_subtree();
1348 continue;
1349 }
1350 hints(event);
1351 }
1352 acc.into_iter().find(|hint| hasher(hint) == hash)
1353 }
1354
1355 fn handle_event(ctx: &mut InlayHintCtx, node: WalkEvent<SyntaxNode>) -> Option<SyntaxNode> {
1356 match node {
1357 WalkEvent::Enter(node) => {
1358 if let Some(node) = ast::AnyHasGenericParams::cast(node.clone()) {
1359 let params = node
1360 .generic_param_list()
1361 .map(|it| {
1362 it.lifetime_params()
1363 .filter_map(|it| {
1364 it.lifetime().map(|it| format_smolstr!("{}", &it.text()[1..]))
1365 })
1366 .collect()
1367 })
1368 .unwrap_or_default();
1369 ctx.lifetime_stacks.push(params);
1370 }
1371 if let Some(node) = ast::ExternBlock::cast(node.clone()) {
1372 ctx.extern_block_parent = Some(node);
1373 }
1374 Some(node)
1375 }
1376 WalkEvent::Leave(n) => {
1377 if ast::AnyHasGenericParams::can_cast(n.kind()) {
1378 ctx.lifetime_stacks.pop();
1379 }
1380 if ast::ExternBlock::can_cast(n.kind()) {
1381 ctx.extern_block_parent = None;
1382 }
1383 None
1384 }
1385 }
1386 }
1387
1388 // At some point when our hir infra is fleshed out enough we should flip this and traverse the
1389 // HIR instead of the syntax tree.
1390 fn hints(
1391 hints: &mut Vec<InlayHint>,
1392 ctx: &mut InlayHintCtx,
1393 famous_defs @ FamousDefs(sema, _krate): &FamousDefs<'_, '_>,
1394 config: &InlayHintsConfig,
1395 file_id: EditionedFileId,
1396 display_target: DisplayTarget,
1397 node: SyntaxNode,
1398 ) {
1399 closing_brace::hints(
1400 hints,
1401 sema,
1402 config,
1403 display_target,
1404 InRealFile { file_id, value: node.clone() },
1405 );
1406 if let Some(any_has_generic_args) = ast::AnyHasGenericArgs::cast(node.clone()) {
1407 generic_param::hints(hints, famous_defs, config, any_has_generic_args);
1408 }
1409
1410 match_ast! {
1411 match node {
1412 ast::Expr(expr) => {
1413 chaining::hints(hints, famous_defs, config, display_target, &expr);
1414 adjustment::hints(hints, famous_defs, config, display_target, &expr);
1415 match expr {
1416 ast::Expr::CallExpr(it) => param_name::hints(hints, famous_defs, config, file_id, ast::Expr::from(it)),
1417 ast::Expr::MethodCallExpr(it) => {
1418 param_name::hints(hints, famous_defs, config, file_id, ast::Expr::from(it))
1419 }
1420 ast::Expr::ClosureExpr(it) => {
1421 closure_captures::hints(hints, famous_defs, config, it.clone());
1422 closure_ret::hints(hints, famous_defs, config, display_target, it)
1423 },
1424 ast::Expr::RangeExpr(it) => range_exclusive::hints(hints, famous_defs, config, it),
1425 _ => Some(()),
1426 }
1427 },
1428 ast::Pat(it) => {
1429 binding_mode::hints(hints, famous_defs, config, &it);
1430 match it {
1431 ast::Pat::IdentPat(it) => {
1432 bind_pat::hints(hints, famous_defs, config, display_target, &it);
1433 }
1434 ast::Pat::RangePat(it) => {
1435 range_exclusive::hints(hints, famous_defs, config, it);
1436 }
1437 _ => {}
1438 }
1439 Some(())
1440 },
1441 ast::Item(it) => match it {
1442 ast::Item::Fn(it) => {
1443 implicit_drop::hints(hints, famous_defs, config, display_target, &it);
1444 if let Some(extern_block) = &ctx.extern_block_parent {
1445 extern_block::fn_hints(hints, famous_defs, config, &it, extern_block);
1446 }
1447 lifetime::fn_hints(hints, ctx, famous_defs, config, it)
1448 },
1449 ast::Item::Static(it) => {
1450 if let Some(extern_block) = &ctx.extern_block_parent {
1451 extern_block::static_hints(hints, famous_defs, config, &it, extern_block);
1452 }
1453 implicit_static::hints(hints, famous_defs, config, Either::Left(it))
1454 },
1455 ast::Item::Const(it) => implicit_static::hints(hints, famous_defs, config, Either::Right(it)),
1456 ast::Item::Enum(it) => discriminant::enum_hints(hints, famous_defs, config, it),
1457 ast::Item::ExternBlock(it) => extern_block::extern_block_hints(hints, famous_defs, config, it),
1458 _ => None,
1459 },
1460 // trait object type elisions
1461 ast::Type(ty) => match ty {
1462 ast::Type::FnPtrType(ptr) => lifetime::fn_ptr_hints(hints, ctx, famous_defs, config, ptr),
1463 ast::Type::PathType(path) => {
1464 lifetime::fn_path_hints(hints, ctx, famous_defs, config, &path);
1465 implied_dyn_trait::hints(hints, famous_defs, config, Either::Left(path));
1466 Some(())
1467 },
1468 ast::Type::DynTraitType(dyn_) => {
1469 implied_dyn_trait::hints(hints, famous_defs, config, Either::Right(dyn_));
1470 Some(())
1471 },
1472 _ => Some(()),
1473 },
1474 ast::GenericParamList(it) => bounds::hints(hints, famous_defs, config, it),
1475 _ => Some(()),
1476 }
1477 };
1478 }
1479 "#});
1480 cx.executor().advance_clock(Duration::from_millis(100));
1481 cx.executor().run_until_parked();
1482
1483 let actual_ranges = cx.update_editor(|editor, window, cx| {
1484 editor
1485 .snapshot(window, cx)
1486 .all_text_highlight_ranges(&|key| matches!(key, HighlightKey::ColorizeBracket(_)))
1487 });
1488
1489 let mut highlighted_brackets = HashMap::default();
1490 for (color, range) in actual_ranges.iter().cloned() {
1491 highlighted_brackets.insert(range, color);
1492 }
1493
1494 let last_bracket = actual_ranges
1495 .iter()
1496 .max_by_key(|(_, p)| p.end.row)
1497 .unwrap()
1498 .clone();
1499
1500 cx.update_editor(|editor, window, cx| {
1501 let was_scrolled = editor.set_scroll_position(
1502 gpui::Point::new(0.0, last_bracket.1.end.row as f64 * 2.0),
1503 window,
1504 cx,
1505 );
1506 assert!(was_scrolled.0);
1507 });
1508 cx.executor().advance_clock(Duration::from_millis(100));
1509 cx.executor().run_until_parked();
1510
1511 let ranges_after_scrolling = cx.update_editor(|editor, window, cx| {
1512 editor
1513 .snapshot(window, cx)
1514 .all_text_highlight_ranges(&|key| matches!(key, HighlightKey::ColorizeBracket(_)))
1515 });
1516 let new_last_bracket = ranges_after_scrolling
1517 .iter()
1518 .max_by_key(|(_, p)| p.end.row)
1519 .unwrap()
1520 .clone();
1521
1522 assert_ne!(
1523 last_bracket, new_last_bracket,
1524 "After scrolling down, we should have highlighted more brackets"
1525 );
1526
1527 cx.update_editor(|editor, window, cx| {
1528 let was_scrolled = editor.set_scroll_position(gpui::Point::default(), window, cx);
1529 assert!(was_scrolled.0);
1530 });
1531
1532 for _ in 0..200 {
1533 cx.update_editor(|editor, window, cx| {
1534 editor.apply_scroll_delta(gpui::Point::new(0.0, 0.25), window, cx);
1535 });
1536 cx.executor().advance_clock(Duration::from_millis(100));
1537 cx.executor().run_until_parked();
1538
1539 let colored_brackets = cx.update_editor(|editor, window, cx| {
1540 editor
1541 .snapshot(window, cx)
1542 .all_text_highlight_ranges(&|key| {
1543 matches!(key, HighlightKey::ColorizeBracket(_))
1544 })
1545 });
1546 for (color, range) in colored_brackets.clone() {
1547 assert!(
1548 highlighted_brackets.entry(range).or_insert(color) == &color,
1549 "Colors should stay consistent while scrolling!"
1550 );
1551 }
1552
1553 let snapshot = cx.update_editor(|editor, window, cx| editor.snapshot(window, cx));
1554 let scroll_position = snapshot.scroll_position();
1555 let visible_lines =
1556 cx.update_editor(|editor, _, _| editor.visible_line_count().unwrap());
1557 let visible_range = DisplayRow(scroll_position.y as u32)
1558 ..DisplayRow((scroll_position.y + visible_lines) as u32);
1559
1560 let current_highlighted_bracket_set: HashSet<Point> = HashSet::from_iter(
1561 colored_brackets
1562 .iter()
1563 .flat_map(|(_, range)| [range.start, range.end]),
1564 );
1565
1566 for highlight_range in highlighted_brackets.keys().filter(|bracket_range| {
1567 visible_range.contains(&bracket_range.start.to_display_point(&snapshot).row())
1568 || visible_range.contains(&bracket_range.end.to_display_point(&snapshot).row())
1569 }) {
1570 assert!(
1571 current_highlighted_bracket_set.contains(&highlight_range.start)
1572 || current_highlighted_bracket_set.contains(&highlight_range.end),
1573 "Should not lose highlights while scrolling in the visible range!"
1574 );
1575 }
1576
1577 let buffer_snapshot = snapshot.buffer().as_singleton().unwrap();
1578 for bracket_match in buffer_snapshot
1579 .fetch_bracket_ranges(
1580 snapshot
1581 .display_point_to_point(
1582 DisplayPoint::new(visible_range.start, 0),
1583 Bias::Left,
1584 )
1585 .to_offset(&buffer_snapshot)
1586 ..snapshot
1587 .display_point_to_point(
1588 DisplayPoint::new(
1589 visible_range.end,
1590 snapshot.line_len(visible_range.end),
1591 ),
1592 Bias::Right,
1593 )
1594 .to_offset(&buffer_snapshot),
1595 None,
1596 )
1597 .iter()
1598 .flat_map(|entry| entry.1)
1599 .filter(|bracket_match| bracket_match.color_index.is_some())
1600 {
1601 let start = bracket_match.open_range.to_point(buffer_snapshot);
1602 let end = bracket_match.close_range.to_point(buffer_snapshot);
1603 let start_bracket = colored_brackets.iter().find(|(_, range)| *range == start);
1604 assert!(
1605 start_bracket.is_some(),
1606 "Existing bracket start in the visible range should be highlighted. Missing color for match: \"{}\" at position {:?}",
1607 buffer_snapshot
1608 .text_for_range(start.start..end.end)
1609 .collect::<String>(),
1610 start
1611 );
1612
1613 let end_bracket = colored_brackets.iter().find(|(_, range)| *range == end);
1614 assert!(
1615 end_bracket.is_some(),
1616 "Existing bracket end in the visible range should be highlighted. Missing color for match: \"{}\" at position {:?}",
1617 buffer_snapshot
1618 .text_for_range(start.start..end.end)
1619 .collect::<String>(),
1620 start
1621 );
1622
1623 assert_eq!(
1624 start_bracket.unwrap().0,
1625 end_bracket.unwrap().0,
1626 "Bracket pair should be highlighted the same color!"
1627 )
1628 }
1629 }
1630 }
1631
1632 #[gpui::test]
1633 async fn test_multi_buffer(cx: &mut gpui::TestAppContext) {
1634 let comment_lines = 100;
1635
1636 init_test(cx, |language_settings| {
1637 language_settings.defaults.colorize_brackets = Some(true);
1638 });
1639 let fs = FakeFs::new(cx.background_executor.clone());
1640 fs.insert_tree(
1641 path!("/a"),
1642 json!({
1643 "main.rs": "fn main() {{()}}",
1644 "lib.rs": separate_with_comment_lines(
1645 indoc! {r#"
1646 mod foo {
1647 fn process_data_1() {
1648 let map: Option<Vec<()>> = None;
1649 // a
1650 // b
1651 // c
1652 }
1653 "#},
1654 indoc! {r#"
1655 fn process_data_2() {
1656 let other_map: Option<Vec<()>> = None;
1657 }
1658 }
1659 "#},
1660 comment_lines,
1661 )
1662 }),
1663 )
1664 .await;
1665
1666 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
1667 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
1668 language_registry.add(rust_lang());
1669
1670 let buffer_1 = project
1671 .update(cx, |project, cx| {
1672 project.open_local_buffer(path!("/a/lib.rs"), cx)
1673 })
1674 .await
1675 .unwrap();
1676 let buffer_2 = project
1677 .update(cx, |project, cx| {
1678 project.open_local_buffer(path!("/a/main.rs"), cx)
1679 })
1680 .await
1681 .unwrap();
1682
1683 let multi_buffer = cx.new(|cx| {
1684 let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
1685 multi_buffer.set_excerpts_for_path(
1686 PathKey::sorted(0),
1687 buffer_2.clone(),
1688 [Point::new(0, 0)..Point::new(1, 0)],
1689 0,
1690 cx,
1691 );
1692
1693 let excerpt_rows = 5;
1694 let rest_of_first_except_rows = 3;
1695 multi_buffer.set_excerpts_for_path(
1696 PathKey::sorted(1),
1697 buffer_1.clone(),
1698 [
1699 Point::new(0, 0)..Point::new(excerpt_rows, 0),
1700 Point::new(
1701 comment_lines as u32 + excerpt_rows + rest_of_first_except_rows,
1702 0,
1703 )
1704 ..Point::new(
1705 comment_lines as u32
1706 + excerpt_rows
1707 + rest_of_first_except_rows
1708 + excerpt_rows,
1709 0,
1710 ),
1711 ],
1712 0,
1713 cx,
1714 );
1715 multi_buffer
1716 });
1717
1718 let editor = cx.add_window(|window, cx| {
1719 Editor::for_multibuffer(multi_buffer, Some(project.clone()), window, cx)
1720 });
1721 cx.executor().advance_clock(Duration::from_millis(100));
1722 cx.executor().run_until_parked();
1723
1724 let editor_snapshot = editor
1725 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
1726 .unwrap();
1727 assert_eq!(
1728 indoc! {r#"
1729
1730
1731fn main«1()1» «1{«2{«3()3»}2»}1»
1732
1733
1734mod foo «1{
1735 fn process_data_1«2()2» «2{
1736 let map: Option«3<Vec«4<«5()5»>4»>3» = None;
1737 // a
1738 // b
1739 // c
1740
1741 fn process_data_2«2()2» «2{
1742 let other_map: Option«3<Vec«4<«5()5»>4»>3» = None;
1743 }2»
1744}1»
1745
17461 hsla(207.80, 81.00%, 66.00%, 1.00)
17472 hsla(29.00, 54.00%, 61.00%, 1.00)
17483 hsla(286.00, 51.00%, 64.00%, 1.00)
17494 hsla(187.00, 47.00%, 55.00%, 1.00)
17505 hsla(355.00, 65.00%, 65.00%, 1.00)
1751"#,},
1752 &editor_bracket_colors_markup(&editor_snapshot),
1753 "Multi buffers should have their brackets colored even if no excerpts contain the bracket counterpart (after fn `process_data_2()`) \
1754or if the buffer pair spans across multiple excerpts (the one after `mod foo`)"
1755 );
1756
1757 editor
1758 .update(cx, |editor, window, cx| {
1759 editor.handle_input("{[]", window, cx);
1760 })
1761 .unwrap();
1762 cx.executor().advance_clock(Duration::from_millis(100));
1763 cx.executor().run_until_parked();
1764 let editor_snapshot = editor
1765 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
1766 .unwrap();
1767 assert_eq!(
1768 indoc! {r#"
1769
1770
1771{«1[]1»fn main«1()1» «1{«2{«3()3»}2»}1»
1772
1773
1774mod foo «1{
1775 fn process_data_1«2()2» «2{
1776 let map: Option«3<Vec«4<«5()5»>4»>3» = None;
1777 // a
1778 // b
1779 // c
1780
1781 fn process_data_2«2()2» «2{
1782 let other_map: Option«3<Vec«4<«5()5»>4»>3» = None;
1783 }2»
1784}1»
1785
17861 hsla(207.80, 81.00%, 66.00%, 1.00)
17872 hsla(29.00, 54.00%, 61.00%, 1.00)
17883 hsla(286.00, 51.00%, 64.00%, 1.00)
17894 hsla(187.00, 47.00%, 55.00%, 1.00)
17905 hsla(355.00, 65.00%, 65.00%, 1.00)
1791"#,},
1792 &editor_bracket_colors_markup(&editor_snapshot),
1793 );
1794
1795 cx.update(|cx| {
1796 let theme = cx.theme().name.clone();
1797 SettingsStore::update_global(cx, |store, cx| {
1798 store.update_user_settings(cx, |settings| {
1799 settings.theme.theme_overrides = HashMap::from_iter([(
1800 theme.to_string(),
1801 ThemeStyleContent {
1802 accents: vec![
1803 AccentContent(Some("#ff0000".to_string())),
1804 AccentContent(Some("#0000ff".to_string())),
1805 ],
1806 ..ThemeStyleContent::default()
1807 },
1808 )]);
1809 });
1810 });
1811 });
1812 cx.executor().advance_clock(Duration::from_millis(100));
1813 cx.executor().run_until_parked();
1814 let editor_snapshot = editor
1815 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
1816 .unwrap();
1817 let adjusted_palette = cx.update(|cx| {
1818 bracket_colorization_accents(
1819 &[
1820 Hsla::from(Rgba::try_from("#ff0000").expect("valid override accent")),
1821 Hsla::from(Rgba::try_from("#0000ff").expect("valid override accent")),
1822 ],
1823 cx.theme().appearance,
1824 cx.theme().colors().editor_background,
1825 )
1826 });
1827 let expected_markup = format!(
1828 "{}\n1 {}\n2 {}\n",
1829 indoc! {r#"
1830
1831
1832{«1[]1»fn main«1()1» «1{«2{«1()1»}2»}1»
1833
1834
1835mod foo «1{
1836 fn process_data_1«2()2» «2{
1837 let map: Option«1<Vec«2<«1()1»>2»>1» = None;
1838 // a
1839 // b
1840 // c
1841
1842 fn process_data_2«2()2» «2{
1843 let other_map: Option«1<Vec«2<«1()1»>2»>1» = None;
1844 }2»
1845}1»
1846"#,},
1847 adjusted_palette[0],
1848 adjusted_palette[1],
1849 );
1850 assert_eq!(
1851 expected_markup,
1852 editor_bracket_colors_markup(&editor_snapshot),
1853 "After updating theme accents, the editor should update the bracket coloring"
1854 );
1855 }
1856
1857 #[gpui::test]
1858 async fn test_multi_buffer_close_excerpts(cx: &mut gpui::TestAppContext) {
1859 let comment_lines = 5;
1860
1861 init_test(cx, |language_settings| {
1862 language_settings.defaults.colorize_brackets = Some(true);
1863 });
1864 let fs = FakeFs::new(cx.background_executor.clone());
1865 fs.insert_tree(
1866 path!("/a"),
1867 json!({
1868 "lib.rs": separate_with_comment_lines(
1869 indoc! {r#"
1870 fn process_data_1() {
1871 let map: Option<Vec<()>> = None;
1872 }
1873 "#},
1874 indoc! {r#"
1875 fn process_data_2() {
1876 let other_map: Option<Vec<()>> = None;
1877 }
1878 "#},
1879 comment_lines,
1880 )
1881 }),
1882 )
1883 .await;
1884
1885 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
1886 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
1887 language_registry.add(rust_lang());
1888
1889 let buffer_1 = project
1890 .update(cx, |project, cx| {
1891 project.open_local_buffer(path!("/a/lib.rs"), cx)
1892 })
1893 .await
1894 .unwrap();
1895
1896 let second_excerpt_start = buffer_1.read_with(cx, |buffer, _| {
1897 let text = buffer.text();
1898 text.lines()
1899 .enumerate()
1900 .find(|(_, line)| line.contains("process_data_2"))
1901 .map(|(row, _)| row as u32)
1902 .unwrap()
1903 });
1904
1905 let multi_buffer = cx.new(|cx| {
1906 let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
1907 multi_buffer.set_excerpts_for_path(
1908 PathKey::sorted(0),
1909 buffer_1.clone(),
1910 [
1911 Point::new(0, 0)..Point::new(3, 0),
1912 Point::new(second_excerpt_start, 0)..Point::new(second_excerpt_start + 3, 0),
1913 ],
1914 0,
1915 cx,
1916 );
1917 multi_buffer
1918 });
1919
1920 let editor = cx.add_window(|window, cx| {
1921 Editor::for_multibuffer(multi_buffer, Some(project.clone()), window, cx)
1922 });
1923 cx.executor().advance_clock(Duration::from_millis(100));
1924 cx.executor().run_until_parked();
1925
1926 let editor_snapshot = editor
1927 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
1928 .unwrap();
1929 assert_eq!(
1930 concat!(
1931 "\n",
1932 "\n",
1933 "fn process_data_1\u{00ab}1()1\u{00bb} \u{00ab}1{\n",
1934 " let map: Option\u{00ab}2<Vec\u{00ab}3<\u{00ab}4()4\u{00bb}>3\u{00bb}>2\u{00bb} = None;\n",
1935 "}1\u{00bb}\n",
1936 "\n",
1937 "\n",
1938 "fn process_data_2\u{00ab}1()1\u{00bb} \u{00ab}1{\n",
1939 " let other_map: Option\u{00ab}2<Vec\u{00ab}3<\u{00ab}4()4\u{00bb}>3\u{00bb}>2\u{00bb} = None;\n",
1940 "}1\u{00bb}\n",
1941 "\n",
1942 "1 hsla(207.80, 81.00%, 66.00%, 1.00)\n",
1943 "2 hsla(29.00, 54.00%, 61.00%, 1.00)\n",
1944 "3 hsla(286.00, 51.00%, 64.00%, 1.00)\n",
1945 "4 hsla(187.00, 47.00%, 55.00%, 1.00)\n",
1946 ),
1947 &editor_bracket_colors_markup(&editor_snapshot),
1948 "Two close excerpts from the same buffer (within same tree-sitter chunk) should both have bracket colors"
1949 );
1950 }
1951
1952 #[gpui::test]
1953 // reproduction of #47846
1954 async fn test_bracket_colorization_with_folds(cx: &mut gpui::TestAppContext) {
1955 init_test(cx, |language_settings| {
1956 language_settings.defaults.colorize_brackets = Some(true);
1957 });
1958 let mut cx = EditorLspTestContext::new(
1959 Arc::into_inner(rust_lang()).unwrap(),
1960 lsp::ServerCapabilities::default(),
1961 cx,
1962 )
1963 .await;
1964
1965 // Generate a large function body. When folded, this collapses
1966 // to a single display line, making small_function visible on screen.
1967 let mut big_body = String::new();
1968 for i in 0..700 {
1969 big_body.push_str(&format!(" let var_{i:04} = ({i});\n"));
1970 }
1971 let source = format!(
1972 "ˇfn big_function() {{\n{big_body}}}\n\nfn small_function() {{\n let x = (1, (2, 3));\n}}\n"
1973 );
1974
1975 cx.set_state(&source);
1976 cx.executor().advance_clock(Duration::from_millis(100));
1977 cx.executor().run_until_parked();
1978
1979 cx.update_editor(|editor, window, cx| {
1980 editor.fold_ranges(
1981 vec![Point::new(0, 0)..Point::new(701, 1)],
1982 false,
1983 window,
1984 cx,
1985 );
1986 });
1987 cx.executor().advance_clock(Duration::from_millis(100));
1988 cx.executor().run_until_parked();
1989
1990 assert_eq!(
1991 indoc! {r#"
1992⋯1»2»1»
1993
1994fn small_function«1()1» «1{
1995 let x = «2(1, «3(2, 3)3»)2»;
1996}1»
1997
19981 hsla(207.80, 81.00%, 66.00%, 1.00)
19992 hsla(29.00, 54.00%, 61.00%, 1.00)
20003 hsla(286.00, 51.00%, 64.00%, 1.00)
2001"#,},
2002 bracket_colors_markup(&mut cx),
2003 );
2004 }
2005
2006 fn separate_with_comment_lines(head: &str, tail: &str, comment_lines: usize) -> String {
2007 let mut result = head.to_string();
2008 result.push_str("\n");
2009 result.push_str(&"//\n".repeat(comment_lines));
2010 result.push_str(tail);
2011 result
2012 }
2013
2014 fn bracket_colors_markup(cx: &mut EditorTestContext) -> String {
2015 cx.update_editor(|editor, window, cx| {
2016 editor_bracket_colors_markup(&editor.snapshot(window, cx))
2017 })
2018 }
2019
2020 fn editor_bracket_colors_markup(snapshot: &EditorSnapshot) -> String {
2021 fn display_point_to_offset(text: &str, point: DisplayPoint) -> usize {
2022 let mut offset = 0;
2023 for (row_idx, line) in text.lines().enumerate() {
2024 if row_idx < point.row().0 as usize {
2025 offset += line.len() + 1; // +1 for newline
2026 } else {
2027 offset += point.column() as usize;
2028 break;
2029 }
2030 }
2031 offset
2032 }
2033
2034 let actual_ranges = snapshot
2035 .all_text_highlight_ranges(&|key| matches!(key, HighlightKey::ColorizeBracket(_)));
2036 let editor_text = snapshot.text();
2037
2038 let mut next_index = 1;
2039 let mut color_to_index = HashMap::default();
2040 let mut annotations = Vec::new();
2041 for (color, range) in &actual_ranges {
2042 let color_index = *color_to_index
2043 .entry(*color)
2044 .or_insert_with(|| post_inc(&mut next_index));
2045 let start = snapshot.point_to_display_point(range.start, Bias::Left);
2046 let end = snapshot.point_to_display_point(range.end, Bias::Right);
2047 let start_offset = display_point_to_offset(&editor_text, start);
2048 let end_offset = display_point_to_offset(&editor_text, end);
2049 let bracket_text = &editor_text[start_offset..end_offset];
2050 let bracket_char = bracket_text.chars().next().unwrap();
2051
2052 if matches!(bracket_char, '{' | '[' | '(' | '<') {
2053 annotations.push((start_offset, format!("«{color_index}")));
2054 } else {
2055 annotations.push((end_offset, format!("{color_index}»")));
2056 }
2057 }
2058
2059 annotations.sort_by(|(pos_a, text_a), (pos_b, text_b)| {
2060 pos_a.cmp(pos_b).reverse().then_with(|| {
2061 let a_is_opening = text_a.starts_with('«');
2062 let b_is_opening = text_b.starts_with('«');
2063 match (a_is_opening, b_is_opening) {
2064 (true, false) => cmp::Ordering::Less,
2065 (false, true) => cmp::Ordering::Greater,
2066 _ => cmp::Ordering::Equal,
2067 }
2068 })
2069 });
2070 annotations.dedup();
2071
2072 let mut markup = editor_text;
2073 for (offset, text) in annotations {
2074 markup.insert_str(offset, &text);
2075 }
2076
2077 markup.push_str("\n");
2078 for (index, color) in color_to_index
2079 .iter()
2080 .map(|(color, index)| (*index, *color))
2081 .sorted_by_key(|(index, _)| *index)
2082 {
2083 markup.push_str(&format!("{index} {color}\n"));
2084 }
2085
2086 markup
2087 }
2088}
2089