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