Skip to content

Commit ef0a092

Browse files
committed
Use TreeCursor to pretty-print :tree-sitter-subtree
The current `:tree-sitter-subtree` has a bug for field-names when the field name belongs to an unnamed child node. Take this ruby example: def self.method_name true end The subtree given by tree-sitter-cli is: (singleton_method [2, 0] - [4, 3] object: (self [2, 4] - [2, 8]) name: (identifier [2, 9] - [2, 20]) body: (body_statement [3, 2] - [3, 6] (true [3, 2] - [3, 6]))) But the `:tree-sitter-subtree` output was (singleton_method object: (self) body: (identifier) (body_statement (true))) The `singleton_method` rule defines the `name` and `body` fields in an unnamed helper rule `_method_rest` and the old implementation of `pretty_print_tree_impl` would pass the `field_name` down from the named `singleton_method` node. To fix it we switch to the [TreeCursor] API which is recommended by the tree-sitter docs for traversing the tree. `TreeCursor::field_name` accurately determines the field name for the current cursor position even when the node is unnamed. [TreeCursor]: https://docs.rs/tree-sitter/0.20.9/tree_sitter/struct.TreeCursor.html
1 parent 3814987 commit ef0a092

File tree

1 file changed

+71
-30
lines changed

1 file changed

+71
-30
lines changed

helix-core/src/syntax.rs

Lines changed: 71 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1260,7 +1260,7 @@ use std::sync::atomic::{AtomicUsize, Ordering};
12601260
use std::{iter, mem, ops, str, usize};
12611261
use tree_sitter::{
12621262
Language as Grammar, Node, Parser, Point, Query, QueryCaptures, QueryCursor, QueryError,
1263-
QueryMatch, Range, TextProvider, Tree,
1263+
QueryMatch, Range, TextProvider, Tree, TreeCursor,
12641264
};
12651265

12661266
const CANCELLATION_CHECK_INTERVAL: usize = 100;
@@ -2130,57 +2130,68 @@ impl<I: Iterator<Item = HighlightEvent>> Iterator for Merge<I> {
21302130
}
21312131
}
21322132

2133+
fn node_is_visible(node: &Node) -> bool {
2134+
node.is_missing() || (node.is_named() && node.language().node_kind_is_visible(node.kind_id()))
2135+
}
2136+
21332137
pub fn pretty_print_tree<W: fmt::Write>(fmt: &mut W, node: Node) -> fmt::Result {
2134-
pretty_print_tree_impl(fmt, node, true, None, 0)
2138+
if node.child_count() == 0 {
2139+
if node_is_visible(&node) {
2140+
write!(fmt, "({})", node.kind())
2141+
} else {
2142+
write!(fmt, "\"{}\"", node.kind())
2143+
}
2144+
} else {
2145+
pretty_print_tree_impl(fmt, &mut node.walk(), 0)
2146+
}
21352147
}
21362148

21372149
fn pretty_print_tree_impl<W: fmt::Write>(
21382150
fmt: &mut W,
2139-
node: Node,
2140-
is_root: bool,
2141-
field_name: Option<&str>,
2151+
cursor: &mut TreeCursor,
21422152
depth: usize,
21432153
) -> fmt::Result {
2144-
fn is_visible(node: Node) -> bool {
2145-
node.is_missing()
2146-
|| (node.is_named() && node.language().node_kind_is_visible(node.kind_id()))
2147-
}
2154+
let node = cursor.node();
2155+
let visible = node_is_visible(&node);
21482156

2149-
if is_visible(node) {
2157+
if visible {
21502158
let indentation_columns = depth * 2;
21512159
write!(fmt, "{:indentation_columns$}", "")?;
21522160

2153-
if let Some(field_name) = field_name {
2161+
if let Some(field_name) = cursor.field_name() {
21542162
write!(fmt, "{}: ", field_name)?;
21552163
}
21562164

21572165
write!(fmt, "({}", node.kind())?;
2158-
} else if is_root {
2159-
write!(fmt, "(\"{}\")", node.kind())?;
21602166
}
21612167

2162-
for child_idx in 0..node.child_count() {
2163-
if let Some(child) = node.child(child_idx) {
2164-
if is_visible(child) {
2168+
// Handle children.
2169+
if cursor.goto_first_child() {
2170+
loop {
2171+
if node_is_visible(&cursor.node()) {
21652172
fmt.write_char('\n')?;
21662173
}
21672174

2168-
pretty_print_tree_impl(
2169-
fmt,
2170-
child,
2171-
false,
2172-
node.field_name_for_child(child_idx as u32),
2173-
depth + 1,
2174-
)?;
2175+
pretty_print_tree_impl(fmt, cursor, depth + 1)?;
2176+
2177+
if !cursor.goto_next_sibling() {
2178+
break;
2179+
}
21752180
}
2181+
2182+
let moved = cursor.goto_parent();
2183+
// The parent of the first child must exist, and must be `node`.
2184+
debug_assert!(moved);
2185+
debug_assert!(cursor.node() == node);
21762186
}
21772187

2178-
if is_visible(node) {
2179-
write!(fmt, ")")?;
2188+
if visible {
2189+
fmt.write_char(')')?;
21802190
}
21812191

21822192
Ok(())
21832193
}
2194+
21842195
#[cfg(test)]
21852196
mod test {
21862197
use super::*;
@@ -2353,11 +2364,17 @@ mod test {
23532364
}
23542365

23552366
#[track_caller]
2356-
fn assert_pretty_print(source: &str, expected: &str, start: usize, end: usize) {
2367+
fn assert_pretty_print(
2368+
language_name: &str,
2369+
source: &str,
2370+
expected: &str,
2371+
start: usize,
2372+
end: usize,
2373+
) {
23572374
let source = Rope::from_str(source);
23582375

23592376
let loader = Loader::new(Configuration { language: vec![] });
2360-
let language = get_language("rust").unwrap();
2377+
let language = get_language(language_name).unwrap();
23612378

23622379
let config = HighlightConfiguration::new(language, "", "", "").unwrap();
23632380
let syntax = Syntax::new(&source, Arc::new(config), Arc::new(loader));
@@ -2377,13 +2394,14 @@ mod test {
23772394
#[test]
23782395
fn test_pretty_print() {
23792396
let source = r#"/// Hello"#;
2380-
assert_pretty_print(source, "(line_comment)", 0, source.len());
2397+
assert_pretty_print("rust", source, "(line_comment)", 0, source.len());
23812398

23822399
// A large tree should be indented with fields:
23832400
let source = r#"fn main() {
23842401
println!("Hello, World!");
23852402
}"#;
23862403
assert_pretty_print(
2404+
"rust",
23872405
source,
23882406
concat!(
23892407
"(function_item\n",
@@ -2402,11 +2420,34 @@ mod test {
24022420

24032421
// Selecting a token should print just that token:
24042422
let source = r#"fn main() {}"#;
2405-
assert_pretty_print(source, r#"("fn")"#, 0, 1);
2423+
assert_pretty_print("rust", source, r#""fn""#, 0, 1);
24062424

24072425
// Error nodes are printed as errors:
24082426
let source = r#"}{"#;
2409-
assert_pretty_print(source, "(ERROR)", 0, source.len());
2427+
assert_pretty_print("rust", source, "(ERROR)", 0, source.len());
2428+
2429+
// Fields broken under unnamed nodes are determined correctly.
2430+
// In the following source, `object` belongs to the `singleton_method`
2431+
// rule but `name` and `body` belong to an unnamed helper `_method_rest`.
2432+
// This can cause a bug with a pretty-printing implementation that
2433+
// uses `Node::field_name_for_child` to determine field names but is
2434+
// fixed when using `TreeCursor::field_name`.
2435+
let source = "def self.method_name
2436+
true
2437+
end";
2438+
assert_pretty_print(
2439+
"ruby",
2440+
source,
2441+
concat!(
2442+
"(singleton_method\n",
2443+
" object: (self)\n",
2444+
" name: (identifier)\n",
2445+
" body: (body_statement\n",
2446+
" (true)))"
2447+
),
2448+
0,
2449+
source.len(),
2450+
);
24102451
}
24112452

24122453
#[test]

0 commit comments

Comments
 (0)