aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2018-08-07 16:28:30 +0100
committerAleksey Kladov <[email protected]>2018-08-07 16:36:33 +0100
commit2fb854ccdae6f1f12b60441e5c3b283bdc81fb0a (patch)
treeed4f31d31473a2faf8e014907960f855b96cca22
parenta04473e2bb95483e84404c57426ee9ed21fa5d6b (diff)
:tada: extend selection
-rw-r--r--code/native/src/lib.rs32
-rw-r--r--code/package.json6
-rw-r--r--code/src/main.ts15
-rw-r--r--libeditor/Cargo.toml1
-rw-r--r--libeditor/src/extend_selection.rs36
-rw-r--r--libeditor/src/lib.rs29
-rw-r--r--src/algo/mod.rs122
-rw-r--r--src/algo/search.rs136
-rw-r--r--src/parser_impl/event.rs1
-rw-r--r--src/yellow/syntax.rs4
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;
3extern crate libeditor; 3extern crate libeditor;
4 4
5use neon::prelude::*; 5use neon::prelude::*;
6use libeditor::TextRange;
6 7
7pub struct Wrapper { 8pub 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
39export function deactivate() { } 49export 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]
8libsyntax2 = { path = "../" } 8libsyntax2 = { path = "../" }
9text_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 @@
1use libsyntax2::{
2 TextRange, SyntaxNodeRef,
3 SyntaxKind::WHITESPACE,
4 algo::{find_leaf_at_offset, find_covering_node, ancestors},
5};
6
7
8pub(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 @@
1extern crate libsyntax2; 1extern crate libsyntax2;
2extern crate text_unit; 2
3mod extend_selection;
3 4
4use libsyntax2::{ 5use libsyntax2::{
5 SyntaxNodeRef, 6 SyntaxNodeRef,
6 algo::walk, 7 algo::walk,
7 SyntaxKind::*, 8 SyntaxKind::*,
8}; 9};
9use text_unit::TextRange; 10pub use libsyntax2::{TextRange, TextUnit};
10 11
11pub struct File { 12pub 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)]
107mod 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 @@
1pub mod walk; 1pub mod walk;
2
3use {SyntaxNodeRef, TextUnit, TextRange};
4
5pub 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)]
40pub enum LeafAtOffset<'a> {
41 None,
42 Single(SyntaxNodeRef<'a>),
43 Between(SyntaxNodeRef<'a>, SyntaxNodeRef<'a>)
44}
45
46impl<'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
64impl<'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
77pub 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
90fn 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
99pub fn ancestors<'a>(node: SyntaxNodeRef<'a>) -> impl Iterator<Item=SyntaxNodeRef<'a>> {
100 Ancestors(Some(node))
101}
102
103#[derive(Debug)]
104struct Ancestors<'a>(Option<SyntaxNodeRef<'a>>);
105
106impl<'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
117fn contains_offset_nonstrict(range: TextRange, offset: TextUnit) -> bool {
118 range.start() <= offset && offset <= range.end()
119}
120
121fn 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 @@
1use {Node, NodeType, TextUnit, TextRange};
2use ::visitor::{visitor, process_subtree_bottom_up};
3
4pub fn child_of_type(node: Node, ty: NodeType) -> Option<Node> {
5 node.children().find(|n| n.ty() == ty)
6}
7
8pub 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
12pub 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
16pub 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
24pub 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
34pub fn ancestors(node: Node) -> Ancestors {
35 Ancestors(Some(node))
36}
37
38pub struct Ancestors<'f>(Option<Node<'f>>);
39
40impl<'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
50pub 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)]
56pub enum Direction {
57 Left, Right
58}
59
60pub 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
69pub 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
105pub 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
121fn 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
128fn 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 }