feat: add typable command to get injection layer for current range

pull/12759/head
Nikita Revenco 2025-02-03 13:34:52 +00:00 committed by Nik Revenco
parent 99d16170dc
commit d451077978
3 changed files with 78 additions and 36 deletions

View File

@ -30,43 +30,20 @@ pub fn get_injected_tokens(
start: usize, start: usize,
end: usize, end: usize,
) -> (Option<Vec<String>>, Option<Vec<BlockCommentToken>>) { ) -> (Option<Vec<String>>, Option<Vec<BlockCommentToken>>) {
let mut best_fit = None;
let mut min_gap = usize::MAX;
// Find the injection with the most tightly encompassing range. // Find the injection with the most tightly encompassing range.
if let Some(syntax) = &syntax { syntax
for (layer_id, layer) in &syntax.layers { .and_then(|syntax| {
for ts_range in &layer.ranges { syntax
let is_encompassing = ts_range.start_byte <= start && ts_range.end_byte >= end; .injection_for_range(start, end)
if is_encompassing { .map(|language_id| syntax.layer_config(language_id))
let this_gap = ts_range.end_byte - ts_range.start_byte; .map(|config| {
if this_gap < min_gap (
// ignore the "comment" language family config.comment_tokens.clone(),
// as that would mean we can't uncomment anything, or config.block_comment_tokens.clone(),
// the comments would be incorrect. )
// })
// Since uncommenting would attempt to use the comment })
// language's non-existing comment tokens .unwrap_or_default()
// 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)
} }
/// Given text, a comment token, and a set of line indices, returns the following: /// Given text, a comment token, and a set of line indices, returns the following:

View File

@ -1437,6 +1437,37 @@ 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 "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 { pub fn tree(&self) -> &Tree {
self.layers[self.root].tree() self.layers[self.root].tree()
} }

View File

@ -1604,6 +1604,33 @@ fn tree_sitter_scopes(
Ok(()) Ok(())
} }
fn tree_sitter_injection(
cx: &mut compositor::Context,
_args: &[Cow<str>],
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( fn tree_sitter_highlight_name(
cx: &mut compositor::Context, cx: &mut compositor::Context,
_args: Args, _args: Args,
@ -3134,6 +3161,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
..Signature::DEFAULT ..Signature::DEFAULT
}, },
}, },
TypableCommand {
name: "tree-sitter-injection",
aliases: &[],
doc: "Display injected language for the primary range.",
fun: tree_sitter_injection,
signature: CommandSignature::none(),
},
TypableCommand { TypableCommand {
name: "debug-start", name: "debug-start",
aliases: &["dbg"], aliases: &["dbg"],