aboutsummaryrefslogtreecommitdiff
path: root/crates/syntax/src/ast/edit.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/syntax/src/ast/edit.rs')
-rw-r--r--crates/syntax/src/ast/edit.rs205
1 files changed, 2 insertions, 203 deletions
diff --git a/crates/syntax/src/ast/edit.rs b/crates/syntax/src/ast/edit.rs
index 4b5f5c571..61952377f 100644
--- a/crates/syntax/src/ast/edit.rs
+++ b/crates/syntax/src/ast/edit.rs
@@ -10,12 +10,8 @@ use arrayvec::ArrayVec;
10 10
11use crate::{ 11use crate::{
12 algo, 12 algo,
13 ast::{ 13 ast::{self, make, AstNode},
14 self, 14 ted, AstToken, InsertPosition, NodeOrToken, SyntaxElement, SyntaxKind,
15 make::{self, tokens},
16 AstNode,
17 },
18 ted, AstToken, Direction, InsertPosition, NodeOrToken, SmolStr, SyntaxElement, SyntaxKind,
19 SyntaxKind::{ATTR, COMMENT, WHITESPACE}, 15 SyntaxKind::{ATTR, COMMENT, WHITESPACE},
20 SyntaxNode, SyntaxToken, T, 16 SyntaxNode, SyntaxToken, T,
21}; 17};
@@ -29,114 +25,6 @@ impl ast::BinExpr {
29 } 25 }
30} 26}
31 27
32fn make_multiline<N>(node: N) -> N
33where
34 N: AstNode + Clone,
35{
36 let l_curly = match node.syntax().children_with_tokens().find(|it| it.kind() == T!['{']) {
37 Some(it) => it,
38 None => return node,
39 };
40 let sibling = match l_curly.next_sibling_or_token() {
41 Some(it) => it,
42 None => return node,
43 };
44 let existing_ws = match sibling.as_token() {
45 None => None,
46 Some(tok) if tok.kind() != WHITESPACE => None,
47 Some(ws) => {
48 if ws.text().contains('\n') {
49 return node;
50 }
51 Some(ws.clone())
52 }
53 };
54
55 let indent = leading_indent(node.syntax()).unwrap_or_default();
56 let ws = tokens::WsBuilder::new(&format!("\n{}", indent));
57 let to_insert = iter::once(ws.ws().into());
58 match existing_ws {
59 None => node.insert_children(InsertPosition::After(l_curly), to_insert),
60 Some(ws) => node.replace_children(single_node(ws), to_insert),
61 }
62}
63
64impl ast::RecordExprFieldList {
65 #[must_use]
66 pub fn append_field(&self, field: &ast::RecordExprField) -> ast::RecordExprFieldList {
67 self.insert_field(InsertPosition::Last, field)
68 }
69
70 #[must_use]
71 pub fn insert_field(
72 &self,
73 position: InsertPosition<&'_ ast::RecordExprField>,
74 field: &ast::RecordExprField,
75 ) -> ast::RecordExprFieldList {
76 let is_multiline = self.syntax().text().contains_char('\n');
77 let ws;
78 let space = if is_multiline {
79 ws = tokens::WsBuilder::new(&format!(
80 "\n{} ",
81 leading_indent(self.syntax()).unwrap_or_default()
82 ));
83 ws.ws()
84 } else {
85 tokens::single_space()
86 };
87
88 let mut to_insert: ArrayVec<SyntaxElement, 4> = ArrayVec::new();
89 to_insert.push(space.into());
90 to_insert.push(field.syntax().clone().into());
91 to_insert.push(make::token(T![,]).into());
92
93 macro_rules! after_l_curly {
94 () => {{
95 let anchor = match self.l_curly_token() {
96 Some(it) => it.into(),
97 None => return self.clone(),
98 };
99 InsertPosition::After(anchor)
100 }};
101 }
102
103 macro_rules! after_field {
104 ($anchor:expr) => {
105 if let Some(comma) = $anchor
106 .syntax()
107 .siblings_with_tokens(Direction::Next)
108 .find(|it| it.kind() == T![,])
109 {
110 InsertPosition::After(comma)
111 } else {
112 to_insert.insert(0, make::token(T![,]).into());
113 InsertPosition::After($anchor.syntax().clone().into())
114 }
115 };
116 }
117
118 let position = match position {
119 InsertPosition::First => after_l_curly!(),
120 InsertPosition::Last => {
121 if !is_multiline {
122 // don't insert comma before curly
123 to_insert.pop();
124 }
125 match self.fields().last() {
126 Some(it) => after_field!(it),
127 None => after_l_curly!(),
128 }
129 }
130 InsertPosition::Before(anchor) => {
131 InsertPosition::Before(anchor.syntax().clone().into())
132 }
133 InsertPosition::After(anchor) => after_field!(anchor),
134 };
135
136 self.insert_children(position, to_insert)
137 }
138}
139
140impl ast::Path { 28impl ast::Path {
141 #[must_use] 29 #[must_use]
142 pub fn with_segment(&self, segment: ast::PathSegment) -> ast::Path { 30 pub fn with_segment(&self, segment: ast::PathSegment) -> ast::Path {
@@ -214,79 +102,6 @@ impl ast::UseTree {
214 } 102 }
215} 103}
216 104
217impl ast::MatchArmList {
218 #[must_use]
219 pub fn append_arms(&self, items: impl IntoIterator<Item = ast::MatchArm>) -> ast::MatchArmList {
220 let mut res = self.clone();
221 res = res.strip_if_only_whitespace();
222 if !res.syntax().text().contains_char('\n') {
223 res = make_multiline(res);
224 }
225 items.into_iter().for_each(|it| res = res.append_arm(it));
226 res
227 }
228
229 fn strip_if_only_whitespace(&self) -> ast::MatchArmList {
230 let mut iter = self.syntax().children_with_tokens().skip_while(|it| it.kind() != T!['{']);
231 iter.next(); // Eat the curly
232 let mut inner = iter.take_while(|it| it.kind() != T!['}']);
233 if !inner.clone().all(|it| it.kind() == WHITESPACE) {
234 return self.clone();
235 }
236 let start = match inner.next() {
237 Some(s) => s,
238 None => return self.clone(),
239 };
240 let end = match inner.last() {
241 Some(s) => s,
242 None => start.clone(),
243 };
244 self.replace_children(start..=end, &mut iter::empty())
245 }
246
247 #[must_use]
248 pub fn remove_placeholder(&self) -> ast::MatchArmList {
249 let placeholder =
250 self.arms().find(|arm| matches!(arm.pat(), Some(ast::Pat::WildcardPat(_))));
251 if let Some(placeholder) = placeholder {
252 self.remove_arm(&placeholder)
253 } else {
254 self.clone()
255 }
256 }
257
258 #[must_use]
259 fn remove_arm(&self, arm: &ast::MatchArm) -> ast::MatchArmList {
260 let start = arm.syntax().clone();
261 let end = if let Some(comma) = start
262 .siblings_with_tokens(Direction::Next)
263 .skip(1)
264 .find(|it| !it.kind().is_trivia())
265 .filter(|it| it.kind() == T![,])
266 {
267 comma
268 } else {
269 start.clone().into()
270 };
271 self.replace_children(start.into()..=end, None)
272 }
273
274 #[must_use]
275 pub fn append_arm(&self, item: ast::MatchArm) -> ast::MatchArmList {
276 let r_curly = match self.syntax().children_with_tokens().find(|it| it.kind() == T!['}']) {
277 Some(t) => t,
278 None => return self.clone(),
279 };
280 let position = InsertPosition::Before(r_curly);
281 let arm_ws = tokens::WsBuilder::new(" ");
282 let match_indent = &leading_indent(self.syntax()).unwrap_or_default();
283 let match_ws = tokens::WsBuilder::new(&format!("\n{}", match_indent));
284 let to_insert: ArrayVec<SyntaxElement, 3> =
285 [arm_ws.ws().into(), item.syntax().clone().into(), match_ws.ws().into()].into();
286 self.insert_children(position, to_insert)
287 }
288}
289
290#[must_use] 105#[must_use]
291pub fn remove_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N { 106pub fn remove_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N {
292 N::cast(remove_attrs_and_docs_inner(node.syntax().clone())).unwrap() 107 N::cast(remove_attrs_and_docs_inner(node.syntax().clone())).unwrap()
@@ -413,22 +228,6 @@ impl IndentLevel {
413 } 228 }
414} 229}
415 230
416// FIXME: replace usages with IndentLevel above
417fn leading_indent(node: &SyntaxNode) -> Option<SmolStr> {
418 for token in prev_tokens(node.first_token()?) {
419 if let Some(ws) = ast::Whitespace::cast(token.clone()) {
420 let ws_text = ws.text();
421 if let Some(pos) = ws_text.rfind('\n') {
422 return Some(ws_text[pos + 1..].into());
423 }
424 }
425 if token.text().contains('\n') {
426 break;
427 }
428 }
429 None
430}
431
432fn prev_tokens(token: SyntaxToken) -> impl Iterator<Item = SyntaxToken> { 231fn prev_tokens(token: SyntaxToken) -> impl Iterator<Item = SyntaxToken> {
433 iter::successors(Some(token), |token| token.prev_token()) 232 iter::successors(Some(token), |token| token.prev_token())
434} 233}