diff options
-rw-r--r-- | crates/assert_eq_text/Cargo.toml | 7 | ||||
-rw-r--r-- | crates/assert_eq_text/src/lib.rs | 25 | ||||
-rw-r--r-- | crates/libeditor/Cargo.toml | 2 | ||||
-rw-r--r-- | crates/libeditor/src/code_actions.rs | 33 | ||||
-rw-r--r-- | crates/libeditor/src/edit.rs | 93 | ||||
-rw-r--r-- | crates/libeditor/src/lib.rs | 7 | ||||
-rw-r--r-- | crates/libeditor/tests/test.rs | 52 | ||||
-rw-r--r-- | crates/libsyntax2/Cargo.toml | 2 | ||||
-rw-r--r-- | crates/libsyntax2/src/algo/mod.rs | 48 | ||||
-rw-r--r-- | crates/libsyntax2/src/syntax_kinds/mod.rs | 2 | ||||
-rw-r--r-- | crates/libsyntax2/src/yellow/syntax.rs | 11 | ||||
-rw-r--r-- | crates/libsyntax2/tests/test/main.rs | 48 |
12 files changed, 273 insertions, 57 deletions
diff --git a/crates/assert_eq_text/Cargo.toml b/crates/assert_eq_text/Cargo.toml new file mode 100644 index 000000000..21858dfd3 --- /dev/null +++ b/crates/assert_eq_text/Cargo.toml | |||
@@ -0,0 +1,7 @@ | |||
1 | [package] | ||
2 | name = "assert_eq_text" | ||
3 | version = "0.1.0" | ||
4 | authors = ["Aleksey Kladov <[email protected]>"] | ||
5 | |||
6 | [dependencies] | ||
7 | difference = "2.0.0" | ||
diff --git a/crates/assert_eq_text/src/lib.rs b/crates/assert_eq_text/src/lib.rs new file mode 100644 index 000000000..ed942d81a --- /dev/null +++ b/crates/assert_eq_text/src/lib.rs | |||
@@ -0,0 +1,25 @@ | |||
1 | extern crate difference; | ||
2 | pub use self::difference::Changeset as __Changeset; | ||
3 | |||
4 | #[macro_export] | ||
5 | macro_rules! assert_eq_text { | ||
6 | ($expected:expr, $actual:expr) => {{ | ||
7 | let expected = $expected; | ||
8 | let actual = $actual; | ||
9 | if expected != actual { | ||
10 | let changeset = $crate::__Changeset::new(actual, expected, "\n"); | ||
11 | println!("Expected:\n{}\n\nActual:\n{}\nDiff:{}\n", expected, actual, changeset); | ||
12 | panic!("text differs"); | ||
13 | } | ||
14 | }}; | ||
15 | ($expected:expr, $actual:expr, $($tt:tt)*) => {{ | ||
16 | let expected = $expected; | ||
17 | let actual = $actual; | ||
18 | if expected != actual { | ||
19 | let changeset = $crate::__Changeset::new(actual, expected, "\n"); | ||
20 | println!("Expected:\n{}\n\nActual:\n{}\n\nDiff:\n{}\n", expected, actual, changeset); | ||
21 | println!($($tt)*); | ||
22 | panic!("text differs"); | ||
23 | } | ||
24 | }}; | ||
25 | } | ||
diff --git a/crates/libeditor/Cargo.toml b/crates/libeditor/Cargo.toml index d6423979b..fe688bc20 100644 --- a/crates/libeditor/Cargo.toml +++ b/crates/libeditor/Cargo.toml | |||
@@ -7,4 +7,6 @@ publish = false | |||
7 | [dependencies] | 7 | [dependencies] |
8 | itertools = "0.7.8" | 8 | itertools = "0.7.8" |
9 | superslice = "0.1.0" | 9 | superslice = "0.1.0" |
10 | |||
10 | libsyntax2 = { path = "../libsyntax2" } | 11 | libsyntax2 = { path = "../libsyntax2" } |
12 | assert_eq_text = { path = "../assert_eq_text" } | ||
diff --git a/crates/libeditor/src/code_actions.rs b/crates/libeditor/src/code_actions.rs new file mode 100644 index 000000000..7c9874588 --- /dev/null +++ b/crates/libeditor/src/code_actions.rs | |||
@@ -0,0 +1,33 @@ | |||
1 | use {TextUnit, File, EditBuilder, Edit}; | ||
2 | use libsyntax2::{ | ||
3 | ast::AstNode, | ||
4 | SyntaxKind::COMMA, | ||
5 | SyntaxNodeRef, | ||
6 | algo::{ | ||
7 | Direction, siblings, | ||
8 | find_leaf_at_offset, | ||
9 | }, | ||
10 | }; | ||
11 | |||
12 | pub fn flip_comma<'a>(file: &'a File, offset: TextUnit) -> Option<impl FnOnce() -> Edit + 'a> { | ||
13 | let syntax = file.syntax(); | ||
14 | let syntax = syntax.as_ref(); | ||
15 | |||
16 | let comma = find_leaf_at_offset(syntax, offset).find(|leaf| leaf.kind() == COMMA)?; | ||
17 | let left = non_trivia_sibling(comma, Direction::Backward)?; | ||
18 | let right = non_trivia_sibling(comma, Direction::Forward)?; | ||
19 | Some(move || { | ||
20 | let mut edit = EditBuilder::new(); | ||
21 | edit.replace(left.range(), right.text()); | ||
22 | edit.replace(right.range(), left.text()); | ||
23 | edit.finish() | ||
24 | }) | ||
25 | } | ||
26 | |||
27 | fn non_trivia_sibling(node: SyntaxNodeRef, direction: Direction) -> Option<SyntaxNodeRef> { | ||
28 | siblings(node, direction) | ||
29 | .skip(1) | ||
30 | .find(|node| !node.kind().is_trivia()) | ||
31 | } | ||
32 | |||
33 | |||
diff --git a/crates/libeditor/src/edit.rs b/crates/libeditor/src/edit.rs new file mode 100644 index 000000000..163ecf6de --- /dev/null +++ b/crates/libeditor/src/edit.rs | |||
@@ -0,0 +1,93 @@ | |||
1 | use {TextRange, TextUnit}; | ||
2 | |||
3 | #[derive(Debug)] | ||
4 | pub struct Edit { | ||
5 | pub atoms: Vec<AtomEdit>, | ||
6 | } | ||
7 | |||
8 | #[derive(Debug)] | ||
9 | pub struct AtomEdit { | ||
10 | pub delete: TextRange, | ||
11 | pub insert: String, | ||
12 | } | ||
13 | |||
14 | #[derive(Debug)] | ||
15 | pub struct EditBuilder { | ||
16 | atoms: Vec<AtomEdit> | ||
17 | } | ||
18 | |||
19 | impl EditBuilder { | ||
20 | pub fn new() -> EditBuilder { | ||
21 | EditBuilder { atoms: Vec::new() } | ||
22 | } | ||
23 | |||
24 | pub fn replace(&mut self, range: TextRange, replacement: String) { | ||
25 | let range = self.translate(range); | ||
26 | self.atoms.push(AtomEdit { delete: range, insert: replacement }) | ||
27 | } | ||
28 | |||
29 | pub fn delete(&mut self, range: TextRange) { | ||
30 | self.replace(range, String::new()); | ||
31 | } | ||
32 | |||
33 | pub fn insert(&mut self, offset: TextUnit, text: String) { | ||
34 | self.replace(TextRange::offset_len(offset, 0.into()), text) | ||
35 | } | ||
36 | |||
37 | pub fn finish(self) -> Edit { | ||
38 | Edit { atoms: self.atoms } | ||
39 | } | ||
40 | |||
41 | fn translate(&self, range: TextRange) -> TextRange { | ||
42 | let mut range = range; | ||
43 | for atom in self.atoms.iter() { | ||
44 | range = atom.apply_to_range(range) | ||
45 | .expect("conflicting edits"); | ||
46 | } | ||
47 | range | ||
48 | } | ||
49 | } | ||
50 | |||
51 | impl Edit { | ||
52 | pub fn apply(&self, text: &str) -> String { | ||
53 | let mut text = text.to_owned(); | ||
54 | for atom in self.atoms.iter() { | ||
55 | text = atom.apply(&text); | ||
56 | } | ||
57 | text | ||
58 | } | ||
59 | } | ||
60 | |||
61 | impl AtomEdit { | ||
62 | fn apply(&self, text: &str) -> String { | ||
63 | let prefix = &text[ | ||
64 | TextRange::from_to(0.into(), self.delete.start()) | ||
65 | ]; | ||
66 | let suffix = &text[ | ||
67 | TextRange::from_to(self.delete.end(), TextUnit::of_str(text)) | ||
68 | ]; | ||
69 | let mut res = String::with_capacity(prefix.len() + self.insert.len() + suffix.len()); | ||
70 | res.push_str(prefix); | ||
71 | res.push_str(&self.insert); | ||
72 | res.push_str(suffix); | ||
73 | res | ||
74 | } | ||
75 | |||
76 | fn apply_to_position(&self, pos: TextUnit) -> Option<TextUnit> { | ||
77 | if pos <= self.delete.start() { | ||
78 | return Some(pos); | ||
79 | } | ||
80 | if pos < self.delete.end() { | ||
81 | return None; | ||
82 | } | ||
83 | Some(pos - self.delete.len() + TextUnit::of_str(&self.insert)) | ||
84 | } | ||
85 | |||
86 | fn apply_to_range(&self, range: TextRange) -> Option<TextRange> { | ||
87 | Some(TextRange::from_to( | ||
88 | self.apply_to_position(range.start())?, | ||
89 | self.apply_to_position(range.end())?, | ||
90 | )) | ||
91 | } | ||
92 | } | ||
93 | |||
diff --git a/crates/libeditor/src/lib.rs b/crates/libeditor/src/lib.rs index 013d27450..103f32190 100644 --- a/crates/libeditor/src/lib.rs +++ b/crates/libeditor/src/lib.rs | |||
@@ -1,9 +1,12 @@ | |||
1 | extern crate libsyntax2; | 1 | extern crate libsyntax2; |
2 | extern crate superslice; | 2 | extern crate superslice; |
3 | extern crate itertools; | ||
3 | 4 | ||
4 | mod extend_selection; | 5 | mod extend_selection; |
5 | mod symbols; | 6 | mod symbols; |
6 | mod line_index; | 7 | mod line_index; |
8 | mod edit; | ||
9 | mod code_actions; | ||
7 | 10 | ||
8 | use libsyntax2::{ | 11 | use libsyntax2::{ |
9 | ast::{self, NameOwner}, | 12 | ast::{self, NameOwner}, |
@@ -15,7 +18,9 @@ pub use libsyntax2::{File, TextRange, TextUnit}; | |||
15 | pub use self::{ | 18 | pub use self::{ |
16 | line_index::{LineIndex, LineCol}, | 19 | line_index::{LineIndex, LineCol}, |
17 | extend_selection::extend_selection, | 20 | extend_selection::extend_selection, |
18 | symbols::{FileSymbol, file_symbols} | 21 | symbols::{FileSymbol, file_symbols}, |
22 | edit::{EditBuilder, Edit}, | ||
23 | code_actions::{flip_comma}, | ||
19 | }; | 24 | }; |
20 | 25 | ||
21 | #[derive(Debug)] | 26 | #[derive(Debug)] |
diff --git a/crates/libeditor/tests/test.rs b/crates/libeditor/tests/test.rs index dedca49a4..369854fed 100644 --- a/crates/libeditor/tests/test.rs +++ b/crates/libeditor/tests/test.rs | |||
@@ -1,9 +1,16 @@ | |||
1 | extern crate libeditor; | 1 | extern crate libeditor; |
2 | extern crate libsyntax2; | ||
2 | extern crate itertools; | 3 | extern crate itertools; |
4 | #[macro_use] | ||
5 | extern crate assert_eq_text; | ||
3 | 6 | ||
4 | use std::fmt; | 7 | use std::fmt; |
5 | use itertools::Itertools; | 8 | use itertools::Itertools; |
6 | use libeditor::{File, highlight, runnables, extend_selection, TextRange, file_symbols}; | 9 | use libsyntax2::AstNode; |
10 | use libeditor::{ | ||
11 | File, TextUnit, TextRange, | ||
12 | highlight, runnables, extend_selection, file_symbols, flip_comma, | ||
13 | }; | ||
7 | 14 | ||
8 | #[test] | 15 | #[test] |
9 | fn test_extend_selection() { | 16 | fn test_extend_selection() { |
@@ -27,13 +34,13 @@ fn main() {} | |||
27 | "#); | 34 | "#); |
28 | let hls = highlight(&file); | 35 | let hls = highlight(&file); |
29 | dbg_eq( | 36 | dbg_eq( |
30 | &hls, | ||
31 | r#"[HighlightedRange { range: [1; 11), tag: "comment" }, | 37 | r#"[HighlightedRange { range: [1; 11), tag: "comment" }, |
32 | HighlightedRange { range: [12; 14), tag: "keyword" }, | 38 | HighlightedRange { range: [12; 14), tag: "keyword" }, |
33 | HighlightedRange { range: [15; 19), tag: "function" }, | 39 | HighlightedRange { range: [15; 19), tag: "function" }, |
34 | HighlightedRange { range: [29; 36), tag: "text" }, | 40 | HighlightedRange { range: [29; 36), tag: "text" }, |
35 | HighlightedRange { range: [38; 50), tag: "string" }, | 41 | HighlightedRange { range: [38; 50), tag: "string" }, |
36 | HighlightedRange { range: [52; 54), tag: "literal" }]"# | 42 | HighlightedRange { range: [52; 54), tag: "literal" }]"#, |
43 | &hls, | ||
37 | ); | 44 | ); |
38 | } | 45 | } |
39 | 46 | ||
@@ -51,10 +58,10 @@ fn test_foo() {} | |||
51 | "#); | 58 | "#); |
52 | let runnables = runnables(&file); | 59 | let runnables = runnables(&file); |
53 | dbg_eq( | 60 | dbg_eq( |
54 | &runnables, | ||
55 | r#"[Runnable { range: [1; 13), kind: Bin }, | 61 | r#"[Runnable { range: [1; 13), kind: Bin }, |
56 | Runnable { range: [15; 39), kind: Test { name: "test_foo" } }, | 62 | Runnable { range: [15; 39), kind: Test { name: "test_foo" } }, |
57 | Runnable { range: [41; 75), kind: Test { name: "test_foo" } }]"#, | 63 | Runnable { range: [41; 75), kind: Test { name: "test_foo" } }]"#, |
64 | &runnables, | ||
58 | ) | 65 | ) |
59 | } | 66 | } |
60 | 67 | ||
@@ -76,7 +83,6 @@ const C: i32 = 92; | |||
76 | "#); | 83 | "#); |
77 | let symbols = file_symbols(&file); | 84 | let symbols = file_symbols(&file); |
78 | dbg_eq( | 85 | dbg_eq( |
79 | &symbols, | ||
80 | r#"[FileSymbol { parent: None, name: "Foo", name_range: [8; 11), node_range: [1; 26), kind: STRUCT }, | 86 | r#"[FileSymbol { parent: None, name: "Foo", name_range: [8; 11), node_range: [1; 26), kind: STRUCT }, |
81 | FileSymbol { parent: None, name: "m", name_range: [32; 33), node_range: [28; 53), kind: MODULE }, | 87 | FileSymbol { parent: None, name: "m", name_range: [32; 33), node_range: [28; 53), kind: MODULE }, |
82 | FileSymbol { parent: Some(1), name: "bar", name_range: [43; 46), node_range: [40; 51), kind: FUNCTION }, | 88 | FileSymbol { parent: Some(1), name: "bar", name_range: [43; 46), node_range: [40; 51), kind: FUNCTION }, |
@@ -84,6 +90,19 @@ const C: i32 = 92; | |||
84 | FileSymbol { parent: None, name: "T", name_range: [81; 82), node_range: [76; 88), kind: TYPE_ITEM }, | 90 | FileSymbol { parent: None, name: "T", name_range: [81; 82), node_range: [76; 88), kind: TYPE_ITEM }, |
85 | FileSymbol { parent: None, name: "S", name_range: [96; 97), node_range: [89; 108), kind: STATIC_ITEM }, | 91 | FileSymbol { parent: None, name: "S", name_range: [96; 97), node_range: [89; 108), kind: STATIC_ITEM }, |
86 | FileSymbol { parent: None, name: "C", name_range: [115; 116), node_range: [109; 127), kind: CONST_ITEM }]"#, | 92 | FileSymbol { parent: None, name: "C", name_range: [115; 116), node_range: [109; 127), kind: CONST_ITEM }]"#, |
93 | &symbols, | ||
94 | ) | ||
95 | } | ||
96 | |||
97 | #[test] | ||
98 | fn test_swap_comma() { | ||
99 | check_modification( | ||
100 | "fn foo(x: i32,<|> y: Result<(), ()>) {}", | ||
101 | "fn foo(y: Result<(), ()>, x: i32) {}", | ||
102 | &|file, offset| { | ||
103 | let edit = flip_comma(file, offset).unwrap()(); | ||
104 | edit.apply(&file.syntax().text()) | ||
105 | }, | ||
87 | ) | 106 | ) |
88 | } | 107 | } |
89 | 108 | ||
@@ -91,8 +110,27 @@ fn file(text: &str) -> File { | |||
91 | File::parse(text) | 110 | File::parse(text) |
92 | } | 111 | } |
93 | 112 | ||
94 | fn dbg_eq(actual: &impl fmt::Debug, expected: &str) { | 113 | fn dbg_eq(expected: &str, actual: &impl fmt::Debug) { |
95 | let actual = format!("{:?}", actual); | 114 | let actual = format!("{:?}", actual); |
96 | let expected = expected.lines().map(|l| l.trim()).join(" "); | 115 | let expected = expected.lines().map(|l| l.trim()).join(" "); |
97 | assert_eq!(actual, expected); | 116 | assert_eq!(expected, actual); |
117 | } | ||
118 | |||
119 | fn check_modification( | ||
120 | before: &str, | ||
121 | after: &str, | ||
122 | f: &impl Fn(&File, TextUnit) -> String, | ||
123 | ) { | ||
124 | let cursor = "<|>"; | ||
125 | let cursor_pos = match before.find(cursor) { | ||
126 | None => panic!("before text should contain cursor marker"), | ||
127 | Some(pos) => pos, | ||
128 | }; | ||
129 | let mut text = String::with_capacity(before.len() - cursor.len()); | ||
130 | text.push_str(&before[..cursor_pos]); | ||
131 | text.push_str(&before[cursor_pos + cursor.len()..]); | ||
132 | let cursor_pos = TextUnit::from(cursor_pos as u32); | ||
133 | let file = file(&text); | ||
134 | let actual = f(&file, cursor_pos); | ||
135 | assert_eq_text!(after, &actual); | ||
98 | } | 136 | } |
diff --git a/crates/libsyntax2/Cargo.toml b/crates/libsyntax2/Cargo.toml index 5a76ea82b..4c4040fe5 100644 --- a/crates/libsyntax2/Cargo.toml +++ b/crates/libsyntax2/Cargo.toml | |||
@@ -12,4 +12,4 @@ drop_bomb = "0.1.4" | |||
12 | parking_lot = "0.6.0" | 12 | parking_lot = "0.6.0" |
13 | 13 | ||
14 | [dev-dependencies] | 14 | [dev-dependencies] |
15 | difference = "2.0.0" | 15 | assert_eq_text = { path = "../assert_eq_text" } |
diff --git a/crates/libsyntax2/src/algo/mod.rs b/crates/libsyntax2/src/algo/mod.rs index 263b58d97..6efdff12f 100644 --- a/crates/libsyntax2/src/algo/mod.rs +++ b/crates/libsyntax2/src/algo/mod.rs | |||
@@ -74,7 +74,6 @@ impl<'f> Iterator for LeafAtOffset<'f> { | |||
74 | } | 74 | } |
75 | } | 75 | } |
76 | 76 | ||
77 | |||
78 | pub fn find_covering_node(root: SyntaxNodeRef, range: TextRange) -> SyntaxNodeRef { | 77 | pub fn find_covering_node(root: SyntaxNodeRef, range: TextRange) -> SyntaxNodeRef { |
79 | assert!(is_subrange(root.range(), range)); | 78 | assert!(is_subrange(root.range(), range)); |
80 | let (left, right) = match ( | 79 | let (left, right) = match ( |
@@ -88,31 +87,33 @@ pub fn find_covering_node(root: SyntaxNodeRef, range: TextRange) -> SyntaxNodeRe | |||
88 | common_ancestor(left, right) | 87 | common_ancestor(left, right) |
89 | } | 88 | } |
90 | 89 | ||
91 | fn common_ancestor<'a>(n1: SyntaxNodeRef<'a>, n2: SyntaxNodeRef<'a>) -> SyntaxNodeRef<'a> { | ||
92 | for p in ancestors(n1) { | ||
93 | if ancestors(n2).any(|a| a == p) { | ||
94 | return p; | ||
95 | } | ||
96 | } | ||
97 | panic!("Can't find common ancestor of {:?} and {:?}", n1, n2) | ||
98 | } | ||
99 | |||
100 | pub fn ancestors<'a>(node: SyntaxNodeRef<'a>) -> impl Iterator<Item=SyntaxNodeRef<'a>> { | 90 | pub fn ancestors<'a>(node: SyntaxNodeRef<'a>) -> impl Iterator<Item=SyntaxNodeRef<'a>> { |
101 | Ancestors(Some(node)) | 91 | generate(Some(node), |&node| node.parent()) |
102 | } | 92 | } |
103 | 93 | ||
104 | #[derive(Debug)] | 94 | #[derive(Debug)] |
105 | struct Ancestors<'a>(Option<SyntaxNodeRef<'a>>); | 95 | pub enum Direction { |
96 | Forward, | ||
97 | Backward, | ||
98 | } | ||
106 | 99 | ||
107 | impl<'a> Iterator for Ancestors<'a> { | 100 | pub fn siblings<'a>( |
108 | type Item = SyntaxNodeRef<'a>; | 101 | node: SyntaxNodeRef<'a>, |
102 | direction: Direction | ||
103 | ) -> impl Iterator<Item=SyntaxNodeRef<'a>> { | ||
104 | generate(Some(node), move |&node| match direction { | ||
105 | Direction::Forward => node.next_sibling(), | ||
106 | Direction::Backward => node.prev_sibling(), | ||
107 | }) | ||
108 | } | ||
109 | 109 | ||
110 | fn next(&mut self) -> Option<Self::Item> { | 110 | fn common_ancestor<'a>(n1: SyntaxNodeRef<'a>, n2: SyntaxNodeRef<'a>) -> SyntaxNodeRef<'a> { |
111 | self.0.take().map(|n| { | 111 | for p in ancestors(n1) { |
112 | self.0 = n.parent(); | 112 | if ancestors(n2).any(|a| a == p) { |
113 | n | 113 | return p; |
114 | }) | 114 | } |
115 | } | 115 | } |
116 | panic!("Can't find common ancestor of {:?} and {:?}", n1, n2) | ||
116 | } | 117 | } |
117 | 118 | ||
118 | fn contains_offset_nonstrict(range: TextRange, offset: TextUnit) -> bool { | 119 | fn contains_offset_nonstrict(range: TextRange, offset: TextUnit) -> bool { |
@@ -122,3 +123,12 @@ fn contains_offset_nonstrict(range: TextRange, offset: TextUnit) -> bool { | |||
122 | fn is_subrange(range: TextRange, subrange: TextRange) -> bool { | 123 | fn is_subrange(range: TextRange, subrange: TextRange) -> bool { |
123 | range.start() <= subrange.start() && subrange.end() <= range.end() | 124 | range.start() <= subrange.start() && subrange.end() <= range.end() |
124 | } | 125 | } |
126 | |||
127 | fn generate<T>(seed: Option<T>, step: impl Fn(&T) -> Option<T>) -> impl Iterator<Item=T> { | ||
128 | ::itertools::unfold(seed, move |slot| { | ||
129 | slot.take().map(|curr| { | ||
130 | *slot = step(&curr); | ||
131 | curr | ||
132 | }) | ||
133 | }) | ||
134 | } | ||
diff --git a/crates/libsyntax2/src/syntax_kinds/mod.rs b/crates/libsyntax2/src/syntax_kinds/mod.rs index ed4fa5d4d..332cd13ac 100644 --- a/crates/libsyntax2/src/syntax_kinds/mod.rs +++ b/crates/libsyntax2/src/syntax_kinds/mod.rs | |||
@@ -17,7 +17,7 @@ pub(crate) struct SyntaxInfo { | |||
17 | } | 17 | } |
18 | 18 | ||
19 | impl SyntaxKind { | 19 | impl SyntaxKind { |
20 | pub(crate) fn is_trivia(self) -> bool { | 20 | pub fn is_trivia(self) -> bool { |
21 | match self { | 21 | match self { |
22 | WHITESPACE | COMMENT | DOC_COMMENT => true, | 22 | WHITESPACE | COMMENT | DOC_COMMENT => true, |
23 | _ => false, | 23 | _ => false, |
diff --git a/crates/libsyntax2/src/yellow/syntax.rs b/crates/libsyntax2/src/yellow/syntax.rs index a22275ed9..00f76e51c 100644 --- a/crates/libsyntax2/src/yellow/syntax.rs +++ b/crates/libsyntax2/src/yellow/syntax.rs | |||
@@ -101,6 +101,17 @@ impl<R: TreeRoot> SyntaxNode<R> { | |||
101 | }) | 101 | }) |
102 | } | 102 | } |
103 | 103 | ||
104 | pub fn prev_sibling(&self) -> Option<SyntaxNode<R>> { | ||
105 | let red = self.red(); | ||
106 | let parent = self.parent()?; | ||
107 | let prev_sibling_idx = red.index_in_parent()?.checked_sub(1)?; | ||
108 | let sibling_red = parent.red().get_child(prev_sibling_idx)?; | ||
109 | Some(SyntaxNode { | ||
110 | root: self.root.clone(), | ||
111 | red: sibling_red, | ||
112 | }) | ||
113 | } | ||
114 | |||
104 | pub fn is_leaf(&self) -> bool { | 115 | pub fn is_leaf(&self) -> bool { |
105 | self.first_child().is_none() | 116 | self.first_child().is_none() |
106 | } | 117 | } |
diff --git a/crates/libsyntax2/tests/test/main.rs b/crates/libsyntax2/tests/test/main.rs index 18e5bc4d4..64d080dfd 100644 --- a/crates/libsyntax2/tests/test/main.rs +++ b/crates/libsyntax2/tests/test/main.rs | |||
@@ -1,5 +1,6 @@ | |||
1 | extern crate libsyntax2; | 1 | extern crate libsyntax2; |
2 | extern crate difference; | 2 | #[macro_use] |
3 | extern crate assert_eq_text; | ||
3 | 4 | ||
4 | use std::{ | 5 | use std::{ |
5 | fs, | 6 | fs, |
@@ -7,8 +8,6 @@ use std::{ | |||
7 | fmt::Write, | 8 | fmt::Write, |
8 | }; | 9 | }; |
9 | 10 | ||
10 | use difference::Changeset; | ||
11 | |||
12 | #[test] | 11 | #[test] |
13 | fn lexer_tests() { | 12 | fn lexer_tests() { |
14 | dir_tests(&["lexer"], |text| { | 13 | dir_tests(&["lexer"], |text| { |
@@ -63,10 +62,26 @@ pub fn dir_tests<F>(paths: &[&str], f: F) | |||
63 | } | 62 | } |
64 | } | 63 | } |
65 | 64 | ||
65 | const REWRITE: bool = false; | ||
66 | |||
66 | fn assert_equal_text(expected: &str, actual: &str, path: &Path) { | 67 | fn assert_equal_text(expected: &str, actual: &str, path: &Path) { |
67 | if expected != actual { | 68 | if expected == actual { |
68 | print_difference(expected, actual, path) | 69 | return; |
70 | } | ||
71 | let dir = project_dir(); | ||
72 | let path = path.strip_prefix(&dir).unwrap_or_else(|_| path); | ||
73 | if expected.trim() == actual.trim() { | ||
74 | println!("whitespace difference, rewriting"); | ||
75 | println!("file: {}\n", path.display()); | ||
76 | fs::write(path, actual).unwrap(); | ||
77 | return; | ||
78 | } | ||
79 | if REWRITE { | ||
80 | println!("rewriting {}", path.display()); | ||
81 | fs::write(path, actual).unwrap(); | ||
82 | return; | ||
69 | } | 83 | } |
84 | assert_eq_text!(expected, actual, "file: {}", path.display()); | ||
70 | } | 85 | } |
71 | 86 | ||
72 | fn collect_tests(paths: &[&str]) -> Vec<PathBuf> { | 87 | fn collect_tests(paths: &[&str]) -> Vec<PathBuf> { |
@@ -92,29 +107,6 @@ fn test_from_dir(dir: &Path) -> Vec<PathBuf> { | |||
92 | acc | 107 | acc |
93 | } | 108 | } |
94 | 109 | ||
95 | const REWRITE: bool = false; | ||
96 | |||
97 | fn print_difference(expected: &str, actual: &str, path: &Path) { | ||
98 | let dir = project_dir(); | ||
99 | let path = path.strip_prefix(&dir).unwrap_or_else(|_| path); | ||
100 | if expected.trim() == actual.trim() { | ||
101 | println!("whitespace difference, rewriting"); | ||
102 | println!("file: {}\n", path.display()); | ||
103 | fs::write(path, actual).unwrap(); | ||
104 | return; | ||
105 | } | ||
106 | if REWRITE { | ||
107 | println!("rewriting {}", path.display()); | ||
108 | fs::write(path, actual).unwrap(); | ||
109 | return; | ||
110 | } | ||
111 | let changeset = Changeset::new(actual, expected, "\n"); | ||
112 | println!("Expected:\n{}\n\nActual:\n{}\n", expected, actual); | ||
113 | print!("{}", changeset); | ||
114 | println!("file: {}\n", path.display()); | ||
115 | panic!("Comparison failed") | ||
116 | } | ||
117 | |||
118 | fn project_dir() -> PathBuf { | 110 | fn project_dir() -> PathBuf { |
119 | let dir = env!("CARGO_MANIFEST_DIR"); | 111 | let dir = env!("CARGO_MANIFEST_DIR"); |
120 | PathBuf::from(dir) | 112 | PathBuf::from(dir) |