1use crate::{Editor, RangeToAnchorExt};
2use gpui::{Context, HighlightStyle, Window};
3use language::CursorShape;
4use multi_buffer::MultiBufferOffset;
5use theme::ActiveTheme;
6
7enum MatchingBracketHighlight {}
8
9impl Editor {
10 pub fn refresh_matching_bracket_highlights(
11 &mut self,
12 window: &Window,
13 cx: &mut Context<Editor>,
14 ) {
15 self.clear_highlights::<MatchingBracketHighlight>(cx);
16
17 let snapshot = self.snapshot(window, cx);
18 let buffer_snapshot = snapshot.buffer_snapshot();
19 let newest_selection = self.selections.newest::<MultiBufferOffset>(&snapshot);
20 // Don't highlight brackets if the selection isn't empty
21 if !newest_selection.is_empty() {
22 return;
23 }
24
25 let head = newest_selection.head();
26 if head > buffer_snapshot.len() {
27 log::error!("bug: cursor offset is out of range while refreshing bracket highlights");
28 return;
29 }
30
31 let mut tail = head;
32 if (self.cursor_shape == CursorShape::Block || self.cursor_shape == CursorShape::Hollow)
33 && head < buffer_snapshot.len()
34 {
35 if let Some(tail_ch) = buffer_snapshot.chars_at(tail).next() {
36 tail += tail_ch.len_utf8();
37 }
38 }
39
40 if let Some((opening_range, closing_range)) =
41 buffer_snapshot.innermost_enclosing_bracket_ranges(head..tail, None)
42 {
43 self.highlight_text::<MatchingBracketHighlight>(
44 vec![
45 opening_range.to_anchors(&buffer_snapshot),
46 closing_range.to_anchors(&buffer_snapshot),
47 ],
48 HighlightStyle {
49 background_color: Some(
50 cx.theme()
51 .colors()
52 .editor_document_highlight_bracket_background,
53 ),
54 ..Default::default()
55 },
56 cx,
57 )
58 }
59 }
60}
61
62#[cfg(test)]
63mod tests {
64 use super::*;
65 use crate::{editor_tests::init_test, test::editor_lsp_test_context::EditorLspTestContext};
66 use indoc::indoc;
67 use language::{BracketPair, BracketPairConfig, Language, LanguageConfig, LanguageMatcher};
68
69 #[gpui::test]
70 async fn test_matching_bracket_highlights(cx: &mut gpui::TestAppContext) {
71 init_test(cx, |_| {});
72
73 let mut cx = EditorLspTestContext::new(
74 Language::new(
75 LanguageConfig {
76 name: "Rust".into(),
77 matcher: LanguageMatcher {
78 path_suffixes: vec!["rs".to_string()],
79 ..Default::default()
80 },
81 brackets: BracketPairConfig {
82 pairs: vec![
83 BracketPair {
84 start: "{".to_string(),
85 end: "}".to_string(),
86 close: false,
87 surround: false,
88 newline: true,
89 },
90 BracketPair {
91 start: "(".to_string(),
92 end: ")".to_string(),
93 close: false,
94 surround: false,
95 newline: true,
96 },
97 ],
98 ..Default::default()
99 },
100 ..Default::default()
101 },
102 Some(tree_sitter_rust::LANGUAGE.into()),
103 )
104 .with_brackets_query(indoc! {r#"
105 ("{" @open "}" @close)
106 ("(" @open ")" @close)
107 "#})
108 .unwrap(),
109 Default::default(),
110 cx,
111 )
112 .await;
113
114 // positioning cursor inside bracket highlights both
115 cx.set_state(indoc! {r#"
116 pub fn test("Test ˇargument") {
117 another_test(1, 2, 3);
118 }
119 "#});
120 cx.assert_editor_text_highlights::<MatchingBracketHighlight>(indoc! {r#"
121 pub fn test«(»"Test argument"«)» {
122 another_test(1, 2, 3);
123 }
124 "#});
125
126 cx.set_state(indoc! {r#"
127 pub fn test("Test argument") {
128 another_test(1, ˇ2, 3);
129 }
130 "#});
131 cx.assert_editor_text_highlights::<MatchingBracketHighlight>(indoc! {r#"
132 pub fn test("Test argument") {
133 another_test«(»1, 2, 3«)»;
134 }
135 "#});
136
137 cx.set_state(indoc! {r#"
138 pub fn test("Test argument") {
139 anotherˇ_test(1, 2, 3);
140 }
141 "#});
142 cx.assert_editor_text_highlights::<MatchingBracketHighlight>(indoc! {r#"
143 pub fn test("Test argument") «{»
144 another_test(1, 2, 3);
145 «}»
146 "#});
147
148 // positioning outside of brackets removes highlight
149 cx.set_state(indoc! {r#"
150 pub fˇn test("Test argument") {
151 another_test(1, 2, 3);
152 }
153 "#});
154 cx.assert_editor_text_highlights::<MatchingBracketHighlight>(indoc! {r#"
155 pub fn test("Test argument") {
156 another_test(1, 2, 3);
157 }
158 "#});
159
160 // non empty selection dismisses highlight
161 cx.set_state(indoc! {r#"
162 pub fn test("Te«st argˇ»ument") {
163 another_test(1, 2, 3);
164 }
165 "#});
166 cx.assert_editor_text_highlights::<MatchingBracketHighlight>(indoc! {r#"
167 pub fn test«("Test argument") {
168 another_test(1, 2, 3);
169 }
170 "#});
171 }
172}