aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_syntax
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_syntax')
-rw-r--r--crates/ra_syntax/src/algo.rs17
-rw-r--r--crates/ra_syntax/src/ast.rs2
-rw-r--r--crates/ra_syntax/src/ast/edit.rs225
-rw-r--r--crates/ra_syntax/src/ast/extensions.rs12
4 files changed, 249 insertions, 7 deletions
diff --git a/crates/ra_syntax/src/algo.rs b/crates/ra_syntax/src/algo.rs
index 46680a08f..f33d2ad4e 100644
--- a/crates/ra_syntax/src/algo.rs
+++ b/crates/ra_syntax/src/algo.rs
@@ -3,6 +3,7 @@ pub mod visit;
3use std::ops::RangeInclusive; 3use std::ops::RangeInclusive;
4 4
5use itertools::Itertools; 5use itertools::Itertools;
6use ra_text_edit::TextEditBuilder;
6use rustc_hash::FxHashMap; 7use rustc_hash::FxHashMap;
7 8
8use crate::{ 9use crate::{
@@ -63,6 +64,18 @@ pub enum InsertPosition<T> {
63 After(T), 64 After(T),
64} 65}
65 66
67pub struct TreeDiff {
68 replacements: FxHashMap<SyntaxElement, SyntaxElement>,
69}
70
71impl TreeDiff {
72 pub fn into_text_edit(&self, builder: &mut TextEditBuilder) {
73 for (from, to) in self.replacements.iter() {
74 builder.replace(from.text_range(), to.to_string())
75 }
76 }
77}
78
66/// Finds minimal the diff, which, applied to `from`, will result in `to`. 79/// Finds minimal the diff, which, applied to `from`, will result in `to`.
67/// 80///
68/// Specifically, returns a map whose keys are descendants of `from` and values 81/// Specifically, returns a map whose keys are descendants of `from` and values
@@ -70,12 +83,12 @@ pub enum InsertPosition<T> {
70/// 83///
71/// A trivial solution is a singletom map `{ from: to }`, but this function 84/// A trivial solution is a singletom map `{ from: to }`, but this function
72/// tries to find a more fine-grained diff. 85/// tries to find a more fine-grained diff.
73pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> FxHashMap<SyntaxElement, SyntaxElement> { 86pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> TreeDiff {
74 let mut buf = FxHashMap::default(); 87 let mut buf = FxHashMap::default();
75 // FIXME: this is both horrible inefficient and gives larger than 88 // FIXME: this is both horrible inefficient and gives larger than
76 // necessary diff. I bet there's a cool algorithm to diff trees properly. 89 // necessary diff. I bet there's a cool algorithm to diff trees properly.
77 go(&mut buf, from.clone().into(), to.clone().into()); 90 go(&mut buf, from.clone().into(), to.clone().into());
78 return buf; 91 return TreeDiff { replacements: buf };
79 92
80 fn go( 93 fn go(
81 buf: &mut FxHashMap<SyntaxElement, SyntaxElement>, 94 buf: &mut FxHashMap<SyntaxElement, SyntaxElement>,
diff --git a/crates/ra_syntax/src/ast.rs b/crates/ra_syntax/src/ast.rs
index fdffd8cb1..1b2ce921a 100644
--- a/crates/ra_syntax/src/ast.rs
+++ b/crates/ra_syntax/src/ast.rs
@@ -5,7 +5,7 @@ mod traits;
5mod tokens; 5mod tokens;
6mod extensions; 6mod extensions;
7mod expr_extensions; 7mod expr_extensions;
8mod edit; 8pub mod edit;
9pub mod make; 9pub mod make;
10 10
11use std::marker::PhantomData; 11use std::marker::PhantomData;
diff --git a/crates/ra_syntax/src/ast/edit.rs b/crates/ra_syntax/src/ast/edit.rs
index c65899812..03f3b5fbb 100644
--- a/crates/ra_syntax/src/ast/edit.rs
+++ b/crates/ra_syntax/src/ast/edit.rs
@@ -1,14 +1,21 @@
1//! This module contains functions for editing syntax trees. As the trees are 1//! This module contains functions for editing syntax trees. As the trees are
2//! immutable, all function here return a fresh copy of the tree, instead of 2//! immutable, all function here return a fresh copy of the tree, instead of
3//! doing an in-place modification. 3//! doing an in-place modification.
4use std::{iter, ops::RangeInclusive};
4 5
5use arrayvec::ArrayVec; 6use arrayvec::ArrayVec;
6use std::ops::RangeInclusive; 7use rustc_hash::FxHashMap;
7 8
8use crate::{ 9use crate::{
9 algo, 10 algo,
10 ast::{self, make, AstNode}, 11 ast::{
11 InsertPosition, SyntaxElement, 12 self,
13 make::{self, tokens},
14 AstNode, TypeBoundsOwner,
15 },
16 AstToken, Direction, InsertPosition, SmolStr, SyntaxElement,
17 SyntaxKind::{ATTR, COMMENT, WHITESPACE},
18 SyntaxNode, T,
12}; 19};
13 20
14impl ast::FnDef { 21impl ast::FnDef {
@@ -31,6 +38,218 @@ impl ast::FnDef {
31 } 38 }
32} 39}
33 40
41impl ast::ItemList {
42 #[must_use]
43 pub fn append_items(&self, items: impl Iterator<Item = ast::ImplItem>) -> ast::ItemList {
44 let mut res = self.clone();
45 if !self.syntax().text().contains_char('\n') {
46 res = res.make_multiline();
47 }
48 items.for_each(|it| res = res.append_item(it));
49 res
50 }
51
52 #[must_use]
53 pub fn append_item(&self, item: ast::ImplItem) -> ast::ItemList {
54 let (indent, position) = match self.impl_items().last() {
55 Some(it) => (
56 leading_indent(it.syntax()).unwrap_or_default().to_string(),
57 InsertPosition::After(it.syntax().clone().into()),
58 ),
59 None => match self.l_curly() {
60 Some(it) => (
61 " ".to_string() + &leading_indent(self.syntax()).unwrap_or_default(),
62 InsertPosition::After(it),
63 ),
64 None => return self.clone(),
65 },
66 };
67 let ws = tokens::WsBuilder::new(&format!("\n{}", indent));
68 let to_insert: ArrayVec<[SyntaxElement; 2]> =
69 [ws.ws().into(), item.syntax().clone().into()].into();
70 insert_children(self, position, to_insert.into_iter())
71 }
72
73 fn l_curly(&self) -> Option<SyntaxElement> {
74 self.syntax().children_with_tokens().find(|it| it.kind() == T!['{'])
75 }
76
77 fn make_multiline(&self) -> ast::ItemList {
78 let l_curly = match self.syntax().children_with_tokens().find(|it| it.kind() == T!['{']) {
79 Some(it) => it,
80 None => return self.clone(),
81 };
82 let sibling = match l_curly.next_sibling_or_token() {
83 Some(it) => it,
84 None => return self.clone(),
85 };
86 let existing_ws = match sibling.as_token() {
87 None => None,
88 Some(tok) if tok.kind() != WHITESPACE => None,
89 Some(ws) => {
90 if ws.text().contains('\n') {
91 return self.clone();
92 }
93 Some(ws.clone())
94 }
95 };
96
97 let indent = leading_indent(self.syntax()).unwrap_or("".into());
98 let ws = tokens::WsBuilder::new(&format!("\n{}", indent));
99 let to_insert = iter::once(ws.ws().into());
100 match existing_ws {
101 None => insert_children(self, InsertPosition::After(l_curly), to_insert),
102 Some(ws) => {
103 replace_children(self, RangeInclusive::new(ws.clone().into(), ws.into()), to_insert)
104 }
105 }
106 }
107}
108
109impl ast::RecordFieldList {
110 #[must_use]
111 pub fn append_field(&self, field: &ast::RecordField) -> ast::RecordFieldList {
112 self.insert_field(InsertPosition::Last, field)
113 }
114
115 #[must_use]
116 pub fn insert_field(
117 &self,
118 position: InsertPosition<&'_ ast::RecordField>,
119 field: &ast::RecordField,
120 ) -> ast::RecordFieldList {
121 let is_multiline = self.syntax().text().contains_char('\n');
122 let ws;
123 let space = if is_multiline {
124 ws = tokens::WsBuilder::new(&format!(
125 "\n{} ",
126 leading_indent(self.syntax()).unwrap_or("".into())
127 ));
128 ws.ws()
129 } else {
130 tokens::single_space()
131 };
132
133 let mut to_insert: ArrayVec<[SyntaxElement; 4]> = ArrayVec::new();
134 to_insert.push(space.into());
135 to_insert.push(field.syntax().clone().into());
136 to_insert.push(tokens::comma().into());
137
138 macro_rules! after_l_curly {
139 () => {{
140 let anchor = match self.l_curly() {
141 Some(it) => it,
142 None => return self.clone(),
143 };
144 InsertPosition::After(anchor)
145 }};
146 }
147
148 macro_rules! after_field {
149 ($anchor:expr) => {
150 if let Some(comma) = $anchor
151 .syntax()
152 .siblings_with_tokens(Direction::Next)
153 .find(|it| it.kind() == T![,])
154 {
155 InsertPosition::After(comma)
156 } else {
157 to_insert.insert(0, tokens::comma().into());
158 InsertPosition::After($anchor.syntax().clone().into())
159 }
160 };
161 };
162
163 let position = match position {
164 InsertPosition::First => after_l_curly!(),
165 InsertPosition::Last => {
166 if !is_multiline {
167 // don't insert comma before curly
168 to_insert.pop();
169 }
170 match self.fields().last() {
171 Some(it) => after_field!(it),
172 None => after_l_curly!(),
173 }
174 }
175 InsertPosition::Before(anchor) => {
176 InsertPosition::Before(anchor.syntax().clone().into())
177 }
178 InsertPosition::After(anchor) => after_field!(anchor),
179 };
180
181 insert_children(self, position, to_insert.iter().cloned())
182 }
183
184 fn l_curly(&self) -> Option<SyntaxElement> {
185 self.syntax().children_with_tokens().find(|it| it.kind() == T!['{'])
186 }
187}
188
189impl ast::TypeParam {
190 #[must_use]
191 pub fn remove_bounds(&self) -> ast::TypeParam {
192 let colon = match self.colon_token() {
193 Some(it) => it,
194 None => return self.clone(),
195 };
196 let end = match self.type_bound_list() {
197 Some(it) => it.syntax().clone().into(),
198 None => colon.clone().into(),
199 };
200 replace_children(self, RangeInclusive::new(colon.into(), end), iter::empty())
201 }
202}
203
204#[must_use]
205pub fn strip_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N {
206 N::cast(strip_attrs_and_docs_inner(node.syntax().clone())).unwrap()
207}
208
209fn strip_attrs_and_docs_inner(mut node: SyntaxNode) -> SyntaxNode {
210 while let Some(start) =
211 node.children_with_tokens().find(|it| it.kind() == ATTR || it.kind() == COMMENT)
212 {
213 let end = match &start.next_sibling_or_token() {
214 Some(el) if el.kind() == WHITESPACE => el.clone(),
215 Some(_) | None => start.clone(),
216 };
217 node = algo::replace_children(&node, RangeInclusive::new(start, end), &mut iter::empty());
218 }
219 node
220}
221
222#[must_use]
223pub fn replace_descendants<N: AstNode, D: AstNode>(
224 parent: &N,
225 replacement_map: impl Iterator<Item = (D, D)>,
226) -> N {
227 let map = replacement_map
228 .map(|(from, to)| (from.syntax().clone().into(), to.syntax().clone().into()))
229 .collect::<FxHashMap<_, _>>();
230 let new_syntax = algo::replace_descendants(parent.syntax(), &map);
231 N::cast(new_syntax).unwrap()
232}
233
234// Note this is copy-pasted from fmt. It seems like fmt should be a separate
235// crate, but basic tree building should be this crate. However, tree building
236// might want to call into fmt...
237fn leading_indent(node: &SyntaxNode) -> Option<SmolStr> {
238 let prev_tokens = std::iter::successors(node.first_token(), |token| token.prev_token());
239 for token in prev_tokens {
240 if let Some(ws) = ast::Whitespace::cast(token.clone()) {
241 let ws_text = ws.text();
242 if let Some(pos) = ws_text.rfind('\n') {
243 return Some(ws_text[pos + 1..].into());
244 }
245 }
246 if token.text().contains('\n') {
247 break;
248 }
249 }
250 None
251}
252
34#[must_use] 253#[must_use]
35fn insert_children<N: AstNode>( 254fn insert_children<N: AstNode>(
36 parent: &N, 255 parent: &N,
diff --git a/crates/ra_syntax/src/ast/extensions.rs b/crates/ra_syntax/src/ast/extensions.rs
index 0433edb84..8c5ece65d 100644
--- a/crates/ra_syntax/src/ast/extensions.rs
+++ b/crates/ra_syntax/src/ast/extensions.rs
@@ -4,7 +4,7 @@
4use itertools::Itertools; 4use itertools::Itertools;
5 5
6use crate::{ 6use crate::{
7 ast::{self, child_opt, children, AstNode, SyntaxNode}, 7 ast::{self, child_opt, children, AstChildren, AstNode, SyntaxNode},
8 SmolStr, SyntaxElement, 8 SmolStr, SyntaxElement,
9 SyntaxKind::*, 9 SyntaxKind::*,
10 SyntaxToken, T, 10 SyntaxToken, T,
@@ -203,6 +203,16 @@ impl ast::ImplBlock {
203 } 203 }
204} 204}
205 205
206impl ast::AttrsOwner for ast::ImplItem {
207 fn attrs(&self) -> AstChildren<ast::Attr> {
208 match self {
209 ast::ImplItem::FnDef(it) => it.attrs(),
210 ast::ImplItem::TypeAliasDef(it) => it.attrs(),
211 ast::ImplItem::ConstDef(it) => it.attrs(),
212 }
213 }
214}
215
206#[derive(Debug, Clone, PartialEq, Eq)] 216#[derive(Debug, Clone, PartialEq, Eq)]
207pub enum StructKind { 217pub enum StructKind {
208 Tuple(ast::TupleFieldDefList), 218 Tuple(ast::TupleFieldDefList),