aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_assists/src')
-rw-r--r--crates/ra_assists/src/ast_editor.rs153
-rw-r--r--crates/ra_assists/src/lib.rs1
2 files changed, 154 insertions, 0 deletions
diff --git a/crates/ra_assists/src/ast_editor.rs b/crates/ra_assists/src/ast_editor.rs
new file mode 100644
index 000000000..7347c1738
--- /dev/null
+++ b/crates/ra_assists/src/ast_editor.rs
@@ -0,0 +1,153 @@
1use arrayvec::ArrayVec;
2use ra_text_edit::{TextEdit, TextEditBuilder};
3use ra_syntax::{AstNode, TreeArc, ast, SyntaxKind::*, SyntaxElement, SourceFile, InsertPosition, Direction};
4
5pub struct AstEditor<N: AstNode> {
6 original_ast: TreeArc<N>,
7 ast: TreeArc<N>,
8}
9
10impl<N: AstNode> AstEditor<N> {
11 pub fn new(node: &N) -> AstEditor<N> {
12 AstEditor { original_ast: node.to_owned(), ast: node.to_owned() }
13 }
14
15 pub fn into_text_edit(self) -> TextEdit {
16 // FIXME: compute a more fine-grained diff here.
17 // If *you* know a nice algorithm to compute diff between two syntax
18 // tree, tell me about it!
19 let mut builder = TextEditBuilder::default();
20 builder.replace(self.original_ast.syntax().range(), self.ast().syntax().text().to_string());
21 builder.finish()
22 }
23
24 pub fn ast(&self) -> &N {
25 &*self.ast
26 }
27}
28
29impl AstEditor<ast::NamedFieldList> {
30 pub fn append_field(&mut self, field: &ast::NamedField) {
31 self.insert_field(InsertPosition::Last, field)
32 }
33
34 pub fn insert_field(
35 &mut self,
36 position: InsertPosition<&'_ ast::NamedField>,
37 field: &ast::NamedField,
38 ) {
39 let mut to_insert: ArrayVec<[SyntaxElement; 2]> =
40 [field.syntax().into(), tokens::comma().into()].into();
41 let position = match position {
42 InsertPosition::First => {
43 let anchor = match self
44 .ast()
45 .syntax()
46 .children_with_tokens()
47 .find(|it| it.kind() == L_CURLY)
48 {
49 Some(it) => it,
50 None => return,
51 };
52 InsertPosition::After(anchor)
53 }
54 InsertPosition::Last => {
55 let anchor = match self
56 .ast()
57 .syntax()
58 .children_with_tokens()
59 .find(|it| it.kind() == R_CURLY)
60 {
61 Some(it) => it,
62 None => return,
63 };
64 InsertPosition::Before(anchor)
65 }
66 InsertPosition::Before(anchor) => InsertPosition::Before(anchor.syntax().into()),
67 InsertPosition::After(anchor) => {
68 if let Some(comma) = anchor
69 .syntax()
70 .siblings_with_tokens(Direction::Next)
71 .find(|it| it.kind() == COMMA)
72 {
73 InsertPosition::After(comma)
74 } else {
75 to_insert.insert(0, tokens::comma().into());
76 InsertPosition::After(anchor.syntax().into())
77 }
78 }
79 };
80 self.ast = insert_children_into_ast(self.ast(), position, to_insert.iter().cloned());
81 }
82}
83
84fn insert_children_into_ast<'a, N: AstNode>(
85 node: &N,
86 position: InsertPosition<SyntaxElement<'_>>,
87 to_insert: impl Iterator<Item = SyntaxElement<'a>>,
88) -> TreeArc<N> {
89 let new_syntax = node.syntax().insert_children(position, to_insert);
90 N::cast(&new_syntax).unwrap().to_owned()
91}
92
93pub struct AstBuilder<N: AstNode> {
94 _phantom: std::marker::PhantomData<N>,
95}
96
97impl AstBuilder<ast::NamedField> {
98 pub fn from_text(text: &str) -> TreeArc<ast::NamedField> {
99 ast_node_from_file_text(&format!("fn f() {{ S {{ {}, }} }}", text))
100 }
101}
102
103fn ast_node_from_file_text<N: AstNode>(text: &str) -> TreeArc<N> {
104 let file = SourceFile::parse(text);
105 let res = file.syntax().descendants().find_map(N::cast).unwrap().to_owned();
106 res
107}
108
109mod tokens {
110 use lazy_static::lazy_static;
111 use ra_syntax::{AstNode, SourceFile, TreeArc, SyntaxToken, SyntaxKind::*};
112
113 lazy_static! {
114 static ref SOURCE_FILE: TreeArc<SourceFile> = SourceFile::parse(",");
115 }
116
117 pub(crate) fn comma() -> SyntaxToken<'static> {
118 SOURCE_FILE
119 .syntax()
120 .descendants_with_tokens()
121 .filter_map(|it| it.as_token())
122 .find(|it| it.kind() == COMMA)
123 .unwrap()
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 use ra_syntax::SourceFile;
132
133 #[test]
134 fn structure_editing() {
135 let file = SourceFile::parse(
136 "\
137fn foo() {
138 let s = S {
139 original: 92,
140 }
141}
142",
143 );
144 let field_list = file.syntax().descendants().find_map(ast::NamedFieldList::cast).unwrap();
145 let mut editor = AstEditor::new(field_list);
146
147 let field = AstBuilder::<ast::NamedField>::from_text("first_inserted: 1");
148 editor.append_field(&field);
149 let field = AstBuilder::<ast::NamedField>::from_text("second_inserted: 2");
150 editor.append_field(&field);
151 eprintln!("{}", editor.ast().syntax());
152 }
153}
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs
index ded401b63..3151b1c44 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -7,6 +7,7 @@
7 7
8mod assist_ctx; 8mod assist_ctx;
9mod marks; 9mod marks;
10pub mod ast_editor;
10 11
11use itertools::Itertools; 12use itertools::Itertools;
12 13