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