Skip to content

Commit 5be7788

Browse files
LukeHalasySchuyler Mortimer
authored andcommitted
Add tree-sitter-highlight-name command (helix-editor#8170)
* adds treesitter-highlight-name command * commit documentation changes * moves the get_highlight_name function into core/syntax * rename get_highlight_name function to get_highlight_for_node_at_position * addresses pr comments: moves fn into helper fn, simplifies a lot * commit updated documentation changes * changes scope method to return &str so that callers can decide whether or not to own
1 parent 46a3740 commit 5be7788

3 files changed

Lines changed: 91 additions & 0 deletions

File tree

book/src/generated/typable-cmd.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
| `:lsp-restart` | Restarts the language servers used by the current doc |
5656
| `:lsp-stop` | Stops the language servers that are used by the current doc |
5757
| `:tree-sitter-scopes` | Display tree sitter scopes, primarily for theming and development. |
58+
| `:tree-sitter-highlight-name` | Display name of tree-sitter highlight scope under the cursor. |
5859
| `:debug-start`, `:dbg` | Start a debug session from a given template with given parameters. |
5960
| `:debug-remote`, `:dbg-tcp` | Connect to a debug adapter by TCP address and start a debugging session from a given template with given parameters. |
6061
| `:debug-eval` | Evaluate expression in current debug context. |

helix-term/src/commands/typed.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1527,6 +1527,84 @@ fn tree_sitter_scopes(
15271527
Ok(())
15281528
}
15291529

1530+
fn tree_sitter_highlight_name(
1531+
cx: &mut compositor::Context,
1532+
_args: &[Cow<str>],
1533+
event: PromptEvent,
1534+
) -> anyhow::Result<()> {
1535+
fn find_highlight_at_cursor(
1536+
cx: &mut compositor::Context<'_>,
1537+
) -> Option<helix_core::syntax::Highlight> {
1538+
use helix_core::syntax::HighlightEvent;
1539+
1540+
let (view, doc) = current!(cx.editor);
1541+
let syntax = doc.syntax()?;
1542+
let text = doc.text().slice(..);
1543+
let cursor = doc.selection(view.id).primary().cursor(text);
1544+
let byte = text.char_to_byte(cursor);
1545+
let node = syntax
1546+
.tree()
1547+
.root_node()
1548+
.descendant_for_byte_range(byte, byte)?;
1549+
// Query the same range as the one used in syntax highlighting.
1550+
let range = {
1551+
// Calculate viewport byte ranges:
1552+
let row = text.char_to_line(view.offset.anchor.min(text.len_chars()));
1553+
// Saturating subs to make it inclusive zero indexing.
1554+
let last_line = text.len_lines().saturating_sub(1);
1555+
let height = view.inner_area(doc).height;
1556+
let last_visible_line = (row + height as usize).saturating_sub(1).min(last_line);
1557+
let start = text.line_to_byte(row.min(last_line));
1558+
let end = text.line_to_byte(last_visible_line + 1);
1559+
1560+
start..end
1561+
};
1562+
1563+
let mut highlight = None;
1564+
1565+
for event in syntax.highlight_iter(text, Some(range), None) {
1566+
match event.unwrap() {
1567+
HighlightEvent::Source { start, end }
1568+
if start == node.start_byte() && end == node.end_byte() =>
1569+
{
1570+
return highlight;
1571+
}
1572+
HighlightEvent::HighlightStart(hl) => {
1573+
highlight = Some(hl);
1574+
}
1575+
_ => (),
1576+
}
1577+
}
1578+
1579+
None
1580+
}
1581+
1582+
if event != PromptEvent::Validate {
1583+
return Ok(());
1584+
}
1585+
1586+
let Some(highlight) = find_highlight_at_cursor(cx) else {
1587+
return Ok(());
1588+
};
1589+
1590+
let content = cx.editor.theme.scope(highlight.0).to_string();
1591+
1592+
let callback = async move {
1593+
let call: job::Callback = Callback::EditorCompositor(Box::new(
1594+
move |editor: &mut Editor, compositor: &mut Compositor| {
1595+
let content = ui::Markdown::new(content, editor.syn_loader.clone());
1596+
let popup = Popup::new("hover", content).auto_close(true);
1597+
compositor.replace_or_push("hover", popup);
1598+
},
1599+
));
1600+
Ok(call)
1601+
};
1602+
1603+
cx.jobs.callback(callback);
1604+
1605+
Ok(())
1606+
}
1607+
15301608
fn vsplit(
15311609
cx: &mut compositor::Context,
15321610
args: &[Cow<str>],
@@ -2703,6 +2781,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
27032781
fun: tree_sitter_scopes,
27042782
signature: CommandSignature::none(),
27052783
},
2784+
TypableCommand {
2785+
name: "tree-sitter-highlight-name",
2786+
aliases: &[],
2787+
doc: "Display name of tree-sitter highlight scope under the cursor.",
2788+
fun: tree_sitter_highlight_name,
2789+
signature: CommandSignature::none(),
2790+
},
27062791
TypableCommand {
27072792
name: "debug-start",
27082793
aliases: &["dbg"],

helix-view/src/theme.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,11 @@ impl Theme {
297297
self.highlights[index]
298298
}
299299

300+
#[inline]
301+
pub fn scope(&self, index: usize) -> &str {
302+
&self.scopes[index]
303+
}
304+
300305
pub fn name(&self) -> &str {
301306
&self.name
302307
}

0 commit comments

Comments
 (0)