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