Skip to content

Commit

Permalink
Pretty print tree-sitter-subtree expression (helix-editor#4295)
Browse files Browse the repository at this point in the history
  • Loading branch information
fisherdarling authored and Frederik Vestre committed Feb 6, 2023
1 parent 47a6c7d commit 822151d
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 1 deletion.
108 changes: 108 additions & 0 deletions helix-core/src/syntax.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2030,6 +2030,57 @@ impl<I: Iterator<Item = HighlightEvent>> Iterator for Merge<I> {
}
}

pub fn pretty_print_tree<W: fmt::Write>(fmt: &mut W, node: Node) -> fmt::Result {
pretty_print_tree_impl(fmt, node, true, None, 0)
}

fn pretty_print_tree_impl<W: fmt::Write>(
fmt: &mut W,
node: Node,
is_root: bool,
field_name: Option<&str>,
depth: usize,
) -> fmt::Result {
fn is_visible(node: Node) -> bool {
node.is_missing()
|| (node.is_named() && node.language().node_kind_is_visible(node.kind_id()))
}

if is_visible(node) {
let indentation_columns = depth * 2;
write!(fmt, "{:indentation_columns$}", "")?;

if let Some(field_name) = field_name {
write!(fmt, "{}: ", field_name)?;
}

write!(fmt, "({}", node.kind())?;
} else if is_root {
write!(fmt, "(\"{}\")", node.kind())?;
}

for child_idx in 0..node.child_count() {
if let Some(child) = node.child(child_idx) {
if is_visible(child) {
fmt.write_char('\n')?;
}

pretty_print_tree_impl(
fmt,
child,
false,
node.field_name_for_child(child_idx as u32),
depth + 1,
)?;
}
}

if is_visible(node) {
write!(fmt, ")")?;
}

Ok(())
}
#[cfg(test)]
mod test {
use super::*;
Expand Down Expand Up @@ -2201,6 +2252,63 @@ mod test {
);
}

#[track_caller]
fn assert_pretty_print(source: &str, expected: &str, start: usize, end: usize) {
let source = Rope::from_str(source);

let loader = Loader::new(Configuration { language: vec![] });
let language = get_language("Rust").unwrap();

let config = HighlightConfiguration::new(language, "", "", "").unwrap();
let syntax = Syntax::new(&source, Arc::new(config), Arc::new(loader));

let root = syntax
.tree()
.root_node()
.descendant_for_byte_range(start, end)
.unwrap();

let mut output = String::new();
pretty_print_tree(&mut output, root).unwrap();

assert_eq!(expected, output);
}

#[test]
fn test_pretty_print() {
let source = r#"/// Hello"#;
assert_pretty_print(source, "(line_comment)", 0, source.len());

// A large tree should be indented with fields:
let source = r#"fn main() {
println!("Hello, World!");
}"#;
assert_pretty_print(
source,
concat!(
"(function_item\n",
" name: (identifier)\n",
" parameters: (parameters)\n",
" body: (block\n",
" (expression_statement\n",
" (macro_invocation\n",
" macro: (identifier)\n",
" (token_tree\n",
" (string_literal))))))",
),
0,
source.len(),
);

// Selecting a token should print just that token:
let source = r#"fn main() {}"#;
assert_pretty_print(source, r#"("fn")"#, 0, 1);

// Error nodes are printed as errors:
let source = r#"}{"#;
assert_pretty_print(source, "(ERROR)", 0, source.len());
}

#[test]
fn test_load_runtime_file() {
// Test to make sure we can load some data from the runtime directory.
Expand Down
4 changes: 3 additions & 1 deletion helix-term/src/commands/typed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1487,7 +1487,9 @@ fn tree_sitter_subtree(
.root_node()
.descendant_for_byte_range(from, to)
{
let contents = format!("```tsq\n{}\n```", selected_node.to_sexp());
let mut contents = String::from("```tsq\n");
helix_core::syntax::pretty_print_tree(&mut contents, selected_node)?;
contents.push_str("\n```");

let callback = async move {
let call: job::Callback =
Expand Down

0 comments on commit 822151d

Please sign in to comment.