diff options
author | Fisher Darling | 2022-10-19 22:17:50 +0000 |
---|---|---|
committer | GitHub | 2022-10-19 22:17:50 +0000 |
commit | 4174b25b3d0d39c09a6d4bcd7fa22ee98be52ed1 (patch) | |
tree | b0ac2509390a19e36e606ad4f3fb959e5542754c /helix-core | |
parent | a7e7c2cc05d0998d8e45664cd3cb8b3ac46cca0e (diff) |
Pretty print `tree-sitter-subtree` expression (#4295)
Diffstat (limited to 'helix-core')
-rw-r--r-- | helix-core/src/syntax.rs | 108 |
1 files changed, 108 insertions, 0 deletions
diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index 61d382fd..b5083f55 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -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::*; @@ -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. |