diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index c16e4a6ddb..5ab7c39585 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -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); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 29e009fdf8..972c59a583 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -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; diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 4f3580da07..dde77bb356 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -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 { diff --git a/crates/editor/src/rainbow_brackets.rs b/crates/editor/src/rainbow_brackets.rs new file mode 100644 index 0000000000..3af46d2335 --- /dev/null +++ b/crates/editor/src/rainbow_brackets.rs @@ -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, +) -> Option>>> { + let language = buffer_snapshot.language()?; + let rainbow_config = language.grammar()?.rainbow_config.as_ref()?; + + let mut highlights_by_level: HashMap>> = HashMap::new(); + + // Similar to Helix's RainbowScope structure + #[derive(Debug)] + struct RainbowScope { + end_byte: usize, + node: Option, // node ID + level: usize, + } + + let mut scope_stack = Vec::::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, +) { + // 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::(multi_buffer_ranges, style, cx), + 1 => editor.highlight_text::(multi_buffer_ranges, style, cx), + 2 => editor.highlight_text::(multi_buffer_ranges, style, cx), + 3 => editor.highlight_text::(multi_buffer_ranges, style, cx), + 4 => editor.highlight_text::(multi_buffer_ranges, style, cx), + 5 => editor.highlight_text::(multi_buffer_ranges, style, cx), + 6 => editor.highlight_text::(multi_buffer_ranges, style, cx), + 7 => editor.highlight_text::(multi_buffer_ranges, style, cx), + 8 => editor.highlight_text::(multi_buffer_ranges, style, cx), + _ => editor.highlight_text::(multi_buffer_ranges, style, cx), + } + } + } + } +} + +fn clear_current_rainbow_highlights(editor: &mut Editor, cx: &mut Context) { + editor.clear_highlights::(cx); + editor.clear_highlights::(cx); + editor.clear_highlights::(cx); + editor.clear_highlights::(cx); + editor.clear_highlights::(cx); + editor.clear_highlights::(cx); + editor.clear_highlights::(cx); + editor.clear_highlights::(cx); + editor.clear_highlights::(cx); + editor.clear_highlights::(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; diff --git a/crates/editor/src/rainbow_brackets_tests.rs b/crates/editor/src/rainbow_brackets_tests.rs new file mode 100644 index 0000000000..f3d69e7d80 --- /dev/null +++ b/crates/editor/src/rainbow_brackets_tests.rs @@ -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()); + } + } +} diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 7ae77c9141..3e2f671ed7 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -1104,6 +1104,7 @@ pub struct Grammar { pub embedding_config: Option, pub(crate) injection_config: Option, pub(crate) override_config: Option, + pub rainbow_config: Option, pub(crate) debug_variables_config: Option, pub(crate) highlight_map: Mutex, } @@ -1246,6 +1247,13 @@ struct BracketsPatternConfig { newline_only: bool, } +pub struct RainbowConfig { + pub query: Query, + pub scope_capture_ix: Option, + pub bracket_capture_ix: Option, + pub include_children_patterns: HashSet, +} + 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 { 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 { + 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()?) } diff --git a/crates/language/src/language_registry.rs b/crates/language/src/language_registry.rs index 4f07240e44..ea01d2bd9d 100644 --- a/crates/language/src/language_registry.rs +++ b/crates/language/src/language_registry.rs @@ -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. diff --git a/crates/languages/src/bash/brackets.scm b/crates/languages/src/bash/brackets.scm index 5ae73cdda7..f8da56c2e2 100644 --- a/crates/languages/src/bash/brackets.scm +++ b/crates/languages/src/bash/brackets.scm @@ -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 \ No newline at end of file diff --git a/crates/languages/src/c/brackets.scm b/crates/languages/src/c/brackets.scm index 2f886c4240..f5833738e6 100644 --- a/crates/languages/src/c/brackets.scm +++ b/crates/languages/src/c/brackets.scm @@ -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 \ No newline at end of file diff --git a/crates/languages/src/cpp/brackets.scm b/crates/languages/src/cpp/brackets.scm index 2f886c4240..b0dba3f7af 100644 --- a/crates/languages/src/cpp/brackets.scm +++ b/crates/languages/src/cpp/brackets.scm @@ -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 \ No newline at end of file diff --git a/crates/languages/src/css/brackets.scm b/crates/languages/src/css/brackets.scm index 2f886c4240..c2bcebbbc8 100644 --- a/crates/languages/src/css/brackets.scm +++ b/crates/languages/src/css/brackets.scm @@ -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 \ No newline at end of file diff --git a/crates/languages/src/go/brackets.scm b/crates/languages/src/go/brackets.scm index 0ced37682d..c0bf5df30f 100644 --- a/crates/languages/src/go/brackets.scm +++ b/crates/languages/src/go/brackets.scm @@ -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 \ No newline at end of file diff --git a/crates/languages/src/javascript/brackets.scm b/crates/languages/src/javascript/brackets.scm index 66bf14f137..49495150d7 100644 --- a/crates/languages/src/javascript/brackets.scm +++ b/crates/languages/src/javascript/brackets.scm @@ -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 \ No newline at end of file diff --git a/crates/languages/src/json/brackets.scm b/crates/languages/src/json/brackets.scm index 9e8c9cd93c..e5799ea751 100644 --- a/crates/languages/src/json/brackets.scm +++ b/crates/languages/src/json/brackets.scm @@ -1,3 +1,16 @@ +; Bracket matching pairs ("[" @open "]" @close) ("{" @open "}" @close) ("\"" @open "\"" @close) + +; Rainbow bracket scopes +[ + (object) + (array) +] @rainbow.scope + +; Rainbow brackets +[ + "[" "]" + "{" "}" +] @rainbow.bracket \ No newline at end of file diff --git a/crates/languages/src/jsonc/brackets.scm b/crates/languages/src/jsonc/brackets.scm index 9e8c9cd93c..e5799ea751 100644 --- a/crates/languages/src/jsonc/brackets.scm +++ b/crates/languages/src/jsonc/brackets.scm @@ -1,3 +1,16 @@ +; Bracket matching pairs ("[" @open "]" @close) ("{" @open "}" @close) ("\"" @open "\"" @close) + +; Rainbow bracket scopes +[ + (object) + (array) +] @rainbow.scope + +; Rainbow brackets +[ + "[" "]" + "{" "}" +] @rainbow.bracket \ No newline at end of file diff --git a/crates/languages/src/python/brackets.scm b/crates/languages/src/python/brackets.scm index be68033587..581329ab83 100644 --- a/crates/languages/src/python/brackets.scm +++ b/crates/languages/src/python/brackets.scm @@ -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 \ No newline at end of file diff --git a/crates/languages/src/regex/brackets.scm b/crates/languages/src/regex/brackets.scm index 191fd9c084..df93f14b75 100644 --- a/crates/languages/src/regex/brackets.scm +++ b/crates/languages/src/regex/brackets.scm @@ -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 \ No newline at end of file diff --git a/crates/languages/src/rust/brackets.scm b/crates/languages/src/rust/brackets.scm index 0bf19b8085..b5692b8e83 100644 --- a/crates/languages/src/rust/brackets.scm +++ b/crates/languages/src/rust/brackets.scm @@ -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 diff --git a/crates/languages/src/tsx/brackets.scm b/crates/languages/src/tsx/brackets.scm index 359ae87aa2..1b20cad20b 100644 --- a/crates/languages/src/tsx/brackets.scm +++ b/crates/languages/src/tsx/brackets.scm @@ -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) \ No newline at end of file diff --git a/crates/languages/src/typescript/brackets.scm b/crates/languages/src/typescript/brackets.scm index 48afefeef0..be415900b7 100644 --- a/crates/languages/src/typescript/brackets.scm +++ b/crates/languages/src/typescript/brackets.scm @@ -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 \ No newline at end of file