Closes #49653 Before you mark this PR as ready for review, make sure that you have: - [x] screen record added - [x] Done a self-review taking into account security and performance aspects - [x] No UI change The original `refresh_matching_bracket_highlights` method always performed a clear --> highlight procedure, even when the bracket positions hadn't changed, causing the highlights to flicker. Normally, for single quotes or brackets, the flickering area is small and hard to notice (though visible if you pay close attention), but for Python docstrings, the three quotes create a larger highlighted area, making this issue much more apparent. After fix: https://github.com/user-attachments/assets/bcd9b464-0def-438d-86b3-7e6b3779b5c6 Release Notes: - Fixed bracket highlights flickering when editing inside brackets
218 lines
7.5 KiB
Rust
218 lines
7.5 KiB
Rust
use crate::{Editor, HighlightKey, RangeToAnchorExt, display_map::DisplaySnapshot};
|
|
use gpui::{AppContext, Context, HighlightStyle};
|
|
use language::CursorShape;
|
|
use multi_buffer::MultiBufferOffset;
|
|
use theme::ActiveTheme;
|
|
|
|
impl Editor {
|
|
#[ztracing::instrument(skip_all)]
|
|
pub fn refresh_matching_bracket_highlights(
|
|
&mut self,
|
|
snapshot: &DisplaySnapshot,
|
|
cx: &mut Context<Editor>,
|
|
) {
|
|
let newest_selection = self.selections.newest::<MultiBufferOffset>(&snapshot);
|
|
// Don't highlight brackets if the selection isn't empty
|
|
if !newest_selection.is_empty() {
|
|
self.clear_highlights(HighlightKey::MatchingBracket, cx);
|
|
return;
|
|
}
|
|
|
|
let buffer_snapshot = snapshot.buffer_snapshot();
|
|
let head = newest_selection.head();
|
|
if head > buffer_snapshot.len() {
|
|
log::error!("bug: cursor offset is out of range while refreshing bracket highlights");
|
|
return;
|
|
}
|
|
|
|
let mut tail = head;
|
|
if (self.cursor_shape == CursorShape::Block || self.cursor_shape == CursorShape::Hollow)
|
|
&& head < buffer_snapshot.len()
|
|
{
|
|
if let Some(tail_ch) = buffer_snapshot.chars_at(tail).next() {
|
|
tail += tail_ch.len_utf8();
|
|
}
|
|
}
|
|
let task = cx.background_spawn({
|
|
let buffer_snapshot = buffer_snapshot.clone();
|
|
async move { buffer_snapshot.innermost_enclosing_bracket_ranges(head..tail, None) }
|
|
});
|
|
self.refresh_matching_bracket_highlights_task = cx.spawn({
|
|
let buffer_snapshot = buffer_snapshot.clone();
|
|
async move |this, cx| {
|
|
let bracket_ranges = task.await;
|
|
let current_ranges = this
|
|
.read_with(cx, |editor, cx| {
|
|
editor
|
|
.display_map
|
|
.read(cx)
|
|
.text_highlights(HighlightKey::MatchingBracket)
|
|
.map(|(_, ranges)| ranges.to_vec())
|
|
})
|
|
.ok()
|
|
.flatten();
|
|
let new_ranges = bracket_ranges.map(|(opening_range, closing_range)| {
|
|
vec![
|
|
opening_range.to_anchors(&buffer_snapshot),
|
|
closing_range.to_anchors(&buffer_snapshot),
|
|
]
|
|
});
|
|
|
|
if current_ranges != new_ranges {
|
|
this.update(cx, |editor, cx| {
|
|
editor.clear_highlights(HighlightKey::MatchingBracket, cx);
|
|
if let Some(new_ranges) = new_ranges {
|
|
editor.highlight_text(
|
|
HighlightKey::MatchingBracket,
|
|
new_ranges,
|
|
HighlightStyle {
|
|
background_color: Some(
|
|
cx.theme()
|
|
.colors()
|
|
.editor_document_highlight_bracket_background,
|
|
),
|
|
..Default::default()
|
|
},
|
|
cx,
|
|
)
|
|
}
|
|
})
|
|
.ok();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::{editor_tests::init_test, test::editor_lsp_test_context::EditorLspTestContext};
|
|
use indoc::indoc;
|
|
use language::{BracketPair, BracketPairConfig, Language, LanguageConfig, LanguageMatcher};
|
|
|
|
#[gpui::test]
|
|
async fn test_matching_bracket_highlights(cx: &mut gpui::TestAppContext) {
|
|
init_test(cx, |_| {});
|
|
|
|
let mut cx = EditorLspTestContext::new(
|
|
Language::new(
|
|
LanguageConfig {
|
|
name: "Rust".into(),
|
|
matcher: LanguageMatcher {
|
|
path_suffixes: vec!["rs".to_string()],
|
|
..Default::default()
|
|
},
|
|
brackets: BracketPairConfig {
|
|
pairs: vec![
|
|
BracketPair {
|
|
start: "{".to_string(),
|
|
end: "}".to_string(),
|
|
close: false,
|
|
surround: false,
|
|
newline: true,
|
|
},
|
|
BracketPair {
|
|
start: "(".to_string(),
|
|
end: ")".to_string(),
|
|
close: false,
|
|
surround: false,
|
|
newline: true,
|
|
},
|
|
],
|
|
..Default::default()
|
|
},
|
|
..Default::default()
|
|
},
|
|
Some(tree_sitter_rust::LANGUAGE.into()),
|
|
)
|
|
.with_brackets_query(indoc! {r#"
|
|
("{" @open "}" @close)
|
|
("(" @open ")" @close)
|
|
"#})
|
|
.unwrap(),
|
|
Default::default(),
|
|
cx,
|
|
)
|
|
.await;
|
|
|
|
// positioning cursor inside bracket highlights both
|
|
cx.set_state(indoc! {r#"
|
|
pub fn test("Test ˇargument") {
|
|
another_test(1, 2, 3);
|
|
}
|
|
"#});
|
|
cx.run_until_parked();
|
|
cx.assert_editor_text_highlights(
|
|
HighlightKey::MatchingBracket,
|
|
indoc! {r#"
|
|
pub fn test«(»"Test argument"«)» {
|
|
another_test(1, 2, 3);
|
|
}
|
|
"#},
|
|
);
|
|
|
|
cx.set_state(indoc! {r#"
|
|
pub fn test("Test argument") {
|
|
another_test(1, ˇ2, 3);
|
|
}
|
|
"#});
|
|
cx.run_until_parked();
|
|
cx.assert_editor_text_highlights(
|
|
HighlightKey::MatchingBracket,
|
|
indoc! {r#"
|
|
pub fn test("Test argument") {
|
|
another_test«(»1, 2, 3«)»;
|
|
}
|
|
"#},
|
|
);
|
|
|
|
cx.set_state(indoc! {r#"
|
|
pub fn test("Test argument") {
|
|
anotherˇ_test(1, 2, 3);
|
|
}
|
|
"#});
|
|
cx.run_until_parked();
|
|
cx.assert_editor_text_highlights(
|
|
HighlightKey::MatchingBracket,
|
|
indoc! {r#"
|
|
pub fn test("Test argument") «{»
|
|
another_test(1, 2, 3);
|
|
«}»
|
|
"#},
|
|
);
|
|
|
|
// positioning outside of brackets removes highlight
|
|
cx.set_state(indoc! {r#"
|
|
pub fˇn test("Test argument") {
|
|
another_test(1, 2, 3);
|
|
}
|
|
"#});
|
|
cx.run_until_parked();
|
|
cx.assert_editor_text_highlights(
|
|
HighlightKey::MatchingBracket,
|
|
indoc! {r#"
|
|
pub fn test("Test argument") {
|
|
another_test(1, 2, 3);
|
|
}
|
|
"#},
|
|
);
|
|
|
|
// non empty selection dismisses highlight
|
|
cx.set_state(indoc! {r#"
|
|
pub fn test("Te«st argˇ»ument") {
|
|
another_test(1, 2, 3);
|
|
}
|
|
"#});
|
|
cx.run_until_parked();
|
|
cx.assert_editor_text_highlights(
|
|
HighlightKey::MatchingBracket,
|
|
indoc! {r#"
|
|
pub fn test«("Test argument") {
|
|
another_test(1, 2, 3);
|
|
}
|
|
"#},
|
|
);
|
|
}
|
|
}
|