diff options
Diffstat (limited to 'crates/syntax/src')
-rw-r--r-- | crates/syntax/src/algo.rs | 582 | ||||
-rw-r--r-- | crates/syntax/src/ast/make.rs | 3 | ||||
-rw-r--r-- | crates/syntax/src/ast/node_ext.rs | 14 | ||||
-rw-r--r-- | crates/syntax/src/display.rs | 83 | ||||
-rw-r--r-- | crates/syntax/src/lib.rs | 1 |
5 files changed, 630 insertions, 53 deletions
diff --git a/crates/syntax/src/algo.rs b/crates/syntax/src/algo.rs index ea199f9b8..065035fe6 100644 --- a/crates/syntax/src/algo.rs +++ b/crates/syntax/src/algo.rs | |||
@@ -2,11 +2,14 @@ | |||
2 | 2 | ||
3 | use std::{ | 3 | use std::{ |
4 | fmt, | 4 | fmt, |
5 | hash::BuildHasherDefault, | ||
5 | ops::{self, RangeInclusive}, | 6 | ops::{self, RangeInclusive}, |
6 | }; | 7 | }; |
7 | 8 | ||
9 | use indexmap::IndexMap; | ||
8 | use itertools::Itertools; | 10 | use itertools::Itertools; |
9 | use rustc_hash::FxHashMap; | 11 | use rustc_hash::FxHashMap; |
12 | use test_utils::mark; | ||
10 | use text_edit::TextEditBuilder; | 13 | use text_edit::TextEditBuilder; |
11 | 14 | ||
12 | use crate::{ | 15 | use crate::{ |
@@ -106,42 +109,56 @@ pub enum InsertPosition<T> { | |||
106 | After(T), | 109 | After(T), |
107 | } | 110 | } |
108 | 111 | ||
112 | type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<rustc_hash::FxHasher>>; | ||
113 | |||
114 | #[derive(Debug)] | ||
109 | pub struct TreeDiff { | 115 | pub struct TreeDiff { |
110 | replacements: FxHashMap<SyntaxElement, SyntaxElement>, | 116 | replacements: FxHashMap<SyntaxElement, SyntaxElement>, |
117 | deletions: Vec<SyntaxElement>, | ||
118 | // the vec as well as the indexmap are both here to preserve order | ||
119 | insertions: FxIndexMap<SyntaxElement, Vec<SyntaxElement>>, | ||
111 | } | 120 | } |
112 | 121 | ||
113 | impl TreeDiff { | 122 | impl TreeDiff { |
114 | pub fn into_text_edit(&self, builder: &mut TextEditBuilder) { | 123 | pub fn into_text_edit(&self, builder: &mut TextEditBuilder) { |
124 | for (anchor, to) in self.insertions.iter() { | ||
125 | to.iter().for_each(|to| builder.insert(anchor.text_range().end(), to.to_string())); | ||
126 | } | ||
115 | for (from, to) in self.replacements.iter() { | 127 | for (from, to) in self.replacements.iter() { |
116 | builder.replace(from.text_range(), to.to_string()) | 128 | builder.replace(from.text_range(), to.to_string()) |
117 | } | 129 | } |
130 | for text_range in self.deletions.iter().map(SyntaxElement::text_range) { | ||
131 | builder.delete(text_range); | ||
132 | } | ||
118 | } | 133 | } |
119 | 134 | ||
120 | pub fn is_empty(&self) -> bool { | 135 | pub fn is_empty(&self) -> bool { |
121 | self.replacements.is_empty() | 136 | self.replacements.is_empty() && self.deletions.is_empty() && self.insertions.is_empty() |
122 | } | 137 | } |
123 | } | 138 | } |
124 | 139 | ||
125 | /// Finds minimal the diff, which, applied to `from`, will result in `to`. | 140 | /// Finds minimal the diff, which, applied to `from`, will result in `to`. |
126 | /// | 141 | /// |
127 | /// Specifically, returns a map whose keys are descendants of `from` and values | 142 | /// Specifically, returns a structure that consists of a replacements, insertions and deletions |
128 | /// are descendants of `to`, such that `replace_descendants(from, map) == to`. | 143 | /// such that applying this map on `from` will result in `to`. |
129 | /// | 144 | /// |
130 | /// A trivial solution is a singleton map `{ from: to }`, but this function | 145 | /// This function tries to find a fine-grained diff. |
131 | /// tries to find a more fine-grained diff. | ||
132 | pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> TreeDiff { | 146 | pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> TreeDiff { |
133 | let mut buf = FxHashMap::default(); | 147 | let mut diff = TreeDiff { |
134 | // FIXME: this is both horrible inefficient and gives larger than | 148 | replacements: FxHashMap::default(), |
135 | // necessary diff. I bet there's a cool algorithm to diff trees properly. | 149 | insertions: FxIndexMap::default(), |
136 | go(&mut buf, from.clone().into(), to.clone().into()); | 150 | deletions: Vec::new(), |
137 | return TreeDiff { replacements: buf }; | 151 | }; |
138 | 152 | let (from, to) = (from.clone().into(), to.clone().into()); | |
139 | fn go( | 153 | |
140 | buf: &mut FxHashMap<SyntaxElement, SyntaxElement>, | 154 | // FIXME: this is horrible inefficient. I bet there's a cool algorithm to diff trees properly. |
141 | lhs: SyntaxElement, | 155 | if !syntax_element_eq(&from, &to) { |
142 | rhs: SyntaxElement, | 156 | go(&mut diff, from, to); |
143 | ) { | 157 | } |
144 | if lhs.kind() == rhs.kind() | 158 | return diff; |
159 | |||
160 | fn syntax_element_eq(lhs: &SyntaxElement, rhs: &SyntaxElement) -> bool { | ||
161 | lhs.kind() == rhs.kind() | ||
145 | && lhs.text_range().len() == rhs.text_range().len() | 162 | && lhs.text_range().len() == rhs.text_range().len() |
146 | && match (&lhs, &rhs) { | 163 | && match (&lhs, &rhs) { |
147 | (NodeOrToken::Node(lhs), NodeOrToken::Node(rhs)) => { | 164 | (NodeOrToken::Node(lhs), NodeOrToken::Node(rhs)) => { |
@@ -150,18 +167,47 @@ pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> TreeDiff { | |||
150 | (NodeOrToken::Token(lhs), NodeOrToken::Token(rhs)) => lhs.text() == rhs.text(), | 167 | (NodeOrToken::Token(lhs), NodeOrToken::Token(rhs)) => lhs.text() == rhs.text(), |
151 | _ => false, | 168 | _ => false, |
152 | } | 169 | } |
153 | { | 170 | } |
154 | return; | 171 | |
155 | } | 172 | fn go(diff: &mut TreeDiff, lhs: SyntaxElement, rhs: SyntaxElement) { |
156 | if let (Some(lhs), Some(rhs)) = (lhs.as_node(), rhs.as_node()) { | 173 | let (lhs, rhs) = match lhs.as_node().zip(rhs.as_node()) { |
157 | if lhs.children_with_tokens().count() == rhs.children_with_tokens().count() { | 174 | Some((lhs, rhs)) => (lhs, rhs), |
158 | for (lhs, rhs) in lhs.children_with_tokens().zip(rhs.children_with_tokens()) { | 175 | _ => { |
159 | go(buf, lhs, rhs) | 176 | mark::hit!(diff_node_token_replace); |
160 | } | 177 | diff.replacements.insert(lhs, rhs); |
161 | return; | 178 | return; |
162 | } | 179 | } |
180 | }; | ||
181 | |||
182 | let mut rhs_children = rhs.children_with_tokens(); | ||
183 | let mut lhs_children = lhs.children_with_tokens(); | ||
184 | let mut last_lhs = None; | ||
185 | loop { | ||
186 | let lhs_child = lhs_children.next(); | ||
187 | match (lhs_child.clone(), rhs_children.next()) { | ||
188 | (None, None) => break, | ||
189 | (None, Some(element)) => match last_lhs.clone() { | ||
190 | Some(prev) => { | ||
191 | mark::hit!(diff_insert); | ||
192 | diff.insertions.entry(prev).or_insert_with(Vec::new).push(element); | ||
193 | } | ||
194 | // first iteration, this means we got no anchor element to insert after | ||
195 | // therefor replace the parent node instead | ||
196 | None => { | ||
197 | mark::hit!(diff_replace_parent); | ||
198 | diff.replacements.insert(lhs.clone().into(), rhs.clone().into()); | ||
199 | break; | ||
200 | } | ||
201 | }, | ||
202 | (Some(element), None) => { | ||
203 | mark::hit!(diff_delete); | ||
204 | diff.deletions.push(element); | ||
205 | } | ||
206 | (Some(ref lhs_ele), Some(ref rhs_ele)) if syntax_element_eq(lhs_ele, rhs_ele) => {} | ||
207 | (Some(lhs_ele), Some(rhs_ele)) => go(diff, lhs_ele, rhs_ele), | ||
208 | } | ||
209 | last_lhs = lhs_child.or(last_lhs); | ||
163 | } | 210 | } |
164 | buf.insert(lhs, rhs); | ||
165 | } | 211 | } |
166 | } | 212 | } |
167 | 213 | ||
@@ -243,11 +289,19 @@ fn _replace_children( | |||
243 | with_children(parent, new_children) | 289 | with_children(parent, new_children) |
244 | } | 290 | } |
245 | 291 | ||
292 | #[derive(Debug, PartialEq, Eq, Hash)] | ||
293 | enum InsertPos { | ||
294 | FirstChildOf(SyntaxNode), | ||
295 | // Before(SyntaxElement), | ||
296 | After(SyntaxElement), | ||
297 | } | ||
298 | |||
246 | #[derive(Default)] | 299 | #[derive(Default)] |
247 | pub struct SyntaxRewriter<'a> { | 300 | pub struct SyntaxRewriter<'a> { |
248 | f: Option<Box<dyn Fn(&SyntaxElement) -> Option<SyntaxElement> + 'a>>, | 301 | f: Option<Box<dyn Fn(&SyntaxElement) -> Option<SyntaxElement> + 'a>>, |
249 | //FIXME: add debug_assertions that all elements are in fact from the same file. | 302 | //FIXME: add debug_assertions that all elements are in fact from the same file. |
250 | replacements: FxHashMap<SyntaxElement, Replacement>, | 303 | replacements: FxHashMap<SyntaxElement, Replacement>, |
304 | insertions: IndexMap<InsertPos, Vec<SyntaxElement>>, | ||
251 | } | 305 | } |
252 | 306 | ||
253 | impl fmt::Debug for SyntaxRewriter<'_> { | 307 | impl fmt::Debug for SyntaxRewriter<'_> { |
@@ -258,13 +312,96 @@ impl fmt::Debug for SyntaxRewriter<'_> { | |||
258 | 312 | ||
259 | impl<'a> SyntaxRewriter<'a> { | 313 | impl<'a> SyntaxRewriter<'a> { |
260 | pub fn from_fn(f: impl Fn(&SyntaxElement) -> Option<SyntaxElement> + 'a) -> SyntaxRewriter<'a> { | 314 | pub fn from_fn(f: impl Fn(&SyntaxElement) -> Option<SyntaxElement> + 'a) -> SyntaxRewriter<'a> { |
261 | SyntaxRewriter { f: Some(Box::new(f)), replacements: FxHashMap::default() } | 315 | SyntaxRewriter { |
316 | f: Some(Box::new(f)), | ||
317 | replacements: FxHashMap::default(), | ||
318 | insertions: IndexMap::default(), | ||
319 | } | ||
262 | } | 320 | } |
263 | pub fn delete<T: Clone + Into<SyntaxElement>>(&mut self, what: &T) { | 321 | pub fn delete<T: Clone + Into<SyntaxElement>>(&mut self, what: &T) { |
264 | let what = what.clone().into(); | 322 | let what = what.clone().into(); |
265 | let replacement = Replacement::Delete; | 323 | let replacement = Replacement::Delete; |
266 | self.replacements.insert(what, replacement); | 324 | self.replacements.insert(what, replacement); |
267 | } | 325 | } |
326 | pub fn insert_before<T: Clone + Into<SyntaxElement>, U: Clone + Into<SyntaxElement>>( | ||
327 | &mut self, | ||
328 | before: &T, | ||
329 | what: &U, | ||
330 | ) { | ||
331 | let before = before.clone().into(); | ||
332 | let pos = match before.prev_sibling_or_token() { | ||
333 | Some(sibling) => InsertPos::After(sibling), | ||
334 | None => match before.parent() { | ||
335 | Some(parent) => InsertPos::FirstChildOf(parent), | ||
336 | None => return, | ||
337 | }, | ||
338 | }; | ||
339 | self.insertions.entry(pos).or_insert_with(Vec::new).push(what.clone().into()); | ||
340 | } | ||
341 | pub fn insert_after<T: Clone + Into<SyntaxElement>, U: Clone + Into<SyntaxElement>>( | ||
342 | &mut self, | ||
343 | after: &T, | ||
344 | what: &U, | ||
345 | ) { | ||
346 | self.insertions | ||
347 | .entry(InsertPos::After(after.clone().into())) | ||
348 | .or_insert_with(Vec::new) | ||
349 | .push(what.clone().into()); | ||
350 | } | ||
351 | pub fn insert_as_first_child<T: Clone + Into<SyntaxNode>, U: Clone + Into<SyntaxElement>>( | ||
352 | &mut self, | ||
353 | parent: &T, | ||
354 | what: &U, | ||
355 | ) { | ||
356 | self.insertions | ||
357 | .entry(InsertPos::FirstChildOf(parent.clone().into())) | ||
358 | .or_insert_with(Vec::new) | ||
359 | .push(what.clone().into()); | ||
360 | } | ||
361 | pub fn insert_many_before< | ||
362 | T: Clone + Into<SyntaxElement>, | ||
363 | U: IntoIterator<Item = SyntaxElement>, | ||
364 | >( | ||
365 | &mut self, | ||
366 | before: &T, | ||
367 | what: U, | ||
368 | ) { | ||
369 | let before = before.clone().into(); | ||
370 | let pos = match before.prev_sibling_or_token() { | ||
371 | Some(sibling) => InsertPos::After(sibling), | ||
372 | None => match before.parent() { | ||
373 | Some(parent) => InsertPos::FirstChildOf(parent), | ||
374 | None => return, | ||
375 | }, | ||
376 | }; | ||
377 | self.insertions.entry(pos).or_insert_with(Vec::new).extend(what); | ||
378 | } | ||
379 | pub fn insert_many_after< | ||
380 | T: Clone + Into<SyntaxElement>, | ||
381 | U: IntoIterator<Item = SyntaxElement>, | ||
382 | >( | ||
383 | &mut self, | ||
384 | after: &T, | ||
385 | what: U, | ||
386 | ) { | ||
387 | self.insertions | ||
388 | .entry(InsertPos::After(after.clone().into())) | ||
389 | .or_insert_with(Vec::new) | ||
390 | .extend(what); | ||
391 | } | ||
392 | pub fn insert_many_as_first_children< | ||
393 | T: Clone + Into<SyntaxNode>, | ||
394 | U: IntoIterator<Item = SyntaxElement>, | ||
395 | >( | ||
396 | &mut self, | ||
397 | parent: &T, | ||
398 | what: U, | ||
399 | ) { | ||
400 | self.insertions | ||
401 | .entry(InsertPos::FirstChildOf(parent.clone().into())) | ||
402 | .or_insert_with(Vec::new) | ||
403 | .extend(what) | ||
404 | } | ||
268 | pub fn replace<T: Clone + Into<SyntaxElement>>(&mut self, what: &T, with: &T) { | 405 | pub fn replace<T: Clone + Into<SyntaxElement>>(&mut self, what: &T, with: &T) { |
269 | let what = what.clone().into(); | 406 | let what = what.clone().into(); |
270 | let replacement = Replacement::Single(with.clone().into()); | 407 | let replacement = Replacement::Single(with.clone().into()); |
@@ -284,7 +421,7 @@ impl<'a> SyntaxRewriter<'a> { | |||
284 | } | 421 | } |
285 | 422 | ||
286 | pub fn rewrite(&self, node: &SyntaxNode) -> SyntaxNode { | 423 | pub fn rewrite(&self, node: &SyntaxNode) -> SyntaxNode { |
287 | if self.f.is_none() && self.replacements.is_empty() { | 424 | if self.f.is_none() && self.replacements.is_empty() && self.insertions.is_empty() { |
288 | return node.clone(); | 425 | return node.clone(); |
289 | } | 426 | } |
290 | self.rewrite_children(node) | 427 | self.rewrite_children(node) |
@@ -300,14 +437,22 @@ impl<'a> SyntaxRewriter<'a> { | |||
300 | /// | 437 | /// |
301 | /// Returns `None` when there are no replacements. | 438 | /// Returns `None` when there are no replacements. |
302 | pub fn rewrite_root(&self) -> Option<SyntaxNode> { | 439 | pub fn rewrite_root(&self) -> Option<SyntaxNode> { |
440 | fn element_to_node_or_parent(element: &SyntaxElement) -> SyntaxNode { | ||
441 | match element { | ||
442 | SyntaxElement::Node(it) => it.clone(), | ||
443 | SyntaxElement::Token(it) => it.parent(), | ||
444 | } | ||
445 | } | ||
446 | |||
303 | assert!(self.f.is_none()); | 447 | assert!(self.f.is_none()); |
304 | self.replacements | 448 | self.replacements |
305 | .keys() | 449 | .keys() |
306 | .map(|element| match element { | 450 | .map(element_to_node_or_parent) |
307 | SyntaxElement::Node(it) => it.clone(), | 451 | .chain(self.insertions.keys().map(|pos| match pos { |
308 | SyntaxElement::Token(it) => it.parent(), | 452 | InsertPos::FirstChildOf(it) => it.clone(), |
309 | }) | 453 | InsertPos::After(it) => element_to_node_or_parent(it), |
310 | // If we only have one replacement, we must return its parent node, since `rewrite` does | 454 | })) |
455 | // If we only have one replacement/insertion, we must return its parent node, since `rewrite` does | ||
311 | // not replace the node passed to it. | 456 | // not replace the node passed to it. |
312 | .map(|it| it.parent().unwrap_or(it)) | 457 | .map(|it| it.parent().unwrap_or(it)) |
313 | .fold1(|a, b| least_common_ancestor(&a, &b).unwrap()) | 458 | .fold1(|a, b| least_common_ancestor(&a, &b).unwrap()) |
@@ -321,9 +466,16 @@ impl<'a> SyntaxRewriter<'a> { | |||
321 | self.replacements.get(element).cloned() | 466 | self.replacements.get(element).cloned() |
322 | } | 467 | } |
323 | 468 | ||
469 | fn insertions(&self, pos: &InsertPos) -> Option<impl Iterator<Item = SyntaxElement> + '_> { | ||
470 | self.insertions.get(pos).map(|insertions| insertions.iter().cloned()) | ||
471 | } | ||
472 | |||
324 | fn rewrite_children(&self, node: &SyntaxNode) -> SyntaxNode { | 473 | fn rewrite_children(&self, node: &SyntaxNode) -> SyntaxNode { |
325 | // FIXME: this could be made much faster. | 474 | // FIXME: this could be made much faster. |
326 | let mut new_children = Vec::new(); | 475 | let mut new_children = Vec::new(); |
476 | if let Some(elements) = self.insertions(&InsertPos::FirstChildOf(node.clone())) { | ||
477 | new_children.extend(elements.map(element_to_green)); | ||
478 | } | ||
327 | for child in node.children_with_tokens() { | 479 | for child in node.children_with_tokens() { |
328 | self.rewrite_self(&mut new_children, &child); | 480 | self.rewrite_self(&mut new_children, &child); |
329 | } | 481 | } |
@@ -337,34 +489,45 @@ impl<'a> SyntaxRewriter<'a> { | |||
337 | ) { | 489 | ) { |
338 | if let Some(replacement) = self.replacement(&element) { | 490 | if let Some(replacement) = self.replacement(&element) { |
339 | match replacement { | 491 | match replacement { |
340 | Replacement::Single(NodeOrToken::Node(it)) => { | 492 | Replacement::Single(element) => acc.push(element_to_green(element)), |
341 | acc.push(NodeOrToken::Node(it.green().clone())) | ||
342 | } | ||
343 | Replacement::Single(NodeOrToken::Token(it)) => { | ||
344 | acc.push(NodeOrToken::Token(it.green().clone())) | ||
345 | } | ||
346 | Replacement::Many(replacements) => { | 493 | Replacement::Many(replacements) => { |
347 | acc.extend(replacements.iter().map(|it| match it { | 494 | acc.extend(replacements.into_iter().map(element_to_green)) |
348 | NodeOrToken::Node(it) => NodeOrToken::Node(it.green().clone()), | ||
349 | NodeOrToken::Token(it) => NodeOrToken::Token(it.green().clone()), | ||
350 | })) | ||
351 | } | 495 | } |
352 | Replacement::Delete => (), | 496 | Replacement::Delete => (), |
353 | }; | 497 | }; |
354 | return; | 498 | } else { |
499 | match element { | ||
500 | NodeOrToken::Token(it) => acc.push(NodeOrToken::Token(it.green().clone())), | ||
501 | NodeOrToken::Node(it) => { | ||
502 | acc.push(NodeOrToken::Node(self.rewrite_children(it).green().clone())); | ||
503 | } | ||
504 | } | ||
505 | } | ||
506 | if let Some(elements) = self.insertions(&InsertPos::After(element.clone())) { | ||
507 | acc.extend(elements.map(element_to_green)); | ||
355 | } | 508 | } |
356 | let res = match element { | 509 | } |
357 | NodeOrToken::Token(it) => NodeOrToken::Token(it.green().clone()), | 510 | } |
358 | NodeOrToken::Node(it) => NodeOrToken::Node(self.rewrite_children(it).green().clone()), | 511 | |
359 | }; | 512 | fn element_to_green(element: SyntaxElement) -> NodeOrToken<rowan::GreenNode, rowan::GreenToken> { |
360 | acc.push(res) | 513 | match element { |
514 | NodeOrToken::Node(it) => NodeOrToken::Node(it.green().clone()), | ||
515 | NodeOrToken::Token(it) => NodeOrToken::Token(it.green().clone()), | ||
361 | } | 516 | } |
362 | } | 517 | } |
363 | 518 | ||
364 | impl ops::AddAssign for SyntaxRewriter<'_> { | 519 | impl ops::AddAssign for SyntaxRewriter<'_> { |
365 | fn add_assign(&mut self, rhs: SyntaxRewriter) { | 520 | fn add_assign(&mut self, rhs: SyntaxRewriter) { |
366 | assert!(rhs.f.is_none()); | 521 | assert!(rhs.f.is_none()); |
367 | self.replacements.extend(rhs.replacements) | 522 | self.replacements.extend(rhs.replacements); |
523 | for (pos, insertions) in rhs.insertions.into_iter() { | ||
524 | match self.insertions.entry(pos) { | ||
525 | indexmap::map::Entry::Occupied(mut occupied) => { | ||
526 | occupied.get_mut().extend(insertions) | ||
527 | } | ||
528 | indexmap::map::Entry::Vacant(vacant) => drop(vacant.insert(insertions)), | ||
529 | } | ||
530 | } | ||
368 | } | 531 | } |
369 | } | 532 | } |
370 | 533 | ||
@@ -404,3 +567,322 @@ fn to_green_element(element: SyntaxElement) -> NodeOrToken<rowan::GreenNode, row | |||
404 | NodeOrToken::Token(it) => it.green().clone().into(), | 567 | NodeOrToken::Token(it) => it.green().clone().into(), |
405 | } | 568 | } |
406 | } | 569 | } |
570 | |||
571 | #[cfg(test)] | ||
572 | mod tests { | ||
573 | use expect_test::{expect, Expect}; | ||
574 | use itertools::Itertools; | ||
575 | use parser::SyntaxKind; | ||
576 | use test_utils::mark; | ||
577 | use text_edit::TextEdit; | ||
578 | |||
579 | use crate::{AstNode, SyntaxElement}; | ||
580 | |||
581 | #[test] | ||
582 | fn replace_node_token() { | ||
583 | mark::check!(diff_node_token_replace); | ||
584 | check_diff( | ||
585 | r#"use node;"#, | ||
586 | r#"ident"#, | ||
587 | expect![[r#" | ||
588 | insertions: | ||
589 | |||
590 | |||
591 | |||
592 | replacements: | ||
593 | |||
594 | Line 0: Token([email protected] "use") -> ident | ||
595 | |||
596 | deletions: | ||
597 | |||
598 | Line 1: " " | ||
599 | Line 1: node | ||
600 | Line 1: ; | ||
601 | "#]], | ||
602 | ); | ||
603 | } | ||
604 | |||
605 | #[test] | ||
606 | fn insert() { | ||
607 | mark::check!(diff_insert); | ||
608 | check_diff( | ||
609 | r#"use foo;"#, | ||
610 | r#"use foo; | ||
611 | use bar;"#, | ||
612 | expect![[r#" | ||
613 | insertions: | ||
614 | |||
615 | Line 0: Node([email protected]) | ||
616 | -> "\n" | ||
617 | -> use bar; | ||
618 | |||
619 | replacements: | ||
620 | |||
621 | |||
622 | |||
623 | deletions: | ||
624 | |||
625 | |||
626 | "#]], | ||
627 | ); | ||
628 | } | ||
629 | |||
630 | #[test] | ||
631 | fn replace_parent() { | ||
632 | mark::check!(diff_replace_parent); | ||
633 | check_diff( | ||
634 | r#""#, | ||
635 | r#"use foo::bar;"#, | ||
636 | expect![[r#" | ||
637 | insertions: | ||
638 | |||
639 | |||
640 | |||
641 | replacements: | ||
642 | |||
643 | Line 0: Node([email protected]) -> use foo::bar; | ||
644 | |||
645 | deletions: | ||
646 | |||
647 | |||
648 | "#]], | ||
649 | ); | ||
650 | } | ||
651 | |||
652 | #[test] | ||
653 | fn delete() { | ||
654 | mark::check!(diff_delete); | ||
655 | check_diff( | ||
656 | r#"use foo; | ||
657 | use bar;"#, | ||
658 | r#"use foo;"#, | ||
659 | expect![[r#" | ||
660 | insertions: | ||
661 | |||
662 | |||
663 | |||
664 | replacements: | ||
665 | |||
666 | |||
667 | |||
668 | deletions: | ||
669 | |||
670 | Line 1: "\n " | ||
671 | Line 2: use bar; | ||
672 | "#]], | ||
673 | ); | ||
674 | } | ||
675 | |||
676 | #[test] | ||
677 | fn insert_use() { | ||
678 | check_diff( | ||
679 | r#" | ||
680 | use expect_test::{expect, Expect}; | ||
681 | |||
682 | use crate::AstNode; | ||
683 | "#, | ||
684 | r#" | ||
685 | use expect_test::{expect, Expect}; | ||
686 | use text_edit::TextEdit; | ||
687 | |||
688 | use crate::AstNode; | ||
689 | "#, | ||
690 | expect![[r#" | ||
691 | insertions: | ||
692 | |||
693 | Line 4: Token([email protected] "\n") | ||
694 | -> use crate::AstNode; | ||
695 | -> "\n" | ||
696 | |||
697 | replacements: | ||
698 | |||
699 | Line 2: Token([email protected] "\n\n") -> "\n" | ||
700 | Line 4: Token([email protected] "crate") -> text_edit | ||
701 | Line 4: Token([email protected] "AstNode") -> TextEdit | ||
702 | Line 4: Token([email protected] "\n") -> "\n\n" | ||
703 | |||
704 | deletions: | ||
705 | |||
706 | |||
707 | "#]], | ||
708 | ) | ||
709 | } | ||
710 | |||
711 | #[test] | ||
712 | fn remove_use() { | ||
713 | check_diff( | ||
714 | r#" | ||
715 | use expect_test::{expect, Expect}; | ||
716 | use text_edit::TextEdit; | ||
717 | |||
718 | use crate::AstNode; | ||
719 | "#, | ||
720 | r#" | ||
721 | use expect_test::{expect, Expect}; | ||
722 | |||
723 | use crate::AstNode; | ||
724 | "#, | ||
725 | expect![[r#" | ||
726 | insertions: | ||
727 | |||
728 | |||
729 | |||
730 | replacements: | ||
731 | |||
732 | Line 2: Token([email protected] "\n") -> "\n\n" | ||
733 | Line 3: Node([email protected]) -> crate | ||
734 | Line 3: Token([email protected] "TextEdit") -> AstNode | ||
735 | Line 3: Token([email protected] "\n\n") -> "\n" | ||
736 | |||
737 | deletions: | ||
738 | |||
739 | Line 4: use crate::AstNode; | ||
740 | Line 5: "\n" | ||
741 | "#]], | ||
742 | ) | ||
743 | } | ||
744 | |||
745 | #[test] | ||
746 | fn merge_use() { | ||
747 | check_diff( | ||
748 | r#" | ||
749 | use std::{ | ||
750 | fmt, | ||
751 | hash::BuildHasherDefault, | ||
752 | ops::{self, RangeInclusive}, | ||
753 | }; | ||
754 | "#, | ||
755 | r#" | ||
756 | use std::fmt; | ||
757 | use std::hash::BuildHasherDefault; | ||
758 | use std::ops::{self, RangeInclusive}; | ||
759 | "#, | ||
760 | expect![[r#" | ||
761 | insertions: | ||
762 | |||
763 | Line 2: Node([email protected]) | ||
764 | -> :: | ||
765 | -> fmt | ||
766 | Line 6: Token([email protected] "\n") | ||
767 | -> use std::hash::BuildHasherDefault; | ||
768 | -> "\n" | ||
769 | -> use std::ops::{self, RangeInclusive}; | ||
770 | -> "\n" | ||
771 | |||
772 | replacements: | ||
773 | |||
774 | Line 2: Token([email protected] "std") -> std | ||
775 | |||
776 | deletions: | ||
777 | |||
778 | Line 2: :: | ||
779 | Line 2: { | ||
780 | fmt, | ||
781 | hash::BuildHasherDefault, | ||
782 | ops::{self, RangeInclusive}, | ||
783 | } | ||
784 | "#]], | ||
785 | ) | ||
786 | } | ||
787 | |||
788 | #[test] | ||
789 | fn early_return_assist() { | ||
790 | check_diff( | ||
791 | r#" | ||
792 | fn main() { | ||
793 | if let Ok(x) = Err(92) { | ||
794 | foo(x); | ||
795 | } | ||
796 | } | ||
797 | "#, | ||
798 | r#" | ||
799 | fn main() { | ||
800 | let x = match Err(92) { | ||
801 | Ok(it) => it, | ||
802 | _ => return, | ||
803 | }; | ||
804 | foo(x); | ||
805 | } | ||
806 | "#, | ||
807 | expect![[r#" | ||
808 | insertions: | ||
809 | |||
810 | Line 3: Node([email protected]) | ||
811 | -> " " | ||
812 | -> match Err(92) { | ||
813 | Ok(it) => it, | ||
814 | _ => return, | ||
815 | } | ||
816 | -> ; | ||
817 | Line 5: Token([email protected] "}") | ||
818 | -> "\n" | ||
819 | -> } | ||
820 | |||
821 | replacements: | ||
822 | |||
823 | Line 3: Token([email protected] "if") -> let | ||
824 | Line 3: Token([email protected] "let") -> x | ||
825 | Line 3: Node([email protected]) -> = | ||
826 | Line 5: Token([email protected] "\n") -> "\n " | ||
827 | Line 5: Token([email protected] "}") -> foo(x); | ||
828 | |||
829 | deletions: | ||
830 | |||
831 | Line 3: " " | ||
832 | Line 3: Ok(x) | ||
833 | Line 3: " " | ||
834 | Line 3: = | ||
835 | Line 3: " " | ||
836 | Line 3: Err(92) | ||
837 | "#]], | ||
838 | ) | ||
839 | } | ||
840 | |||
841 | fn check_diff(from: &str, to: &str, expected_diff: Expect) { | ||
842 | let from_node = crate::SourceFile::parse(from).tree().syntax().clone(); | ||
843 | let to_node = crate::SourceFile::parse(to).tree().syntax().clone(); | ||
844 | let diff = super::diff(&from_node, &to_node); | ||
845 | |||
846 | let line_number = | ||
847 | |syn: &SyntaxElement| from[..syn.text_range().start().into()].lines().count(); | ||
848 | |||
849 | let fmt_syntax = |syn: &SyntaxElement| match syn.kind() { | ||
850 | SyntaxKind::WHITESPACE => format!("{:?}", syn.to_string()), | ||
851 | _ => format!("{}", syn), | ||
852 | }; | ||
853 | |||
854 | let insertions = diff.insertions.iter().format_with("\n", |(k, v), f| { | ||
855 | f(&format!( | ||
856 | "Line {}: {:?}\n-> {}", | ||
857 | line_number(k), | ||
858 | k, | ||
859 | v.iter().format_with("\n-> ", |v, f| f(&fmt_syntax(v))) | ||
860 | )) | ||
861 | }); | ||
862 | |||
863 | let replacements = diff | ||
864 | .replacements | ||
865 | .iter() | ||
866 | .sorted_by_key(|(syntax, _)| syntax.text_range().start()) | ||
867 | .format_with("\n", |(k, v), f| { | ||
868 | f(&format!("Line {}: {:?} -> {}", line_number(k), k, fmt_syntax(v))) | ||
869 | }); | ||
870 | |||
871 | let deletions = diff | ||
872 | .deletions | ||
873 | .iter() | ||
874 | .format_with("\n", |v, f| f(&format!("Line {}: {}", line_number(v), &fmt_syntax(v)))); | ||
875 | |||
876 | let actual = format!( | ||
877 | "insertions:\n\n{}\n\nreplacements:\n\n{}\n\ndeletions:\n\n{}\n", | ||
878 | insertions, replacements, deletions | ||
879 | ); | ||
880 | expected_diff.assert_eq(&actual); | ||
881 | |||
882 | let mut from = from.to_owned(); | ||
883 | let mut text_edit = TextEdit::builder(); | ||
884 | diff.into_text_edit(&mut text_edit); | ||
885 | text_edit.finish().apply(&mut from); | ||
886 | assert_eq!(&*from, to, "diff did not turn `from` to `to`"); | ||
887 | } | ||
888 | } | ||
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs index 74dbdfaf7..5b06cb767 100644 --- a/crates/syntax/src/ast/make.rs +++ b/crates/syntax/src/ast/make.rs | |||
@@ -172,6 +172,9 @@ pub fn expr_call(f: ast::Expr, arg_list: ast::ArgList) -> ast::Expr { | |||
172 | pub fn expr_method_call(receiver: ast::Expr, method: &str, arg_list: ast::ArgList) -> ast::Expr { | 172 | pub fn expr_method_call(receiver: ast::Expr, method: &str, arg_list: ast::ArgList) -> ast::Expr { |
173 | expr_from_text(&format!("{}.{}{}", receiver, method, arg_list)) | 173 | expr_from_text(&format!("{}.{}{}", receiver, method, arg_list)) |
174 | } | 174 | } |
175 | pub fn expr_ref(expr: ast::Expr, exclusive: bool) -> ast::Expr { | ||
176 | expr_from_text(&if exclusive { format!("&mut {}", expr) } else { format!("&{}", expr) }) | ||
177 | } | ||
175 | fn expr_from_text(text: &str) -> ast::Expr { | 178 | fn expr_from_text(text: &str) -> ast::Expr { |
176 | ast_from_text(&format!("const C: () = {};", text)) | 179 | ast_from_text(&format!("const C: () = {};", text)) |
177 | } | 180 | } |
diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs index 50c1c157d..c5cd1c504 100644 --- a/crates/syntax/src/ast/node_ext.rs +++ b/crates/syntax/src/ast/node_ext.rs | |||
@@ -7,7 +7,7 @@ use itertools::Itertools; | |||
7 | use parser::SyntaxKind; | 7 | use parser::SyntaxKind; |
8 | 8 | ||
9 | use crate::{ | 9 | use crate::{ |
10 | ast::{self, support, AstNode, NameOwner, SyntaxNode}, | 10 | ast::{self, support, token_ext::HasStringValue, AstNode, AstToken, NameOwner, SyntaxNode}, |
11 | SmolStr, SyntaxElement, SyntaxToken, T, | 11 | SmolStr, SyntaxElement, SyntaxToken, T, |
12 | }; | 12 | }; |
13 | 13 | ||
@@ -53,8 +53,16 @@ impl ast::Attr { | |||
53 | pub fn as_simple_key_value(&self) -> Option<(SmolStr, SmolStr)> { | 53 | pub fn as_simple_key_value(&self) -> Option<(SmolStr, SmolStr)> { |
54 | let lit = self.literal()?; | 54 | let lit = self.literal()?; |
55 | let key = self.simple_name()?; | 55 | let key = self.simple_name()?; |
56 | // FIXME: escape? raw string? | 56 | let value_token = lit.syntax().first_token()?; |
57 | let value = lit.syntax().first_token()?.text().trim_matches('"').into(); | 57 | |
58 | let value: SmolStr = if let Some(s) = ast::String::cast(value_token.clone()) { | ||
59 | s.value()?.into() | ||
60 | } else if let Some(s) = ast::RawString::cast(value_token) { | ||
61 | s.value()?.into() | ||
62 | } else { | ||
63 | return None; | ||
64 | }; | ||
65 | |||
58 | Some((key, value)) | 66 | Some((key, value)) |
59 | } | 67 | } |
60 | 68 | ||
diff --git a/crates/syntax/src/display.rs b/crates/syntax/src/display.rs new file mode 100644 index 000000000..8d2c7eae4 --- /dev/null +++ b/crates/syntax/src/display.rs | |||
@@ -0,0 +1,83 @@ | |||
1 | //! This module contains utilities for turning SyntaxNodes and HIR types | ||
2 | //! into types that may be used to render in a UI. | ||
3 | |||
4 | use crate::{ | ||
5 | ast::{self, AstNode, AttrsOwner, GenericParamsOwner, NameOwner}, | ||
6 | SyntaxKind::{ATTR, COMMENT}, | ||
7 | }; | ||
8 | |||
9 | use ast::VisibilityOwner; | ||
10 | use stdx::format_to; | ||
11 | |||
12 | pub fn function_declaration(node: &ast::Fn) -> String { | ||
13 | let mut buf = String::new(); | ||
14 | if let Some(vis) = node.visibility() { | ||
15 | format_to!(buf, "{} ", vis); | ||
16 | } | ||
17 | if node.async_token().is_some() { | ||
18 | format_to!(buf, "async "); | ||
19 | } | ||
20 | if node.const_token().is_some() { | ||
21 | format_to!(buf, "const "); | ||
22 | } | ||
23 | if node.unsafe_token().is_some() { | ||
24 | format_to!(buf, "unsafe "); | ||
25 | } | ||
26 | if let Some(abi) = node.abi() { | ||
27 | // Keyword `extern` is included in the string. | ||
28 | format_to!(buf, "{} ", abi); | ||
29 | } | ||
30 | if let Some(name) = node.name() { | ||
31 | format_to!(buf, "fn {}", name) | ||
32 | } | ||
33 | if let Some(type_params) = node.generic_param_list() { | ||
34 | format_to!(buf, "{}", type_params); | ||
35 | } | ||
36 | if let Some(param_list) = node.param_list() { | ||
37 | let params: Vec<String> = param_list | ||
38 | .self_param() | ||
39 | .into_iter() | ||
40 | .map(|self_param| self_param.to_string()) | ||
41 | .chain(param_list.params().map(|param| param.to_string())) | ||
42 | .collect(); | ||
43 | // Useful to inline parameters | ||
44 | format_to!(buf, "({})", params.join(", ")); | ||
45 | } | ||
46 | if let Some(ret_type) = node.ret_type() { | ||
47 | if ret_type.ty().is_some() { | ||
48 | format_to!(buf, " {}", ret_type); | ||
49 | } | ||
50 | } | ||
51 | if let Some(where_clause) = node.where_clause() { | ||
52 | format_to!(buf, "\n{}", where_clause); | ||
53 | } | ||
54 | buf | ||
55 | } | ||
56 | |||
57 | pub fn const_label(node: &ast::Const) -> String { | ||
58 | let label: String = node | ||
59 | .syntax() | ||
60 | .children_with_tokens() | ||
61 | .filter(|child| !(child.kind() == COMMENT || child.kind() == ATTR)) | ||
62 | .map(|node| node.to_string()) | ||
63 | .collect(); | ||
64 | |||
65 | label.trim().to_owned() | ||
66 | } | ||
67 | |||
68 | pub fn type_label(node: &ast::TypeAlias) -> String { | ||
69 | let label: String = node | ||
70 | .syntax() | ||
71 | .children_with_tokens() | ||
72 | .filter(|child| !(child.kind() == COMMENT || child.kind() == ATTR)) | ||
73 | .map(|node| node.to_string()) | ||
74 | .collect(); | ||
75 | |||
76 | label.trim().to_owned() | ||
77 | } | ||
78 | |||
79 | pub fn macro_label(node: &ast::MacroCall) -> String { | ||
80 | let name = node.name().map(|name| name.syntax().text().to_string()).unwrap_or_default(); | ||
81 | let vis = if node.has_atom_attr("macro_export") { "#[macro_export]\n" } else { "" }; | ||
82 | format!("{}macro_rules! {}", vis, name) | ||
83 | } | ||
diff --git a/crates/syntax/src/lib.rs b/crates/syntax/src/lib.rs index 7f8da66af..849a1cdd6 100644 --- a/crates/syntax/src/lib.rs +++ b/crates/syntax/src/lib.rs | |||
@@ -32,6 +32,7 @@ mod ptr; | |||
32 | #[cfg(test)] | 32 | #[cfg(test)] |
33 | mod tests; | 33 | mod tests; |
34 | 34 | ||
35 | pub mod display; | ||
35 | pub mod algo; | 36 | pub mod algo; |
36 | pub mod ast; | 37 | pub mod ast; |
37 | #[doc(hidden)] | 38 | #[doc(hidden)] |