This PR introduces a new `MultiBufferOffset` new type wrapping size. The goal of this is to make it clear at the type level when we are interacting with offsets of a multi buffer versus offsets of a language / text buffer. This improves readability of things quite a bit by making it clear what kind of offsets one is working with while also reducing accidental bugs by using the wrong kin of offset for the wrong API. This PR also uncovered two minor bugs due to that. Does not yet introduce the MultiBufferPoint equivalent, that is for a follow up PR. Release Notes: - N/A *or* Added/Fixed/Improved ...
173 lines
5.7 KiB
Rust
173 lines
5.7 KiB
Rust
use crate::{Editor, RangeToAnchorExt};
|
|
use gpui::{Context, HighlightStyle, Window};
|
|
use language::CursorShape;
|
|
use multi_buffer::MultiBufferOffset;
|
|
use theme::ActiveTheme;
|
|
|
|
enum MatchingBracketHighlight {}
|
|
|
|
impl Editor {
|
|
pub fn refresh_matching_bracket_highlights(
|
|
&mut self,
|
|
window: &Window,
|
|
cx: &mut Context<Editor>,
|
|
) {
|
|
self.clear_highlights::<MatchingBracketHighlight>(cx);
|
|
|
|
let snapshot = self.snapshot(window, cx);
|
|
let buffer_snapshot = snapshot.buffer_snapshot();
|
|
let newest_selection = self.selections.newest::<MultiBufferOffset>(&snapshot);
|
|
// Don't highlight brackets if the selection isn't empty
|
|
if !newest_selection.is_empty() {
|
|
return;
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
if let Some((opening_range, closing_range)) =
|
|
buffer_snapshot.innermost_enclosing_bracket_ranges(head..tail, None)
|
|
{
|
|
self.highlight_text::<MatchingBracketHighlight>(
|
|
vec![
|
|
opening_range.to_anchors(&buffer_snapshot),
|
|
closing_range.to_anchors(&buffer_snapshot),
|
|
],
|
|
HighlightStyle {
|
|
background_color: Some(
|
|
cx.theme()
|
|
.colors()
|
|
.editor_document_highlight_bracket_background,
|
|
),
|
|
..Default::default()
|
|
},
|
|
cx,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
#[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.assert_editor_text_highlights::<MatchingBracketHighlight>(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.assert_editor_text_highlights::<MatchingBracketHighlight>(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.assert_editor_text_highlights::<MatchingBracketHighlight>(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.assert_editor_text_highlights::<MatchingBracketHighlight>(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.assert_editor_text_highlights::<MatchingBracketHighlight>(indoc! {r#"
|
|
pub fn test«("Test argument") {
|
|
another_test(1, 2, 3);
|
|
}
|
|
"#});
|
|
}
|
|
}
|