diff options
Diffstat (limited to 'crates/libeditor/src')
-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 |
3 files changed, 132 insertions, 1 deletions
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)] |