diff --git a/book/src/generated/typable-cmd.md b/book/src/generated/typable-cmd.md index 5e1fde2bb..c838bebc0 100644 --- a/book/src/generated/typable-cmd.md +++ b/book/src/generated/typable-cmd.md @@ -56,7 +56,7 @@ | `:lsp-stop` | Stops the given language servers, or all language servers that are used by the current file if no arguments are supplied | | `:tree-sitter-scopes` | Display tree sitter scopes, primarily for theming and development. | | `:tree-sitter-highlight-name` | Display name of tree-sitter highlight scope under the cursor. | -| `:tree-sitter-injection` | Display injected language for the primary range. | +| `:tree-sitter-injections` | Display injected languages for the file. | | `:debug-start`, `:dbg` | Start a debug session from a given template with given parameters. | | `:debug-remote`, `:dbg-tcp` | Connect to a debug adapter by TCP address and start a debugging session from a given template with given parameters. | | `:debug-eval` | Evaluate expression in current debug context. | diff --git a/helix-core/src/comment.rs b/helix-core/src/comment.rs index ef9131a53..3e809bc53 100644 --- a/helix-core/src/comment.rs +++ b/helix-core/src/comment.rs @@ -1,6 +1,7 @@ //! This module contains the functionality toggle comments on lines over the selection //! using the comment character defined in the user's `languages.toml` +use slotmap::DefaultKey as LayerId; use smallvec::SmallVec; use crate::{syntax::BlockCommentToken, Change, Range, Rope, RopeSlice, Syntax, Tendril}; @@ -25,6 +26,38 @@ pub fn get_comment_token<'a, S: AsRef>( .max_by_key(|token| token.len()) } +/// For a given range in the document, get the most tightly encompassing +/// injection layer corresponding to that range. +pub fn injection_for_range(syntax: &Syntax, from: usize, to: usize) -> Option { + let mut best_fit = None; + let mut min_gap = usize::MAX; + + for (layer_id, layer) in &syntax.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; + let config = syntax.layer_config(layer_id); + let has_comment_tokens = + config.comment_tokens.is_some() || config.block_comment_tokens.is_some(); + + if this_gap < min_gap + // ignore the language family for which it won't make + // sense to consider their comment. + // + // This includes, for instance, `comment`, `jsdoc`, `regex` + && has_comment_tokens + { + best_fit = Some(layer_id); + min_gap = this_gap; + } + } + } + } + + best_fit +} + pub fn get_injected_tokens( syntax: Option<&Syntax>, start: usize, @@ -33,8 +66,7 @@ pub fn get_injected_tokens( // Find the injection with the most tightly encompassing range. syntax .and_then(|syntax| { - syntax - .injection_for_range(start, end) + injection_for_range(syntax, start, end) .map(|language_id| syntax.layer_config(language_id)) .map(|config| { ( diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index 4a6ad7576..8f6184330 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -1437,36 +1437,6 @@ 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 language family for which it won't make - // sense to consider their comment. - // - // 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" | "regex") - { - 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 b23bfb319..88d3d02a4 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -1604,7 +1604,7 @@ fn tree_sitter_scopes( Ok(()) } -fn tree_sitter_injection( +fn tree_sitter_injections( cx: &mut compositor::Context, _args: &[Cow], event: PromptEvent, @@ -1613,20 +1613,57 @@ fn tree_sitter_injection( return Ok(()); } - let (view, doc) = current!(cx.editor); + let doc = doc!(cx.editor); let syntax = doc .syntax() .context("No tree-sitter grammar found for this file.")?; - let range = doc.selection(view.id).primary(); + let mut ranges = vec![]; - 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.")?; + for (language_id, layer) in &syntax.layers { + let language_name = &syntax.layer_config(language_id).language_name; + for range in &layer.ranges { + ranges.push((range, language_name.clone())); + } + } - cx.editor.set_status(language_name); + if ranges.is_empty() { + bail!("No injections found for the current file"); + } + + ranges.sort_unstable_by(|(range_a, _), (range_b, _)| { + range_a + .start_byte + .cmp(&range_b.start_byte) + .then(range_a.end_byte.cmp(&range_b.end_byte)) + }); + + let char_count = doc.text().len_chars(); + + let mut contents = String::new(); + + for (range, language_name) in ranges { + let range = if range.end_byte < char_count { + format!("`{}` - `{}`", range.start_byte, range.end_byte) + } else { + "full file".into() + }; + writeln!(contents, "**{}**: {}", language_name, range)?; + } + + let callback = async move { + let call: job::Callback = Callback::EditorCompositor(Box::new( + move |editor: &mut Editor, compositor: &mut Compositor| { + let contents = ui::Markdown::new(contents, editor.syn_loader.clone()); + let popup = Popup::new("hover", contents).auto_close(true); + compositor.replace_or_push("hover", popup); + }, + )); + Ok(call) + }; + + cx.jobs.callback(callback); Ok(()) } @@ -3162,10 +3199,10 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ }, }, TypableCommand { - name: "tree-sitter-injection", + name: "tree-sitter-injections", aliases: &[], - doc: "Display injected language for the primary range.", - fun: tree_sitter_injection, + doc: "Display injected languages for the file.", + fun: tree_sitter_injections, signature: CommandSignature::none(), }, TypableCommand {