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