From d45107797858e9e4e09224a45c78cd564298bddf Mon Sep 17 00:00:00 2001 From: Nikita Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Mon, 3 Feb 2025 13:34:52 +0000 Subject: [PATCH] feat: add typable command to get injection layer for current range --- helix-core/src/comment.rs | 49 +++++++++----------------------- helix-core/src/syntax.rs | 31 ++++++++++++++++++++ helix-term/src/commands/typed.rs | 34 ++++++++++++++++++++++ 3 files changed, 78 insertions(+), 36 deletions(-) diff --git a/helix-core/src/comment.rs b/helix-core/src/comment.rs index ec57a4ef9..ef9131a53 100644 --- a/helix-core/src/comment.rs +++ b/helix-core/src/comment.rs @@ -30,43 +30,20 @@ pub fn get_injected_tokens( start: usize, end: usize, ) -> (Option>, Option>) { - let mut best_fit = None; - let mut min_gap = usize::MAX; - // Find the injection with the most tightly encompassing range. - if let Some(syntax) = &syntax { - for (layer_id, layer) in &syntax.layers { - for ts_range in &layer.ranges { - let is_encompassing = ts_range.start_byte <= start && ts_range.end_byte >= end; - if is_encompassing { - let this_gap = ts_range.end_byte - ts_range.start_byte; - if this_gap < min_gap - // ignore the "comment" language family - // as that would mean we can't uncomment anything, or - // the comments would be incorrect. - // - // Since uncommenting would attempt to use the comment - // language's non-existing comment tokens - // TODO: add this as a language configuration key? - && !matches!(syntax.layer_config(layer_id).language_name.as_ref(), "jsdoc" | "comment") - { - best_fit = Some(layer_id); - min_gap = this_gap; - } - } - } - } - - if let Some(best_fit) = best_fit { - let config = syntax.layer_config(best_fit); - return ( - config.comment_tokens.clone(), - config.block_comment_tokens.clone(), - ); - } - } - - (None, None) + syntax + .and_then(|syntax| { + syntax + .injection_for_range(start, end) + .map(|language_id| syntax.layer_config(language_id)) + .map(|config| { + ( + config.comment_tokens.clone(), + config.block_comment_tokens.clone(), + ) + }) + }) + .unwrap_or_default() } /// Given text, a comment token, and a set of line indices, returns the following: diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index 8f6184330..11201b26b 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -1437,6 +1437,37 @@ impl Syntax { Arc::clone(&loader.language_configs[language_id]) } + /// For a given range in the document, get the most tightly encompassing + /// injection layer corresponding to that range. + pub fn injection_for_range(&self, from: usize, to: usize) -> Option { + let mut best_fit = None; + let mut min_gap = usize::MAX; + + for (layer_id, layer) in &self.layers { + for ts_range in &layer.ranges { + let is_encompassing = ts_range.start_byte <= from && ts_range.end_byte >= to; + if is_encompassing { + let this_gap = ts_range.end_byte - ts_range.start_byte; + if this_gap < min_gap + // ignore the "comment" language family + // as that would mean we can't uncomment anything, or + // the comments would be incorrect. + // + // Since uncommenting would attempt to use the comment + // language's non-existing comment tokens + // TODO: add this as a language configuration key? + && !matches!(self.layer_config(layer_id).language_name.as_ref(), "jsdoc" | "comment") + { + best_fit = Some(layer_id); + min_gap = this_gap; + } + } + } + } + + best_fit + } + pub fn tree(&self) -> &Tree { self.layers[self.root].tree() } diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 1d57930cc..b23bfb319 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -1604,6 +1604,33 @@ fn tree_sitter_scopes( Ok(()) } +fn tree_sitter_injection( + cx: &mut compositor::Context, + _args: &[Cow], + event: PromptEvent, +) -> anyhow::Result<()> { + if event != PromptEvent::Validate { + return Ok(()); + } + + let (view, doc) = current!(cx.editor); + + let syntax = doc + .syntax() + .context("No tree-sitter grammar found for this file.")?; + + let range = doc.selection(view.id).primary(); + + let language_name = syntax + .injection_for_range(range.from(), range.to()) + .map(|language_id| syntax.layer_config(language_id).language_name.clone()) + .context("No injection layer found for the current range.")?; + + cx.editor.set_status(language_name); + + Ok(()) +} + fn tree_sitter_highlight_name( cx: &mut compositor::Context, _args: Args, @@ -3134,6 +3161,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ ..Signature::DEFAULT }, }, + TypableCommand { + name: "tree-sitter-injection", + aliases: &[], + doc: "Display injected language for the primary range.", + fun: tree_sitter_injection, + signature: CommandSignature::none(), + }, TypableCommand { name: "debug-start", aliases: &["dbg"],