diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index 73d9db921efc7..56ff774d0e962 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -119,6 +119,10 @@ pub struct LanguageConfiguration { #[serde(skip_serializing_if = "Option::is_none")] pub debugger: Option, + /// The Grammar query for Sticky Context + #[serde(skip)] + pub(crate) context_query: OnceCell>, + /// Automatic insertion of pairs to parentheses, brackets, /// etc. Defaults to true. Optionally, this can be a list of 2-tuples /// to specify a list of characters to pair. This overrides the @@ -127,9 +131,6 @@ pub struct LanguageConfiguration { pub auto_pairs: Option, pub rulers: Option>, // if set, override editor's rulers - - /// List of tree-sitter nodes that should be displayed in the sticky context. - pub sticky_context_nodes: Option>, } #[derive(Debug, PartialEq, Eq, Hash)] @@ -336,6 +337,15 @@ pub struct TextObjectQuery { pub query: Query, } +#[derive(Debug)] +pub struct ContextQuery { + pub query: Query, +} + +impl ContextQuery { + // pub fn is_node(&self, node: &Node) -> bool {} +} + #[derive(Debug)] pub enum CapturedNode<'a> { Single(Node<'a>), @@ -528,6 +538,15 @@ impl LanguageConfiguration { .as_ref() } + pub fn context_query(&self) -> Option<&ContextQuery> { + self.context_query + .get_or_init(|| { + self.load_query("context.scm") + .map(|query| ContextQuery { query }) + }) + .as_ref() + } + pub fn scope(&self) -> &str { &self.scope } diff --git a/helix-term/src/health.rs b/helix-term/src/health.rs index 480c2c67579be..72d64b9fa5cbf 100644 --- a/helix-term/src/health.rs +++ b/helix-term/src/health.rs @@ -12,6 +12,7 @@ pub enum TsFeature { Highlight, TextObject, AutoIndent, + Context } impl TsFeature { @@ -24,6 +25,7 @@ impl TsFeature { Self::Highlight => "highlights.scm", Self::TextObject => "textobjects.scm", Self::AutoIndent => "indents.scm", + Self::Context => "context.scm", } } @@ -32,6 +34,7 @@ impl TsFeature { Self::Highlight => "Syntax Highlighting", Self::TextObject => "Treesitter Textobjects", Self::AutoIndent => "Auto Indent", + Self::Context => "Sticky Context", } } @@ -40,6 +43,7 @@ impl TsFeature { Self::Highlight => "Highlight", Self::TextObject => "Textobject", Self::AutoIndent => "Indent", + Self::Context => "Context", } } } diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 1cf1d12e191e7..b1a621cad5d3e 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -15,8 +15,9 @@ use helix_core::{ ensure_grapheme_boundary_next_byte, next_grapheme_boundary, prev_grapheme_boundary, }, movement::Direction, - syntax::{self, HighlightEvent}, + syntax::{self, HighlightEvent, RopeProvider}, text_annotations::TextAnnotations, + tree_sitter::QueryCursor, unicode::width::UnicodeWidthStr, visual_offset_from_block, Position, Range, Selection, Transaction, }; @@ -456,7 +457,6 @@ impl EditorView { let base_primary_cursor_scope = theme .find_scope_index("ui.cursor.primary") .unwrap_or(base_cursor_scope); - let cursor_scope = match mode { Mode::Insert => theme.find_scope_index_exact("ui.cursor.insert"), Mode::Select => theme.find_scope_index_exact("ui.cursor.select"), @@ -843,7 +843,7 @@ impl EditorView { let context_nodes = doc .language_config() - .and_then(|lc| lc.sticky_context_nodes.as_ref()); + .and_then(|lang| lang.context_query())?; let mut parent = tree .root_node() @@ -855,7 +855,6 @@ impl EditorView { while let Some(node) = parent { let line = text.char_to_line(node.start_byte()); - // if parent of previous node is still on the same line, use the parent node if let Some(prev_line) = context.last() { if prev_line.line_nr == line { @@ -863,7 +862,23 @@ impl EditorView { } } - if context_nodes.map_or(true, |nodes| nodes.iter().any(|n| n == node.kind())) { + let mut cursor = QueryCursor::new(); + let query = &context_nodes.query; + let query_nodes = cursor.matches(query, node, RopeProvider(text)); + + let query_ranges = query_nodes + .flat_map(|qnode| { + qnode + .captures + .iter() + .map(|capture| capture.node.start_byte()..capture.node.end_byte()) + }) + .collect::>>(); + + if query_ranges + .iter() + .any(|query_range| query_range.contains(&node.start_byte())) + { context.push(StickyNode { visual_line: 0, // with sorting it will be done line_nr: line, @@ -903,19 +918,23 @@ impl EditorView { .collect(); if config.sticky_context.indicator { - let mut str = String::new(); let message = "┤Sticky Context├"; let side_placeholder = (viewport.width as usize) .saturating_div(2) .saturating_sub(message.len() - 1); - str.push_str(&"─".repeat(side_placeholder)); + let added_length = if side_placeholder > 1 { + message.len() + } else { + 0 + }; + let mut str = String::with_capacity("─".len() * side_placeholder * 2 + added_length); + str.extend(std::iter::repeat("─").take(side_placeholder)); if side_placeholder > 1 { str.push_str(message); } - - str.push_str(&"─".repeat(side_placeholder)); + str.extend(std::iter::repeat("─").take(side_placeholder)); context.push(StickyNode { visual_line: context.len() as u16, diff --git a/languages.toml b/languages.toml index 099162341c72c..8697f9fcc84bd 100644 --- a/languages.toml +++ b/languages.toml @@ -11,7 +11,6 @@ auto-format = true comment-token = "//" language-server = { command = "rust-analyzer" } indent = { tab-width = 4, unit = " " } -sticky-context-nodes = ["impl_item", "function_item", "struct_item", "enum_item", "match_expression", "match_arm", "let_declaration"] [language.auto-pairs] '(' = ')' diff --git a/runtime/queries/rust/context.scm b/runtime/queries/rust/context.scm new file mode 100644 index 0000000000000..ab106c209124b --- /dev/null +++ b/runtime/queries/rust/context.scm @@ -0,0 +1,29 @@ +; Credits to: nvim-treesitter/nvim-treesitter-context +(for_expression + body: (_ (_) @context.end) +) @context + +(if_expression + consequence: (_ (_) @context.end) +) @context + +(function_item + body: (_ (_) @context.end) +) @context + +(impl_item + type: (_) @context.final +) @context + +(struct_item + body: (_ (_) @context.end) +) @context + +([ + (mod_item) + (enum_item) + (closure_expression) + (expression_statement) + (loop_expression) + (match_expression) +] @context) diff --git a/xtask/src/querycheck.rs b/xtask/src/querycheck.rs index 454d0e5cd9cbf..6ed6e29b39826 100644 --- a/xtask/src/querycheck.rs +++ b/xtask/src/querycheck.rs @@ -11,6 +11,7 @@ pub fn query_check() -> Result<(), DynError> { "injections.scm", "textobjects.scm", "indents.scm", + "context.scm", ]; for language in lang_config().language {