diff --git a/helix-core/src/indent.rs b/helix-core/src/indent.rs index 04ce9a28d..3617ef981 100644 --- a/helix-core/src/indent.rs +++ b/helix-core/src/indent.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, collections::HashMap, iter}; +use std::{borrow::Cow, collections::BTreeMap, collections::HashMap, iter}; use helix_stdx::rope::RopeSliceExt; use tree_sitter::{Query, QueryCursor, QueryPredicateArg}; @@ -1040,6 +1040,41 @@ pub fn get_scopes(syntax: Option<&Syntax>, text: RopeSlice, pos: usize) -> Vec<& scopes } +pub fn get_breadcrumbs(syntax: Option<&Syntax>, text: RopeSlice, pos: usize) -> Vec { + let mut breadcrumb_set: BTreeMap = BTreeMap::new(); + if let Some(syntax) = syntax { + let pos = text.char_to_byte(pos); + let mut node = match syntax + .tree() + .root_node() + .descendant_for_byte_range(pos, pos) + { + Some(node) => node, + None => return vec![], + }; + + while let Some(parent) = node.parent() { + if node.is_extra() { + continue; + } + if node.is_named() { + let line_idx = text.byte_to_line(parent.start_byte()); + let line: String = text.line(line_idx).into(); + breadcrumb_set.insert(line_idx, format!("{}: {}", line_idx + 1, line.trim_end())); + } + node = parent; + } + } + + let breadcrumbs: Vec = breadcrumb_set + .into_iter() + .map(|(_k, v)| v.into()) + // Remove the `source_code` node + .skip(1) + .collect(); + breadcrumbs +} + #[cfg(test)] mod test { use super::*; diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index c35ff714a..445160621 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -1667,6 +1667,43 @@ fn tree_sitter_scopes( Ok(()) } +fn tree_sitter_breadcrumbs( + cx: &mut compositor::Context, + _args: Args, + event: PromptEvent, +) -> anyhow::Result<()> { + if event != PromptEvent::Validate { + return Ok(()); + } + + let (view, doc) = current!(cx.editor); + let text = doc.text().slice(..); + + let pos = doc.selection(view.id).primary().cursor(text); + let breadcrumbs = indent::get_breadcrumbs(doc.syntax(), text, pos); + let language = match doc.language_config() { + Some(l) => &l.language_id, + None => "txt", + }; + + let contents = format!("```{}\n{}\n```\n", language, breadcrumbs.join("\n")); + + 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(()) +} + fn tree_sitter_highlight_name( cx: &mut compositor::Context, _args: Args, @@ -3195,6 +3232,17 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ ..Signature::DEFAULT }, }, + TypableCommand { + name: "tree-sitter-breadcrumbs", + aliases: &["breadcrumbs", "bread"], + doc: "Display lines of parents to reach the token under the cursor. Useful to know where you are in the program.", + fun: tree_sitter_breadcrumbs, + completer: CommandCompleter::none(), + signature: Signature { + positionals: (0, Some(0)), + ..Signature::DEFAULT + }, + }, TypableCommand { name: "tree-sitter-highlight-name", aliases: &[],