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