Merge 99a44bac2b
into b1b60bb7fe
This commit is contained in:
commit
05e7b6e133
20 changed files with 1034 additions and 3 deletions
|
@ -979,7 +979,18 @@ impl DisplaySnapshot {
|
|||
}
|
||||
}
|
||||
|
||||
if let Some(highlight_style) = highlight_style.as_mut() {
|
||||
// If the highlight has a color with full opacity (alpha = 1.0),
|
||||
// it should completely replace the syntax highlight color
|
||||
if let Some(color) = processed_highlight.color {
|
||||
if color.a >= 1.0 {
|
||||
// Replace syntax highlighting entirely with our highlight
|
||||
highlight_style = Some(processed_highlight);
|
||||
} else if let Some(highlight_style) = highlight_style.as_mut() {
|
||||
highlight_style.highlight(processed_highlight);
|
||||
} else {
|
||||
highlight_style = Some(processed_highlight);
|
||||
}
|
||||
} else if let Some(highlight_style) = highlight_style.as_mut() {
|
||||
highlight_style.highlight(processed_highlight);
|
||||
} else {
|
||||
highlight_style = Some(processed_highlight);
|
||||
|
|
|
@ -35,6 +35,7 @@ mod mouse_context_menu;
|
|||
pub mod movement;
|
||||
mod persistence;
|
||||
mod proposed_changes_editor;
|
||||
mod rainbow_brackets;
|
||||
mod rust_analyzer_ext;
|
||||
pub mod scroll;
|
||||
mod selections_collection;
|
||||
|
|
|
@ -28,6 +28,7 @@ use crate::{
|
|||
inlay_hint_settings,
|
||||
items::BufferSearchHighlights,
|
||||
mouse_context_menu::{self, MenuPosition},
|
||||
rainbow_brackets,
|
||||
scroll::{ActiveScrollbarState, ScrollbarThumbState, scroll_amount::ScrollAmount},
|
||||
};
|
||||
use buffer_diff::{DiffHunkStatus, DiffHunkStatusKind};
|
||||
|
@ -9043,8 +9044,11 @@ impl Element for EditorElement {
|
|||
diff_hunk_control_bounds,
|
||||
});
|
||||
|
||||
self.editor.update(cx, |editor, _| {
|
||||
editor.last_position_map = Some(position_map.clone())
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.last_position_map = Some(position_map.clone());
|
||||
|
||||
// Refresh rainbow brackets for the visible range
|
||||
rainbow_brackets::refresh_rainbow_bracket_highlights(editor, window, cx);
|
||||
});
|
||||
|
||||
EditorLayout {
|
||||
|
|
223
crates/editor/src/rainbow_brackets.rs
Normal file
223
crates/editor/src/rainbow_brackets.rs
Normal file
|
@ -0,0 +1,223 @@
|
|||
use crate::Editor;
|
||||
use gpui::{Context, HighlightStyle, Window};
|
||||
use language::{Bias, BufferSnapshot};
|
||||
use std::collections::HashMap;
|
||||
use std::ops::Range;
|
||||
use text::ToOffset;
|
||||
use theme::ActiveTheme;
|
||||
|
||||
/// Compute rainbow bracket highlights for the visible range
|
||||
pub fn compute_rainbow_brackets_for_range(
|
||||
buffer_snapshot: &BufferSnapshot,
|
||||
range: Range<usize>,
|
||||
) -> Option<HashMap<usize, Vec<Range<usize>>>> {
|
||||
let language = buffer_snapshot.language()?;
|
||||
let rainbow_config = language.grammar()?.rainbow_config.as_ref()?;
|
||||
|
||||
let mut highlights_by_level: HashMap<usize, Vec<Range<usize>>> = HashMap::new();
|
||||
|
||||
// Similar to Helix's RainbowScope structure
|
||||
#[derive(Debug)]
|
||||
struct RainbowScope {
|
||||
end_byte: usize,
|
||||
node: Option<usize>, // node ID
|
||||
level: usize,
|
||||
}
|
||||
|
||||
let mut scope_stack = Vec::<RainbowScope>::new();
|
||||
|
||||
// Use the proper tree-sitter query matching API
|
||||
let mut matches = buffer_snapshot.matches(range, |grammar| {
|
||||
grammar.rainbow_config.as_ref().map(|c| &c.query)
|
||||
});
|
||||
|
||||
// Process all matches in order
|
||||
while let Some(mat) = matches.peek() {
|
||||
let byte_range = mat.captures[0].node.byte_range();
|
||||
|
||||
// Pop any scopes that end before this capture begins
|
||||
while scope_stack
|
||||
.last()
|
||||
.is_some_and(|scope| byte_range.start >= scope.end_byte)
|
||||
{
|
||||
scope_stack.pop();
|
||||
}
|
||||
|
||||
// Check which capture this is
|
||||
let is_scope_capture = rainbow_config
|
||||
.scope_capture_ix
|
||||
.map_or(false, |ix| mat.captures.iter().any(|c| c.index == ix));
|
||||
let is_bracket_capture = rainbow_config
|
||||
.bracket_capture_ix
|
||||
.map_or(false, |ix| mat.captures.iter().any(|c| c.index == ix));
|
||||
|
||||
if is_scope_capture {
|
||||
// Process scope capture
|
||||
if let Some(scope_capture) = rainbow_config
|
||||
.scope_capture_ix
|
||||
.and_then(|ix| mat.captures.iter().find(|c| c.index == ix))
|
||||
{
|
||||
let node = scope_capture.node;
|
||||
let byte_range = node.byte_range();
|
||||
|
||||
scope_stack.push(RainbowScope {
|
||||
end_byte: byte_range.end,
|
||||
node: if rainbow_config
|
||||
.include_children_patterns
|
||||
.contains(&mat.pattern_index)
|
||||
{
|
||||
None
|
||||
} else {
|
||||
Some(node.id())
|
||||
},
|
||||
level: scope_stack.len(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if is_bracket_capture {
|
||||
// Process bracket capture
|
||||
if let Some(bracket_capture) = rainbow_config
|
||||
.bracket_capture_ix
|
||||
.and_then(|ix| mat.captures.iter().find(|c| c.index == ix))
|
||||
{
|
||||
let node = bracket_capture.node;
|
||||
let byte_range = node.byte_range();
|
||||
|
||||
if let Some(scope) = scope_stack.last() {
|
||||
// Check if this bracket should be highlighted
|
||||
let should_highlight = if let Some(scope_node_id) = scope.node {
|
||||
// Only highlight if bracket is a direct child of the scope node
|
||||
node.parent()
|
||||
.map_or(false, |parent| parent.id() == scope_node_id)
|
||||
} else {
|
||||
// include-children mode: highlight all brackets in this scope
|
||||
true
|
||||
};
|
||||
|
||||
if should_highlight {
|
||||
let level = scope.level % 10;
|
||||
highlights_by_level
|
||||
.entry(level)
|
||||
.or_default()
|
||||
.push(byte_range);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IMPORTANT: Always advance to prevent infinite loop
|
||||
matches.advance();
|
||||
}
|
||||
|
||||
Some(highlights_by_level)
|
||||
}
|
||||
|
||||
/// Rainbow bracket highlighting uses multiple colors to distinguish bracket nesting levels
|
||||
pub fn refresh_rainbow_bracket_highlights(
|
||||
editor: &mut Editor,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Editor>,
|
||||
) {
|
||||
// Clear existing rainbow highlights for all levels
|
||||
clear_current_rainbow_highlights(editor, cx);
|
||||
|
||||
let multi_buffer = editor.buffer().read(cx);
|
||||
let multi_buffer_snapshot = multi_buffer.snapshot(cx);
|
||||
|
||||
// For now, handle only singleton buffers
|
||||
if let Some((_, _, buffer_snapshot)) = multi_buffer_snapshot.as_singleton() {
|
||||
// Compute only for the visible range
|
||||
// Get the display map to find visible rows
|
||||
let display_map = editor.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let scroll_position = editor.scroll_position(cx);
|
||||
let height = editor.visible_line_count().unwrap_or(50.0);
|
||||
|
||||
// Calculate visible display rows
|
||||
let start_row = scroll_position.y.floor() as u32;
|
||||
let end_row =
|
||||
((scroll_position.y + height).ceil() as u32).min(display_map.max_point().row().0);
|
||||
|
||||
// Convert display rows to buffer offsets
|
||||
let start_point = display_map.display_point_to_point(
|
||||
crate::DisplayPoint::new(crate::DisplayRow(start_row), 0),
|
||||
crate::Bias::Left,
|
||||
);
|
||||
let end_point = display_map.display_point_to_point(
|
||||
crate::DisplayPoint::new(crate::DisplayRow(end_row), 0),
|
||||
crate::Bias::Right,
|
||||
);
|
||||
|
||||
let start_offset = start_point.to_offset(buffer_snapshot);
|
||||
let end_offset = end_point.to_offset(buffer_snapshot);
|
||||
|
||||
if let Some(highlights_by_level) =
|
||||
compute_rainbow_brackets_for_range(buffer_snapshot, start_offset..end_offset)
|
||||
{
|
||||
// Use Theme's accent colors for rainbow brackets
|
||||
let accent_colors = cx.theme().accents().clone();
|
||||
|
||||
// Apply highlights by level
|
||||
for (level, ranges) in highlights_by_level {
|
||||
// Convert text ranges to multi-buffer anchors
|
||||
let multi_buffer_ranges: Vec<_> = ranges
|
||||
.into_iter()
|
||||
.map(|range| {
|
||||
let start = multi_buffer_snapshot.anchor_at(range.start, Bias::Left);
|
||||
let end = multi_buffer_snapshot.anchor_at(range.end, Bias::Right);
|
||||
start..end
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Get color from theme accents based on level
|
||||
let color = accent_colors.color_for_index(level as u32);
|
||||
let style = HighlightStyle {
|
||||
color: Some(color),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
match level {
|
||||
0 => editor.highlight_text::<RainbowLevel0>(multi_buffer_ranges, style, cx),
|
||||
1 => editor.highlight_text::<RainbowLevel1>(multi_buffer_ranges, style, cx),
|
||||
2 => editor.highlight_text::<RainbowLevel2>(multi_buffer_ranges, style, cx),
|
||||
3 => editor.highlight_text::<RainbowLevel3>(multi_buffer_ranges, style, cx),
|
||||
4 => editor.highlight_text::<RainbowLevel4>(multi_buffer_ranges, style, cx),
|
||||
5 => editor.highlight_text::<RainbowLevel5>(multi_buffer_ranges, style, cx),
|
||||
6 => editor.highlight_text::<RainbowLevel6>(multi_buffer_ranges, style, cx),
|
||||
7 => editor.highlight_text::<RainbowLevel7>(multi_buffer_ranges, style, cx),
|
||||
8 => editor.highlight_text::<RainbowLevel8>(multi_buffer_ranges, style, cx),
|
||||
_ => editor.highlight_text::<RainbowLevel9>(multi_buffer_ranges, style, cx),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_current_rainbow_highlights(editor: &mut Editor, cx: &mut Context<Editor>) {
|
||||
editor.clear_highlights::<RainbowLevel0>(cx);
|
||||
editor.clear_highlights::<RainbowLevel1>(cx);
|
||||
editor.clear_highlights::<RainbowLevel2>(cx);
|
||||
editor.clear_highlights::<RainbowLevel3>(cx);
|
||||
editor.clear_highlights::<RainbowLevel4>(cx);
|
||||
editor.clear_highlights::<RainbowLevel5>(cx);
|
||||
editor.clear_highlights::<RainbowLevel6>(cx);
|
||||
editor.clear_highlights::<RainbowLevel7>(cx);
|
||||
editor.clear_highlights::<RainbowLevel8>(cx);
|
||||
editor.clear_highlights::<RainbowLevel9>(cx);
|
||||
}
|
||||
|
||||
// Marker types for different rainbow levels
|
||||
enum RainbowLevel0 {}
|
||||
enum RainbowLevel1 {}
|
||||
enum RainbowLevel2 {}
|
||||
enum RainbowLevel3 {}
|
||||
enum RainbowLevel4 {}
|
||||
enum RainbowLevel5 {}
|
||||
enum RainbowLevel6 {}
|
||||
enum RainbowLevel7 {}
|
||||
enum RainbowLevel8 {}
|
||||
enum RainbowLevel9 {}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "rainbow_brackets_tests.rs"]
|
||||
mod tests;
|
341
crates/editor/src/rainbow_brackets_tests.rs
Normal file
341
crates/editor/src/rainbow_brackets_tests.rs
Normal file
|
@ -0,0 +1,341 @@
|
|||
use crate::editor_tests::init_test;
|
||||
use crate::rainbow_brackets::compute_rainbow_brackets_for_range;
|
||||
use crate::test::editor_lsp_test_context::EditorLspTestContext;
|
||||
use gpui::TestAppContext;
|
||||
use indoc::indoc;
|
||||
use language::{BracketPair, BracketPairConfig, Language, LanguageConfig, LanguageMatcher};
|
||||
|
||||
/// Helper function to create a test language with bracket configuration
|
||||
fn create_test_language() -> Language {
|
||||
// Use a simpler language without tree-sitter queries for basic tests
|
||||
Language::new(
|
||||
LanguageConfig {
|
||||
name: "TestLang".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["test".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,
|
||||
},
|
||||
BracketPair {
|
||||
start: "[".to_string(),
|
||||
end: "]".to_string(),
|
||||
close: false,
|
||||
surround: false,
|
||||
newline: true,
|
||||
},
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
None, // No tree-sitter grammar for simple tests
|
||||
)
|
||||
}
|
||||
|
||||
// Test that the empty buffer test works correctly
|
||||
#[gpui::test]
|
||||
async fn test_empty_buffer(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let language = create_test_language();
|
||||
let mut cx = EditorLspTestContext::new(language, Default::default(), cx).await;
|
||||
|
||||
cx.set_state("ˇ");
|
||||
|
||||
let buffer = cx.editor(|editor, _, cx| {
|
||||
let multi_buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
multi_buffer.as_singleton().unwrap().2.clone()
|
||||
});
|
||||
let highlights = compute_rainbow_brackets_for_range(&buffer, 0..0);
|
||||
|
||||
// Without tree-sitter, should return None
|
||||
assert!(highlights.is_none());
|
||||
}
|
||||
|
||||
// Test simple single bracket pair detection
|
||||
#[gpui::test]
|
||||
async fn test_simple_bracket_detection(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let language = create_test_language();
|
||||
let mut cx = EditorLspTestContext::new(language, Default::default(), cx).await;
|
||||
|
||||
// Test single bracket pair
|
||||
cx.set_state(indoc! {r#"
|
||||
fn test() {
|
||||
printlnˇ!("Hello");
|
||||
}
|
||||
"#});
|
||||
|
||||
let buffer = cx.editor(|editor, _, cx| {
|
||||
let multi_buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
multi_buffer.as_singleton().unwrap().2.clone()
|
||||
});
|
||||
let highlights = compute_rainbow_brackets_for_range(&buffer, 0..buffer.len());
|
||||
|
||||
// Without tree-sitter, should return None
|
||||
assert!(highlights.is_none());
|
||||
}
|
||||
|
||||
// Test nested bracket levels
|
||||
#[gpui::test]
|
||||
async fn test_nested_bracket_levels(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let language = create_test_language();
|
||||
let mut cx = EditorLspTestContext::new(language, Default::default(), cx).await;
|
||||
|
||||
// Test nested brackets with different types
|
||||
cx.set_state(indoc! {r#"
|
||||
fn test() {
|
||||
let array = [1, 2, 3];
|
||||
if (condition) {
|
||||
processˇ(array[0]);
|
||||
}
|
||||
}
|
||||
"#});
|
||||
|
||||
let buffer = cx.editor(|editor, _, cx| {
|
||||
let multi_buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
multi_buffer.as_singleton().unwrap().2.clone()
|
||||
});
|
||||
let highlights = compute_rainbow_brackets_for_range(&buffer, 0..buffer.len());
|
||||
|
||||
// Without tree-sitter, should return None
|
||||
assert!(highlights.is_none());
|
||||
}
|
||||
|
||||
// Test level wrapping
|
||||
#[gpui::test]
|
||||
async fn test_level_wrapping(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let language = create_test_language();
|
||||
let mut cx = EditorLspTestContext::new(language, Default::default(), cx).await;
|
||||
|
||||
// Test that levels wrap around after 10 (0-9)
|
||||
let deeply_nested = r#"
|
||||
fn testˇ() { // Level 0
|
||||
{ // Level 1
|
||||
{ // Level 2
|
||||
{ // Level 3
|
||||
{ // Level 4
|
||||
{ // Level 5
|
||||
{ // Level 6
|
||||
{ // Level 7
|
||||
{ // Level 8
|
||||
{ // Level 9
|
||||
{ // Level 0 (wrapped)
|
||||
println!("Deep!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
cx.set_state(deeply_nested);
|
||||
|
||||
let buffer = cx.editor(|editor, _, cx| {
|
||||
let multi_buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
multi_buffer.as_singleton().unwrap().2.clone()
|
||||
});
|
||||
let highlights = compute_rainbow_brackets_for_range(&buffer, 0..buffer.len());
|
||||
|
||||
// Without tree-sitter, should return None
|
||||
assert!(highlights.is_none());
|
||||
}
|
||||
|
||||
// Test mixed bracket types
|
||||
#[gpui::test]
|
||||
async fn test_mixed_bracket_types(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let language = create_test_language();
|
||||
let mut cx = EditorLspTestContext::new(language, Default::default(), cx).await;
|
||||
|
||||
cx.set_state(indoc! {r#"
|
||||
fn test() {
|
||||
let tuple = (1, 2, 3);
|
||||
let array = [4, 5, 6];
|
||||
let result = computeˇ({
|
||||
value: tuple.0 + array[0]
|
||||
});
|
||||
}
|
||||
"#});
|
||||
|
||||
let buffer = cx.editor(|editor, _, cx| {
|
||||
let multi_buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
multi_buffer.as_singleton().unwrap().2.clone()
|
||||
});
|
||||
let highlights = compute_rainbow_brackets_for_range(&buffer, 0..buffer.len());
|
||||
|
||||
// Without tree-sitter, should return None
|
||||
assert!(highlights.is_none());
|
||||
}
|
||||
|
||||
// Test that unclosed brackets are handled gracefully
|
||||
#[gpui::test]
|
||||
async fn test_unclosed_brackets(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let language = create_test_language();
|
||||
let mut cx = EditorLspTestContext::new(language, Default::default(), cx).await;
|
||||
|
||||
// Test with unclosed brackets
|
||||
cx.set_state(indoc! {r#"
|
||||
fn test() {
|
||||
let array = [1, 2, 3; // Missing closing bracket
|
||||
if (condition { // Mixed up brackets
|
||||
printlnˇ!("Test");
|
||||
}
|
||||
// Missing closing brace for function
|
||||
"#});
|
||||
|
||||
let buffer = cx.editor(|editor, _, cx| {
|
||||
let multi_buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
multi_buffer.as_singleton().unwrap().2.clone()
|
||||
});
|
||||
let highlights = compute_rainbow_brackets_for_range(&buffer, 0..buffer.len());
|
||||
|
||||
// Without tree-sitter, should return None
|
||||
assert!(highlights.is_none());
|
||||
}
|
||||
|
||||
// Test brackets in strings and comments
|
||||
#[gpui::test]
|
||||
async fn test_brackets_in_strings_and_comments(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let language = create_test_language();
|
||||
let mut cx = EditorLspTestContext::new(language, Default::default(), cx).await;
|
||||
|
||||
cx.set_state(indoc! {r#"
|
||||
fn test() {
|
||||
// This { bracket } should not be highlighted
|
||||
let string = "Another { bracket } in string";
|
||||
let actual = { valueˇ: 42 };
|
||||
}
|
||||
"#});
|
||||
|
||||
let buffer = cx.editor(|editor, _, cx| {
|
||||
let multi_buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
multi_buffer.as_singleton().unwrap().2.clone()
|
||||
});
|
||||
let highlights = compute_rainbow_brackets_for_range(&buffer, 0..buffer.len());
|
||||
|
||||
// Without tree-sitter, should return None
|
||||
assert!(highlights.is_none());
|
||||
}
|
||||
|
||||
// Test with real Rust language and brackets.scm query
|
||||
#[gpui::test]
|
||||
async fn test_rust_rainbow_brackets(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
// Create a Rust-like language with proper tree-sitter and bracket query
|
||||
let rust_lang = 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,
|
||||
},
|
||||
BracketPair {
|
||||
start: "[".to_string(),
|
||||
end: "]".to_string(),
|
||||
close: false,
|
||||
surround: false,
|
||||
newline: false,
|
||||
},
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::LANGUAGE.into()),
|
||||
)
|
||||
.with_brackets_query(indoc! {r#"
|
||||
; Rainbow bracket scopes for common Rust constructs
|
||||
[(block) (match_block) (use_list) (field_initializer_list)] @rainbow.scope
|
||||
|
||||
; Rainbow brackets - actual bracket characters
|
||||
["{" "}" "(" ")" "[" "]"] @rainbow.bracket
|
||||
"#})
|
||||
.unwrap();
|
||||
|
||||
let mut cx = EditorLspTestContext::new(rust_lang, Default::default(), cx).await;
|
||||
|
||||
// Test real Rust code
|
||||
cx.set_state(indoc! {r#"
|
||||
fn process_data() {
|
||||
let array = [1, 2, 3];
|
||||
if true {
|
||||
println!ˇ("Hello");
|
||||
}
|
||||
}
|
||||
"#});
|
||||
|
||||
let buffer = cx.editor(|editor, _, cx| {
|
||||
let multi_buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
multi_buffer.as_singleton().unwrap().2.clone()
|
||||
});
|
||||
|
||||
let highlights = compute_rainbow_brackets_for_range(&buffer, 0..buffer.len());
|
||||
|
||||
// With proper tree-sitter setup, we should get highlights
|
||||
assert!(highlights.is_some());
|
||||
let highlights = highlights.unwrap();
|
||||
|
||||
// Verify we have some levels
|
||||
assert!(
|
||||
!highlights.is_empty(),
|
||||
"Should have at least one level of brackets"
|
||||
);
|
||||
|
||||
// Verify structure is correct
|
||||
for (level, ranges) in highlights {
|
||||
assert!(level < 10, "Level {} should be < 10", level);
|
||||
for range in ranges {
|
||||
assert!(range.start < range.end);
|
||||
assert!(range.end <= buffer.len());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1104,6 +1104,7 @@ pub struct Grammar {
|
|||
pub embedding_config: Option<EmbeddingConfig>,
|
||||
pub(crate) injection_config: Option<InjectionConfig>,
|
||||
pub(crate) override_config: Option<OverrideConfig>,
|
||||
pub rainbow_config: Option<RainbowConfig>,
|
||||
pub(crate) debug_variables_config: Option<DebugVariablesConfig>,
|
||||
pub(crate) highlight_map: Mutex<HighlightMap>,
|
||||
}
|
||||
|
@ -1246,6 +1247,13 @@ struct BracketsPatternConfig {
|
|||
newline_only: bool,
|
||||
}
|
||||
|
||||
pub struct RainbowConfig {
|
||||
pub query: Query,
|
||||
pub scope_capture_ix: Option<u32>,
|
||||
pub bracket_capture_ix: Option<u32>,
|
||||
pub include_children_patterns: HashSet<usize>,
|
||||
}
|
||||
|
||||
pub struct DebugVariablesConfig {
|
||||
pub query: Query,
|
||||
pub objects_by_capture_ix: Vec<(u32, DebuggerTextObject)>,
|
||||
|
@ -1281,6 +1289,7 @@ impl Language {
|
|||
override_config: None,
|
||||
redactions_config: None,
|
||||
runnable_config: None,
|
||||
rainbow_config: None,
|
||||
error_query: Query::new(&ts_language, "(ERROR) @error").ok(),
|
||||
debug_variables_config: None,
|
||||
ts_language,
|
||||
|
@ -1358,6 +1367,7 @@ impl Language {
|
|||
.with_text_object_query(query.as_ref())
|
||||
.context("Error loading textobject query")?;
|
||||
}
|
||||
// Rainbow queries are now loaded as part of brackets.scm
|
||||
if let Some(query) = queries.debugger {
|
||||
self = self
|
||||
.with_debug_variables_query(query.as_ref())
|
||||
|
@ -1499,16 +1509,24 @@ impl Language {
|
|||
|
||||
pub fn with_brackets_query(mut self, source: &str) -> Result<Self> {
|
||||
let grammar = self.grammar_mut().context("cannot mutate grammar")?;
|
||||
|
||||
// Check if we have rainbow captures in the query
|
||||
let query = Query::new(&grammar.ts_language, source)?;
|
||||
let mut open_capture_ix = None;
|
||||
let mut close_capture_ix = None;
|
||||
let mut rainbow_scope_capture_ix = None;
|
||||
let mut rainbow_bracket_capture_ix = None;
|
||||
get_capture_indices(
|
||||
&query,
|
||||
&mut [
|
||||
("open", &mut open_capture_ix),
|
||||
("close", &mut close_capture_ix),
|
||||
("rainbow.scope", &mut rainbow_scope_capture_ix),
|
||||
("rainbow.bracket", &mut rainbow_bracket_capture_ix),
|
||||
],
|
||||
);
|
||||
|
||||
// Process bracket matching patterns
|
||||
let patterns = (0..query.pattern_count())
|
||||
.map(|ix| {
|
||||
let mut config = BracketsPatternConfig::default();
|
||||
|
@ -1520,6 +1538,8 @@ impl Language {
|
|||
config
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Set brackets config if we have bracket matching captures
|
||||
if let Some((open_capture_ix, close_capture_ix)) = open_capture_ix.zip(close_capture_ix) {
|
||||
grammar.brackets_config = Some(BracketsConfig {
|
||||
query,
|
||||
|
@ -1528,6 +1548,28 @@ impl Language {
|
|||
patterns,
|
||||
});
|
||||
}
|
||||
|
||||
// Set rainbow config if we have rainbow captures
|
||||
// We need to create a new query for rainbow config since we can't share the query for bracket highlights
|
||||
if rainbow_scope_capture_ix.is_some() || rainbow_bracket_capture_ix.is_some() {
|
||||
let rainbow_query = Query::new(&grammar.ts_language, source)?;
|
||||
let mut include_children_patterns = HashSet::default();
|
||||
for ix in 0..rainbow_query.pattern_count() {
|
||||
for setting in rainbow_query.property_settings(ix) {
|
||||
if setting.key.as_ref() == "rainbow.include-children" {
|
||||
include_children_patterns.insert(ix);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
grammar.rainbow_config = Some(RainbowConfig {
|
||||
query: rainbow_query,
|
||||
scope_capture_ix: rainbow_scope_capture_ix,
|
||||
bracket_capture_ix: rainbow_bracket_capture_ix,
|
||||
include_children_patterns,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
|
@ -1730,6 +1772,38 @@ impl Language {
|
|||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn with_rainbow_query(mut self, source: &str) -> Result<Self> {
|
||||
let grammar = self.grammar_mut().context("cannot mutate grammar")?;
|
||||
let query = Query::new(&grammar.ts_language, source)?;
|
||||
let mut scope_capture_ix = None;
|
||||
let mut bracket_capture_ix = None;
|
||||
get_capture_indices(
|
||||
&query,
|
||||
&mut [
|
||||
("rainbow.scope", &mut scope_capture_ix),
|
||||
("rainbow.bracket", &mut bracket_capture_ix),
|
||||
],
|
||||
);
|
||||
|
||||
let mut include_children_patterns = HashSet::default();
|
||||
for ix in 0..query.pattern_count() {
|
||||
for setting in query.property_settings(ix) {
|
||||
if setting.key.as_ref() == "rainbow.include-children" {
|
||||
include_children_patterns.insert(ix);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
grammar.rainbow_config = Some(RainbowConfig {
|
||||
query,
|
||||
scope_capture_ix,
|
||||
bracket_capture_ix,
|
||||
include_children_patterns,
|
||||
});
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
fn grammar_mut(&mut self) -> Option<&mut Grammar> {
|
||||
Arc::get_mut(self.grammar.as_mut()?)
|
||||
}
|
||||
|
|
|
@ -232,6 +232,7 @@ pub const QUERY_FILENAME_PREFIXES: &[(
|
|||
("runnables", |q| &mut q.runnables),
|
||||
("debugger", |q| &mut q.debugger),
|
||||
("textobjects", |q| &mut q.text_objects),
|
||||
// Rainbow queries are now loaded as part of brackets.scm
|
||||
];
|
||||
|
||||
/// Tree-sitter language queries for a given language.
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
; Bracket matching pairs
|
||||
("(" @open ")" @close)
|
||||
("[" @open "]" @close)
|
||||
("{" @open "}" @close)
|
||||
|
@ -10,3 +11,26 @@
|
|||
((if_statement ("then" @open) (elif_clause ("elif" @close))) (#set! newline.only))
|
||||
((if_statement ("then" @open) (else_clause ("else" @close))) (#set! newline.only))
|
||||
((if_statement ("then" @open "fi" @close)) (#set! newline.only))
|
||||
|
||||
; Rainbow bracket scopes
|
||||
[
|
||||
(function_definition)
|
||||
(compound_statement)
|
||||
(subshell)
|
||||
(test_command)
|
||||
(subscript)
|
||||
(parenthesized_expression)
|
||||
(array)
|
||||
(expansion)
|
||||
(command_substitution)
|
||||
] @rainbow.scope
|
||||
|
||||
; Rainbow brackets
|
||||
[
|
||||
"(" ")"
|
||||
"((" "))"
|
||||
"${" "$("
|
||||
"{" "}"
|
||||
"[" "]"
|
||||
"[[" "]]"
|
||||
] @rainbow.bracket
|
|
@ -1,5 +1,38 @@
|
|||
; Bracket matching pairs
|
||||
("(" @open ")" @close)
|
||||
("[" @open "]" @close)
|
||||
("{" @open "}" @close)
|
||||
("\"" @open "\"" @close)
|
||||
("'" @open "'" @close)
|
||||
|
||||
; Rainbow bracket scopes
|
||||
[
|
||||
(preproc_params)
|
||||
(preproc_defined)
|
||||
(argument_list)
|
||||
(attribute_specifier)
|
||||
(ms_declspec_modifier)
|
||||
(declaration_list)
|
||||
(parenthesized_declarator)
|
||||
(parenthesized_expression)
|
||||
(abstract_parenthesized_declarator)
|
||||
(array_declarator)
|
||||
(compound_statement)
|
||||
(initializer_list)
|
||||
(compound_literal_expression)
|
||||
(enumerator_list)
|
||||
(field_declaration_list)
|
||||
(parameter_list)
|
||||
(for_statement)
|
||||
(macro_type_specifier)
|
||||
(subscript_expression)
|
||||
(subscript_designator)
|
||||
(cast_expression)
|
||||
] @rainbow.scope
|
||||
|
||||
; Rainbow brackets
|
||||
[
|
||||
"(" ")"
|
||||
"{" "}"
|
||||
"[" "]"
|
||||
] @rainbow.bracket
|
|
@ -1,5 +1,58 @@
|
|||
; Bracket matching pairs
|
||||
("(" @open ")" @close)
|
||||
("[" @open "]" @close)
|
||||
("{" @open "}" @close)
|
||||
("\"" @open "\"" @close)
|
||||
("'" @open "'" @close)
|
||||
|
||||
; Rainbow bracket scopes
|
||||
[
|
||||
; c
|
||||
(preproc_params)
|
||||
(preproc_defined)
|
||||
(argument_list)
|
||||
(attribute_specifier)
|
||||
(ms_declspec_modifier)
|
||||
(declaration_list)
|
||||
(parenthesized_declarator)
|
||||
(parenthesized_expression)
|
||||
(abstract_parenthesized_declarator)
|
||||
(array_declarator)
|
||||
(compound_statement)
|
||||
(initializer_list)
|
||||
(compound_literal_expression)
|
||||
(enumerator_list)
|
||||
(field_declaration_list)
|
||||
(parameter_list)
|
||||
(for_statement)
|
||||
; (macro_type_specifier) - not part of cpp
|
||||
(subscript_expression)
|
||||
(subscript_designator)
|
||||
(cast_expression)
|
||||
|
||||
; cpp
|
||||
(decltype)
|
||||
(explicit_function_specifier)
|
||||
(template_parameter_list)
|
||||
(template_argument_list)
|
||||
(parameter_list)
|
||||
(argument_list)
|
||||
(structured_binding_declarator)
|
||||
(noexcept)
|
||||
(throw_specifier)
|
||||
(static_assert_declaration)
|
||||
(condition_clause)
|
||||
(for_range_loop)
|
||||
(new_declarator)
|
||||
(delete_expression "[" "]")
|
||||
(lambda_capture_specifier)
|
||||
(sizeof_expression)
|
||||
] @rainbow.scope
|
||||
|
||||
; Rainbow brackets
|
||||
[
|
||||
"(" ")"
|
||||
"{" "}"
|
||||
"[" "]"
|
||||
"<" ">"
|
||||
] @rainbow.bracket
|
|
@ -1,5 +1,24 @@
|
|||
; Bracket matching pairs
|
||||
("(" @open ")" @close)
|
||||
("[" @open "]" @close)
|
||||
("{" @open "}" @close)
|
||||
("\"" @open "\"" @close)
|
||||
("'" @open "'" @close)
|
||||
|
||||
; Rainbow bracket scopes
|
||||
[
|
||||
(keyframe_block_list)
|
||||
(block)
|
||||
(attribute_selector)
|
||||
(feature_query)
|
||||
(parenthesized_query)
|
||||
(selector_query)
|
||||
(parenthesized_value)
|
||||
(arguments)
|
||||
] @rainbow.scope
|
||||
|
||||
; Rainbow brackets
|
||||
[
|
||||
"{" "}"
|
||||
"(" ")"
|
||||
] @rainbow.bracket
|
|
@ -1,6 +1,43 @@
|
|||
; Bracket matching pairs
|
||||
("(" @open ")" @close)
|
||||
("[" @open "]" @close)
|
||||
("{" @open "}" @close)
|
||||
("\"" @open "\"" @close)
|
||||
("`" @open "`" @close)
|
||||
((rune_literal) @open @close)
|
||||
|
||||
; Rainbow bracket scopes
|
||||
[
|
||||
(import_spec_list)
|
||||
(const_declaration)
|
||||
(var_declaration)
|
||||
(type_parameter_list)
|
||||
(parameter_list)
|
||||
(type_declaration)
|
||||
(parenthesized_type)
|
||||
(type_arguments)
|
||||
(array_type)
|
||||
(implicit_length_array_type)
|
||||
(slice_type)
|
||||
(field_declaration_list)
|
||||
(interface_type)
|
||||
(map_type)
|
||||
(block)
|
||||
(expression_switch_statement)
|
||||
(type_switch_statement)
|
||||
(select_statement)
|
||||
(parenthesized_expression)
|
||||
(argument_list)
|
||||
(index_expression)
|
||||
(slice_expression)
|
||||
(type_assertion_expression)
|
||||
(type_conversion_expression)
|
||||
(literal_value)
|
||||
] @rainbow.scope
|
||||
|
||||
; Rainbow brackets
|
||||
[
|
||||
"(" ")"
|
||||
"[" "]"
|
||||
"{" "}"
|
||||
] @rainbow.bracket
|
|
@ -1,3 +1,4 @@
|
|||
; Bracket matching pairs
|
||||
("(" @open ")" @close)
|
||||
("[" @open "]" @close)
|
||||
("{" @open "}" @close)
|
||||
|
@ -7,3 +8,21 @@
|
|||
("\"" @open "\"" @close)
|
||||
("'" @open "'" @close)
|
||||
("`" @open "`" @close)
|
||||
|
||||
; Rainbow bracket scopes
|
||||
[
|
||||
(object)
|
||||
(array)
|
||||
(arguments)
|
||||
(formal_parameters)
|
||||
(statement_block)
|
||||
(parenthesized_expression)
|
||||
(call_expression)
|
||||
] @rainbow.scope
|
||||
|
||||
; Rainbow brackets
|
||||
[
|
||||
"[" "]"
|
||||
"{" "}"
|
||||
"(" ")"
|
||||
] @rainbow.bracket
|
|
@ -1,3 +1,16 @@
|
|||
; Bracket matching pairs
|
||||
("[" @open "]" @close)
|
||||
("{" @open "}" @close)
|
||||
("\"" @open "\"" @close)
|
||||
|
||||
; Rainbow bracket scopes
|
||||
[
|
||||
(object)
|
||||
(array)
|
||||
] @rainbow.scope
|
||||
|
||||
; Rainbow brackets
|
||||
[
|
||||
"[" "]"
|
||||
"{" "}"
|
||||
] @rainbow.bracket
|
|
@ -1,3 +1,16 @@
|
|||
; Bracket matching pairs
|
||||
("[" @open "]" @close)
|
||||
("{" @open "}" @close)
|
||||
("\"" @open "\"" @close)
|
||||
|
||||
; Rainbow bracket scopes
|
||||
[
|
||||
(object)
|
||||
(array)
|
||||
] @rainbow.scope
|
||||
|
||||
; Rainbow brackets
|
||||
[
|
||||
"[" "]"
|
||||
"{" "}"
|
||||
] @rainbow.bracket
|
|
@ -1,4 +1,36 @@
|
|||
; Bracket matching pairs
|
||||
("(" @open ")" @close)
|
||||
("[" @open "]" @close)
|
||||
("{" @open "}" @close)
|
||||
((string_start) @open (string_end) @close)
|
||||
|
||||
; Rainbow bracket scopes
|
||||
[
|
||||
(future_import_statement)
|
||||
(import_from_statement)
|
||||
(with_clause)
|
||||
(parameters)
|
||||
(parenthesized_list_splat)
|
||||
(argument_list)
|
||||
(tuple_pattern)
|
||||
(list_pattern)
|
||||
(subscript)
|
||||
(list)
|
||||
(set)
|
||||
(tuple)
|
||||
(dictionary)
|
||||
(dictionary_comprehension)
|
||||
(set_comprehension)
|
||||
(list_comprehension)
|
||||
(generator_expression)
|
||||
(parenthesized_expression)
|
||||
(interpolation)
|
||||
(format_expression)
|
||||
] @rainbow.scope
|
||||
|
||||
; Rainbow brackets
|
||||
[
|
||||
"(" ")"
|
||||
"{" "}"
|
||||
"[" "]"
|
||||
] @rainbow.bracket
|
|
@ -1,3 +1,23 @@
|
|||
; Bracket matching pairs
|
||||
("(" @open ")" @close)
|
||||
("[" @open "]" @close)
|
||||
("{" @open "}" @close)
|
||||
|
||||
; Rainbow bracket scopes
|
||||
[
|
||||
(character_class)
|
||||
(anonymous_capturing_group)
|
||||
(named_capturing_group)
|
||||
(non_capturing_group)
|
||||
(count_quantifier)
|
||||
(character_class_escape)
|
||||
] @rainbow.scope
|
||||
|
||||
; Rainbow brackets
|
||||
[
|
||||
"(?" "(?:"
|
||||
"(?<" ">"
|
||||
"(" ")"
|
||||
"[" "]"
|
||||
"{" "}"
|
||||
] @rainbow.bracket
|
|
@ -1,3 +1,4 @@
|
|||
; Bracket matching pairs
|
||||
("(" @open ")" @close)
|
||||
("[" @open "]" @close)
|
||||
("{" @open "}" @close)
|
||||
|
@ -5,3 +6,66 @@
|
|||
("\"" @open "\"" @close)
|
||||
(closure_parameters "|" @open "|" @close)
|
||||
("'" @open "'" @close)
|
||||
|
||||
; Rainbow bracket scopes
|
||||
[
|
||||
; {/}
|
||||
(declaration_list)
|
||||
(field_declaration_list)
|
||||
(field_initializer_list)
|
||||
(enum_variant_list)
|
||||
(block)
|
||||
(match_block)
|
||||
(use_list)
|
||||
(struct_pattern)
|
||||
|
||||
; (/)
|
||||
(ordered_field_declaration_list)
|
||||
(arguments)
|
||||
(parameters)
|
||||
(tuple_type)
|
||||
(tuple_expression)
|
||||
(tuple_pattern)
|
||||
(tuple_struct_pattern)
|
||||
(unit_type)
|
||||
(unit_expression)
|
||||
(visibility_modifier)
|
||||
(parenthesized_expression)
|
||||
(token_repetition_pattern)
|
||||
|
||||
; </>
|
||||
(type_parameters)
|
||||
(type_arguments)
|
||||
(bracketed_type)
|
||||
(for_lifetimes)
|
||||
|
||||
; [/]
|
||||
(array_type)
|
||||
(array_expression)
|
||||
(index_expression)
|
||||
(slice_pattern)
|
||||
|
||||
; attributes #[]
|
||||
(attribute_item)
|
||||
(inner_attribute_item)
|
||||
|
||||
; macros
|
||||
(token_tree_pattern)
|
||||
(macro_definition)
|
||||
|
||||
; closures
|
||||
(closure_parameters)
|
||||
] @rainbow.scope
|
||||
|
||||
; attributes like `#[serde(rename_all = "kebab-case")]`
|
||||
(attribute arguments: (token_tree) @rainbow.scope)
|
||||
|
||||
; Rainbow brackets
|
||||
[
|
||||
"#"
|
||||
"[" "]"
|
||||
"(" ")"
|
||||
"{" "}"
|
||||
"<" ">"
|
||||
"|"
|
||||
] @rainbow.bracket
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
; Bracket matching pairs
|
||||
("(" @open ")" @close)
|
||||
("[" @open "]" @close)
|
||||
("{" @open "}" @close)
|
||||
|
@ -9,3 +10,29 @@
|
|||
("`" @open "`" @close)
|
||||
|
||||
((jsx_element (jsx_opening_element) @open (jsx_closing_element) @close) (#set! newline.only))
|
||||
|
||||
; Rainbow bracket scopes
|
||||
[
|
||||
(object)
|
||||
(array)
|
||||
(arguments)
|
||||
(formal_parameters)
|
||||
(statement_block)
|
||||
(parenthesized_expression)
|
||||
(call_expression)
|
||||
(type_parameters)
|
||||
(type_arguments)
|
||||
(jsx_element)
|
||||
(jsx_self_closing_element)
|
||||
] @rainbow.scope
|
||||
|
||||
; Rainbow brackets
|
||||
[
|
||||
"[" "]"
|
||||
"{" "}"
|
||||
"(" ")"
|
||||
] @rainbow.bracket
|
||||
|
||||
; TypeScript generics (but not JSX tags)
|
||||
(type_parameters ["<" ">"] @rainbow.bracket)
|
||||
(type_arguments ["<" ">"] @rainbow.bracket)
|
|
@ -1,3 +1,4 @@
|
|||
; Bracket matching pairs
|
||||
("(" @open ")" @close)
|
||||
("[" @open "]" @close)
|
||||
("{" @open "}" @close)
|
||||
|
@ -5,3 +6,24 @@
|
|||
("\"" @open "\"" @close)
|
||||
("'" @open "'" @close)
|
||||
("`" @open "`" @close)
|
||||
|
||||
; Rainbow bracket scopes
|
||||
[
|
||||
(object)
|
||||
(array)
|
||||
(arguments)
|
||||
(formal_parameters)
|
||||
(statement_block)
|
||||
(parenthesized_expression)
|
||||
(call_expression)
|
||||
(type_parameters)
|
||||
(type_arguments)
|
||||
] @rainbow.scope
|
||||
|
||||
; Rainbow brackets
|
||||
[
|
||||
"[" "]"
|
||||
"{" "}"
|
||||
"(" ")"
|
||||
"<" ">"
|
||||
] @rainbow.bracket
|
Loading…
Add table
Add a link
Reference in a new issue