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 tail += 1;
33 }
34
35 if let Some((opening_range, closing_range)) = snapshot
36 .buffer_snapshot
37 .innermost_enclosing_bracket_ranges(head..tail, None)
38 {
39 editor.highlight_text::<MatchingBracketHighlight>(
40 vec![
41 opening_range.to_anchors(&snapshot.buffer_snapshot),
42 closing_range.to_anchors(&snapshot.buffer_snapshot),
43 ],
44 HighlightStyle {
45 background_color: Some(
46 cx.theme()
47 .colors()
48 .editor_document_highlight_bracket_background,
49 ),
50 ..Default::default()
51 },
52 cx,
53 )
54 }
55}
56
57#[cfg(test)]
58mod tests {
59 use super::*;
60 use crate::{editor_tests::init_test, test::editor_lsp_test_context::EditorLspTestContext};
61 use indoc::indoc;
62 use language::{BracketPair, BracketPairConfig, Language, LanguageConfig, LanguageMatcher};
63
64 #[gpui::test]
65 async fn test_matching_bracket_highlights(cx: &mut gpui::TestAppContext) {
66 init_test(cx, |_| {});
67
68 let mut cx = EditorLspTestContext::new(
69 Language::new(
70 LanguageConfig {
71 name: "Rust".into(),
72 matcher: LanguageMatcher {
73 path_suffixes: vec!["rs".to_string()],
74 ..Default::default()
75 },
76 brackets: BracketPairConfig {
77 pairs: vec![
78 BracketPair {
79 start: "{".to_string(),
80 end: "}".to_string(),
81 close: false,
82 surround: false,
83 newline: true,
84 },
85 BracketPair {
86 start: "(".to_string(),
87 end: ")".to_string(),
88 close: false,
89 surround: false,
90 newline: true,
91 },
92 ],
93 ..Default::default()
94 },
95 ..Default::default()
96 },
97 Some(tree_sitter_rust::LANGUAGE.into()),
98 )
99 .with_brackets_query(indoc! {r#"
100 ("{" @open "}" @close)
101 ("(" @open ")" @close)
102 "#})
103 .unwrap(),
104 Default::default(),
105 cx,
106 )
107 .await;
108
109 // positioning cursor inside bracket highlights both
110 cx.set_state(indoc! {r#"
111 pub fn test("Test ˇargument") {
112 another_test(1, 2, 3);
113 }
114 "#});
115 cx.assert_editor_text_highlights::<MatchingBracketHighlight>(indoc! {r#"
116 pub fn test«(»"Test argument"«)» {
117 another_test(1, 2, 3);
118 }
119 "#});
120
121 cx.set_state(indoc! {r#"
122 pub fn test("Test argument") {
123 another_test(1, ˇ2, 3);
124 }
125 "#});
126 cx.assert_editor_text_highlights::<MatchingBracketHighlight>(indoc! {r#"
127 pub fn test("Test argument") {
128 another_test«(»1, 2, 3«)»;
129 }
130 "#});
131
132 cx.set_state(indoc! {r#"
133 pub fn test("Test argument") {
134 anotherˇ_test(1, 2, 3);
135 }
136 "#});
137 cx.assert_editor_text_highlights::<MatchingBracketHighlight>(indoc! {r#"
138 pub fn test("Test argument") «{»
139 another_test(1, 2, 3);
140 «}»
141 "#});
142
143 // positioning outside of brackets removes highlight
144 cx.set_state(indoc! {r#"
145 pub fˇn test("Test argument") {
146 another_test(1, 2, 3);
147 }
148 "#});
149 cx.assert_editor_text_highlights::<MatchingBracketHighlight>(indoc! {r#"
150 pub fn test("Test argument") {
151 another_test(1, 2, 3);
152 }
153 "#});
154
155 // non empty selection dismisses highlight
156 cx.set_state(indoc! {r#"
157 pub fn test("Te«st argˇ»ument") {
158 another_test(1, 2, 3);
159 }
160 "#});
161 cx.assert_editor_text_highlights::<MatchingBracketHighlight>(indoc! {r#"
162 pub fn test«("Test argument") {
163 another_test(1, 2, 3);
164 }
165 "#});
166 }
167}