]>
Commit | Line | Data |
---|---|---|
064997fb FG |
1 | //! Structural editing for ast. |
2 | ||
3 | use std::iter::{empty, successors}; | |
4 | ||
5 | use parser::{SyntaxKind, T}; | |
6 | use rowan::SyntaxElement; | |
7 | ||
8 | use crate::{ | |
9 | algo::{self, neighbor}, | |
10 | ast::{self, edit::IndentLevel, make, HasGenericParams}, | |
11 | ted::{self, Position}, | |
12 | AstNode, AstToken, Direction, | |
13 | SyntaxKind::{ATTR, COMMENT, WHITESPACE}, | |
f2b60f7d | 14 | SyntaxNode, SyntaxToken, |
064997fb FG |
15 | }; |
16 | ||
17 | use super::HasName; | |
18 | ||
19 | pub trait GenericParamsOwnerEdit: ast::HasGenericParams { | |
20 | fn get_or_create_generic_param_list(&self) -> ast::GenericParamList; | |
21 | fn get_or_create_where_clause(&self) -> ast::WhereClause; | |
22 | } | |
23 | ||
24 | impl GenericParamsOwnerEdit for ast::Fn { | |
25 | fn get_or_create_generic_param_list(&self) -> ast::GenericParamList { | |
26 | match self.generic_param_list() { | |
27 | Some(it) => it, | |
28 | None => { | |
29 | let position = if let Some(name) = self.name() { | |
30 | Position::after(name.syntax) | |
31 | } else if let Some(fn_token) = self.fn_token() { | |
32 | Position::after(fn_token) | |
33 | } else if let Some(param_list) = self.param_list() { | |
34 | Position::before(param_list.syntax) | |
35 | } else { | |
36 | Position::last_child_of(self.syntax()) | |
37 | }; | |
38 | create_generic_param_list(position) | |
39 | } | |
40 | } | |
41 | } | |
42 | ||
43 | fn get_or_create_where_clause(&self) -> ast::WhereClause { | |
44 | if self.where_clause().is_none() { | |
45 | let position = if let Some(ty) = self.ret_type() { | |
46 | Position::after(ty.syntax()) | |
47 | } else if let Some(param_list) = self.param_list() { | |
48 | Position::after(param_list.syntax()) | |
49 | } else { | |
50 | Position::last_child_of(self.syntax()) | |
51 | }; | |
52 | create_where_clause(position); | |
53 | } | |
54 | self.where_clause().unwrap() | |
55 | } | |
56 | } | |
57 | ||
58 | impl GenericParamsOwnerEdit for ast::Impl { | |
59 | fn get_or_create_generic_param_list(&self) -> ast::GenericParamList { | |
60 | match self.generic_param_list() { | |
61 | Some(it) => it, | |
62 | None => { | |
63 | let position = match self.impl_token() { | |
64 | Some(imp_token) => Position::after(imp_token), | |
65 | None => Position::last_child_of(self.syntax()), | |
66 | }; | |
67 | create_generic_param_list(position) | |
68 | } | |
69 | } | |
70 | } | |
71 | ||
72 | fn get_or_create_where_clause(&self) -> ast::WhereClause { | |
73 | if self.where_clause().is_none() { | |
74 | let position = match self.assoc_item_list() { | |
75 | Some(items) => Position::before(items.syntax()), | |
76 | None => Position::last_child_of(self.syntax()), | |
77 | }; | |
78 | create_where_clause(position); | |
79 | } | |
80 | self.where_clause().unwrap() | |
81 | } | |
82 | } | |
83 | ||
84 | impl GenericParamsOwnerEdit for ast::Trait { | |
85 | fn get_or_create_generic_param_list(&self) -> ast::GenericParamList { | |
86 | match self.generic_param_list() { | |
87 | Some(it) => it, | |
88 | None => { | |
89 | let position = if let Some(name) = self.name() { | |
90 | Position::after(name.syntax) | |
91 | } else if let Some(trait_token) = self.trait_token() { | |
92 | Position::after(trait_token) | |
93 | } else { | |
94 | Position::last_child_of(self.syntax()) | |
95 | }; | |
96 | create_generic_param_list(position) | |
97 | } | |
98 | } | |
99 | } | |
100 | ||
101 | fn get_or_create_where_clause(&self) -> ast::WhereClause { | |
102 | if self.where_clause().is_none() { | |
103 | let position = match self.assoc_item_list() { | |
104 | Some(items) => Position::before(items.syntax()), | |
105 | None => Position::last_child_of(self.syntax()), | |
106 | }; | |
107 | create_where_clause(position); | |
108 | } | |
109 | self.where_clause().unwrap() | |
110 | } | |
111 | } | |
112 | ||
113 | impl GenericParamsOwnerEdit for ast::Struct { | |
114 | fn get_or_create_generic_param_list(&self) -> ast::GenericParamList { | |
115 | match self.generic_param_list() { | |
116 | Some(it) => it, | |
117 | None => { | |
118 | let position = if let Some(name) = self.name() { | |
119 | Position::after(name.syntax) | |
120 | } else if let Some(struct_token) = self.struct_token() { | |
121 | Position::after(struct_token) | |
122 | } else { | |
123 | Position::last_child_of(self.syntax()) | |
124 | }; | |
125 | create_generic_param_list(position) | |
126 | } | |
127 | } | |
128 | } | |
129 | ||
130 | fn get_or_create_where_clause(&self) -> ast::WhereClause { | |
131 | if self.where_clause().is_none() { | |
132 | let tfl = self.field_list().and_then(|fl| match fl { | |
133 | ast::FieldList::RecordFieldList(_) => None, | |
134 | ast::FieldList::TupleFieldList(it) => Some(it), | |
135 | }); | |
136 | let position = if let Some(tfl) = tfl { | |
137 | Position::after(tfl.syntax()) | |
138 | } else if let Some(gpl) = self.generic_param_list() { | |
139 | Position::after(gpl.syntax()) | |
140 | } else if let Some(name) = self.name() { | |
141 | Position::after(name.syntax()) | |
142 | } else { | |
143 | Position::last_child_of(self.syntax()) | |
144 | }; | |
145 | create_where_clause(position); | |
146 | } | |
147 | self.where_clause().unwrap() | |
148 | } | |
149 | } | |
150 | ||
151 | impl GenericParamsOwnerEdit for ast::Enum { | |
152 | fn get_or_create_generic_param_list(&self) -> ast::GenericParamList { | |
153 | match self.generic_param_list() { | |
154 | Some(it) => it, | |
155 | None => { | |
156 | let position = if let Some(name) = self.name() { | |
157 | Position::after(name.syntax) | |
158 | } else if let Some(enum_token) = self.enum_token() { | |
159 | Position::after(enum_token) | |
160 | } else { | |
161 | Position::last_child_of(self.syntax()) | |
162 | }; | |
163 | create_generic_param_list(position) | |
164 | } | |
165 | } | |
166 | } | |
167 | ||
168 | fn get_or_create_where_clause(&self) -> ast::WhereClause { | |
169 | if self.where_clause().is_none() { | |
170 | let position = if let Some(gpl) = self.generic_param_list() { | |
171 | Position::after(gpl.syntax()) | |
172 | } else if let Some(name) = self.name() { | |
173 | Position::after(name.syntax()) | |
174 | } else { | |
175 | Position::last_child_of(self.syntax()) | |
176 | }; | |
177 | create_where_clause(position); | |
178 | } | |
179 | self.where_clause().unwrap() | |
180 | } | |
181 | } | |
182 | ||
183 | fn create_where_clause(position: Position) { | |
184 | let where_clause = make::where_clause(empty()).clone_for_update(); | |
185 | ted::insert(position, where_clause.syntax()); | |
186 | } | |
187 | ||
188 | fn create_generic_param_list(position: Position) -> ast::GenericParamList { | |
189 | let gpl = make::generic_param_list(empty()).clone_for_update(); | |
190 | ted::insert_raw(position, gpl.syntax()); | |
191 | gpl | |
192 | } | |
193 | ||
194 | pub trait AttrsOwnerEdit: ast::HasAttrs { | |
195 | fn remove_attrs_and_docs(&self) { | |
196 | remove_attrs_and_docs(self.syntax()); | |
197 | ||
198 | fn remove_attrs_and_docs(node: &SyntaxNode) { | |
199 | let mut remove_next_ws = false; | |
200 | for child in node.children_with_tokens() { | |
201 | match child.kind() { | |
202 | ATTR | COMMENT => { | |
203 | remove_next_ws = true; | |
204 | child.detach(); | |
205 | continue; | |
206 | } | |
207 | WHITESPACE if remove_next_ws => { | |
208 | child.detach(); | |
209 | } | |
210 | _ => (), | |
211 | } | |
212 | remove_next_ws = false; | |
213 | } | |
214 | } | |
215 | } | |
216 | } | |
217 | ||
218 | impl<T: ast::HasAttrs> AttrsOwnerEdit for T {} | |
219 | ||
220 | impl ast::GenericParamList { | |
221 | pub fn add_generic_param(&self, generic_param: ast::GenericParam) { | |
222 | match self.generic_params().last() { | |
223 | Some(last_param) => { | |
224 | let position = Position::after(last_param.syntax()); | |
225 | let elements = vec![ | |
226 | make::token(T![,]).into(), | |
227 | make::tokens::single_space().into(), | |
228 | generic_param.syntax().clone().into(), | |
229 | ]; | |
230 | ted::insert_all(position, elements); | |
231 | } | |
232 | None => { | |
233 | let after_l_angle = Position::after(self.l_angle_token().unwrap()); | |
234 | ted::insert(after_l_angle, generic_param.syntax()); | |
235 | } | |
236 | } | |
237 | } | |
2b03887a FG |
238 | |
239 | /// Constructs a matching [`ast::GenericArgList`] | |
240 | pub fn to_generic_args(&self) -> ast::GenericArgList { | |
241 | let args = self.generic_params().filter_map(|param| match param { | |
242 | ast::GenericParam::LifetimeParam(it) => { | |
243 | Some(ast::GenericArg::LifetimeArg(make::lifetime_arg(it.lifetime()?))) | |
244 | } | |
245 | ast::GenericParam::TypeParam(it) => { | |
246 | Some(ast::GenericArg::TypeArg(make::type_arg(make::ext::ty_name(it.name()?)))) | |
247 | } | |
248 | ast::GenericParam::ConstParam(it) => { | |
249 | // Name-only const params get parsed as `TypeArg`s | |
250 | Some(ast::GenericArg::TypeArg(make::type_arg(make::ext::ty_name(it.name()?)))) | |
251 | } | |
252 | }); | |
253 | ||
254 | make::generic_arg_list(args) | |
255 | } | |
064997fb FG |
256 | } |
257 | ||
258 | impl ast::WhereClause { | |
259 | pub fn add_predicate(&self, predicate: ast::WherePred) { | |
260 | if let Some(pred) = self.predicates().last() { | |
261 | if !pred.syntax().siblings_with_tokens(Direction::Next).any(|it| it.kind() == T![,]) { | |
262 | ted::append_child_raw(self.syntax(), make::token(T![,])); | |
263 | } | |
264 | } | |
265 | ted::append_child(self.syntax(), predicate.syntax()); | |
266 | } | |
267 | } | |
268 | ||
2b03887a FG |
269 | impl ast::TypeParam { |
270 | pub fn remove_default(&self) { | |
271 | if let Some((eq, last)) = self | |
272 | .syntax() | |
273 | .children_with_tokens() | |
274 | .find(|it| it.kind() == T![=]) | |
275 | .zip(self.syntax().last_child_or_token()) | |
276 | { | |
277 | ted::remove_all(eq..=last); | |
278 | ||
279 | // remove any trailing ws | |
280 | if let Some(last) = self.syntax().last_token().filter(|it| it.kind() == WHITESPACE) { | |
281 | last.detach(); | |
282 | } | |
283 | } | |
284 | } | |
285 | } | |
286 | ||
287 | impl ast::ConstParam { | |
288 | pub fn remove_default(&self) { | |
289 | if let Some((eq, last)) = self | |
290 | .syntax() | |
291 | .children_with_tokens() | |
292 | .find(|it| it.kind() == T![=]) | |
293 | .zip(self.syntax().last_child_or_token()) | |
294 | { | |
295 | ted::remove_all(eq..=last); | |
296 | ||
297 | // remove any trailing ws | |
298 | if let Some(last) = self.syntax().last_token().filter(|it| it.kind() == WHITESPACE) { | |
299 | last.detach(); | |
300 | } | |
301 | } | |
302 | } | |
303 | } | |
304 | ||
f2b60f7d FG |
305 | pub trait Removable: AstNode { |
306 | fn remove(&self); | |
307 | } | |
308 | ||
309 | impl Removable for ast::TypeBoundList { | |
310 | fn remove(&self) { | |
064997fb FG |
311 | match self.syntax().siblings_with_tokens(Direction::Prev).find(|it| it.kind() == T![:]) { |
312 | Some(colon) => ted::remove_all(colon..=self.syntax().clone().into()), | |
313 | None => ted::remove(self.syntax()), | |
314 | } | |
315 | } | |
316 | } | |
317 | ||
318 | impl ast::PathSegment { | |
319 | pub fn get_or_create_generic_arg_list(&self) -> ast::GenericArgList { | |
320 | if self.generic_arg_list().is_none() { | |
2b03887a | 321 | let arg_list = make::generic_arg_list(empty()).clone_for_update(); |
064997fb FG |
322 | ted::append_child(self.syntax(), arg_list.syntax()); |
323 | } | |
324 | self.generic_arg_list().unwrap() | |
325 | } | |
326 | } | |
327 | ||
f2b60f7d FG |
328 | impl Removable for ast::UseTree { |
329 | fn remove(&self) { | |
064997fb FG |
330 | for dir in [Direction::Next, Direction::Prev] { |
331 | if let Some(next_use_tree) = neighbor(self, dir) { | |
332 | let separators = self | |
333 | .syntax() | |
334 | .siblings_with_tokens(dir) | |
335 | .skip(1) | |
336 | .take_while(|it| it.as_node() != Some(next_use_tree.syntax())); | |
337 | ted::remove_all_iter(separators); | |
338 | break; | |
339 | } | |
340 | } | |
341 | ted::remove(self.syntax()); | |
342 | } | |
f2b60f7d | 343 | } |
064997fb | 344 | |
f2b60f7d | 345 | impl ast::UseTree { |
064997fb FG |
346 | pub fn get_or_create_use_tree_list(&self) -> ast::UseTreeList { |
347 | match self.use_tree_list() { | |
348 | Some(it) => it, | |
349 | None => { | |
350 | let position = Position::last_child_of(self.syntax()); | |
351 | let use_tree_list = make::use_tree_list(empty()).clone_for_update(); | |
352 | let mut elements = Vec::with_capacity(2); | |
353 | if self.coloncolon_token().is_none() { | |
354 | elements.push(make::token(T![::]).into()); | |
355 | } | |
356 | elements.push(use_tree_list.syntax().clone().into()); | |
357 | ted::insert_all_raw(position, elements); | |
358 | use_tree_list | |
359 | } | |
360 | } | |
361 | } | |
362 | ||
363 | /// Splits off the given prefix, making it the path component of the use tree, | |
364 | /// appending the rest of the path to all UseTreeList items. | |
365 | /// | |
366 | /// # Examples | |
367 | /// | |
368 | /// `prefix$0::suffix` -> `prefix::{suffix}` | |
369 | /// | |
370 | /// `prefix$0` -> `prefix::{self}` | |
371 | /// | |
372 | /// `prefix$0::*` -> `prefix::{*}` | |
373 | pub fn split_prefix(&self, prefix: &ast::Path) { | |
374 | debug_assert_eq!(self.path(), Some(prefix.top_path())); | |
375 | let path = self.path().unwrap(); | |
376 | if &path == prefix && self.use_tree_list().is_none() { | |
377 | if self.star_token().is_some() { | |
378 | // path$0::* -> * | |
379 | self.coloncolon_token().map(ted::remove); | |
380 | ted::remove(prefix.syntax()); | |
381 | } else { | |
382 | // path$0 -> self | |
383 | let self_suffix = | |
384 | make::path_unqualified(make::path_segment_self()).clone_for_update(); | |
385 | ted::replace(path.syntax(), self_suffix.syntax()); | |
386 | } | |
387 | } else if split_path_prefix(prefix).is_none() { | |
388 | return; | |
389 | } | |
390 | // At this point, prefix path is detached; _self_ use tree has suffix path. | |
391 | // Next, transform 'suffix' use tree into 'prefix::{suffix}' | |
392 | let subtree = self.clone_subtree().clone_for_update(); | |
393 | ted::remove_all_iter(self.syntax().children_with_tokens()); | |
394 | ted::insert(Position::first_child_of(self.syntax()), prefix.syntax()); | |
395 | self.get_or_create_use_tree_list().add_use_tree(subtree); | |
396 | ||
397 | fn split_path_prefix(prefix: &ast::Path) -> Option<()> { | |
398 | let parent = prefix.parent_path()?; | |
399 | let segment = parent.segment()?; | |
400 | if algo::has_errors(segment.syntax()) { | |
401 | return None; | |
402 | } | |
403 | for p in successors(parent.parent_path(), |it| it.parent_path()) { | |
404 | p.segment()?; | |
405 | } | |
406 | prefix.parent_path().and_then(|p| p.coloncolon_token()).map(ted::remove); | |
407 | ted::remove(prefix.syntax()); | |
408 | Some(()) | |
409 | } | |
410 | } | |
411 | } | |
412 | ||
413 | impl ast::UseTreeList { | |
414 | pub fn add_use_tree(&self, use_tree: ast::UseTree) { | |
415 | let (position, elements) = match self.use_trees().last() { | |
416 | Some(last_tree) => ( | |
417 | Position::after(last_tree.syntax()), | |
418 | vec![ | |
419 | make::token(T![,]).into(), | |
420 | make::tokens::single_space().into(), | |
421 | use_tree.syntax.into(), | |
422 | ], | |
423 | ), | |
424 | None => { | |
425 | let position = match self.l_curly_token() { | |
426 | Some(l_curly) => Position::after(l_curly), | |
427 | None => Position::last_child_of(self.syntax()), | |
428 | }; | |
429 | (position, vec![use_tree.syntax.into()]) | |
430 | } | |
431 | }; | |
432 | ted::insert_all_raw(position, elements); | |
433 | } | |
434 | } | |
435 | ||
f2b60f7d FG |
436 | impl Removable for ast::Use { |
437 | fn remove(&self) { | |
064997fb FG |
438 | let next_ws = self |
439 | .syntax() | |
440 | .next_sibling_or_token() | |
441 | .and_then(|it| it.into_token()) | |
442 | .and_then(ast::Whitespace::cast); | |
443 | if let Some(next_ws) = next_ws { | |
444 | let ws_text = next_ws.syntax().text(); | |
445 | if let Some(rest) = ws_text.strip_prefix('\n') { | |
446 | if rest.is_empty() { | |
447 | ted::remove(next_ws.syntax()); | |
448 | } else { | |
449 | ted::replace(next_ws.syntax(), make::tokens::whitespace(rest)); | |
450 | } | |
451 | } | |
452 | } | |
453 | ted::remove(self.syntax()); | |
454 | } | |
455 | } | |
456 | ||
457 | impl ast::Impl { | |
458 | pub fn get_or_create_assoc_item_list(&self) -> ast::AssocItemList { | |
459 | if self.assoc_item_list().is_none() { | |
460 | let assoc_item_list = make::assoc_item_list().clone_for_update(); | |
461 | ted::append_child(self.syntax(), assoc_item_list.syntax()); | |
462 | } | |
463 | self.assoc_item_list().unwrap() | |
464 | } | |
465 | } | |
466 | ||
467 | impl ast::AssocItemList { | |
468 | pub fn add_item(&self, item: ast::AssocItem) { | |
469 | let (indent, position, whitespace) = match self.assoc_items().last() { | |
470 | Some(last_item) => ( | |
471 | IndentLevel::from_node(last_item.syntax()), | |
472 | Position::after(last_item.syntax()), | |
473 | "\n\n", | |
474 | ), | |
475 | None => match self.l_curly_token() { | |
476 | Some(l_curly) => { | |
477 | normalize_ws_between_braces(self.syntax()); | |
478 | (IndentLevel::from_token(&l_curly) + 1, Position::after(&l_curly), "\n") | |
479 | } | |
480 | None => (IndentLevel::single(), Position::last_child_of(self.syntax()), "\n"), | |
481 | }, | |
482 | }; | |
483 | let elements: Vec<SyntaxElement<_>> = vec![ | |
484 | make::tokens::whitespace(&format!("{}{}", whitespace, indent)).into(), | |
485 | item.syntax().clone().into(), | |
486 | ]; | |
487 | ted::insert_all(position, elements); | |
488 | } | |
489 | } | |
490 | ||
491 | impl ast::Fn { | |
492 | pub fn get_or_create_body(&self) -> ast::BlockExpr { | |
493 | if self.body().is_none() { | |
494 | let body = make::ext::empty_block_expr().clone_for_update(); | |
495 | match self.semicolon_token() { | |
496 | Some(semi) => { | |
497 | ted::replace(semi, body.syntax()); | |
498 | ted::insert(Position::before(body.syntax), make::tokens::single_space()); | |
499 | } | |
500 | None => ted::append_child(self.syntax(), body.syntax()), | |
501 | } | |
502 | } | |
503 | self.body().unwrap() | |
504 | } | |
505 | } | |
506 | ||
f2b60f7d FG |
507 | impl Removable for ast::MatchArm { |
508 | fn remove(&self) { | |
064997fb FG |
509 | if let Some(sibling) = self.syntax().prev_sibling_or_token() { |
510 | if sibling.kind() == SyntaxKind::WHITESPACE { | |
511 | ted::remove(sibling); | |
512 | } | |
513 | } | |
514 | if let Some(sibling) = self.syntax().next_sibling_or_token() { | |
515 | if sibling.kind() == T![,] { | |
516 | ted::remove(sibling); | |
517 | } | |
518 | } | |
519 | ted::remove(self.syntax()); | |
520 | } | |
521 | } | |
522 | ||
523 | impl ast::MatchArmList { | |
524 | pub fn add_arm(&self, arm: ast::MatchArm) { | |
525 | normalize_ws_between_braces(self.syntax()); | |
526 | let mut elements = Vec::new(); | |
527 | let position = match self.arms().last() { | |
528 | Some(last_arm) => { | |
529 | if needs_comma(&last_arm) { | |
530 | ted::append_child(last_arm.syntax(), make::token(SyntaxKind::COMMA)); | |
531 | } | |
532 | Position::after(last_arm.syntax().clone()) | |
533 | } | |
534 | None => match self.l_curly_token() { | |
535 | Some(it) => Position::after(it), | |
536 | None => Position::last_child_of(self.syntax()), | |
537 | }, | |
538 | }; | |
539 | let indent = IndentLevel::from_node(self.syntax()) + 1; | |
540 | elements.push(make::tokens::whitespace(&format!("\n{}", indent)).into()); | |
541 | elements.push(arm.syntax().clone().into()); | |
542 | if needs_comma(&arm) { | |
543 | ted::append_child(arm.syntax(), make::token(SyntaxKind::COMMA)); | |
544 | } | |
545 | ted::insert_all(position, elements); | |
546 | ||
547 | fn needs_comma(arm: &ast::MatchArm) -> bool { | |
548 | arm.expr().map_or(false, |e| !e.is_block_like()) && arm.comma_token().is_none() | |
549 | } | |
550 | } | |
551 | } | |
552 | ||
553 | impl ast::RecordExprFieldList { | |
554 | pub fn add_field(&self, field: ast::RecordExprField) { | |
555 | let is_multiline = self.syntax().text().contains_char('\n'); | |
556 | let whitespace = if is_multiline { | |
557 | let indent = IndentLevel::from_node(self.syntax()) + 1; | |
558 | make::tokens::whitespace(&format!("\n{}", indent)) | |
559 | } else { | |
560 | make::tokens::single_space() | |
561 | }; | |
562 | ||
563 | if is_multiline { | |
564 | normalize_ws_between_braces(self.syntax()); | |
565 | } | |
566 | ||
567 | let position = match self.fields().last() { | |
568 | Some(last_field) => { | |
f2b60f7d | 569 | let comma = get_or_insert_comma_after(last_field.syntax()); |
064997fb FG |
570 | Position::after(comma) |
571 | } | |
572 | None => match self.l_curly_token() { | |
573 | Some(it) => Position::after(it), | |
574 | None => Position::last_child_of(self.syntax()), | |
575 | }, | |
576 | }; | |
577 | ||
578 | ted::insert_all(position, vec![whitespace.into(), field.syntax().clone().into()]); | |
579 | if is_multiline { | |
580 | ted::insert(Position::after(field.syntax()), ast::make::token(T![,])); | |
581 | } | |
582 | } | |
583 | } | |
584 | ||
585 | impl ast::RecordExprField { | |
586 | /// This will either replace the initializer, or in the case that this is a shorthand convert | |
587 | /// the initializer into the name ref and insert the expr as the new initializer. | |
588 | pub fn replace_expr(&self, expr: ast::Expr) { | |
589 | if self.name_ref().is_some() { | |
590 | match self.expr() { | |
591 | Some(prev) => ted::replace(prev.syntax(), expr.syntax()), | |
592 | None => ted::append_child(self.syntax(), expr.syntax()), | |
593 | } | |
594 | return; | |
595 | } | |
596 | // this is a shorthand | |
597 | if let Some(ast::Expr::PathExpr(path_expr)) = self.expr() { | |
598 | if let Some(path) = path_expr.path() { | |
599 | if let Some(name_ref) = path.as_single_name_ref() { | |
600 | path_expr.syntax().detach(); | |
601 | let children = vec![ | |
602 | name_ref.syntax().clone().into(), | |
603 | ast::make::token(T![:]).into(), | |
604 | ast::make::tokens::single_space().into(), | |
605 | expr.syntax().clone().into(), | |
606 | ]; | |
607 | ted::insert_all_raw(Position::last_child_of(self.syntax()), children); | |
608 | } | |
609 | } | |
610 | } | |
611 | } | |
612 | } | |
613 | ||
614 | impl ast::RecordPatFieldList { | |
615 | pub fn add_field(&self, field: ast::RecordPatField) { | |
616 | let is_multiline = self.syntax().text().contains_char('\n'); | |
617 | let whitespace = if is_multiline { | |
618 | let indent = IndentLevel::from_node(self.syntax()) + 1; | |
619 | make::tokens::whitespace(&format!("\n{}", indent)) | |
620 | } else { | |
621 | make::tokens::single_space() | |
622 | }; | |
623 | ||
624 | if is_multiline { | |
625 | normalize_ws_between_braces(self.syntax()); | |
626 | } | |
627 | ||
628 | let position = match self.fields().last() { | |
629 | Some(last_field) => { | |
f2b60f7d FG |
630 | let syntax = last_field.syntax(); |
631 | let comma = get_or_insert_comma_after(syntax); | |
064997fb FG |
632 | Position::after(comma) |
633 | } | |
634 | None => match self.l_curly_token() { | |
635 | Some(it) => Position::after(it), | |
636 | None => Position::last_child_of(self.syntax()), | |
637 | }, | |
638 | }; | |
639 | ||
640 | ted::insert_all(position, vec![whitespace.into(), field.syntax().clone().into()]); | |
641 | if is_multiline { | |
642 | ted::insert(Position::after(field.syntax()), ast::make::token(T![,])); | |
643 | } | |
644 | } | |
645 | } | |
f2b60f7d FG |
646 | |
647 | fn get_or_insert_comma_after(syntax: &SyntaxNode) -> SyntaxToken { | |
2b03887a | 648 | match syntax |
f2b60f7d FG |
649 | .siblings_with_tokens(Direction::Next) |
650 | .filter_map(|it| it.into_token()) | |
651 | .find(|it| it.kind() == T![,]) | |
652 | { | |
653 | Some(it) => it, | |
654 | None => { | |
655 | let comma = ast::make::token(T![,]); | |
656 | ted::insert(Position::after(syntax), &comma); | |
657 | comma | |
658 | } | |
2b03887a | 659 | } |
f2b60f7d FG |
660 | } |
661 | ||
064997fb FG |
662 | impl ast::StmtList { |
663 | pub fn push_front(&self, statement: ast::Stmt) { | |
664 | ted::insert(Position::after(self.l_curly_token().unwrap()), statement.syntax()); | |
665 | } | |
666 | } | |
667 | ||
f2b60f7d FG |
668 | impl ast::VariantList { |
669 | pub fn add_variant(&self, variant: ast::Variant) { | |
670 | let (indent, position) = match self.variants().last() { | |
671 | Some(last_item) => ( | |
672 | IndentLevel::from_node(last_item.syntax()), | |
673 | Position::after(get_or_insert_comma_after(last_item.syntax())), | |
674 | ), | |
675 | None => match self.l_curly_token() { | |
676 | Some(l_curly) => { | |
677 | normalize_ws_between_braces(self.syntax()); | |
678 | (IndentLevel::from_token(&l_curly) + 1, Position::after(&l_curly)) | |
679 | } | |
680 | None => (IndentLevel::single(), Position::last_child_of(self.syntax())), | |
681 | }, | |
682 | }; | |
683 | let elements: Vec<SyntaxElement<_>> = vec![ | |
684 | make::tokens::whitespace(&format!("{}{}", "\n", indent)).into(), | |
685 | variant.syntax().clone().into(), | |
686 | ast::make::token(T![,]).into(), | |
687 | ]; | |
688 | ted::insert_all(position, elements); | |
689 | } | |
690 | } | |
691 | ||
064997fb FG |
692 | fn normalize_ws_between_braces(node: &SyntaxNode) -> Option<()> { |
693 | let l = node | |
694 | .children_with_tokens() | |
695 | .filter_map(|it| it.into_token()) | |
696 | .find(|it| it.kind() == T!['{'])?; | |
697 | let r = node | |
698 | .children_with_tokens() | |
699 | .filter_map(|it| it.into_token()) | |
700 | .find(|it| it.kind() == T!['}'])?; | |
701 | ||
702 | let indent = IndentLevel::from_node(node); | |
703 | ||
704 | match l.next_sibling_or_token() { | |
705 | Some(ws) if ws.kind() == SyntaxKind::WHITESPACE => { | |
706 | if ws.next_sibling_or_token()?.into_token()? == r { | |
707 | ted::replace(ws, make::tokens::whitespace(&format!("\n{}", indent))); | |
708 | } | |
709 | } | |
710 | Some(ws) if ws.kind() == T!['}'] => { | |
711 | ted::insert(Position::after(l), make::tokens::whitespace(&format!("\n{}", indent))); | |
712 | } | |
713 | _ => (), | |
714 | } | |
715 | Some(()) | |
716 | } | |
717 | ||
718 | pub trait Indent: AstNode + Clone + Sized { | |
719 | fn indent_level(&self) -> IndentLevel { | |
720 | IndentLevel::from_node(self.syntax()) | |
721 | } | |
722 | fn indent(&self, by: IndentLevel) { | |
723 | by.increase_indent(self.syntax()); | |
724 | } | |
725 | fn dedent(&self, by: IndentLevel) { | |
726 | by.decrease_indent(self.syntax()); | |
727 | } | |
728 | fn reindent_to(&self, target_level: IndentLevel) { | |
729 | let current_level = IndentLevel::from_node(self.syntax()); | |
730 | self.dedent(current_level); | |
731 | self.indent(target_level); | |
732 | } | |
733 | } | |
734 | ||
735 | impl<N: AstNode + Clone> Indent for N {} | |
736 | ||
737 | #[cfg(test)] | |
738 | mod tests { | |
739 | use std::fmt; | |
740 | ||
f2b60f7d FG |
741 | use stdx::trim_indent; |
742 | use test_utils::assert_eq_text; | |
743 | ||
064997fb FG |
744 | use crate::SourceFile; |
745 | ||
746 | use super::*; | |
747 | ||
748 | fn ast_mut_from_text<N: AstNode>(text: &str) -> N { | |
749 | let parse = SourceFile::parse(text); | |
750 | parse.tree().syntax().descendants().find_map(N::cast).unwrap().clone_for_update() | |
751 | } | |
752 | ||
753 | #[test] | |
754 | fn test_create_generic_param_list() { | |
755 | fn check_create_gpl<N: GenericParamsOwnerEdit + fmt::Display>(before: &str, after: &str) { | |
756 | let gpl_owner = ast_mut_from_text::<N>(before); | |
757 | gpl_owner.get_or_create_generic_param_list(); | |
758 | assert_eq!(gpl_owner.to_string(), after); | |
759 | } | |
760 | ||
761 | check_create_gpl::<ast::Fn>("fn foo", "fn foo<>"); | |
762 | check_create_gpl::<ast::Fn>("fn foo() {}", "fn foo<>() {}"); | |
763 | ||
764 | check_create_gpl::<ast::Impl>("impl", "impl<>"); | |
765 | check_create_gpl::<ast::Impl>("impl Struct {}", "impl<> Struct {}"); | |
766 | check_create_gpl::<ast::Impl>("impl Trait for Struct {}", "impl<> Trait for Struct {}"); | |
767 | ||
768 | check_create_gpl::<ast::Trait>("trait Trait<>", "trait Trait<>"); | |
769 | check_create_gpl::<ast::Trait>("trait Trait<> {}", "trait Trait<> {}"); | |
770 | ||
771 | check_create_gpl::<ast::Struct>("struct A", "struct A<>"); | |
772 | check_create_gpl::<ast::Struct>("struct A;", "struct A<>;"); | |
773 | check_create_gpl::<ast::Struct>("struct A();", "struct A<>();"); | |
774 | check_create_gpl::<ast::Struct>("struct A {}", "struct A<> {}"); | |
775 | ||
776 | check_create_gpl::<ast::Enum>("enum E", "enum E<>"); | |
777 | check_create_gpl::<ast::Enum>("enum E {", "enum E<> {"); | |
778 | } | |
779 | ||
780 | #[test] | |
781 | fn test_increase_indent() { | |
782 | let arm_list = ast_mut_from_text::<ast::Fn>( | |
783 | "fn foo() { | |
784 | ; | |
785 | ; | |
786 | }", | |
787 | ); | |
788 | arm_list.indent(IndentLevel(2)); | |
789 | assert_eq!( | |
790 | arm_list.to_string(), | |
791 | "fn foo() { | |
792 | ; | |
793 | ; | |
794 | }", | |
795 | ); | |
796 | } | |
f2b60f7d FG |
797 | |
798 | #[test] | |
799 | fn add_variant_to_empty_enum() { | |
800 | let variant = make::variant(make::name("Bar"), None).clone_for_update(); | |
801 | ||
802 | check_add_variant( | |
803 | r#" | |
804 | enum Foo {} | |
805 | "#, | |
806 | r#" | |
807 | enum Foo { | |
808 | Bar, | |
809 | } | |
810 | "#, | |
811 | variant, | |
812 | ); | |
813 | } | |
814 | ||
815 | #[test] | |
816 | fn add_variant_to_non_empty_enum() { | |
817 | let variant = make::variant(make::name("Baz"), None).clone_for_update(); | |
818 | ||
819 | check_add_variant( | |
820 | r#" | |
821 | enum Foo { | |
822 | Bar, | |
823 | } | |
824 | "#, | |
825 | r#" | |
826 | enum Foo { | |
827 | Bar, | |
828 | Baz, | |
829 | } | |
830 | "#, | |
831 | variant, | |
832 | ); | |
833 | } | |
834 | ||
835 | #[test] | |
836 | fn add_variant_with_tuple_field_list() { | |
837 | let variant = make::variant( | |
838 | make::name("Baz"), | |
839 | Some(ast::FieldList::TupleFieldList(make::tuple_field_list(std::iter::once( | |
840 | make::tuple_field(None, make::ty("bool")), | |
841 | )))), | |
842 | ) | |
843 | .clone_for_update(); | |
844 | ||
845 | check_add_variant( | |
846 | r#" | |
847 | enum Foo { | |
848 | Bar, | |
849 | } | |
850 | "#, | |
851 | r#" | |
852 | enum Foo { | |
853 | Bar, | |
854 | Baz(bool), | |
855 | } | |
856 | "#, | |
857 | variant, | |
858 | ); | |
859 | } | |
860 | ||
861 | #[test] | |
862 | fn add_variant_with_record_field_list() { | |
863 | let variant = make::variant( | |
864 | make::name("Baz"), | |
865 | Some(ast::FieldList::RecordFieldList(make::record_field_list(std::iter::once( | |
866 | make::record_field(None, make::name("x"), make::ty("bool")), | |
867 | )))), | |
868 | ) | |
869 | .clone_for_update(); | |
870 | ||
871 | check_add_variant( | |
872 | r#" | |
873 | enum Foo { | |
874 | Bar, | |
875 | } | |
876 | "#, | |
877 | r#" | |
878 | enum Foo { | |
879 | Bar, | |
880 | Baz { x: bool }, | |
881 | } | |
882 | "#, | |
883 | variant, | |
884 | ); | |
885 | } | |
886 | ||
887 | fn check_add_variant(before: &str, expected: &str, variant: ast::Variant) { | |
888 | let enum_ = ast_mut_from_text::<ast::Enum>(before); | |
889 | enum_.variant_list().map(|it| it.add_variant(variant)); | |
890 | let after = enum_.to_string(); | |
891 | assert_eq_text!(&trim_indent(expected.trim()), &trim_indent(&after.trim())); | |
892 | } | |
064997fb | 893 | } |