mirror of https://github.com/helix-editor/helix
feat: upgrade `tree-sitter-injection` to show injections for entire file
parent
92469b431a
commit
37f8cbed3c
|
@ -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 |
|
| `: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-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-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-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-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. |
|
| `:debug-eval` | Evaluate expression in current debug context. |
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
//! This module contains the functionality toggle comments on lines over the selection
|
//! This module contains the functionality toggle comments on lines over the selection
|
||||||
//! using the comment character defined in the user's `languages.toml`
|
//! using the comment character defined in the user's `languages.toml`
|
||||||
|
|
||||||
|
use slotmap::DefaultKey as LayerId;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
use crate::{syntax::BlockCommentToken, Change, Range, Rope, RopeSlice, Syntax, Tendril};
|
use crate::{syntax::BlockCommentToken, Change, Range, Rope, RopeSlice, Syntax, Tendril};
|
||||||
|
@ -25,6 +26,38 @@ pub fn get_comment_token<'a, S: AsRef<str>>(
|
||||||
.max_by_key(|token| token.len())
|
.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<LayerId> {
|
||||||
|
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(
|
pub fn get_injected_tokens(
|
||||||
syntax: Option<&Syntax>,
|
syntax: Option<&Syntax>,
|
||||||
start: usize,
|
start: usize,
|
||||||
|
@ -33,8 +66,7 @@ pub fn get_injected_tokens(
|
||||||
// Find the injection with the most tightly encompassing range.
|
// Find the injection with the most tightly encompassing range.
|
||||||
syntax
|
syntax
|
||||||
.and_then(|syntax| {
|
.and_then(|syntax| {
|
||||||
syntax
|
injection_for_range(syntax, start, end)
|
||||||
.injection_for_range(start, end)
|
|
||||||
.map(|language_id| syntax.layer_config(language_id))
|
.map(|language_id| syntax.layer_config(language_id))
|
||||||
.map(|config| {
|
.map(|config| {
|
||||||
(
|
(
|
||||||
|
|
|
@ -1437,36 +1437,6 @@ impl Syntax {
|
||||||
Arc::clone(&loader.language_configs[language_id])
|
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<LayerId> {
|
|
||||||
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 {
|
pub fn tree(&self) -> &Tree {
|
||||||
self.layers[self.root].tree()
|
self.layers[self.root].tree()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1604,7 +1604,7 @@ fn tree_sitter_scopes(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tree_sitter_injection(
|
fn tree_sitter_injections(
|
||||||
cx: &mut compositor::Context,
|
cx: &mut compositor::Context,
|
||||||
_args: &[Cow<str>],
|
_args: &[Cow<str>],
|
||||||
event: PromptEvent,
|
event: PromptEvent,
|
||||||
|
@ -1613,20 +1613,57 @@ fn tree_sitter_injection(
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let (view, doc) = current!(cx.editor);
|
let doc = doc!(cx.editor);
|
||||||
|
|
||||||
let syntax = doc
|
let syntax = doc
|
||||||
.syntax()
|
.syntax()
|
||||||
.context("No tree-sitter grammar found for this file.")?;
|
.context("No tree-sitter grammar found for this file.")?;
|
||||||
|
|
||||||
let range = doc.selection(view.id).primary();
|
let mut ranges = vec![];
|
||||||
|
|
||||||
let language_name = syntax
|
for (language_id, layer) in &syntax.layers {
|
||||||
.injection_for_range(range.from(), range.to())
|
let language_name = &syntax.layer_config(language_id).language_name;
|
||||||
.map(|language_id| syntax.layer_config(language_id).language_name.clone())
|
for range in &layer.ranges {
|
||||||
.context("No injection layer found for the current range.")?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -3162,10 +3199,10 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
TypableCommand {
|
TypableCommand {
|
||||||
name: "tree-sitter-injection",
|
name: "tree-sitter-injections",
|
||||||
aliases: &[],
|
aliases: &[],
|
||||||
doc: "Display injected language for the primary range.",
|
doc: "Display injected languages for the file.",
|
||||||
fun: tree_sitter_injection,
|
fun: tree_sitter_injections,
|
||||||
signature: CommandSignature::none(),
|
signature: CommandSignature::none(),
|
||||||
},
|
},
|
||||||
TypableCommand {
|
TypableCommand {
|
||||||
|
|
Loading…
Reference in New Issue