diff options
author | Aleksey Kladov <[email protected]> | 2018-08-07 16:28:30 +0100 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2018-08-07 16:36:33 +0100 |
commit | 2fb854ccdae6f1f12b60441e5c3b283bdc81fb0a (patch) | |
tree | ed4f31d31473a2faf8e014907960f855b96cca22 | |
parent | a04473e2bb95483e84404c57426ee9ed21fa5d6b (diff) |
:tada: extend selection
-rw-r--r-- | code/native/src/lib.rs | 32 | ||||
-rw-r--r-- | code/package.json | 6 | ||||
-rw-r--r-- | code/src/main.ts | 15 | ||||
-rw-r--r-- | libeditor/Cargo.toml | 1 | ||||
-rw-r--r-- | libeditor/src/extend_selection.rs | 36 | ||||
-rw-r--r-- | libeditor/src/lib.rs | 29 | ||||
-rw-r--r-- | src/algo/mod.rs | 122 | ||||
-rw-r--r-- | src/algo/search.rs | 136 | ||||
-rw-r--r-- | src/parser_impl/event.rs | 1 | ||||
-rw-r--r-- | src/yellow/syntax.rs | 4 |
10 files changed, 376 insertions, 6 deletions
diff --git a/code/native/src/lib.rs b/code/native/src/lib.rs index aae7ad2f3..cb304a141 100644 --- a/code/native/src/lib.rs +++ b/code/native/src/lib.rs | |||
@@ -3,6 +3,7 @@ extern crate neon; | |||
3 | extern crate libeditor; | 3 | extern crate libeditor; |
4 | 4 | ||
5 | use neon::prelude::*; | 5 | use neon::prelude::*; |
6 | use libeditor::TextRange; | ||
6 | 7 | ||
7 | pub struct Wrapper { | 8 | pub struct Wrapper { |
8 | inner: libeditor::File, | 9 | inner: libeditor::File, |
@@ -19,8 +20,8 @@ declare_types! { | |||
19 | } | 20 | } |
20 | 21 | ||
21 | method syntaxTree(mut cx) { | 22 | method syntaxTree(mut cx) { |
22 | let this = cx.this(); | ||
23 | let tree = { | 23 | let tree = { |
24 | let this = cx.this(); | ||
24 | let guard = cx.lock(); | 25 | let guard = cx.lock(); |
25 | let wrapper = this.borrow(&guard); | 26 | let wrapper = this.borrow(&guard); |
26 | wrapper.inner.syntax_tree() | 27 | wrapper.inner.syntax_tree() |
@@ -29,8 +30,8 @@ declare_types! { | |||
29 | } | 30 | } |
30 | 31 | ||
31 | method highlight(mut cx) { | 32 | method highlight(mut cx) { |
32 | let this = cx.this(); | ||
33 | let highlights = { | 33 | let highlights = { |
34 | let this = cx.this(); | ||
34 | let guard = cx.lock(); | 35 | let guard = cx.lock(); |
35 | let wrapper = this.borrow(&guard); | 36 | let wrapper = this.borrow(&guard); |
36 | wrapper.inner.highlight() | 37 | wrapper.inner.highlight() |
@@ -51,6 +52,33 @@ declare_types! { | |||
51 | 52 | ||
52 | Ok(res.upcast()) | 53 | Ok(res.upcast()) |
53 | } | 54 | } |
55 | |||
56 | method extendSelection(mut cx) { | ||
57 | let from_offset = cx.argument::<JsNumber>(0)?.value() as u32; | ||
58 | let to_offset = cx.argument::<JsNumber>(1)?.value() as u32; | ||
59 | let text_range = TextRange::from_to(from_offset.into(), to_offset.into()); | ||
60 | let extended_range = { | ||
61 | let this = cx.this(); | ||
62 | let guard = cx.lock(); | ||
63 | let wrapper = this.borrow(&guard); | ||
64 | wrapper.inner.extend_selection(text_range) | ||
65 | }; | ||
66 | |||
67 | match extended_range { | ||
68 | None => Ok(cx.null().upcast()), | ||
69 | Some(range) => { | ||
70 | let start: u32 = range.start().into(); | ||
71 | let end: u32 = range.end().into(); | ||
72 | let start = cx.number(start); | ||
73 | let end = cx.number(end); | ||
74 | let arr = cx.empty_array(); | ||
75 | arr.set(&mut cx, 0, start)?; | ||
76 | arr.set(&mut cx, 1, end)?; | ||
77 | Ok(arr.upcast()) | ||
78 | } | ||
79 | } | ||
80 | |||
81 | } | ||
54 | } | 82 | } |
55 | 83 | ||
56 | } | 84 | } |
diff --git a/code/package.json b/code/package.json index 2f97bb5c0..66c4f266f 100644 --- a/code/package.json +++ b/code/package.json | |||
@@ -36,11 +36,15 @@ | |||
36 | { | 36 | { |
37 | "command": "libsyntax-rust.syntaxTree", | 37 | "command": "libsyntax-rust.syntaxTree", |
38 | "title": "Show Rust syntax tree" | 38 | "title": "Show Rust syntax tree" |
39 | }, | ||
40 | { | ||
41 | "command": "libsyntax-rust.extendSelection", | ||
42 | "title": "Rust Extend Selection" | ||
39 | } | 43 | } |
40 | ], | 44 | ], |
41 | "keybindings": [ | 45 | "keybindings": [ |
42 | { | 46 | { |
43 | "command": "libsyntax-rust.semanticSelection", | 47 | "command": "libsyntax-rust.extendSelection", |
44 | "key": "ctrl+w", | 48 | "key": "ctrl+w", |
45 | "when": "editorTextFocus && editorLangId == rust" | 49 | "when": "editorTextFocus && editorLangId == rust" |
46 | } | 50 | } |
diff --git a/code/src/main.ts b/code/src/main.ts index 75a824b7b..72bef7061 100644 --- a/code/src/main.ts +++ b/code/src/main.ts | |||
@@ -34,6 +34,16 @@ export function activate(context: vscode.ExtensionContext) { | |||
34 | )) | 34 | )) |
35 | 35 | ||
36 | registerCommand('libsyntax-rust.syntaxTree', () => openDoc(uris.syntaxTree)) | 36 | registerCommand('libsyntax-rust.syntaxTree', () => openDoc(uris.syntaxTree)) |
37 | registerCommand('libsyntax-rust.extendSelection', () => { | ||
38 | let editor = vscode.window.activeTextEditor | ||
39 | let file = activeSyntax() | ||
40 | if (editor == null || file == null) return | ||
41 | editor.selections = editor.selections.map((s) => { | ||
42 | let range = file.extendSelection(s) | ||
43 | if (range == null) return null | ||
44 | return new vscode.Selection(range.start, range.end) | ||
45 | }) | ||
46 | }) | ||
37 | } | 47 | } |
38 | 48 | ||
39 | export function deactivate() { } | 49 | export function deactivate() { } |
@@ -49,6 +59,11 @@ export class Syntax { | |||
49 | 59 | ||
50 | syntaxTree(): string { return this.imp.syntaxTree() } | 60 | syntaxTree(): string { return this.imp.syntaxTree() } |
51 | highlight(): Array<[number, number, string]> { return this.imp.highlight() } | 61 | highlight(): Array<[number, number, string]> { return this.imp.highlight() } |
62 | extendSelection(range: vscode.Range): vscode.Range { | ||
63 | let range_ = fromVsRange(this.doc, range); | ||
64 | let extRange = this.imp.extendSelection(range_[0], range_[1]); | ||
65 | return toVsRange(this.doc, extRange); | ||
66 | } | ||
52 | } | 67 | } |
53 | 68 | ||
54 | 69 | ||
diff --git a/libeditor/Cargo.toml b/libeditor/Cargo.toml index 1a532ce2f..92abd3289 100644 --- a/libeditor/Cargo.toml +++ b/libeditor/Cargo.toml | |||
@@ -6,4 +6,3 @@ publish = false | |||
6 | 6 | ||
7 | [dependencies] | 7 | [dependencies] |
8 | libsyntax2 = { path = "../" } | 8 | libsyntax2 = { path = "../" } |
9 | text_unit = "0.1.2" | ||
diff --git a/libeditor/src/extend_selection.rs b/libeditor/src/extend_selection.rs new file mode 100644 index 000000000..16d4bc084 --- /dev/null +++ b/libeditor/src/extend_selection.rs | |||
@@ -0,0 +1,36 @@ | |||
1 | use libsyntax2::{ | ||
2 | TextRange, SyntaxNodeRef, | ||
3 | SyntaxKind::WHITESPACE, | ||
4 | algo::{find_leaf_at_offset, find_covering_node, ancestors}, | ||
5 | }; | ||
6 | |||
7 | |||
8 | pub(crate) fn extend_selection(root: SyntaxNodeRef, range: TextRange) -> Option<TextRange> { | ||
9 | if range.is_empty() { | ||
10 | let offset = range.start(); | ||
11 | let mut leaves = find_leaf_at_offset(root, offset); | ||
12 | if let Some(leaf) = leaves.clone().find(|node| node.kind() != WHITESPACE) { | ||
13 | return Some(leaf.range()); | ||
14 | } | ||
15 | let ws = leaves.next()?; | ||
16 | // let ws_suffix = file.text().slice( | ||
17 | // TextRange::from_to(offset, ws.range().end()) | ||
18 | // ); | ||
19 | // if ws.text().contains("\n") && !ws_suffix.contains("\n") { | ||
20 | // if let Some(line_end) = file.text() | ||
21 | // .slice(TextSuffix::from(ws.range().end())) | ||
22 | // .find("\n") | ||
23 | // { | ||
24 | // let range = TextRange::from_len(ws.range().end(), line_end); | ||
25 | // return Some(find_covering_node(file.root(), range).range()); | ||
26 | // } | ||
27 | // } | ||
28 | return Some(ws.range()); | ||
29 | }; | ||
30 | let node = find_covering_node(root, range); | ||
31 | |||
32 | match ancestors(node).skip_while(|n| n.range() == range).next() { | ||
33 | None => None, | ||
34 | Some(parent) => Some(parent.range()), | ||
35 | } | ||
36 | } | ||
diff --git a/libeditor/src/lib.rs b/libeditor/src/lib.rs index 091aed125..a0c003fb5 100644 --- a/libeditor/src/lib.rs +++ b/libeditor/src/lib.rs | |||
@@ -1,12 +1,13 @@ | |||
1 | extern crate libsyntax2; | 1 | extern crate libsyntax2; |
2 | extern crate text_unit; | 2 | |
3 | mod extend_selection; | ||
3 | 4 | ||
4 | use libsyntax2::{ | 5 | use libsyntax2::{ |
5 | SyntaxNodeRef, | 6 | SyntaxNodeRef, |
6 | algo::walk, | 7 | algo::walk, |
7 | SyntaxKind::*, | 8 | SyntaxKind::*, |
8 | }; | 9 | }; |
9 | use text_unit::TextRange; | 10 | pub use libsyntax2::{TextRange, TextUnit}; |
10 | 11 | ||
11 | pub struct File { | 12 | pub struct File { |
12 | inner: libsyntax2::File | 13 | inner: libsyntax2::File |
@@ -71,6 +72,11 @@ impl File { | |||
71 | .collect(); | 72 | .collect(); |
72 | res // NLL :-( | 73 | res // NLL :-( |
73 | } | 74 | } |
75 | |||
76 | pub fn extend_selection(&self, range: TextRange) -> Option<TextRange> { | ||
77 | let syntax = self.inner.syntax(); | ||
78 | extend_selection::extend_selection(syntax.as_ref(), range) | ||
79 | } | ||
74 | } | 80 | } |
75 | 81 | ||
76 | 82 | ||
@@ -96,3 +102,22 @@ impl<'f> Declaration<'f> { | |||
96 | self.0.range() | 102 | self.0.range() |
97 | } | 103 | } |
98 | } | 104 | } |
105 | |||
106 | #[cfg(test)] | ||
107 | mod tests { | ||
108 | use super::*; | ||
109 | |||
110 | #[test] | ||
111 | fn test_extend_selection() { | ||
112 | let text = r#"fn foo() { | ||
113 | 1 + 1 | ||
114 | } | ||
115 | "#; | ||
116 | let file = File::new(text); | ||
117 | let range = TextRange::offset_len(18.into(), 0.into()); | ||
118 | let range = file.extend_selection(range).unwrap(); | ||
119 | assert_eq!(range, TextRange::from_to(17.into(), 18.into())); | ||
120 | let range = file.extend_selection(range).unwrap(); | ||
121 | assert_eq!(range, TextRange::from_to(15.into(), 20.into())); | ||
122 | } | ||
123 | } | ||
diff --git a/src/algo/mod.rs b/src/algo/mod.rs index c1644d16d..d2de70fd4 100644 --- a/src/algo/mod.rs +++ b/src/algo/mod.rs | |||
@@ -1 +1,123 @@ | |||
1 | pub mod walk; | 1 | pub mod walk; |
2 | |||
3 | use {SyntaxNodeRef, TextUnit, TextRange}; | ||
4 | |||
5 | pub fn find_leaf_at_offset(node: SyntaxNodeRef, offset: TextUnit) -> LeafAtOffset { | ||
6 | let range = node.range(); | ||
7 | assert!( | ||
8 | contains_offset_nonstrict(range, offset), | ||
9 | "Bad offset: range {:?} offset {:?}", range, offset | ||
10 | ); | ||
11 | if range.is_empty() { | ||
12 | return LeafAtOffset::None; | ||
13 | } | ||
14 | |||
15 | if node.is_leaf() { | ||
16 | return LeafAtOffset::Single(node); | ||
17 | } | ||
18 | |||
19 | let mut children = node.children() | ||
20 | .filter(|child| { | ||
21 | let child_range = child.range(); | ||
22 | !child_range.is_empty() && contains_offset_nonstrict(child_range, offset) | ||
23 | }); | ||
24 | |||
25 | let left = children.next().unwrap(); | ||
26 | let right = children.next(); | ||
27 | assert!(children.next().is_none()); | ||
28 | return if let Some(right) = right { | ||
29 | match (find_leaf_at_offset(left, offset), find_leaf_at_offset(right, offset)) { | ||
30 | (LeafAtOffset::Single(left), LeafAtOffset::Single(right)) => | ||
31 | LeafAtOffset::Between(left, right), | ||
32 | _ => unreachable!() | ||
33 | } | ||
34 | } else { | ||
35 | find_leaf_at_offset(left, offset) | ||
36 | }; | ||
37 | } | ||
38 | |||
39 | #[derive(Clone, Copy, Debug)] | ||
40 | pub enum LeafAtOffset<'a> { | ||
41 | None, | ||
42 | Single(SyntaxNodeRef<'a>), | ||
43 | Between(SyntaxNodeRef<'a>, SyntaxNodeRef<'a>) | ||
44 | } | ||
45 | |||
46 | impl<'a> LeafAtOffset<'a> { | ||
47 | pub fn right_biased(self) -> Option<SyntaxNodeRef<'a>> { | ||
48 | match self { | ||
49 | LeafAtOffset::None => None, | ||
50 | LeafAtOffset::Single(node) => Some(node), | ||
51 | LeafAtOffset::Between(_, right) => Some(right) | ||
52 | } | ||
53 | } | ||
54 | |||
55 | pub fn left_biased(self) -> Option<SyntaxNodeRef<'a>> { | ||
56 | match self { | ||
57 | LeafAtOffset::None => None, | ||
58 | LeafAtOffset::Single(node) => Some(node), | ||
59 | LeafAtOffset::Between(left, _) => Some(left) | ||
60 | } | ||
61 | } | ||
62 | } | ||
63 | |||
64 | impl<'f> Iterator for LeafAtOffset<'f> { | ||
65 | type Item = SyntaxNodeRef<'f>; | ||
66 | |||
67 | fn next(&mut self) -> Option<SyntaxNodeRef<'f>> { | ||
68 | match *self { | ||
69 | LeafAtOffset::None => None, | ||
70 | LeafAtOffset::Single(node) => { *self = LeafAtOffset::None; Some(node) } | ||
71 | LeafAtOffset::Between(left, right) => { *self = LeafAtOffset::Single(right); Some(left) } | ||
72 | } | ||
73 | } | ||
74 | } | ||
75 | |||
76 | |||
77 | pub fn find_covering_node(root: SyntaxNodeRef, range: TextRange) -> SyntaxNodeRef { | ||
78 | assert!(is_subrange(root.range(), range)); | ||
79 | let (left, right) = match ( | ||
80 | find_leaf_at_offset(root, range.start()).right_biased(), | ||
81 | find_leaf_at_offset(root, range.end()).left_biased() | ||
82 | ) { | ||
83 | (Some(l), Some(r)) => (l, r), | ||
84 | _ => return root | ||
85 | }; | ||
86 | |||
87 | common_ancestor(left, right) | ||
88 | } | ||
89 | |||
90 | fn common_ancestor<'a>(n1: SyntaxNodeRef<'a>, n2: SyntaxNodeRef<'a>) -> SyntaxNodeRef<'a> { | ||
91 | for p in ancestors(n1) { | ||
92 | if ancestors(n2).any(|a| a == p) { | ||
93 | return p; | ||
94 | } | ||
95 | } | ||
96 | panic!("Can't find common ancestor of {:?} and {:?}", n1, n2) | ||
97 | } | ||
98 | |||
99 | pub fn ancestors<'a>(node: SyntaxNodeRef<'a>) -> impl Iterator<Item=SyntaxNodeRef<'a>> { | ||
100 | Ancestors(Some(node)) | ||
101 | } | ||
102 | |||
103 | #[derive(Debug)] | ||
104 | struct Ancestors<'a>(Option<SyntaxNodeRef<'a>>); | ||
105 | |||
106 | impl<'a> Iterator for Ancestors<'a> { | ||
107 | type Item = SyntaxNodeRef<'a>; | ||
108 | |||
109 | fn next(&mut self) -> Option<Self::Item> { | ||
110 | self.0.take().map(|n| { | ||
111 | self.0 = n.parent(); | ||
112 | n | ||
113 | }) | ||
114 | } | ||
115 | } | ||
116 | |||
117 | fn contains_offset_nonstrict(range: TextRange, offset: TextUnit) -> bool { | ||
118 | range.start() <= offset && offset <= range.end() | ||
119 | } | ||
120 | |||
121 | fn is_subrange(range: TextRange, subrange: TextRange) -> bool { | ||
122 | range.start() <= subrange.start() && subrange.end() <= range.end() | ||
123 | } | ||
diff --git a/src/algo/search.rs b/src/algo/search.rs new file mode 100644 index 000000000..46404f537 --- /dev/null +++ b/src/algo/search.rs | |||
@@ -0,0 +1,136 @@ | |||
1 | use {Node, NodeType, TextUnit, TextRange}; | ||
2 | use ::visitor::{visitor, process_subtree_bottom_up}; | ||
3 | |||
4 | pub fn child_of_type(node: Node, ty: NodeType) -> Option<Node> { | ||
5 | node.children().find(|n| n.ty() == ty) | ||
6 | } | ||
7 | |||
8 | pub fn children_of_type<'f>(node: Node<'f>, ty: NodeType) -> Box<Iterator<Item=Node<'f>> + 'f> { | ||
9 | Box::new(node.children().filter(move |n| n.ty() == ty)) | ||
10 | } | ||
11 | |||
12 | pub fn subtree<'f>(node: Node<'f>) -> Box<Iterator<Item=Node<'f>> + 'f> { | ||
13 | Box::new(node.children().flat_map(subtree).chain(::std::iter::once(node))) | ||
14 | } | ||
15 | |||
16 | pub fn descendants_of_type<'f>(node: Node<'f>, ty: NodeType) -> Vec<Node<'f>> { | ||
17 | process_subtree_bottom_up( | ||
18 | node, | ||
19 | visitor(Vec::new()) | ||
20 | .visit_nodes(&[ty], |node, nodes| nodes.push(node)) | ||
21 | ) | ||
22 | } | ||
23 | |||
24 | pub fn child_of_type_exn(node: Node, ty: NodeType) -> Node { | ||
25 | child_of_type(node, ty).unwrap_or_else(|| { | ||
26 | panic!("No child of type {:?} for {:?}\ | ||
27 | ----\ | ||
28 | {}\ | ||
29 | ----", ty, node.ty(), node.text()) | ||
30 | }) | ||
31 | } | ||
32 | |||
33 | |||
34 | pub fn ancestors(node: Node) -> Ancestors { | ||
35 | Ancestors(Some(node)) | ||
36 | } | ||
37 | |||
38 | pub struct Ancestors<'f>(Option<Node<'f>>); | ||
39 | |||
40 | impl<'f> Iterator for Ancestors<'f> { | ||
41 | type Item = Node<'f>; | ||
42 | |||
43 | fn next(&mut self) -> Option<Self::Item> { | ||
44 | let current = self.0; | ||
45 | self.0 = current.and_then(|n| n.parent()); | ||
46 | current | ||
47 | } | ||
48 | } | ||
49 | |||
50 | pub fn is_leaf(node: Node) -> bool { | ||
51 | node.children().next().is_none() && !node.range().is_empty() | ||
52 | } | ||
53 | |||
54 | |||
55 | #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] | ||
56 | pub enum Direction { | ||
57 | Left, Right | ||
58 | } | ||
59 | |||
60 | pub fn sibling(node: Node, dir: Direction) -> Option<Node> { | ||
61 | let (parent, idx) = child_position(node)?; | ||
62 | let idx = match dir { | ||
63 | Direction::Left => idx.checked_sub(1)?, | ||
64 | Direction::Right => idx + 1, | ||
65 | }; | ||
66 | parent.children().nth(idx) | ||
67 | } | ||
68 | |||
69 | pub mod ast { | ||
70 | use {Node, AstNode, TextUnit, AstChildren}; | ||
71 | use visitor::{visitor, process_subtree_bottom_up}; | ||
72 | use super::{ancestors, find_leaf_at_offset, LeafAtOffset}; | ||
73 | |||
74 | pub fn ancestor<'f, T: AstNode<'f>>(node: Node<'f>) -> Option<T> { | ||
75 | ancestors(node) | ||
76 | .filter_map(T::wrap) | ||
77 | .next() | ||
78 | } | ||
79 | |||
80 | pub fn ancestor_exn<'f, T: AstNode<'f>>(node: Node<'f>) -> T { | ||
81 | ancestor(node).unwrap() | ||
82 | } | ||
83 | |||
84 | pub fn children_of_type<'f, N: AstNode<'f>>(node: Node<'f>) -> AstChildren<N> { | ||
85 | AstChildren::new(node.children()) | ||
86 | } | ||
87 | |||
88 | pub fn descendants_of_type<'f, N: AstNode<'f>>(node: Node<'f>) -> Vec<N> { | ||
89 | process_subtree_bottom_up( | ||
90 | node, | ||
91 | visitor(Vec::new()) | ||
92 | .visit::<N, _>(|node, acc| acc.push(node)) | ||
93 | ) | ||
94 | } | ||
95 | |||
96 | pub fn node_at_offset<'f, T: AstNode<'f>>(node: Node<'f>, offset: TextUnit) -> Option<T> { | ||
97 | match find_leaf_at_offset(node, offset) { | ||
98 | LeafAtOffset::None => None, | ||
99 | LeafAtOffset::Single(node) => ancestor(node), | ||
100 | LeafAtOffset::Between(left, right) => ancestor(left).or_else(|| ancestor(right)), | ||
101 | } | ||
102 | } | ||
103 | } | ||
104 | |||
105 | pub mod traversal { | ||
106 | use {Node}; | ||
107 | |||
108 | pub fn bottom_up<'f, F: FnMut(Node<'f>)>(node: Node<'f>, mut f: F) | ||
109 | { | ||
110 | go(node, &mut f); | ||
111 | |||
112 | fn go<'f, F: FnMut(Node<'f>)>(node: Node<'f>, f: &mut F) { | ||
113 | for child in node.children() { | ||
114 | go(child, f) | ||
115 | } | ||
116 | f(node); | ||
117 | } | ||
118 | } | ||
119 | } | ||
120 | |||
121 | fn child_position(child: Node) -> Option<(Node, usize)> { | ||
122 | child.parent() | ||
123 | .map(|parent| { | ||
124 | (parent, parent.children().position(|n| n == child).unwrap()) | ||
125 | }) | ||
126 | } | ||
127 | |||
128 | fn common_ancestor<'f>(n1: Node<'f>, n2: Node<'f>) -> Node<'f> { | ||
129 | for p in ancestors(n1) { | ||
130 | if ancestors(n2).any(|a| a == p) { | ||
131 | return p; | ||
132 | } | ||
133 | } | ||
134 | panic!("Can't find common ancestor of {:?} and {:?}", n1, n2) | ||
135 | } | ||
136 | |||
diff --git a/src/parser_impl/event.rs b/src/parser_impl/event.rs index e64087480..4c97b1bea 100644 --- a/src/parser_impl/event.rs +++ b/src/parser_impl/event.rs | |||
@@ -13,6 +13,7 @@ use { | |||
13 | SyntaxKind::{self, TOMBSTONE}, | 13 | SyntaxKind::{self, TOMBSTONE}, |
14 | }; | 14 | }; |
15 | 15 | ||
16 | |||
16 | /// `Parser` produces a flat list of `Event`s. | 17 | /// `Parser` produces a flat list of `Event`s. |
17 | /// They are converted to a tree-structure in | 18 | /// They are converted to a tree-structure in |
18 | /// a separate pass, via `TreeBuilder`. | 19 | /// a separate pass, via `TreeBuilder`. |
diff --git a/src/yellow/syntax.rs b/src/yellow/syntax.rs index 5b88a3b07..07607ec2d 100644 --- a/src/yellow/syntax.rs +++ b/src/yellow/syntax.rs | |||
@@ -115,6 +115,10 @@ impl<R: TreeRoot> SyntaxNode<R> { | |||
115 | }) | 115 | }) |
116 | } | 116 | } |
117 | 117 | ||
118 | pub fn is_leaf(&self) -> bool { | ||
119 | self.first_child().is_none() | ||
120 | } | ||
121 | |||
118 | fn red(&self) -> &RedNode { | 122 | fn red(&self) -> &RedNode { |
119 | unsafe { self.red.as_ref() } | 123 | unsafe { self.red.as_ref() } |
120 | } | 124 | } |