From 7aec469f9e667c1512b239e39fb5a56b59c1b094 Mon Sep 17 00:00:00 2001 From: GoldStrikeArch Date: Fri, 1 Aug 2025 15:18:23 +0400 Subject: [PATCH 01/14] feat: initial rainbows bracket pairs version (only javascript) inspired by helix --- crates/editor/src/editor.rs | 3 + crates/editor/src/rainbow_brackets.rs | 198 ++++++++++++++++++++ crates/language/src/language.rs | 46 +++++ crates/language/src/language_registry.rs | 2 + crates/languages/src/javascript/rainbow.scm | 15 ++ crates/languages/src/tsx/rainbow.scm | 23 +++ crates/languages/src/typescript/rainbow.scm | 18 ++ 7 files changed, 305 insertions(+) create mode 100644 crates/editor/src/rainbow_brackets.rs create mode 100644 crates/languages/src/javascript/rainbow.scm create mode 100644 crates/languages/src/tsx/rainbow.scm create mode 100644 crates/languages/src/typescript/rainbow.scm diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 3516eff45c..3701ee506a 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -25,6 +25,7 @@ mod highlight_matching_bracket; mod hover_links; pub mod hover_popover; mod indent_guides; +mod rainbow_brackets; mod inlay_hint_cache; pub mod items; mod jsx_tag_auto_close; @@ -102,6 +103,7 @@ use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file}; pub use hover_popover::hover_markdown_style; use hover_popover::{HoverState, hide_hover}; +use rainbow_brackets::refresh_rainbow_bracket_highlights; use indent_guides::ActiveIndentGuidesState; use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy}; pub use inline_completion::Direction; @@ -3050,6 +3052,7 @@ impl Editor { self.refresh_document_highlights(cx); self.refresh_selected_text_highlights(false, window, cx); refresh_matching_bracket_highlights(self, window, cx); + refresh_rainbow_bracket_highlights(self, window, cx); self.update_visible_inline_completion(window, cx); self.edit_prediction_requires_modifier_in_indent_conflict = true; linked_editing_ranges::refresh_linked_ranges(self, window, cx); diff --git a/crates/editor/src/rainbow_brackets.rs b/crates/editor/src/rainbow_brackets.rs new file mode 100644 index 0000000000..f57cc0c82a --- /dev/null +++ b/crates/editor/src/rainbow_brackets.rs @@ -0,0 +1,198 @@ +use crate::Editor; +use collections::HashMap; +use gpui::{Context, Hsla, Window}; +use language::Bias; +use std::ops::Range; +use text::{Anchor, ToOffset}; + +/// 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 + editor.clear_background_highlights::(cx); + editor.clear_background_highlights::(cx); + editor.clear_background_highlights::(cx); + editor.clear_background_highlights::(cx); + editor.clear_background_highlights::(cx); + editor.clear_background_highlights::(cx); + editor.clear_background_highlights::(cx); + editor.clear_background_highlights::(cx); + editor.clear_background_highlights::(cx); + editor.clear_background_highlights::(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() { + let language = buffer_snapshot.language(); + + if let Some(language) = language { + if let Some(grammar) = language.grammar() { + if let Some(rainbow_config) = &grammar.rainbow_config { + let mut highlights_by_level: HashMap>> = HashMap::new(); + + collect_rainbow_highlights( + buffer_snapshot, + rainbow_config, + &mut highlights_by_level, + ); + + // Apply highlights by level + for (level, ranges) in highlights_by_level { + // Convert text anchors to multi-buffer anchors + let multi_buffer_ranges: Vec<_> = ranges.into_iter() + .map(|range| { + let start_offset = range.start.to_offset(buffer_snapshot); + let end_offset = range.end.to_offset(buffer_snapshot); + let start = multi_buffer_snapshot.anchor_at(start_offset, Bias::Left); + let end = multi_buffer_snapshot.anchor_at(end_offset, Bias::Right); + start..end + }) + .collect(); + + // Create a unique type for each level to avoid conflicts + match level { + 0 => editor.highlight_background::(&multi_buffer_ranges, get_rainbow_color_0, cx), + 1 => editor.highlight_background::(&multi_buffer_ranges, get_rainbow_color_1, cx), + 2 => editor.highlight_background::(&multi_buffer_ranges, get_rainbow_color_2, cx), + 3 => editor.highlight_background::(&multi_buffer_ranges, get_rainbow_color_3, cx), + 4 => editor.highlight_background::(&multi_buffer_ranges, get_rainbow_color_4, cx), + 5 => editor.highlight_background::(&multi_buffer_ranges, get_rainbow_color_5, cx), + 6 => editor.highlight_background::(&multi_buffer_ranges, get_rainbow_color_6, cx), + 7 => editor.highlight_background::(&multi_buffer_ranges, get_rainbow_color_7, cx), + 8 => editor.highlight_background::(&multi_buffer_ranges, get_rainbow_color_8, cx), + _ => editor.highlight_background::(&multi_buffer_ranges, get_rainbow_color_9, cx), + } + } + } + } + } + } +} + +fn collect_rainbow_highlights( + buffer: &language::BufferSnapshot, + _rainbow_config: &language::RainbowConfig, + highlights_by_level: &mut HashMap>>, +) { + // For now, just collect all brackets without scope tracking + // This is a simplified implementation to get it working + for layer in buffer.syntax_layers() { + let tree = layer.node(); + + // Find all bracket nodes + let mut bracket_nodes = Vec::new(); + find_brackets(tree, &mut bracket_nodes); + + // Assign colors based on depth + for (node, depth) in bracket_nodes { + let byte_range = node.byte_range(); + let start = buffer.anchor_after(byte_range.start); + let end = buffer.anchor_before(byte_range.end); + let range = start..end; + + highlights_by_level + .entry(depth % 10) // Cycle through 10 levels + .or_default() + .push(range); + } + } +} + +fn find_brackets<'a>( + node: language::Node<'a>, + brackets: &mut Vec<(language::Node<'a>, usize)>, +) { + // Simple depth-based approach + fn walk_tree<'a>(node: language::Node<'a>, brackets: &mut Vec<(language::Node<'a>, usize)>, depth: usize) { + let kind = node.kind(); + + // Check if this is a bracket + if matches!(kind, "[" | "]" | "{" | "}" | "(" | ")") { + brackets.push((node, depth)); + } + + // Increase depth for scope nodes + let new_depth = match kind { + "object" | "array" | "arguments" | "formal_parameters" | "statement_block" + | "parenthesized_expression" | "call_expression" | "type_parameters" + | "type_arguments" | "jsx_element" | "jsx_self_closing_element" => depth + 1, + _ => depth, + }; + + // Recurse to children + for i in 0..node.child_count() { + if let Some(child) = node.child(i) { + walk_tree(child, brackets, new_depth); + } + } + } + + walk_tree(node, brackets, 0); +} + + +fn get_rainbow_color_0(_theme: &theme::Theme) -> Hsla { + hsla(0.0, 0.7, 0.6, 0.3) // Red +} + +fn get_rainbow_color_1(_theme: &theme::Theme) -> Hsla { + hsla(30.0, 0.7, 0.6, 0.3) // Orange +} + +fn get_rainbow_color_2(_theme: &theme::Theme) -> Hsla { + hsla(60.0, 0.7, 0.6, 0.3) // Yellow +} + +fn get_rainbow_color_3(_theme: &theme::Theme) -> Hsla { + hsla(120.0, 0.7, 0.6, 0.3) // Green +} + +fn get_rainbow_color_4(_theme: &theme::Theme) -> Hsla { + hsla(180.0, 0.7, 0.6, 0.3) // Cyan +} + +fn get_rainbow_color_5(_theme: &theme::Theme) -> Hsla { + hsla(240.0, 0.7, 0.6, 0.3) // Blue +} + +fn get_rainbow_color_6(_theme: &theme::Theme) -> Hsla { + hsla(270.0, 0.7, 0.6, 0.3) // Purple +} + +fn get_rainbow_color_7(_theme: &theme::Theme) -> Hsla { + hsla(0.0, 0.7, 0.6, 0.3) // Red (repeat) +} + +fn get_rainbow_color_8(_theme: &theme::Theme) -> Hsla { + hsla(30.0, 0.7, 0.6, 0.3) // Orange (repeat) +} + +fn get_rainbow_color_9(_theme: &theme::Theme) -> Hsla { + hsla(60.0, 0.7, 0.6, 0.3) // Yellow (repeat) +} + +fn hsla(hue: f32, saturation: f32, lightness: f32, alpha: f32) -> Hsla { + Hsla { + h: hue / 360.0, + s: saturation, + l: lightness, + a: alpha, + } +} + +// 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 {} \ No newline at end of file diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 549afc931c..484c453441 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -1132,6 +1132,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, } @@ -1274,6 +1275,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)>, @@ -1309,6 +1317,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, @@ -1381,6 +1390,11 @@ impl Language { .with_text_object_query(query.as_ref()) .context("Error loading textobject query")?; } + if let Some(query) = queries.rainbow { + self = self + .with_rainbow_query(query.as_ref()) + .context("Error loading rainbow query")?; + } if let Some(query) = queries.debugger { self = self .with_debug_variables_query(query.as_ref()) @@ -1756,6 +1770,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 ab3c0f9b37..5362792d86 100644 --- a/crates/language/src/language_registry.rs +++ b/crates/language/src/language_registry.rs @@ -231,6 +231,7 @@ pub const QUERY_FILENAME_PREFIXES: &[( ("runnables", |q| &mut q.runnables), ("debugger", |q| &mut q.debugger), ("textobjects", |q| &mut q.text_objects), + ("rainbow", |q| &mut q.rainbow), ]; /// Tree-sitter language queries for a given language. @@ -247,6 +248,7 @@ pub struct LanguageQueries { pub runnables: Option>, pub text_objects: Option>, pub debugger: Option>, + pub rainbow: Option>, } #[derive(Clone, Default)] diff --git a/crates/languages/src/javascript/rainbow.scm b/crates/languages/src/javascript/rainbow.scm new file mode 100644 index 0000000000..b7ac4e8e13 --- /dev/null +++ b/crates/languages/src/javascript/rainbow.scm @@ -0,0 +1,15 @@ +[ + (object) + (array) + (arguments) + (formal_parameters) + (statement_block) + (parenthesized_expression) + (call_expression) +] @rainbow.scope + +[ + "[" "]" + "{" "}" + "(" ")" +] @rainbow.bracket \ No newline at end of file diff --git a/crates/languages/src/tsx/rainbow.scm b/crates/languages/src/tsx/rainbow.scm new file mode 100644 index 0000000000..0f32bb0a1c --- /dev/null +++ b/crates/languages/src/tsx/rainbow.scm @@ -0,0 +1,23 @@ +[ + (object) + (array) + (arguments) + (formal_parameters) + (statement_block) + (parenthesized_expression) + (call_expression) + (type_parameters) + (type_arguments) + (jsx_element) + (jsx_self_closing_element) +] @rainbow.scope + +[ + "[" "]" + "{" "}" + "(" ")" +] @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/rainbow.scm b/crates/languages/src/typescript/rainbow.scm new file mode 100644 index 0000000000..7dd228746c --- /dev/null +++ b/crates/languages/src/typescript/rainbow.scm @@ -0,0 +1,18 @@ +[ + (object) + (array) + (arguments) + (formal_parameters) + (statement_block) + (parenthesized_expression) + (call_expression) + (type_parameters) + (type_arguments) +] @rainbow.scope + +[ + "[" "]" + "{" "}" + "(" ")" + "<" ">" +] @rainbow.bracket \ No newline at end of file From a8df9ff65e83640662cbc417d09864bdec836ec0 Mon Sep 17 00:00:00 2001 From: GoldStrikeArch Date: Sat, 2 Aug 2025 16:25:44 +0400 Subject: [PATCH 02/14] trying to make it more generic and work on rust language --- crates/editor/src/rainbow_brackets.rs | 345 ++++++++++++++++++++------ 1 file changed, 266 insertions(+), 79 deletions(-) diff --git a/crates/editor/src/rainbow_brackets.rs b/crates/editor/src/rainbow_brackets.rs index f57cc0c82a..5a4d9f7f69 100644 --- a/crates/editor/src/rainbow_brackets.rs +++ b/crates/editor/src/rainbow_brackets.rs @@ -25,47 +25,90 @@ pub fn refresh_rainbow_bracket_highlights( 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() { let language = buffer_snapshot.language(); - + if let Some(language) = language { if let Some(grammar) = language.grammar() { if let Some(rainbow_config) = &grammar.rainbow_config { - let mut highlights_by_level: HashMap>> = HashMap::new(); - + let mut highlights_by_level: HashMap>> = + HashMap::new(); + collect_rainbow_highlights( buffer_snapshot, rainbow_config, &mut highlights_by_level, ); - + // Apply highlights by level for (level, ranges) in highlights_by_level { // Convert text anchors to multi-buffer anchors - let multi_buffer_ranges: Vec<_> = ranges.into_iter() + let multi_buffer_ranges: Vec<_> = ranges + .into_iter() .map(|range| { let start_offset = range.start.to_offset(buffer_snapshot); let end_offset = range.end.to_offset(buffer_snapshot); - let start = multi_buffer_snapshot.anchor_at(start_offset, Bias::Left); + let start = + multi_buffer_snapshot.anchor_at(start_offset, Bias::Left); let end = multi_buffer_snapshot.anchor_at(end_offset, Bias::Right); start..end }) .collect(); - + // Create a unique type for each level to avoid conflicts match level { - 0 => editor.highlight_background::(&multi_buffer_ranges, get_rainbow_color_0, cx), - 1 => editor.highlight_background::(&multi_buffer_ranges, get_rainbow_color_1, cx), - 2 => editor.highlight_background::(&multi_buffer_ranges, get_rainbow_color_2, cx), - 3 => editor.highlight_background::(&multi_buffer_ranges, get_rainbow_color_3, cx), - 4 => editor.highlight_background::(&multi_buffer_ranges, get_rainbow_color_4, cx), - 5 => editor.highlight_background::(&multi_buffer_ranges, get_rainbow_color_5, cx), - 6 => editor.highlight_background::(&multi_buffer_ranges, get_rainbow_color_6, cx), - 7 => editor.highlight_background::(&multi_buffer_ranges, get_rainbow_color_7, cx), - 8 => editor.highlight_background::(&multi_buffer_ranges, get_rainbow_color_8, cx), - _ => editor.highlight_background::(&multi_buffer_ranges, get_rainbow_color_9, cx), + 0 => editor.highlight_background::( + &multi_buffer_ranges, + get_rainbow_color_0, + cx, + ), + 1 => editor.highlight_background::( + &multi_buffer_ranges, + get_rainbow_color_1, + cx, + ), + 2 => editor.highlight_background::( + &multi_buffer_ranges, + get_rainbow_color_2, + cx, + ), + 3 => editor.highlight_background::( + &multi_buffer_ranges, + get_rainbow_color_3, + cx, + ), + 4 => editor.highlight_background::( + &multi_buffer_ranges, + get_rainbow_color_4, + cx, + ), + 5 => editor.highlight_background::( + &multi_buffer_ranges, + get_rainbow_color_5, + cx, + ), + 6 => editor.highlight_background::( + &multi_buffer_ranges, + get_rainbow_color_6, + cx, + ), + 7 => editor.highlight_background::( + &multi_buffer_ranges, + get_rainbow_color_7, + cx, + ), + 8 => editor.highlight_background::( + &multi_buffer_ranges, + get_rainbow_color_8, + cx, + ), + _ => editor.highlight_background::( + &multi_buffer_ranges, + get_rainbow_color_9, + cx, + ), } } } @@ -76,104 +119,248 @@ pub fn refresh_rainbow_bracket_highlights( fn collect_rainbow_highlights( buffer: &language::BufferSnapshot, - _rainbow_config: &language::RainbowConfig, + rainbow_config: &language::RainbowConfig, highlights_by_level: &mut HashMap>>, ) { - // For now, just collect all brackets without scope tracking - // This is a simplified implementation to get it working + #[derive(Debug)] + struct RainbowScope { + end_byte: usize, + node_id: Option, + pattern_ix: usize, + level: usize, + } + + #[derive(Debug)] + struct CaptureInfo<'a> { + byte_pos: usize, + is_scope: bool, + node: language::Node<'a>, + pattern_ix: usize, + } + + let mut all_captures = Vec::new(); + + // Process each syntax layer for layer in buffer.syntax_layers() { let tree = layer.node(); - - // Find all bracket nodes - let mut bracket_nodes = Vec::new(); - find_brackets(tree, &mut bracket_nodes); - - // Assign colors based on depth - for (node, depth) in bracket_nodes { - let byte_range = node.byte_range(); - let start = buffer.anchor_after(byte_range.start); - let end = buffer.anchor_before(byte_range.end); - let range = start..end; - - highlights_by_level - .entry(depth % 10) // Cycle through 10 levels - .or_default() - .push(range); - } - } -} -fn find_brackets<'a>( - node: language::Node<'a>, - brackets: &mut Vec<(language::Node<'a>, usize)>, -) { - // Simple depth-based approach - fn walk_tree<'a>(node: language::Node<'a>, brackets: &mut Vec<(language::Node<'a>, usize)>, depth: usize) { - let kind = node.kind(); - - // Check if this is a bracket - if matches!(kind, "[" | "]" | "{" | "}" | "(" | ")") { - brackets.push((node, depth)); - } - - // Increase depth for scope nodes - let new_depth = match kind { - "object" | "array" | "arguments" | "formal_parameters" | "statement_block" - | "parenthesized_expression" | "call_expression" | "type_parameters" - | "type_arguments" | "jsx_element" | "jsx_self_closing_element" => depth + 1, - _ => depth, - }; - - // Recurse to children - for i in 0..node.child_count() { - if let Some(child) = node.child(i) { - walk_tree(child, brackets, new_depth); + // Collect all nodes and check them against the query + let mut stack = vec![(tree, 0)]; + let mut visited_nodes = collections::HashSet::default(); + + while let Some((node, depth)) = stack.pop() { + let node_id = node.id(); + if !visited_nodes.insert(node_id) { + continue; + } + + // Use query cursor to check if this node matches + language::with_query_cursor(|cursor| { + cursor.set_byte_range(node.byte_range()); + + // Check if this node matches any pattern in the rainbow query + // We'll use a simplified approach to check for matches + let rope = buffer.as_rope(); + let node_text = { + let byte_range = node.byte_range(); + rope.chunks_in_range(byte_range) + .collect::() + .into_bytes() + }; + + // Try to match starting at this node + let mut matched = false; + + // Check if this node matches our query patterns + // For now, we'll use a simple heuristic based on node kind + if let Some(scope_ix) = rainbow_config.scope_capture_ix { + // Check if this is a scope node + let is_scope_node = match node.kind() { + // JavaScript scopes + "object" + | "array" + | "arguments" + | "formal_parameters" + | "statement_block" + | "parenthesized_expression" + | "call_expression" + | "type_parameters" + | "type_arguments" + | "jsx_element" + | "jsx_self_closing_element" => true, + // Rust scopes from the rainbow.scm file + "declaration_list" + | "field_declaration_list" + | "field_initializer_list" + | "enum_variant_list" + | "block" + | "match_block" + | "use_list" + | "struct_pattern" + | "ordered_field_declaration_list" + | "parameters" + | "tuple_type" + | "tuple_expression" + | "tuple_pattern" + | "tuple_struct_pattern" + | "unit_type" + | "unit_expression" + | "visibility_modifier" + | "token_repetition_pattern" + | "bracketed_type" + | "for_lifetimes" + | "array_type" + | "array_expression" + | "index_expression" + | "slice_pattern" + | "attribute_item" + | "inner_attribute_item" + | "token_tree_pattern" + | "macro_definition" + | "closure_parameters" => true, + _ => false, + }; + + if is_scope_node { + all_captures.push(CaptureInfo { + byte_pos: node.start_byte(), + is_scope: true, + node, + pattern_ix: 0, // Simplified - we'd need to match actual pattern + }); + matched = true; + } + } + + if let Some(bracket_ix) = rainbow_config.bracket_capture_ix { + // Check if this is a bracket node + let is_bracket_node = matches!( + node.kind(), + "[" | "]" | "{" | "}" | "(" | ")" | "<" | ">" | "#" | "|" + ); + + if is_bracket_node { + all_captures.push(CaptureInfo { + byte_pos: node.start_byte(), + is_scope: false, + node, + pattern_ix: 0, + }); + matched = true; + } + } + }); + + // Add children to stack + for i in 0..node.child_count() { + if let Some(child) = node.child(i) { + stack.push((child, depth + 1)); + } + } + } + } + + // Sort captures by byte position to process them in order + all_captures.sort_by_key(|c| c.byte_pos); + + // Process captures to assign rainbow levels + let mut scope_stack = Vec::::new(); + + for capture in all_captures { + let byte_range = capture.node.byte_range(); + + // Pop any scopes that have ended + while scope_stack + .last() + .is_some_and(|scope| byte_range.start >= scope.end_byte) + { + scope_stack.pop(); + } + + if capture.is_scope { + // This is a scope capture - push it onto the stack + let node_id = if rainbow_config + .include_children_patterns + .contains(&capture.pattern_ix) + { + None + } else { + Some(capture.node.id()) + }; + + scope_stack.push(RainbowScope { + end_byte: byte_range.end, + node_id, + pattern_ix: capture.pattern_ix, + level: scope_stack.len(), + }); + } else { + // This is a bracket capture - assign it a color based on the current scope + if let Some(scope) = scope_stack.last() { + let should_highlight = if let Some(scope_node_id) = scope.node_id { + // Only highlight if bracket is direct child of scope + capture + .node + .parent() + .map_or(false, |parent| parent.id() == scope_node_id) + } else { + // include-children mode: highlight all brackets in scope + true + }; + + if should_highlight { + let start = buffer.anchor_after(byte_range.start); + let end = buffer.anchor_before(byte_range.end); + let range = start..end; + + highlights_by_level + .entry(scope.level % 10) + .or_default() + .push(range); + } } } } - - walk_tree(node, brackets, 0); } - fn get_rainbow_color_0(_theme: &theme::Theme) -> Hsla { - hsla(0.0, 0.7, 0.6, 0.3) // Red + hsla(0.0, 0.7, 0.6, 0.3) // Red } fn get_rainbow_color_1(_theme: &theme::Theme) -> Hsla { - hsla(30.0, 0.7, 0.6, 0.3) // Orange + hsla(30.0, 0.7, 0.6, 0.3) // Orange } fn get_rainbow_color_2(_theme: &theme::Theme) -> Hsla { - hsla(60.0, 0.7, 0.6, 0.3) // Yellow + hsla(60.0, 0.7, 0.6, 0.3) // Yellow } fn get_rainbow_color_3(_theme: &theme::Theme) -> Hsla { - hsla(120.0, 0.7, 0.6, 0.3) // Green + hsla(120.0, 0.7, 0.6, 0.3) // Green } fn get_rainbow_color_4(_theme: &theme::Theme) -> Hsla { - hsla(180.0, 0.7, 0.6, 0.3) // Cyan + hsla(180.0, 0.7, 0.6, 0.3) // Cyan } fn get_rainbow_color_5(_theme: &theme::Theme) -> Hsla { - hsla(240.0, 0.7, 0.6, 0.3) // Blue + hsla(240.0, 0.7, 0.6, 0.3) // Blue } fn get_rainbow_color_6(_theme: &theme::Theme) -> Hsla { - hsla(270.0, 0.7, 0.6, 0.3) // Purple + hsla(270.0, 0.7, 0.6, 0.3) // Purple } fn get_rainbow_color_7(_theme: &theme::Theme) -> Hsla { - hsla(0.0, 0.7, 0.6, 0.3) // Red (repeat) + hsla(0.0, 0.7, 0.6, 0.3) // Red (repeat) } fn get_rainbow_color_8(_theme: &theme::Theme) -> Hsla { - hsla(30.0, 0.7, 0.6, 0.3) // Orange (repeat) + hsla(30.0, 0.7, 0.6, 0.3) // Orange (repeat) } fn get_rainbow_color_9(_theme: &theme::Theme) -> Hsla { - hsla(60.0, 0.7, 0.6, 0.3) // Yellow (repeat) + hsla(60.0, 0.7, 0.6, 0.3) // Yellow (repeat) } fn hsla(hue: f32, saturation: f32, lightness: f32, alpha: f32) -> Hsla { @@ -195,4 +382,4 @@ enum RainbowLevel5 {} enum RainbowLevel6 {} enum RainbowLevel7 {} enum RainbowLevel8 {} -enum RainbowLevel9 {} \ No newline at end of file +enum RainbowLevel9 {} From 1f6fbf8be0dae9fe933841ede13d7c7d60db0a0b Mon Sep 17 00:00:00 2001 From: GoldStrikeArch Date: Sat, 2 Aug 2025 16:40:01 +0400 Subject: [PATCH 03/14] temp solution before we decide how to proceed with it --- crates/editor/src/rainbow_brackets.rs | 294 ++++++++++---------------- crates/languages/src/rust/rainbow.scm | 60 ++++++ 2 files changed, 173 insertions(+), 181 deletions(-) create mode 100644 crates/languages/src/rust/rainbow.scm diff --git a/crates/editor/src/rainbow_brackets.rs b/crates/editor/src/rainbow_brackets.rs index 5a4d9f7f69..442a428e1f 100644 --- a/crates/editor/src/rainbow_brackets.rs +++ b/crates/editor/src/rainbow_brackets.rs @@ -117,210 +117,142 @@ pub fn refresh_rainbow_bracket_highlights( } } +// Similar to Helix's RainbowScope structure +#[derive(Debug)] +struct RainbowScope { + end_byte: usize, + node: Option, // node ID, similar to Helix's Option + level: usize, +} + fn collect_rainbow_highlights( buffer: &language::BufferSnapshot, rainbow_config: &language::RainbowConfig, highlights_by_level: &mut HashMap>>, ) { - #[derive(Debug)] - struct RainbowScope { - end_byte: usize, - node_id: Option, - pattern_ix: usize, - level: usize, - } + let mut scope_stack = Vec::::new(); - #[derive(Debug)] - struct CaptureInfo<'a> { - byte_pos: usize, - is_scope: bool, - node: language::Node<'a>, - pattern_ix: usize, - } + // TODO: Currently we can't use tree-sitter queries properly due to API limitations + // in Zed. The proper implementation would use syntax.matches() but that's not + // publicly accessible. For now, we have to iterate through the tree manually. - let mut all_captures = Vec::new(); - - // Process each syntax layer + // This is a temporary workaround until we can access the query matching API for layer in buffer.syntax_layers() { let tree = layer.node(); - // Collect all nodes and check them against the query - let mut stack = vec![(tree, 0)]; - let mut visited_nodes = collections::HashSet::default(); + // Walk the tree and match against the rainbow query + // In the future, this should use proper query matching like Helix does + walk_tree_for_rainbow( + tree, + buffer, + rainbow_config, + &mut scope_stack, + highlights_by_level, + ); + } +} - while let Some((node, depth)) = stack.pop() { - let node_id = node.id(); - if !visited_nodes.insert(node_id) { - continue; - } +// Temporary tree walking function until we can use proper query matching +fn walk_tree_for_rainbow( + node: language::Node, + buffer: &language::BufferSnapshot, + rainbow_config: &language::RainbowConfig, + scope_stack: &mut Vec, + highlights_by_level: &mut HashMap>>, +) { + let byte_range = node.byte_range(); - // Use query cursor to check if this node matches - language::with_query_cursor(|cursor| { - cursor.set_byte_range(node.byte_range()); - - // Check if this node matches any pattern in the rainbow query - // We'll use a simplified approach to check for matches - let rope = buffer.as_rope(); - let node_text = { - let byte_range = node.byte_range(); - rope.chunks_in_range(byte_range) - .collect::() - .into_bytes() - }; - - // Try to match starting at this node - let mut matched = false; - - // Check if this node matches our query patterns - // For now, we'll use a simple heuristic based on node kind - if let Some(scope_ix) = rainbow_config.scope_capture_ix { - // Check if this is a scope node - let is_scope_node = match node.kind() { - // JavaScript scopes - "object" - | "array" - | "arguments" - | "formal_parameters" - | "statement_block" - | "parenthesized_expression" - | "call_expression" - | "type_parameters" - | "type_arguments" - | "jsx_element" - | "jsx_self_closing_element" => true, - // Rust scopes from the rainbow.scm file - "declaration_list" - | "field_declaration_list" - | "field_initializer_list" - | "enum_variant_list" - | "block" - | "match_block" - | "use_list" - | "struct_pattern" - | "ordered_field_declaration_list" - | "parameters" - | "tuple_type" - | "tuple_expression" - | "tuple_pattern" - | "tuple_struct_pattern" - | "unit_type" - | "unit_expression" - | "visibility_modifier" - | "token_repetition_pattern" - | "bracketed_type" - | "for_lifetimes" - | "array_type" - | "array_expression" - | "index_expression" - | "slice_pattern" - | "attribute_item" - | "inner_attribute_item" - | "token_tree_pattern" - | "macro_definition" - | "closure_parameters" => true, - _ => false, - }; - - if is_scope_node { - all_captures.push(CaptureInfo { - byte_pos: node.start_byte(), - is_scope: true, - node, - pattern_ix: 0, // Simplified - we'd need to match actual pattern - }); - matched = true; - } - } - - if let Some(bracket_ix) = rainbow_config.bracket_capture_ix { - // Check if this is a bracket node - let is_bracket_node = matches!( - node.kind(), - "[" | "]" | "{" | "}" | "(" | ")" | "<" | ">" | "#" | "|" - ); - - if is_bracket_node { - all_captures.push(CaptureInfo { - byte_pos: node.start_byte(), - is_scope: false, - node, - pattern_ix: 0, - }); - matched = true; - } - } - }); - - // Add children to stack - for i in 0..node.child_count() { - if let Some(child) = node.child(i) { - stack.push((child, depth + 1)); - } - } - } + // Pop any scopes that end before this node begins + while scope_stack + .last() + .is_some_and(|scope| byte_range.start >= scope.end_byte) + { + scope_stack.pop(); } - // Sort captures by byte position to process them in order - all_captures.sort_by_key(|c| c.byte_pos); + // TODO: This is where we would use the actual query match result + // For now, we check if this node should be a scope or bracket based on the query - // Process captures to assign rainbow levels - let mut scope_stack = Vec::::new(); + // Temporary: Check if this node matches a scope pattern + // In proper implementation, this would come from query match results + if rainbow_config.scope_capture_ix.is_some() && is_potential_scope_node(node.kind()) { + scope_stack.push(RainbowScope { + end_byte: byte_range.end, + node: Some(node.id()), + level: scope_stack.len(), + }); + } - for capture in all_captures { - let byte_range = capture.node.byte_range(); - - // Pop any scopes that have ended - while scope_stack - .last() - .is_some_and(|scope| byte_range.start >= scope.end_byte) - { - scope_stack.pop(); - } - - if capture.is_scope { - // This is a scope capture - push it onto the stack - let node_id = if rainbow_config - .include_children_patterns - .contains(&capture.pattern_ix) - { - None + // Check if this node is a bracket + if rainbow_config.bracket_capture_ix.is_some() && is_bracket_node(node.kind()) { + 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 { - Some(capture.node.id()) + // include-children mode: highlight all brackets in this scope + true }; - scope_stack.push(RainbowScope { - end_byte: byte_range.end, - node_id, - pattern_ix: capture.pattern_ix, - level: scope_stack.len(), - }); - } else { - // This is a bracket capture - assign it a color based on the current scope - if let Some(scope) = scope_stack.last() { - let should_highlight = if let Some(scope_node_id) = scope.node_id { - // Only highlight if bracket is direct child of scope - capture - .node - .parent() - .map_or(false, |parent| parent.id() == scope_node_id) - } else { - // include-children mode: highlight all brackets in scope - true - }; + if should_highlight { + let start = buffer.anchor_after(byte_range.start); + let end = buffer.anchor_before(byte_range.end); + let range = start..end; - if should_highlight { - let start = buffer.anchor_after(byte_range.start); - let end = buffer.anchor_before(byte_range.end); - let range = start..end; - - highlights_by_level - .entry(scope.level % 10) - .or_default() - .push(range); - } + highlights_by_level + .entry(scope.level % 10) + .or_default() + .push(range); } } } + + // Recurse to children + for i in 0..node.child_count() { + if let Some(child) = node.child(i) { + walk_tree_for_rainbow( + child, + buffer, + rainbow_config, + scope_stack, + highlights_by_level, + ); + } + } +} + +// Temporary helper until we can use query results +// This is not ideal - we should be using the actual query matches +fn is_potential_scope_node(kind: &str) -> bool { + // This function exists only because we can't access the proper query matching API + // In a proper implementation, this would be determined by the rainbow.scm query + matches!( + kind, + // Common scope nodes across languages + "block" + | "statement_block" + | "compound_statement" + | "object" + | "array" + | "list" + | "arguments" + | "parameters" + | "formal_parameters" + | "parenthesized_expression" + | "tuple_expression" + | "declaration_list" + | "field_declaration_list" + | "call_expression" + | "function_call" + ) || kind.contains("block") + || kind.contains("list") + || kind.contains("expression") +} + +fn is_bracket_node(kind: &str) -> bool { + matches!(kind, "[" | "]" | "{" | "}" | "(" | ")" | "<" | ">") } fn get_rainbow_color_0(_theme: &theme::Theme) -> Hsla { diff --git a/crates/languages/src/rust/rainbow.scm b/crates/languages/src/rust/rainbow.scm new file mode 100644 index 0000000000..ede7133e51 --- /dev/null +++ b/crates/languages/src/rust/rainbow.scm @@ -0,0 +1,60 @@ +[ + ; {/} + (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.bracket \ No newline at end of file From ae0ace71716f3d3f616c1866f1117809f1d5c842 Mon Sep 17 00:00:00 2001 From: GoldStrikeArch Date: Sat, 2 Aug 2025 16:55:49 +0400 Subject: [PATCH 04/14] feat: use the rainbow.scm files (similar to Helix) --- crates/editor/src/rainbow_brackets.rs | 188 ++++++++++-------------- crates/languages/src/bash/rainbow.scm | 20 +++ crates/languages/src/c/rainbow.scm | 29 ++++ crates/languages/src/cpp/rainbow.scm | 49 ++++++ crates/languages/src/css/rainbow.scm | 15 ++ crates/languages/src/go/rainbow.scm | 33 +++++ crates/languages/src/json/rainbow.scm | 9 ++ crates/languages/src/jsonc/rainbow.scm | 9 ++ crates/languages/src/python/rainbow.scm | 28 ++++ crates/languages/src/regex/rainbow.scm | 16 ++ crates/languages/src/rust/rainbow.scm | 2 +- 11 files changed, 286 insertions(+), 112 deletions(-) create mode 100644 crates/languages/src/bash/rainbow.scm create mode 100644 crates/languages/src/c/rainbow.scm create mode 100644 crates/languages/src/cpp/rainbow.scm create mode 100644 crates/languages/src/css/rainbow.scm create mode 100644 crates/languages/src/go/rainbow.scm create mode 100644 crates/languages/src/json/rainbow.scm create mode 100644 crates/languages/src/jsonc/rainbow.scm create mode 100644 crates/languages/src/python/rainbow.scm create mode 100644 crates/languages/src/regex/rainbow.scm diff --git a/crates/editor/src/rainbow_brackets.rs b/crates/editor/src/rainbow_brackets.rs index 442a428e1f..c97cf73fef 100644 --- a/crates/editor/src/rainbow_brackets.rs +++ b/crates/editor/src/rainbow_brackets.rs @@ -57,6 +57,7 @@ pub fn refresh_rainbow_bracket_highlights( }) .collect(); + // TODO: make it a text style instead of a background highlight // Create a unique type for each level to avoid conflicts match level { 0 => editor.highlight_background::( @@ -132,129 +133,94 @@ fn collect_rainbow_highlights( ) { let mut scope_stack = Vec::::new(); - // TODO: Currently we can't use tree-sitter queries properly due to API limitations - // in Zed. The proper implementation would use syntax.matches() but that's not - // publicly accessible. For now, we have to iterate through the tree manually. + // Use the proper tree-sitter query matching API + let mut matches = buffer.matches(0..buffer.len(), |grammar| { + grammar.rainbow_config.as_ref().map(|c| &c.query) + }); - // This is a temporary workaround until we can access the query matching API - for layer in buffer.syntax_layers() { - let tree = layer.node(); + // Process all matches in order + while let Some(mat) = matches.peek() { + let byte_range = mat.captures[0].node.byte_range(); - // Walk the tree and match against the rainbow query - // In the future, this should use proper query matching like Helix does - walk_tree_for_rainbow( - tree, - buffer, - rainbow_config, - &mut scope_stack, - highlights_by_level, - ); - } -} + // 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(); + } -// Temporary tree walking function until we can use proper query matching -fn walk_tree_for_rainbow( - node: language::Node, - buffer: &language::BufferSnapshot, - rainbow_config: &language::RainbowConfig, - scope_stack: &mut Vec, - highlights_by_level: &mut HashMap>>, -) { - let byte_range = node.byte_range(); + // 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)); - // Pop any scopes that end before this node begins - while scope_stack - .last() - .is_some_and(|scope| byte_range.start >= scope.end_byte) - { - scope_stack.pop(); - } + 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(); - // TODO: This is where we would use the actual query match result - // For now, we check if this node should be a scope or bracket based on the query - - // Temporary: Check if this node matches a scope pattern - // In proper implementation, this would come from query match results - if rainbow_config.scope_capture_ix.is_some() && is_potential_scope_node(node.kind()) { - scope_stack.push(RainbowScope { - end_byte: byte_range.end, - node: Some(node.id()), - level: scope_stack.len(), - }); - } - - // Check if this node is a bracket - if rainbow_config.bracket_capture_ix.is_some() && is_bracket_node(node.kind()) { - 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 start = buffer.anchor_after(byte_range.start); - let end = buffer.anchor_before(byte_range.end); - let range = start..end; - - highlights_by_level - .entry(scope.level % 10) - .or_default() - .push(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(), + }); } } - } - // Recurse to children - for i in 0..node.child_count() { - if let Some(child) = node.child(i) { - walk_tree_for_rainbow( - child, - buffer, - rainbow_config, - scope_stack, - highlights_by_level, - ); + 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 start = buffer.anchor_after(byte_range.start); + let end = buffer.anchor_before(byte_range.end); + let range = start..end; + + highlights_by_level + .entry(scope.level % 10) + .or_default() + .push(range); + } + } + } } + + matches.advance(); } } -// Temporary helper until we can use query results -// This is not ideal - we should be using the actual query matches -fn is_potential_scope_node(kind: &str) -> bool { - // This function exists only because we can't access the proper query matching API - // In a proper implementation, this would be determined by the rainbow.scm query - matches!( - kind, - // Common scope nodes across languages - "block" - | "statement_block" - | "compound_statement" - | "object" - | "array" - | "list" - | "arguments" - | "parameters" - | "formal_parameters" - | "parenthesized_expression" - | "tuple_expression" - | "declaration_list" - | "field_declaration_list" - | "call_expression" - | "function_call" - ) || kind.contains("block") - || kind.contains("list") - || kind.contains("expression") -} - -fn is_bracket_node(kind: &str) -> bool { - matches!(kind, "[" | "]" | "{" | "}" | "(" | ")" | "<" | ">") -} - +// TODO! Make it configurable from settings fn get_rainbow_color_0(_theme: &theme::Theme) -> Hsla { hsla(0.0, 0.7, 0.6, 0.3) // Red } diff --git a/crates/languages/src/bash/rainbow.scm b/crates/languages/src/bash/rainbow.scm new file mode 100644 index 0000000000..422e2fbddd --- /dev/null +++ b/crates/languages/src/bash/rainbow.scm @@ -0,0 +1,20 @@ +[ + (function_definition) + (compound_statement) + (subshell) + (test_command) + (subscript) + (parenthesized_expression) + (array) + (expansion) + (command_substitution) +] @rainbow.scope + +[ + "(" ")" + "((" "))" + "${" "$(" + "{" "}" + "[" "]" + "[[" "]]" +] @rainbow.bracket diff --git a/crates/languages/src/c/rainbow.scm b/crates/languages/src/c/rainbow.scm new file mode 100644 index 0000000000..1f80868ae5 --- /dev/null +++ b/crates/languages/src/c/rainbow.scm @@ -0,0 +1,29 @@ +[ + (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.bracket diff --git a/crates/languages/src/cpp/rainbow.scm b/crates/languages/src/cpp/rainbow.scm new file mode 100644 index 0000000000..ff4882c2d4 --- /dev/null +++ b/crates/languages/src/cpp/rainbow.scm @@ -0,0 +1,49 @@ +[ + ; 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.bracket diff --git a/crates/languages/src/css/rainbow.scm b/crates/languages/src/css/rainbow.scm new file mode 100644 index 0000000000..66b60d515a --- /dev/null +++ b/crates/languages/src/css/rainbow.scm @@ -0,0 +1,15 @@ +[ + (keyframe_block_list) + (block) + (attribute_selector) + (feature_query) + (parenthesized_query) + (selector_query) + (parenthesized_value) + (arguments) +] @rainbow.scope + +[ + "{" "}" + "(" ")" +] @rainbow.bracket diff --git a/crates/languages/src/go/rainbow.scm b/crates/languages/src/go/rainbow.scm new file mode 100644 index 0000000000..81004bf885 --- /dev/null +++ b/crates/languages/src/go/rainbow.scm @@ -0,0 +1,33 @@ +[ + (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.bracket diff --git a/crates/languages/src/json/rainbow.scm b/crates/languages/src/json/rainbow.scm new file mode 100644 index 0000000000..5c21bdcc60 --- /dev/null +++ b/crates/languages/src/json/rainbow.scm @@ -0,0 +1,9 @@ +[ + (object) + (array) +] @rainbow.scope + +[ + "[" "]" + "{" "}" +] @rainbow.bracket diff --git a/crates/languages/src/jsonc/rainbow.scm b/crates/languages/src/jsonc/rainbow.scm new file mode 100644 index 0000000000..5c21bdcc60 --- /dev/null +++ b/crates/languages/src/jsonc/rainbow.scm @@ -0,0 +1,9 @@ +[ + (object) + (array) +] @rainbow.scope + +[ + "[" "]" + "{" "}" +] @rainbow.bracket diff --git a/crates/languages/src/python/rainbow.scm b/crates/languages/src/python/rainbow.scm new file mode 100644 index 0000000000..15e5db29c7 --- /dev/null +++ b/crates/languages/src/python/rainbow.scm @@ -0,0 +1,28 @@ +[ + (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.bracket diff --git a/crates/languages/src/regex/rainbow.scm b/crates/languages/src/regex/rainbow.scm new file mode 100644 index 0000000000..4cd2ec7c38 --- /dev/null +++ b/crates/languages/src/regex/rainbow.scm @@ -0,0 +1,16 @@ +[ + (character_class) + (anonymous_capturing_group) + (named_capturing_group) + (non_capturing_group) + (count_quantifier) + (character_class_escape) +] @rainbow.scope + +[ + "(?" "(?:" + "(?<" ">" + "(" ")" + "[" "]" + "{" "}" +] @rainbow.bracket diff --git a/crates/languages/src/rust/rainbow.scm b/crates/languages/src/rust/rainbow.scm index ede7133e51..0656047be4 100644 --- a/crates/languages/src/rust/rainbow.scm +++ b/crates/languages/src/rust/rainbow.scm @@ -57,4 +57,4 @@ "{" "}" "<" ">" "|" -] @rainbow.bracket \ No newline at end of file +] @rainbow.bracket From 8660948f8624a542a50022b49c90bdb2b20074ac Mon Sep 17 00:00:00 2001 From: GoldStrikeArch Date: Sat, 2 Aug 2025 17:15:35 +0400 Subject: [PATCH 05/14] refactor: extract 'current rainbow clear' into a separate function --- crates/editor/src/rainbow_brackets.rs | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/crates/editor/src/rainbow_brackets.rs b/crates/editor/src/rainbow_brackets.rs index c97cf73fef..7a9ad56f0d 100644 --- a/crates/editor/src/rainbow_brackets.rs +++ b/crates/editor/src/rainbow_brackets.rs @@ -1,7 +1,7 @@ use crate::Editor; -use collections::HashMap; use gpui::{Context, Hsla, Window}; use language::Bias; +use std::collections::HashMap; use std::ops::Range; use text::{Anchor, ToOffset}; @@ -12,16 +12,7 @@ pub fn refresh_rainbow_bracket_highlights( cx: &mut Context, ) { // Clear existing rainbow highlights for all levels - editor.clear_background_highlights::(cx); - editor.clear_background_highlights::(cx); - editor.clear_background_highlights::(cx); - editor.clear_background_highlights::(cx); - editor.clear_background_highlights::(cx); - editor.clear_background_highlights::(cx); - editor.clear_background_highlights::(cx); - editor.clear_background_highlights::(cx); - editor.clear_background_highlights::(cx); - editor.clear_background_highlights::(cx); + clear_current_rainbow_highlights(editor, cx); let multi_buffer = editor.buffer().read(cx); let multi_buffer_snapshot = multi_buffer.snapshot(cx); @@ -118,6 +109,18 @@ pub fn refresh_rainbow_bracket_highlights( } } +fn clear_current_rainbow_highlights(editor: &mut Editor, cx: &mut Context) { + editor.clear_background_highlights::(cx); + editor.clear_background_highlights::(cx); + editor.clear_background_highlights::(cx); + editor.clear_background_highlights::(cx); + editor.clear_background_highlights::(cx); + editor.clear_background_highlights::(cx); + editor.clear_background_highlights::(cx); + editor.clear_background_highlights::(cx); + editor.clear_background_highlights::(cx); +} + // Similar to Helix's RainbowScope structure #[derive(Debug)] struct RainbowScope { From ae3c31eb087a1b743f34a995255b8209e98ba65d Mon Sep 17 00:00:00 2001 From: GoldStrikeArch Date: Sun, 3 Aug 2025 10:31:06 +0400 Subject: [PATCH 06/14] review: use 'and_then' to remove the level of nesting --- crates/editor/src/rainbow_brackets.rs | 151 ++++++++++++-------------- 1 file changed, 72 insertions(+), 79 deletions(-) diff --git a/crates/editor/src/rainbow_brackets.rs b/crates/editor/src/rainbow_brackets.rs index 7a9ad56f0d..7364908198 100644 --- a/crates/editor/src/rainbow_brackets.rs +++ b/crates/editor/src/rainbow_brackets.rs @@ -21,88 +21,81 @@ pub fn refresh_rainbow_bracket_highlights( if let Some((_, _, buffer_snapshot)) = multi_buffer_snapshot.as_singleton() { let language = buffer_snapshot.language(); - if let Some(language) = language { - if let Some(grammar) = language.grammar() { - if let Some(rainbow_config) = &grammar.rainbow_config { - let mut highlights_by_level: HashMap>> = - HashMap::new(); + if let Some(rainbow_config) = language + .and_then(|lang| lang.grammar()) + .and_then(|grammar| grammar.rainbow_config.as_ref()) + { + let mut highlights_by_level: HashMap>> = HashMap::new(); - collect_rainbow_highlights( - buffer_snapshot, - rainbow_config, - &mut highlights_by_level, - ); + collect_rainbow_highlights(buffer_snapshot, rainbow_config, &mut highlights_by_level); - // Apply highlights by level - for (level, ranges) in highlights_by_level { - // Convert text anchors to multi-buffer anchors - let multi_buffer_ranges: Vec<_> = ranges - .into_iter() - .map(|range| { - let start_offset = range.start.to_offset(buffer_snapshot); - let end_offset = range.end.to_offset(buffer_snapshot); - let start = - multi_buffer_snapshot.anchor_at(start_offset, Bias::Left); - let end = multi_buffer_snapshot.anchor_at(end_offset, Bias::Right); - start..end - }) - .collect(); + // Apply highlights by level + for (level, ranges) in highlights_by_level { + // Convert text anchors to multi-buffer anchors + let multi_buffer_ranges: Vec<_> = ranges + .into_iter() + .map(|range| { + let start_offset = range.start.to_offset(buffer_snapshot); + let end_offset = range.end.to_offset(buffer_snapshot); + let start = multi_buffer_snapshot.anchor_at(start_offset, Bias::Left); + let end = multi_buffer_snapshot.anchor_at(end_offset, Bias::Right); + start..end + }) + .collect(); - // TODO: make it a text style instead of a background highlight - // Create a unique type for each level to avoid conflicts - match level { - 0 => editor.highlight_background::( - &multi_buffer_ranges, - get_rainbow_color_0, - cx, - ), - 1 => editor.highlight_background::( - &multi_buffer_ranges, - get_rainbow_color_1, - cx, - ), - 2 => editor.highlight_background::( - &multi_buffer_ranges, - get_rainbow_color_2, - cx, - ), - 3 => editor.highlight_background::( - &multi_buffer_ranges, - get_rainbow_color_3, - cx, - ), - 4 => editor.highlight_background::( - &multi_buffer_ranges, - get_rainbow_color_4, - cx, - ), - 5 => editor.highlight_background::( - &multi_buffer_ranges, - get_rainbow_color_5, - cx, - ), - 6 => editor.highlight_background::( - &multi_buffer_ranges, - get_rainbow_color_6, - cx, - ), - 7 => editor.highlight_background::( - &multi_buffer_ranges, - get_rainbow_color_7, - cx, - ), - 8 => editor.highlight_background::( - &multi_buffer_ranges, - get_rainbow_color_8, - cx, - ), - _ => editor.highlight_background::( - &multi_buffer_ranges, - get_rainbow_color_9, - cx, - ), - } - } + // TODO: make it a text style instead of a background highlight + // Create a unique type for each level to avoid conflicts + match level { + 0 => editor.highlight_background::( + &multi_buffer_ranges, + get_rainbow_color_0, + cx, + ), + 1 => editor.highlight_background::( + &multi_buffer_ranges, + get_rainbow_color_1, + cx, + ), + 2 => editor.highlight_background::( + &multi_buffer_ranges, + get_rainbow_color_2, + cx, + ), + 3 => editor.highlight_background::( + &multi_buffer_ranges, + get_rainbow_color_3, + cx, + ), + 4 => editor.highlight_background::( + &multi_buffer_ranges, + get_rainbow_color_4, + cx, + ), + 5 => editor.highlight_background::( + &multi_buffer_ranges, + get_rainbow_color_5, + cx, + ), + 6 => editor.highlight_background::( + &multi_buffer_ranges, + get_rainbow_color_6, + cx, + ), + 7 => editor.highlight_background::( + &multi_buffer_ranges, + get_rainbow_color_7, + cx, + ), + 8 => editor.highlight_background::( + &multi_buffer_ranges, + get_rainbow_color_8, + cx, + ), + _ => editor.highlight_background::( + &multi_buffer_ranges, + get_rainbow_color_9, + cx, + ), } } } From f407fc80f163bdc3728148dea2e721746fab2822 Mon Sep 17 00:00:00 2001 From: GoldStrikeArch Date: Wed, 13 Aug 2025 10:33:38 +0200 Subject: [PATCH 07/14] fix: compute rainbow brackets for visible range --- PR.md | 76 ++++++++ RAINBOW_BRACKETS_COMPARISON.md | 65 +++++++ crates/editor/src/editor.rs | 4 +- crates/editor/src/element.rs | 9 +- crates/editor/src/rainbow_brackets.rs | 258 ++++++++++++++------------ how_it_looks_now.png | Bin 0 -> 32143 bytes how_should_it_look.png | Bin 0 -> 31710 bytes test_rainbow.js | 32 ++++ test_rust_rainbow.rs | 19 ++ 9 files changed, 338 insertions(+), 125 deletions(-) create mode 100644 PR.md create mode 100644 RAINBOW_BRACKETS_COMPARISON.md create mode 100644 how_it_looks_now.png create mode 100644 how_should_it_look.png create mode 100644 test_rainbow.js create mode 100644 test_rust_rainbow.rs diff --git a/PR.md b/PR.md new file mode 100644 index 0000000000..7c7c88917d --- /dev/null +++ b/PR.md @@ -0,0 +1,76 @@ +# Rainbow Brackets for Zed Editor + +## Overview + +This PR implements rainbow bracket highlighting for Zed, addressing issue #5259. The implementation is inspired by the recent Helix PR ([helix-editor/helix#13530](https://github.com/helix-editor/helix/pull/13530)) and provides similar functionality using tree-sitter queries. + +## Context + +- **Parent Issue:** https://github.com/zed-industries/zed/issues/5259 +- **Inspiration:** https://github.com/helix-editor/helix/pull/13530 +- **Implementation:** Heavily assisted by Claude Code as I'm not proficient in Rust or familiar with the Zed codebase +- **Motivation:** I'm missing this feature badly and decided to implement it myself + +## Implementation Details + +The implementation works conceptually the same way as in Helix: +- Uses `rainbow.scm` files to define bracket and scope patterns for each language +- Leverages tree-sitter for syntax-aware bracket matching +- Maintains a scope stack to track nesting levels +- Colors brackets based on their nesting depth + +### Key Components + +1. **Rainbow Query Files** (`crates/languages/src/*/rainbow.scm`): + - Define which syntax nodes are scopes (`@rainbow.scope`) + - Define which tokens are brackets (`@rainbow.bracket`) + - Support `rainbow.include-children` property for fine-grained control + +2. **Core Implementation** (`crates/editor/src/rainbow_brackets.rs`): + - Uses `BufferSnapshot::matches()` for proper tree-sitter query matching + - Implements scope tracking algorithm similar to Helix + - Maps nesting levels to colors (cycling through 10 levels) + +## Current Limitations & Help Needed + +### 1. Text Highlighting vs Background Highlighting +**Current:** Using `editor.highlight_background()` which colors the background behind brackets +**Desired:** Need to highlight the actual text color of the brackets themselves + +I tried using `editor.highlight_text` but couldn't get it to work. I need help understanding: +- How to properly use text highlighting in Zed +- Whether there's a different API I should be using +- If text highlighting requires a different approach than background highlighting + +### 2. TODO Items + +- [ ] **Create tests** - Need to add comprehensive tests for the rainbow bracket functionality +- [ ] **Use text highlight instead of background highlight** - Main blocker, need help with Zed's text highlighting API +- [ ] **Make colors configurable from settings** - Currently colors are hardcoded in the implementation + +## Code Example + +Currently working for Rust: +```rust +fn main() { + let vec = vec![1, 2, 3]; // Different colors for [ ] + if true { // Different color for { } + println!("Hello"); // Different color for ( ) + } +} +``` + +## Request for Guidance + +As someone unfamiliar with Rust and the Zed codebase, I particularly need help with: +1. Understanding how to use Zed's text highlighting API correctly +2. Best practices for adding configuration options to Zed's settings +3. Guidance on the testing approach for this feature + +Any assistance or code examples would be greatly appreciated! + +## Release Notes + +- Added rainbow bracket highlighting support for better visualization of nested code structures +- Fixed bracket matching to be syntax-aware using tree-sitter queries +- Improved code readability by assigning different colors to brackets based on their nesting depth \ No newline at end of file diff --git a/RAINBOW_BRACKETS_COMPARISON.md b/RAINBOW_BRACKETS_COMPARISON.md new file mode 100644 index 0000000000..102d185c5c --- /dev/null +++ b/RAINBOW_BRACKETS_COMPARISON.md @@ -0,0 +1,65 @@ +# Rainbow Brackets Implementation: Zed vs Helix Comparison + +## Key Differences + +### 1. **Query Matching Approach** + +**Helix:** +- Uses proper tree-sitter query matching via `query_iter` and `QueryIterEvent::Match` +- Directly processes query matches in order +- Has access to pattern indices and capture information from queries + +**Zed (Updated Implementation):** +- Now uses proper tree-sitter query matching via `BufferSnapshot::matches()` +- Directly processes query matches in order +- Has access to pattern indices and capture information from queries +- Fully respects the `rainbow.scm` query files + +### 2. **Scope Tracking** + +Both implementations use a similar scope stack approach: +- Pop scopes that have ended before the current node +- Track whether to include children based on pattern properties +- Use pattern indices to determine if `rainbow.include-children` applies + +Both implementations now properly respect the `rainbow.include-children` property from the query. + +### 3. **Node Type Detection** + +Both Helix and Zed now: +- Use tree-sitter queries to determine which nodes are scopes/brackets +- Fully respect the `rainbow.scm` query files +- Don't require any hardcoded node type checks +- Support new languages simply by adding appropriate `rainbow.scm` files + +### 4. **Implementation Complexity** + +Both implementations now have similar complexity: +- Clean, maintainable implementation +- Language-specific behavior defined entirely in `rainbow.scm` files +- Helix: ~50 lines of core logic +- Zed: ~80 lines of core logic (slightly more due to Zed's highlight API requiring level-specific types) + +## Technical Limitations Resolved + +**Update:** We discovered that `BufferSnapshot::matches()` is actually public! This allowed us to rewrite the implementation to use proper tree-sitter query matching. + +Previously identified limitations that are now resolved: +1. ✅ **API Access:** `BufferSnapshot::matches()` provides the needed query matching functionality +2. ✅ **Query Infrastructure:** Rainbow queries are pre-parsed in the grammar, no runtime creation needed +3. ✅ **Pattern Matching:** We can access pattern indices through `mat.pattern_index` + +## Current Status + +The implementation now: +- Works correctly for all languages with `rainbow.scm` files +- Properly respects all query properties including `rainbow.include-children` +- Has similar maintainability to the Helix approach +- Supports new languages by simply adding appropriate `rainbow.scm` files + +## Key Implementation Details + +1. **Scope Stack Algorithm:** Tracks nesting levels and scope boundaries +2. **Pattern Index Handling:** Uses pattern indices to determine `include-children` behavior +3. **Direct Child Detection:** Uses `node.parent()` to check if brackets are direct children +4. **Level-based Highlighting:** Maps nesting levels to color levels (modulo 10) \ No newline at end of file diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 6f708898d0..2218d2134c 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -25,7 +25,6 @@ mod highlight_matching_bracket; mod hover_links; pub mod hover_popover; mod indent_guides; -mod rainbow_brackets; mod inlay_hint_cache; pub mod items; mod jsx_tag_auto_close; @@ -36,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; @@ -117,7 +117,6 @@ use gpui::{ use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file}; use hover_popover::{HoverState, hide_hover}; -use rainbow_brackets::refresh_rainbow_bracket_highlights; use indent_guides::ActiveIndentGuidesState; use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy}; use itertools::{Either, Itertools}; @@ -3072,7 +3071,6 @@ impl Editor { self.refresh_selected_text_highlights(false, window, cx); refresh_matching_bracket_highlights(self, window, cx); self.update_visible_edit_prediction(window, cx); - refresh_rainbow_bracket_highlights(self, window, cx); self.edit_prediction_requires_modifier_in_indent_conflict = true; linked_editing_ranges::refresh_linked_ranges(self, window, cx); self.inline_blame_popover.take(); diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index a7fd0abf88..dad946b6c9 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -9,7 +9,7 @@ use crate::{ MAX_LINE_LEN, MINIMAP_FONT_SIZE, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT, OpenExcerpts, PageDown, PageUp, PhantomBreakpointIndicator, Point, RowExt, RowRangeExt, SelectPhase, SelectedTextHighlight, Selection, SelectionDragState, SoftWrap, StickyHeaderExcerpt, ToPoint, - ToggleFold, ToggleFoldAll, + ToggleFold, ToggleFoldAll, rainbow_brackets, code_context_menus::{CodeActionsMenu, MENU_ASIDE_MAX_WIDTH, MENU_ASIDE_MIN_WIDTH, MENU_GAP}, display_map::{ Block, BlockContext, BlockStyle, ChunkRendererId, DisplaySnapshot, EditorMargins, @@ -8913,8 +8913,11 @@ impl Element for EditorElement { diff_hunk_control_bounds: diff_hunk_control_bounds.clone(), }); - 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 index 7364908198..0cf740776b 100644 --- a/crates/editor/src/rainbow_brackets.rs +++ b/crates/editor/src/rainbow_brackets.rs @@ -1,9 +1,115 @@ use crate::Editor; use gpui::{Context, Hsla, Window}; -use language::Bias; +use language::{Bias, BufferSnapshot}; use std::collections::HashMap; use std::ops::Range; -use text::{Anchor, ToOffset}; +use text::ToOffset; + +/// 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); + } + } + } + } + + matches.advance(); + } + + Some(highlights_by_level) +} /// Rainbow bracket highlighting uses multiple colors to distinguish bracket nesting levels pub fn refresh_rainbow_bracket_highlights( @@ -19,26 +125,41 @@ pub fn refresh_rainbow_bracket_highlights( // For now, handle only singleton buffers if let Some((_, _, buffer_snapshot)) = multi_buffer_snapshot.as_singleton() { - let language = buffer_snapshot.language(); - - if let Some(rainbow_config) = language - .and_then(|lang| lang.grammar()) - .and_then(|grammar| grammar.rainbow_config.as_ref()) - { - let mut highlights_by_level: HashMap>> = HashMap::new(); - - collect_rainbow_highlights(buffer_snapshot, rainbow_config, &mut highlights_by_level); - + // 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, + ) { // Apply highlights by level for (level, ranges) in highlights_by_level { - // Convert text anchors to multi-buffer anchors + // Convert text ranges to multi-buffer anchors let multi_buffer_ranges: Vec<_> = ranges .into_iter() .map(|range| { - let start_offset = range.start.to_offset(buffer_snapshot); - let end_offset = range.end.to_offset(buffer_snapshot); - let start = multi_buffer_snapshot.anchor_at(start_offset, Bias::Left); - let end = multi_buffer_snapshot.anchor_at(end_offset, Bias::Right); + 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(); @@ -103,6 +224,7 @@ pub fn refresh_rainbow_bracket_highlights( } fn clear_current_rainbow_highlights(editor: &mut Editor, cx: &mut Context) { + editor.clear_background_highlights::(cx); editor.clear_background_highlights::(cx); editor.clear_background_highlights::(cx); editor.clear_background_highlights::(cx); @@ -114,108 +236,6 @@ fn clear_current_rainbow_highlights(editor: &mut Editor, cx: &mut Context(cx); } -// Similar to Helix's RainbowScope structure -#[derive(Debug)] -struct RainbowScope { - end_byte: usize, - node: Option, // node ID, similar to Helix's Option - level: usize, -} - -fn collect_rainbow_highlights( - buffer: &language::BufferSnapshot, - rainbow_config: &language::RainbowConfig, - highlights_by_level: &mut HashMap>>, -) { - let mut scope_stack = Vec::::new(); - - // Use the proper tree-sitter query matching API - let mut matches = buffer.matches(0..buffer.len(), |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 start = buffer.anchor_after(byte_range.start); - let end = buffer.anchor_before(byte_range.end); - let range = start..end; - - highlights_by_level - .entry(scope.level % 10) - .or_default() - .push(range); - } - } - } - } - - matches.advance(); - } -} - // TODO! Make it configurable from settings fn get_rainbow_color_0(_theme: &theme::Theme) -> Hsla { hsla(0.0, 0.7, 0.6, 0.3) // Red diff --git a/how_it_looks_now.png b/how_it_looks_now.png new file mode 100644 index 0000000000000000000000000000000000000000..8d222eb4d42f390453b9b030aacc83af08a2b8c7 GIT binary patch literal 32143 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 CsE`f- literal 0 HcmV?d00001 diff --git a/how_should_it_look.png b/how_should_it_look.png new file mode 100644 index 0000000000000000000000000000000000000000..b3157587ceb622fe5a69b48f246a60c608cd5055 GIT binary patch literal 31710 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%0A!bNKm^*3v{56UDu((D&x=x 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 N@O1TaS?83{1OWSqVjTbg literal 0 HcmV?d00001 diff --git a/test_rainbow.js b/test_rainbow.js new file mode 100644 index 0000000000..63f2b7f57b --- /dev/null +++ b/test_rainbow.js @@ -0,0 +1,32 @@ +// Test file for rainbow brackets +function test() { + const arr = [1, 2, 3]; + const obj = { + key: "value", + nested: { + deep: [4, 5, 6] + } + }; + + if (true) { + console.log("Hello"); + for (let i = 0; i < 10; i++) { + console.log(i); + } + } + + return arr.map((x) => x * 2); +} + +// More nested structures +const complex = { + a: [ + { + b: [ + { + c: [1, 2, 3] + } + ] + } + ] +}; \ No newline at end of file diff --git a/test_rust_rainbow.rs b/test_rust_rainbow.rs new file mode 100644 index 0000000000..e48784835e --- /dev/null +++ b/test_rust_rainbow.rs @@ -0,0 +1,19 @@ +fn main() { + let vec = vec![1, 2, 3]; + let map = HashMap::new(); + + if true { + println!("Hello {}", "world"); + let nested = vec![(1, 2), (3, 4)]; + } + + match vec.len() { + 0 => println!("empty"), + 1..=5 => { + for item in vec { + println!("{}", item); + } + } + _ => println!("large"), + } +} \ No newline at end of file From 96e25a24758e3628059d0ec52c321893e79feb30 Mon Sep 17 00:00:00 2001 From: GoldStrikeArch Date: Wed, 13 Aug 2025 12:32:44 +0200 Subject: [PATCH 08/14] fix: use existing brackets.scm instead of a new rainbow.scm --- crates/language/src/language.rs | 40 ++++++++++-- crates/language/src/language_registry.rs | 3 +- crates/languages/src/bash/brackets.scm | 24 ++++++++ crates/languages/src/bash/rainbow.scm | 20 ------ crates/languages/src/c/brackets.scm | 33 ++++++++++ crates/languages/src/c/rainbow.scm | 29 --------- crates/languages/src/cpp/brackets.scm | 53 ++++++++++++++++ crates/languages/src/cpp/rainbow.scm | 49 --------------- crates/languages/src/css/brackets.scm | 19 ++++++ crates/languages/src/css/rainbow.scm | 15 ----- crates/languages/src/go/brackets.scm | 37 +++++++++++ crates/languages/src/go/rainbow.scm | 33 ---------- crates/languages/src/javascript/brackets.scm | 19 ++++++ crates/languages/src/javascript/rainbow.scm | 15 ----- crates/languages/src/json/brackets.scm | 13 ++++ crates/languages/src/json/rainbow.scm | 9 --- crates/languages/src/jsonc/brackets.scm | 13 ++++ crates/languages/src/jsonc/rainbow.scm | 9 --- crates/languages/src/python/brackets.scm | 32 ++++++++++ crates/languages/src/python/rainbow.scm | 28 --------- crates/languages/src/regex/brackets.scm | 20 ++++++ crates/languages/src/regex/rainbow.scm | 16 ----- crates/languages/src/rust/brackets.scm | 64 ++++++++++++++++++++ crates/languages/src/rust/rainbow.scm | 60 ------------------ crates/languages/src/tsx/brackets.scm | 27 +++++++++ crates/languages/src/tsx/rainbow.scm | 23 ------- crates/languages/src/typescript/brackets.scm | 22 +++++++ crates/languages/src/typescript/rainbow.scm | 18 ------ 28 files changed, 411 insertions(+), 332 deletions(-) delete mode 100644 crates/languages/src/bash/rainbow.scm delete mode 100644 crates/languages/src/c/rainbow.scm delete mode 100644 crates/languages/src/cpp/rainbow.scm delete mode 100644 crates/languages/src/css/rainbow.scm delete mode 100644 crates/languages/src/go/rainbow.scm delete mode 100644 crates/languages/src/javascript/rainbow.scm delete mode 100644 crates/languages/src/json/rainbow.scm delete mode 100644 crates/languages/src/jsonc/rainbow.scm delete mode 100644 crates/languages/src/python/rainbow.scm delete mode 100644 crates/languages/src/regex/rainbow.scm delete mode 100644 crates/languages/src/rust/rainbow.scm delete mode 100644 crates/languages/src/tsx/rainbow.scm delete mode 100644 crates/languages/src/typescript/rainbow.scm diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index f294790827..4f1ed68b7b 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -1391,11 +1391,7 @@ impl Language { .with_text_object_query(query.as_ref()) .context("Error loading textobject query")?; } - if let Some(query) = queries.rainbow { - self = self - .with_rainbow_query(query.as_ref()) - .context("Error loading rainbow 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()) @@ -1539,16 +1535,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(); @@ -1561,6 +1565,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, @@ -1569,6 +1575,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) } @@ -1783,7 +1811,7 @@ impl Language { ("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) { diff --git a/crates/language/src/language_registry.rs b/crates/language/src/language_registry.rs index 693bdc7d0c..00a56e9e91 100644 --- a/crates/language/src/language_registry.rs +++ b/crates/language/src/language_registry.rs @@ -231,7 +231,7 @@ pub const QUERY_FILENAME_PREFIXES: &[( ("runnables", |q| &mut q.runnables), ("debugger", |q| &mut q.debugger), ("textobjects", |q| &mut q.text_objects), - ("rainbow", |q| &mut q.rainbow), + // Rainbow queries are now loaded as part of brackets.scm ]; /// Tree-sitter language queries for a given language. @@ -248,7 +248,6 @@ pub struct LanguageQueries { pub runnables: Option>, pub text_objects: Option>, pub debugger: Option>, - pub rainbow: Option>, } #[derive(Clone, Default)] 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/bash/rainbow.scm b/crates/languages/src/bash/rainbow.scm deleted file mode 100644 index 422e2fbddd..0000000000 --- a/crates/languages/src/bash/rainbow.scm +++ /dev/null @@ -1,20 +0,0 @@ -[ - (function_definition) - (compound_statement) - (subshell) - (test_command) - (subscript) - (parenthesized_expression) - (array) - (expansion) - (command_substitution) -] @rainbow.scope - -[ - "(" ")" - "((" "))" - "${" "$(" - "{" "}" - "[" "]" - "[[" "]]" -] @rainbow.bracket 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/c/rainbow.scm b/crates/languages/src/c/rainbow.scm deleted file mode 100644 index 1f80868ae5..0000000000 --- a/crates/languages/src/c/rainbow.scm +++ /dev/null @@ -1,29 +0,0 @@ -[ - (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.bracket 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/cpp/rainbow.scm b/crates/languages/src/cpp/rainbow.scm deleted file mode 100644 index ff4882c2d4..0000000000 --- a/crates/languages/src/cpp/rainbow.scm +++ /dev/null @@ -1,49 +0,0 @@ -[ - ; 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.bracket 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/css/rainbow.scm b/crates/languages/src/css/rainbow.scm deleted file mode 100644 index 66b60d515a..0000000000 --- a/crates/languages/src/css/rainbow.scm +++ /dev/null @@ -1,15 +0,0 @@ -[ - (keyframe_block_list) - (block) - (attribute_selector) - (feature_query) - (parenthesized_query) - (selector_query) - (parenthesized_value) - (arguments) -] @rainbow.scope - -[ - "{" "}" - "(" ")" -] @rainbow.bracket 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/go/rainbow.scm b/crates/languages/src/go/rainbow.scm deleted file mode 100644 index 81004bf885..0000000000 --- a/crates/languages/src/go/rainbow.scm +++ /dev/null @@ -1,33 +0,0 @@ -[ - (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.bracket 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/javascript/rainbow.scm b/crates/languages/src/javascript/rainbow.scm deleted file mode 100644 index b7ac4e8e13..0000000000 --- a/crates/languages/src/javascript/rainbow.scm +++ /dev/null @@ -1,15 +0,0 @@ -[ - (object) - (array) - (arguments) - (formal_parameters) - (statement_block) - (parenthesized_expression) - (call_expression) -] @rainbow.scope - -[ - "[" "]" - "{" "}" - "(" ")" -] @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/json/rainbow.scm b/crates/languages/src/json/rainbow.scm deleted file mode 100644 index 5c21bdcc60..0000000000 --- a/crates/languages/src/json/rainbow.scm +++ /dev/null @@ -1,9 +0,0 @@ -[ - (object) - (array) -] @rainbow.scope - -[ - "[" "]" - "{" "}" -] @rainbow.bracket 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/jsonc/rainbow.scm b/crates/languages/src/jsonc/rainbow.scm deleted file mode 100644 index 5c21bdcc60..0000000000 --- a/crates/languages/src/jsonc/rainbow.scm +++ /dev/null @@ -1,9 +0,0 @@ -[ - (object) - (array) -] @rainbow.scope - -[ - "[" "]" - "{" "}" -] @rainbow.bracket 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/python/rainbow.scm b/crates/languages/src/python/rainbow.scm deleted file mode 100644 index 15e5db29c7..0000000000 --- a/crates/languages/src/python/rainbow.scm +++ /dev/null @@ -1,28 +0,0 @@ -[ - (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.bracket 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/regex/rainbow.scm b/crates/languages/src/regex/rainbow.scm deleted file mode 100644 index 4cd2ec7c38..0000000000 --- a/crates/languages/src/regex/rainbow.scm +++ /dev/null @@ -1,16 +0,0 @@ -[ - (character_class) - (anonymous_capturing_group) - (named_capturing_group) - (non_capturing_group) - (count_quantifier) - (character_class_escape) -] @rainbow.scope - -[ - "(?" "(?:" - "(?<" ">" - "(" ")" - "[" "]" - "{" "}" -] @rainbow.bracket 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/rust/rainbow.scm b/crates/languages/src/rust/rainbow.scm deleted file mode 100644 index 0656047be4..0000000000 --- a/crates/languages/src/rust/rainbow.scm +++ /dev/null @@ -1,60 +0,0 @@ -[ - ; {/} - (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.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/tsx/rainbow.scm b/crates/languages/src/tsx/rainbow.scm deleted file mode 100644 index 0f32bb0a1c..0000000000 --- a/crates/languages/src/tsx/rainbow.scm +++ /dev/null @@ -1,23 +0,0 @@ -[ - (object) - (array) - (arguments) - (formal_parameters) - (statement_block) - (parenthesized_expression) - (call_expression) - (type_parameters) - (type_arguments) - (jsx_element) - (jsx_self_closing_element) -] @rainbow.scope - -[ - "[" "]" - "{" "}" - "(" ")" -] @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 diff --git a/crates/languages/src/typescript/rainbow.scm b/crates/languages/src/typescript/rainbow.scm deleted file mode 100644 index 7dd228746c..0000000000 --- a/crates/languages/src/typescript/rainbow.scm +++ /dev/null @@ -1,18 +0,0 @@ -[ - (object) - (array) - (arguments) - (formal_parameters) - (statement_block) - (parenthesized_expression) - (call_expression) - (type_parameters) - (type_arguments) -] @rainbow.scope - -[ - "[" "]" - "{" "}" - "(" ")" - "<" ">" -] @rainbow.bracket \ No newline at end of file From a476f2db3e70afb17673cd6094087ce696feab6b Mon Sep 17 00:00:00 2001 From: GoldStrikeArch Date: Wed, 13 Aug 2025 14:48:23 +0200 Subject: [PATCH 09/14] fix: remove meta files --- PR.md | 76 --------------------------------- RAINBOW_BRACKETS_COMPARISON.md | 65 ---------------------------- how_it_looks_now.png | Bin 32143 -> 0 bytes how_should_it_look.png | Bin 31710 -> 0 bytes test_rainbow.js | 32 -------------- test_rust_rainbow.rs | 19 --------- 6 files changed, 192 deletions(-) delete mode 100644 PR.md delete mode 100644 RAINBOW_BRACKETS_COMPARISON.md delete mode 100644 how_it_looks_now.png delete mode 100644 how_should_it_look.png delete mode 100644 test_rainbow.js delete mode 100644 test_rust_rainbow.rs diff --git a/PR.md b/PR.md deleted file mode 100644 index 7c7c88917d..0000000000 --- a/PR.md +++ /dev/null @@ -1,76 +0,0 @@ -# Rainbow Brackets for Zed Editor - -## Overview - -This PR implements rainbow bracket highlighting for Zed, addressing issue #5259. The implementation is inspired by the recent Helix PR ([helix-editor/helix#13530](https://github.com/helix-editor/helix/pull/13530)) and provides similar functionality using tree-sitter queries. - -## Context - -- **Parent Issue:** https://github.com/zed-industries/zed/issues/5259 -- **Inspiration:** https://github.com/helix-editor/helix/pull/13530 -- **Implementation:** Heavily assisted by Claude Code as I'm not proficient in Rust or familiar with the Zed codebase -- **Motivation:** I'm missing this feature badly and decided to implement it myself - -## Implementation Details - -The implementation works conceptually the same way as in Helix: -- Uses `rainbow.scm` files to define bracket and scope patterns for each language -- Leverages tree-sitter for syntax-aware bracket matching -- Maintains a scope stack to track nesting levels -- Colors brackets based on their nesting depth - -### Key Components - -1. **Rainbow Query Files** (`crates/languages/src/*/rainbow.scm`): - - Define which syntax nodes are scopes (`@rainbow.scope`) - - Define which tokens are brackets (`@rainbow.bracket`) - - Support `rainbow.include-children` property for fine-grained control - -2. **Core Implementation** (`crates/editor/src/rainbow_brackets.rs`): - - Uses `BufferSnapshot::matches()` for proper tree-sitter query matching - - Implements scope tracking algorithm similar to Helix - - Maps nesting levels to colors (cycling through 10 levels) - -## Current Limitations & Help Needed - -### 1. Text Highlighting vs Background Highlighting -**Current:** Using `editor.highlight_background()` which colors the background behind brackets -**Desired:** Need to highlight the actual text color of the brackets themselves - -I tried using `editor.highlight_text` but couldn't get it to work. I need help understanding: -- How to properly use text highlighting in Zed -- Whether there's a different API I should be using -- If text highlighting requires a different approach than background highlighting - -### 2. TODO Items - -- [ ] **Create tests** - Need to add comprehensive tests for the rainbow bracket functionality -- [ ] **Use text highlight instead of background highlight** - Main blocker, need help with Zed's text highlighting API -- [ ] **Make colors configurable from settings** - Currently colors are hardcoded in the implementation - -## Code Example - -Currently working for Rust: -```rust -fn main() { - let vec = vec![1, 2, 3]; // Different colors for [ ] - if true { // Different color for { } - println!("Hello"); // Different color for ( ) - } -} -``` - -## Request for Guidance - -As someone unfamiliar with Rust and the Zed codebase, I particularly need help with: -1. Understanding how to use Zed's text highlighting API correctly -2. Best practices for adding configuration options to Zed's settings -3. Guidance on the testing approach for this feature - -Any assistance or code examples would be greatly appreciated! - -## Release Notes - -- Added rainbow bracket highlighting support for better visualization of nested code structures -- Fixed bracket matching to be syntax-aware using tree-sitter queries -- Improved code readability by assigning different colors to brackets based on their nesting depth \ No newline at end of file diff --git a/RAINBOW_BRACKETS_COMPARISON.md b/RAINBOW_BRACKETS_COMPARISON.md deleted file mode 100644 index 102d185c5c..0000000000 --- a/RAINBOW_BRACKETS_COMPARISON.md +++ /dev/null @@ -1,65 +0,0 @@ -# Rainbow Brackets Implementation: Zed vs Helix Comparison - -## Key Differences - -### 1. **Query Matching Approach** - -**Helix:** -- Uses proper tree-sitter query matching via `query_iter` and `QueryIterEvent::Match` -- Directly processes query matches in order -- Has access to pattern indices and capture information from queries - -**Zed (Updated Implementation):** -- Now uses proper tree-sitter query matching via `BufferSnapshot::matches()` -- Directly processes query matches in order -- Has access to pattern indices and capture information from queries -- Fully respects the `rainbow.scm` query files - -### 2. **Scope Tracking** - -Both implementations use a similar scope stack approach: -- Pop scopes that have ended before the current node -- Track whether to include children based on pattern properties -- Use pattern indices to determine if `rainbow.include-children` applies - -Both implementations now properly respect the `rainbow.include-children` property from the query. - -### 3. **Node Type Detection** - -Both Helix and Zed now: -- Use tree-sitter queries to determine which nodes are scopes/brackets -- Fully respect the `rainbow.scm` query files -- Don't require any hardcoded node type checks -- Support new languages simply by adding appropriate `rainbow.scm` files - -### 4. **Implementation Complexity** - -Both implementations now have similar complexity: -- Clean, maintainable implementation -- Language-specific behavior defined entirely in `rainbow.scm` files -- Helix: ~50 lines of core logic -- Zed: ~80 lines of core logic (slightly more due to Zed's highlight API requiring level-specific types) - -## Technical Limitations Resolved - -**Update:** We discovered that `BufferSnapshot::matches()` is actually public! This allowed us to rewrite the implementation to use proper tree-sitter query matching. - -Previously identified limitations that are now resolved: -1. ✅ **API Access:** `BufferSnapshot::matches()` provides the needed query matching functionality -2. ✅ **Query Infrastructure:** Rainbow queries are pre-parsed in the grammar, no runtime creation needed -3. ✅ **Pattern Matching:** We can access pattern indices through `mat.pattern_index` - -## Current Status - -The implementation now: -- Works correctly for all languages with `rainbow.scm` files -- Properly respects all query properties including `rainbow.include-children` -- Has similar maintainability to the Helix approach -- Supports new languages by simply adding appropriate `rainbow.scm` files - -## Key Implementation Details - -1. **Scope Stack Algorithm:** Tracks nesting levels and scope boundaries -2. **Pattern Index Handling:** Uses pattern indices to determine `include-children` behavior -3. **Direct Child Detection:** Uses `node.parent()` to check if brackets are direct children -4. **Level-based Highlighting:** Maps nesting levels to color levels (modulo 10) \ No newline at end of file diff --git a/how_it_looks_now.png b/how_it_looks_now.png deleted file mode 100644 index 8d222eb4d42f390453b9b030aacc83af08a2b8c7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32143 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 CsE`f- diff --git a/how_should_it_look.png b/how_should_it_look.png deleted file mode 100644 index b3157587ceb622fe5a69b48f246a60c608cd5055..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31710 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%0A!bNKm^*3v{56UDu((D&x=x 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 N@O1TaS?83{1OWSqVjTbg diff --git a/test_rainbow.js b/test_rainbow.js deleted file mode 100644 index 63f2b7f57b..0000000000 --- a/test_rainbow.js +++ /dev/null @@ -1,32 +0,0 @@ -// Test file for rainbow brackets -function test() { - const arr = [1, 2, 3]; - const obj = { - key: "value", - nested: { - deep: [4, 5, 6] - } - }; - - if (true) { - console.log("Hello"); - for (let i = 0; i < 10; i++) { - console.log(i); - } - } - - return arr.map((x) => x * 2); -} - -// More nested structures -const complex = { - a: [ - { - b: [ - { - c: [1, 2, 3] - } - ] - } - ] -}; \ No newline at end of file diff --git a/test_rust_rainbow.rs b/test_rust_rainbow.rs deleted file mode 100644 index e48784835e..0000000000 --- a/test_rust_rainbow.rs +++ /dev/null @@ -1,19 +0,0 @@ -fn main() { - let vec = vec![1, 2, 3]; - let map = HashMap::new(); - - if true { - println!("Hello {}", "world"); - let nested = vec![(1, 2), (3, 4)]; - } - - match vec.len() { - 0 => println!("empty"), - 1..=5 => { - for item in vec { - println!("{}", item); - } - } - _ => println!("large"), - } -} \ No newline at end of file From 6d63ac15196e9d48a5a64bdb3002cef92748cada Mon Sep 17 00:00:00 2001 From: GoldStrikeArch Date: Wed, 13 Aug 2025 16:29:28 +0200 Subject: [PATCH 10/14] feat: add tests --- crates/editor/src/editor.rs | 2 + crates/editor/src/rainbow_brackets.rs | 5 + crates/editor/src/rainbow_brackets_tests.rs | 338 ++++++++++++++++++++ 3 files changed, 345 insertions(+) create mode 100644 crates/editor/src/rainbow_brackets_tests.rs diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 2218d2134c..bef738d6ee 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -41,6 +41,8 @@ pub mod scroll; mod selections_collection; pub mod tasks; +#[cfg(test)] +mod rainbow_brackets_tests; #[cfg(test)] mod code_completion_tests; #[cfg(test)] diff --git a/crates/editor/src/rainbow_brackets.rs b/crates/editor/src/rainbow_brackets.rs index 0cf740776b..73ba4cb31a 100644 --- a/crates/editor/src/rainbow_brackets.rs +++ b/crates/editor/src/rainbow_brackets.rs @@ -105,6 +105,7 @@ pub fn compute_rainbow_brackets_for_range( } } + // IMPORTANT: Always advance to prevent infinite loop matches.advance(); } @@ -297,3 +298,7 @@ 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..8faba24352 --- /dev/null +++ b/crates/editor/src/rainbow_brackets_tests.rs @@ -0,0 +1,338 @@ +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()); + } + } +} \ No newline at end of file From d342a900d5e97d4a303880f61557deb44951b14c Mon Sep 17 00:00:00 2001 From: GoldStrikeArch Date: Wed, 13 Aug 2025 17:31:15 +0200 Subject: [PATCH 11/14] feat: applying the editor.highlight_text instead of editor.highlight_background --- crates/editor/src/display_map.rs | 13 +- crates/editor/src/element.rs | 5 +- crates/editor/src/rainbow_brackets.rs | 217 +++++++++++++------------- 3 files changed, 125 insertions(+), 110 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index a16e516a70..2fa3494ba1 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/element.rs b/crates/editor/src/element.rs index dad946b6c9..964267f3b0 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -9,7 +9,7 @@ use crate::{ MAX_LINE_LEN, MINIMAP_FONT_SIZE, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT, OpenExcerpts, PageDown, PageUp, PhantomBreakpointIndicator, Point, RowExt, RowRangeExt, SelectPhase, SelectedTextHighlight, Selection, SelectionDragState, SoftWrap, StickyHeaderExcerpt, ToPoint, - ToggleFold, ToggleFoldAll, rainbow_brackets, + ToggleFold, ToggleFoldAll, code_context_menus::{CodeActionsMenu, MENU_ASIDE_MAX_WIDTH, MENU_ASIDE_MIN_WIDTH, MENU_GAP}, display_map::{ Block, BlockContext, BlockStyle, ChunkRendererId, DisplaySnapshot, EditorMargins, @@ -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}; @@ -8915,7 +8916,7 @@ impl Element for EditorElement { 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); }); diff --git a/crates/editor/src/rainbow_brackets.rs b/crates/editor/src/rainbow_brackets.rs index 73ba4cb31a..3f83291f3f 100644 --- a/crates/editor/src/rainbow_brackets.rs +++ b/crates/editor/src/rainbow_brackets.rs @@ -1,5 +1,5 @@ use crate::Editor; -use gpui::{Context, Hsla, Window}; +use gpui::{Context, HighlightStyle, Hsla, Window}; use language::{Bias, BufferSnapshot}; use std::collections::HashMap; use std::ops::Range; @@ -12,9 +12,9 @@ pub fn compute_rainbow_brackets_for_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 { @@ -22,18 +22,18 @@ pub fn compute_rainbow_brackets_for_range( 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() @@ -41,7 +41,7 @@ pub fn compute_rainbow_brackets_for_range( { scope_stack.pop(); } - + // Check which capture this is let is_scope_capture = rainbow_config .scope_capture_ix @@ -49,7 +49,7 @@ pub fn compute_rainbow_brackets_for_range( 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 @@ -58,7 +58,7 @@ pub fn compute_rainbow_brackets_for_range( { let node = scope_capture.node; let byte_range = node.byte_range(); - + scope_stack.push(RainbowScope { end_byte: byte_range.end, node: if rainbow_config @@ -73,7 +73,7 @@ pub fn compute_rainbow_brackets_for_range( }); } } - + if is_bracket_capture { // Process bracket capture if let Some(bracket_capture) = rainbow_config @@ -82,7 +82,7 @@ pub fn compute_rainbow_brackets_for_range( { 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 { @@ -93,7 +93,7 @@ pub fn compute_rainbow_brackets_for_range( // include-children mode: highlight all brackets in this scope true }; - + if should_highlight { let level = scope.level % 10; highlights_by_level @@ -104,11 +104,11 @@ pub fn compute_rainbow_brackets_for_range( } } } - + // IMPORTANT: Always advance to prevent infinite loop matches.advance(); } - + Some(highlights_by_level) } @@ -131,28 +131,28 @@ pub fn refresh_rainbow_bracket_highlights( 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); - + 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 + crate::Bias::Left, ); let end_point = display_map.display_point_to_point( crate::DisplayPoint::new(crate::DisplayRow(end_row), 0), - crate::Bias::Right + 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, - ) { + + if let Some(highlights_by_level) = + compute_rainbow_brackets_for_range(buffer_snapshot, start_offset..end_offset) + { // Apply highlights by level for (level, ranges) in highlights_by_level { // Convert text ranges to multi-buffer anchors @@ -165,59 +165,32 @@ pub fn refresh_rainbow_bracket_highlights( }) .collect(); - // TODO: make it a text style instead of a background highlight + // Use text highlighting instead of background highlighting // Create a unique type for each level to avoid conflicts + let style = match level { + 0 => get_rainbow_style_0(), + 1 => get_rainbow_style_1(), + 2 => get_rainbow_style_2(), + 3 => get_rainbow_style_3(), + 4 => get_rainbow_style_4(), + 5 => get_rainbow_style_5(), + 6 => get_rainbow_style_6(), + 7 => get_rainbow_style_7(), + 8 => get_rainbow_style_8(), + _ => get_rainbow_style_9(), + }; + match level { - 0 => editor.highlight_background::( - &multi_buffer_ranges, - get_rainbow_color_0, - cx, - ), - 1 => editor.highlight_background::( - &multi_buffer_ranges, - get_rainbow_color_1, - cx, - ), - 2 => editor.highlight_background::( - &multi_buffer_ranges, - get_rainbow_color_2, - cx, - ), - 3 => editor.highlight_background::( - &multi_buffer_ranges, - get_rainbow_color_3, - cx, - ), - 4 => editor.highlight_background::( - &multi_buffer_ranges, - get_rainbow_color_4, - cx, - ), - 5 => editor.highlight_background::( - &multi_buffer_ranges, - get_rainbow_color_5, - cx, - ), - 6 => editor.highlight_background::( - &multi_buffer_ranges, - get_rainbow_color_6, - cx, - ), - 7 => editor.highlight_background::( - &multi_buffer_ranges, - get_rainbow_color_7, - cx, - ), - 8 => editor.highlight_background::( - &multi_buffer_ranges, - get_rainbow_color_8, - cx, - ), - _ => editor.highlight_background::( - &multi_buffer_ranges, - get_rainbow_color_9, - cx, - ), + 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), } } } @@ -225,57 +198,87 @@ pub fn refresh_rainbow_bracket_highlights( } fn clear_current_rainbow_highlights(editor: &mut Editor, cx: &mut Context) { - editor.clear_background_highlights::(cx); - editor.clear_background_highlights::(cx); - editor.clear_background_highlights::(cx); - editor.clear_background_highlights::(cx); - editor.clear_background_highlights::(cx); - editor.clear_background_highlights::(cx); - editor.clear_background_highlights::(cx); - editor.clear_background_highlights::(cx); - editor.clear_background_highlights::(cx); - editor.clear_background_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); + editor.clear_highlights::(cx); } // TODO! Make it configurable from settings -fn get_rainbow_color_0(_theme: &theme::Theme) -> Hsla { - hsla(0.0, 0.7, 0.6, 0.3) // Red +fn get_rainbow_style_0() -> HighlightStyle { + HighlightStyle { + color: Some(hsla(0.0, 0.8, 0.6, 1.0)), // Red + ..Default::default() + } } -fn get_rainbow_color_1(_theme: &theme::Theme) -> Hsla { - hsla(30.0, 0.7, 0.6, 0.3) // Orange +fn get_rainbow_style_1() -> HighlightStyle { + HighlightStyle { + color: Some(hsla(30.0, 0.8, 0.6, 1.0)), // Orange + ..Default::default() + } } -fn get_rainbow_color_2(_theme: &theme::Theme) -> Hsla { - hsla(60.0, 0.7, 0.6, 0.3) // Yellow +fn get_rainbow_style_2() -> HighlightStyle { + HighlightStyle { + color: Some(hsla(60.0, 0.8, 0.6, 1.0)), // Yellow + ..Default::default() + } } -fn get_rainbow_color_3(_theme: &theme::Theme) -> Hsla { - hsla(120.0, 0.7, 0.6, 0.3) // Green +fn get_rainbow_style_3() -> HighlightStyle { + HighlightStyle { + color: Some(hsla(120.0, 0.8, 0.6, 1.0)), // Green + ..Default::default() + } } -fn get_rainbow_color_4(_theme: &theme::Theme) -> Hsla { - hsla(180.0, 0.7, 0.6, 0.3) // Cyan +fn get_rainbow_style_4() -> HighlightStyle { + HighlightStyle { + color: Some(hsla(180.0, 0.8, 0.6, 1.0)), // Cyan + ..Default::default() + } } -fn get_rainbow_color_5(_theme: &theme::Theme) -> Hsla { - hsla(240.0, 0.7, 0.6, 0.3) // Blue +fn get_rainbow_style_5() -> HighlightStyle { + HighlightStyle { + color: Some(hsla(240.0, 0.8, 0.6, 1.0)), // Blue + ..Default::default() + } } -fn get_rainbow_color_6(_theme: &theme::Theme) -> Hsla { - hsla(270.0, 0.7, 0.6, 0.3) // Purple +fn get_rainbow_style_6() -> HighlightStyle { + HighlightStyle { + color: Some(hsla(270.0, 0.8, 0.6, 1.0)), // Purple + ..Default::default() + } } -fn get_rainbow_color_7(_theme: &theme::Theme) -> Hsla { - hsla(0.0, 0.7, 0.6, 0.3) // Red (repeat) +fn get_rainbow_style_7() -> HighlightStyle { + HighlightStyle { + color: Some(hsla(0.0, 0.8, 0.6, 1.0)), // Red (repeat) + ..Default::default() + } } -fn get_rainbow_color_8(_theme: &theme::Theme) -> Hsla { - hsla(30.0, 0.7, 0.6, 0.3) // Orange (repeat) +fn get_rainbow_style_8() -> HighlightStyle { + HighlightStyle { + color: Some(hsla(30.0, 0.8, 0.6, 1.0)), // Orange (repeat) + ..Default::default() + } } -fn get_rainbow_color_9(_theme: &theme::Theme) -> Hsla { - hsla(60.0, 0.7, 0.6, 0.3) // Yellow (repeat) +fn get_rainbow_style_9() -> HighlightStyle { + HighlightStyle { + color: Some(hsla(60.0, 0.8, 0.6, 1.0)), // Yellow (repeat) + ..Default::default() + } } fn hsla(hue: f32, saturation: f32, lightness: f32, alpha: f32) -> Hsla { From 9d9bb0607d543b2de42dd129973e1f3f3763ba06 Mon Sep 17 00:00:00 2001 From: GoldStrikeArch Date: Thu, 14 Aug 2025 10:35:15 +0200 Subject: [PATCH 12/14] feat: use theme's accent colors for rainbow brackets --- crates/editor/src/rainbow_brackets.rs | 104 +++----------------------- 1 file changed, 10 insertions(+), 94 deletions(-) diff --git a/crates/editor/src/rainbow_brackets.rs b/crates/editor/src/rainbow_brackets.rs index 3f83291f3f..3af46d2335 100644 --- a/crates/editor/src/rainbow_brackets.rs +++ b/crates/editor/src/rainbow_brackets.rs @@ -1,9 +1,10 @@ use crate::Editor; -use gpui::{Context, HighlightStyle, Hsla, Window}; +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( @@ -153,6 +154,9 @@ pub fn refresh_rainbow_bracket_highlights( 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 @@ -165,19 +169,11 @@ pub fn refresh_rainbow_bracket_highlights( }) .collect(); - // Use text highlighting instead of background highlighting - // Create a unique type for each level to avoid conflicts - let style = match level { - 0 => get_rainbow_style_0(), - 1 => get_rainbow_style_1(), - 2 => get_rainbow_style_2(), - 3 => get_rainbow_style_3(), - 4 => get_rainbow_style_4(), - 5 => get_rainbow_style_5(), - 6 => get_rainbow_style_6(), - 7 => get_rainbow_style_7(), - 8 => get_rainbow_style_8(), - _ => get_rainbow_style_9(), + // 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 { @@ -210,86 +206,6 @@ fn clear_current_rainbow_highlights(editor: &mut Editor, cx: &mut Context(cx); } -// TODO! Make it configurable from settings -fn get_rainbow_style_0() -> HighlightStyle { - HighlightStyle { - color: Some(hsla(0.0, 0.8, 0.6, 1.0)), // Red - ..Default::default() - } -} - -fn get_rainbow_style_1() -> HighlightStyle { - HighlightStyle { - color: Some(hsla(30.0, 0.8, 0.6, 1.0)), // Orange - ..Default::default() - } -} - -fn get_rainbow_style_2() -> HighlightStyle { - HighlightStyle { - color: Some(hsla(60.0, 0.8, 0.6, 1.0)), // Yellow - ..Default::default() - } -} - -fn get_rainbow_style_3() -> HighlightStyle { - HighlightStyle { - color: Some(hsla(120.0, 0.8, 0.6, 1.0)), // Green - ..Default::default() - } -} - -fn get_rainbow_style_4() -> HighlightStyle { - HighlightStyle { - color: Some(hsla(180.0, 0.8, 0.6, 1.0)), // Cyan - ..Default::default() - } -} - -fn get_rainbow_style_5() -> HighlightStyle { - HighlightStyle { - color: Some(hsla(240.0, 0.8, 0.6, 1.0)), // Blue - ..Default::default() - } -} - -fn get_rainbow_style_6() -> HighlightStyle { - HighlightStyle { - color: Some(hsla(270.0, 0.8, 0.6, 1.0)), // Purple - ..Default::default() - } -} - -fn get_rainbow_style_7() -> HighlightStyle { - HighlightStyle { - color: Some(hsla(0.0, 0.8, 0.6, 1.0)), // Red (repeat) - ..Default::default() - } -} - -fn get_rainbow_style_8() -> HighlightStyle { - HighlightStyle { - color: Some(hsla(30.0, 0.8, 0.6, 1.0)), // Orange (repeat) - ..Default::default() - } -} - -fn get_rainbow_style_9() -> HighlightStyle { - HighlightStyle { - color: Some(hsla(60.0, 0.8, 0.6, 1.0)), // Yellow (repeat) - ..Default::default() - } -} - -fn hsla(hue: f32, saturation: f32, lightness: f32, alpha: f32) -> Hsla { - Hsla { - h: hue / 360.0, - s: saturation, - l: lightness, - a: alpha, - } -} - // Marker types for different rainbow levels enum RainbowLevel0 {} enum RainbowLevel1 {} From a8acda0addc300704aceeadd6fe0b3a9129d6002 Mon Sep 17 00:00:00 2001 From: GoldStrikeArch Date: Thu, 14 Aug 2025 19:42:06 +0200 Subject: [PATCH 13/14] fix: remove test duplications --- crates/editor/src/editor.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index bef738d6ee..2218d2134c 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -41,8 +41,6 @@ pub mod scroll; mod selections_collection; pub mod tasks; -#[cfg(test)] -mod rainbow_brackets_tests; #[cfg(test)] mod code_completion_tests; #[cfg(test)] From d27cb8ab17ae3bbf648c028001506f9ca71e4265 Mon Sep 17 00:00:00 2001 From: GoldStrikeArch Date: Thu, 14 Aug 2025 19:51:05 +0200 Subject: [PATCH 14/14] fix: formatting issues --- crates/editor/src/rainbow_brackets_tests.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/crates/editor/src/rainbow_brackets_tests.rs b/crates/editor/src/rainbow_brackets_tests.rs index 8faba24352..f3d69e7d80 100644 --- a/crates/editor/src/rainbow_brackets_tests.rs +++ b/crates/editor/src/rainbow_brackets_tests.rs @@ -295,7 +295,7 @@ async fn test_rust_rainbow_brackets(cx: &mut TestAppContext) { .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 "#}) @@ -317,7 +317,7 @@ async fn test_rust_rainbow_brackets(cx: &mut TestAppContext) { 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 @@ -325,8 +325,11 @@ async fn test_rust_rainbow_brackets(cx: &mut TestAppContext) { let highlights = highlights.unwrap(); // Verify we have some levels - assert!(!highlights.is_empty(), "Should have at least one level of brackets"); - + 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); @@ -335,4 +338,4 @@ async fn test_rust_rainbow_brackets(cx: &mut TestAppContext) { assert!(range.end <= buffer.len()); } } -} \ No newline at end of file +}