]> git.lizzy.rs Git - rust.git/commitdiff
rewrite merge use trees assist to use muatable syntax trees
authorAleksey Kladov <aleksey.kladov@gmail.com>
Thu, 18 Mar 2021 09:57:55 +0000 (12:57 +0300)
committerAleksey Kladov <aleksey.kladov@gmail.com>
Mon, 22 Mar 2021 17:47:46 +0000 (20:47 +0300)
changelog internal

crates/ide_assists/src/handlers/merge_imports.rs
crates/ide_assists/src/handlers/unmerge_use.rs
crates/ide_db/src/helpers/insert_use.rs
crates/syntax/src/ast/edit.rs
crates/syntax/src/ast/edit_in_place.rs
crates/syntax/src/ast/make.rs
crates/syntax/src/ted.rs

index 7bd7e1e3697fa703c16cb7de843505dc37d7a124..cfc472a3258f133cc3217cef57b30fcf9c1ab404 100644 (file)
@@ -1,8 +1,5 @@
 use ide_db::helpers::insert_use::{try_merge_imports, try_merge_trees, MergeBehavior};
-use syntax::{
-    algo::{neighbor, SyntaxRewriter},
-    ast, AstNode,
-};
+use syntax::{algo::neighbor, ast, ted, AstNode};
 
 use crate::{
     assist_context::{AssistContext, Assists},
 // ```
 pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
     let tree: ast::UseTree = ctx.find_node_at_offset()?;
-    let mut rewriter = SyntaxRewriter::default();
+    let original_parent = tree.syntax().ancestors().last()?;
+
+    let tree = tree.clone_for_update();
+    let new_parent = tree.syntax().ancestors().last()?;
+
     let mut offset = ctx.offset();
 
+    let mut imports = None;
+    let mut uses = None;
     if let Some(use_item) = tree.syntax().parent().and_then(ast::Use::cast) {
-        let (merged, to_delete) =
+        let (merged, to_remove) =
             next_prev().filter_map(|dir| neighbor(&use_item, dir)).find_map(|use_item2| {
                 try_merge_imports(&use_item, &use_item2, MergeBehavior::Full).zip(Some(use_item2))
             })?;
 
-        rewriter.replace_ast(&use_item, &merged);
-        rewriter += to_delete.remove();
-
-        if to_delete.syntax().text_range().end() < offset {
-            offset -= to_delete.syntax().text_range().len();
-        }
+        imports = Some((use_item, merged, to_remove));
     } else {
-        let (merged, to_delete) =
+        let (merged, to_remove) =
             next_prev().filter_map(|dir| neighbor(&tree, dir)).find_map(|use_tree| {
                 try_merge_trees(&tree, &use_tree, MergeBehavior::Full).zip(Some(use_tree))
             })?;
 
-        rewriter.replace_ast(&tree, &merged);
-        rewriter += to_delete.remove();
-
-        if to_delete.syntax().text_range().end() < offset {
-            offset -= to_delete.syntax().text_range().len();
-        }
+        uses = Some((tree.clone(), merged, to_remove))
     };
 
     let target = tree.syntax().text_range();
@@ -59,7 +52,23 @@ pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext) -> Option<()
         "Merge imports",
         target,
         |builder| {
-            builder.rewrite(rewriter);
+            if let Some((to_replace, replacement, to_remove)) = imports {
+                if to_remove.syntax().text_range().end() < offset {
+                    offset -= to_remove.syntax().text_range().len();
+                }
+                ted::replace(to_replace.syntax().clone(), replacement.syntax().clone());
+                to_remove.remove();
+            }
+
+            if let Some((to_replace, replacement, to_remove)) = uses {
+                if to_remove.syntax().text_range().end() < offset {
+                    offset -= to_remove.syntax().text_range().len();
+                }
+                ted::replace(to_replace.syntax().clone(), replacement.syntax().clone());
+                to_remove.remove()
+            }
+
+            builder.replace(original_parent.text_range(), new_parent.to_string())
         },
     )
 }
index 616af7c2e6ea13551e462da5cd1ac4015bd14da1..8d271e056f597ace5104e47ac4adf4c5fc5c45ab 100644 (file)
@@ -1,6 +1,6 @@
 use syntax::{
-    algo::SyntaxRewriter,
-    ast::{self, edit::AstNodeEdit, VisibilityOwner},
+    ast::{self, VisibilityOwner},
+    ted::{self, Position},
     AstNode, SyntaxKind,
 };
 
@@ -22,7 +22,7 @@
 // use std::fmt::Display;
 // ```
 pub(crate) fn unmerge_use(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
-    let tree: ast::UseTree = ctx.find_node_at_offset()?;
+    let tree: ast::UseTree = ctx.find_node_at_offset::<ast::UseTree>()?.clone_for_update();
 
     let tree_list = tree.syntax().parent().and_then(ast::UseTreeList::cast)?;
     if tree_list.use_trees().count() < 2 {
@@ -33,6 +33,9 @@ pub(crate) fn unmerge_use(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
     let use_: ast::Use = tree_list.syntax().ancestors().find_map(ast::Use::cast)?;
     let path = resolve_full_path(&tree)?;
 
+    let old_parent_range = use_.syntax().parent()?.text_range();
+    let new_parent = use_.syntax().parent()?;
+
     let target = tree.syntax().text_range();
     acc.add(
         AssistId("unmerge_use", AssistKind::RefactorRewrite),
@@ -47,20 +50,13 @@ pub(crate) fn unmerge_use(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
                     tree.rename(),
                     tree.star_token().is_some(),
                 ),
-            );
-
-            let mut rewriter = SyntaxRewriter::default();
-            rewriter += tree.remove();
-            rewriter.insert_after(use_.syntax(), &ast::make::tokens::single_newline());
-            if let ident_level @ 1..=usize::MAX = use_.indent_level().0 as usize {
-                rewriter.insert_after(
-                    use_.syntax(),
-                    &ast::make::tokens::whitespace(&" ".repeat(4 * ident_level)),
-                );
-            }
-            rewriter.insert_after(use_.syntax(), new_use.syntax());
-
-            builder.rewrite(rewriter);
+            )
+            .clone_for_update();
+
+            tree.remove();
+            ted::insert(Position::after(use_.syntax()), new_use.syntax());
+
+            builder.replace(old_parent_range, new_parent.to_string());
         },
     )
 }
index 37acf95f0fd9e89b639fd731624e1f36f8018031..20c195f82519bc4a4532e710681255ada13ded24 100644 (file)
@@ -213,7 +213,7 @@ pub fn try_merge_imports(
     let lhs_tree = lhs.use_tree()?;
     let rhs_tree = rhs.use_tree()?;
     let merged = try_merge_trees(&lhs_tree, &rhs_tree, merge_behavior)?;
-    Some(lhs.with_use_tree(merged))
+    Some(lhs.with_use_tree(merged).clone_for_update())
 }
 
 pub fn try_merge_trees(
@@ -234,7 +234,7 @@ pub fn try_merge_trees(
     } else {
         (lhs.split_prefix(&lhs_prefix), rhs.split_prefix(&rhs_prefix))
     };
-    recursive_merge(&lhs, &rhs, merge)
+    recursive_merge(&lhs, &rhs, merge).map(|it| it.clone_for_update())
 }
 
 /// Recursively "zips" together lhs and rhs.
index 347862b8a4ccafeb3b92c6f05d9747f861d38e55..18820786a5cfea4269dabd7fbdacee4b03a028ef 100644 (file)
@@ -9,7 +9,7 @@
 use arrayvec::ArrayVec;
 
 use crate::{
-    algo::{self, neighbor, SyntaxRewriter},
+    algo::{self, SyntaxRewriter},
     ast::{
         self,
         make::{self, tokens},
@@ -322,27 +322,6 @@ pub fn with_use_tree(&self, use_tree: ast::UseTree) -> ast::Use {
         }
         self.clone()
     }
-
-    pub fn remove(&self) -> SyntaxRewriter<'static> {
-        let mut res = SyntaxRewriter::default();
-        res.delete(self.syntax());
-        let next_ws = self
-            .syntax()
-            .next_sibling_or_token()
-            .and_then(|it| it.into_token())
-            .and_then(ast::Whitespace::cast);
-        if let Some(next_ws) = next_ws {
-            let ws_text = next_ws.syntax().text();
-            if let Some(rest) = ws_text.strip_prefix('\n') {
-                if rest.is_empty() {
-                    res.delete(next_ws.syntax())
-                } else {
-                    res.replace(next_ws.syntax(), &make::tokens::whitespace(rest));
-                }
-            }
-        }
-        res
-    }
 }
 
 impl ast::UseTree {
@@ -396,22 +375,6 @@ fn split_path_prefix(prefix: &ast::Path) -> Option<ast::Path> {
             Some(res)
         }
     }
-
-    pub fn remove(&self) -> SyntaxRewriter<'static> {
-        let mut res = SyntaxRewriter::default();
-        res.delete(self.syntax());
-        for &dir in [Direction::Next, Direction::Prev].iter() {
-            if let Some(nb) = neighbor(self, dir) {
-                self.syntax()
-                    .siblings_with_tokens(dir)
-                    .skip(1)
-                    .take_while(|it| it.as_node() != Some(nb.syntax()))
-                    .for_each(|el| res.delete(&el));
-                return res;
-            }
-        }
-        res
-    }
 }
 
 impl ast::MatchArmList {
@@ -592,6 +555,13 @@ fn add(self, rhs: u8) -> IndentLevel {
 }
 
 impl IndentLevel {
+    pub fn from_element(element: &SyntaxElement) -> IndentLevel {
+        match element {
+            rowan::NodeOrToken::Node(it) => IndentLevel::from_node(it),
+            rowan::NodeOrToken::Token(it) => IndentLevel::from_token(it),
+        }
+    }
+
     pub fn from_node(node: &SyntaxNode) -> IndentLevel {
         match node.first_token() {
             Some(it) => Self::from_token(&it),
index b1eed0a2ccabc3db8f0b97341f98eea050dc17ea..529bd0eb14c34b2979e2ee9ea93c8a99976b937a 100644 (file)
@@ -2,13 +2,13 @@
 
 use std::iter::empty;
 
-use ast::{edit::AstNodeEdit, make, GenericParamsOwner, WhereClause};
 use parser::T;
 
 use crate::{
-    ast,
+    algo::neighbor,
+    ast::{self, edit::AstNodeEdit, make, GenericParamsOwner, WhereClause},
     ted::{self, Position},
-    AstNode, Direction,
+    AstNode, AstToken, Direction,
 };
 
 use super::NameOwner;
@@ -126,3 +126,41 @@ pub fn remove(&self) {
         }
     }
 }
+
+impl ast::UseTree {
+    pub fn remove(&self) {
+        for &dir in [Direction::Next, Direction::Prev].iter() {
+            if let Some(next_use_tree) = neighbor(self, dir) {
+                let separators = self
+                    .syntax()
+                    .siblings_with_tokens(dir)
+                    .skip(1)
+                    .take_while(|it| it.as_node() != Some(next_use_tree.syntax()));
+                ted::remove_all_iter(separators);
+                break;
+            }
+        }
+        ted::remove(self.syntax())
+    }
+}
+
+impl ast::Use {
+    pub fn remove(&self) {
+        let next_ws = self
+            .syntax()
+            .next_sibling_or_token()
+            .and_then(|it| it.into_token())
+            .and_then(ast::Whitespace::cast);
+        if let Some(next_ws) = next_ws {
+            let ws_text = next_ws.syntax().text();
+            if let Some(rest) = ws_text.strip_prefix('\n') {
+                if rest.is_empty() {
+                    ted::remove(next_ws.syntax())
+                } else {
+                    ted::replace(next_ws.syntax(), make::tokens::whitespace(rest))
+                }
+            }
+        }
+        ted::remove(self.syntax())
+    }
+}
index 7049affd95fd2b4dbf4186e39f5469bcfd41fdd7..c08f2c14f565ec2c14fd388cdac838c0294f6328 100644 (file)
@@ -560,7 +560,7 @@ pub fn single_space() -> SyntaxToken {
     pub fn whitespace(text: &str) -> SyntaxToken {
         assert!(text.trim().is_empty());
         let sf = SourceFile::parse(text).ok().unwrap();
-        sf.syntax().first_child_or_token().unwrap().into_token().unwrap()
+        sf.syntax().clone_for_update().first_child_or_token().unwrap().into_token().unwrap()
     }
 
     pub fn doc_comment(text: &str) -> SyntaxToken {
index be2b846b1513f5eb7dbba212eaed621c60932869..177d4ff67a0080eb1a8b9e508320fcf509c71c2f 100644 (file)
@@ -2,11 +2,14 @@
 //!
 //! The `_raw`-suffixed functions insert elements as is, unsuffixed versions fix
 //! up elements around the edges.
-use std::ops::RangeInclusive;
+use std::{mem, ops::RangeInclusive};
 
 use parser::T;
 
-use crate::{ast::make, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken};
+use crate::{
+    ast::{edit::IndentLevel, make},
+    SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken,
+};
 
 /// Utility trait to allow calling `ted` functions with references or owned
 /// nodes. Do not use outside of this module.
@@ -101,12 +104,25 @@ pub fn insert_all_raw(position: Position, elements: Vec<SyntaxElement>) {
 }
 
 pub fn remove(elem: impl Element) {
-    let elem = elem.syntax_element();
-    remove_all(elem.clone()..=elem)
+    elem.syntax_element().detach()
 }
 pub fn remove_all(range: RangeInclusive<SyntaxElement>) {
     replace_all(range, Vec::new())
 }
+pub fn remove_all_iter(range: impl IntoIterator<Item = SyntaxElement>) {
+    let mut it = range.into_iter();
+    if let Some(mut first) = it.next() {
+        match it.last() {
+            Some(mut last) => {
+                if first.index() > last.index() {
+                    mem::swap(&mut first, &mut last)
+                }
+                remove_all(first..=last)
+            }
+            None => remove(first),
+        }
+    }
+}
 
 pub fn replace(old: impl Element, new: impl Element) {
     let old = old.syntax_element();
@@ -149,5 +165,9 @@ fn ws_between(left: &SyntaxElement, right: &SyntaxElement) -> Option<SyntaxToken
     if right.kind() == T![;] || right.kind() == T![,] {
         return None;
     }
+    if right.kind() == SyntaxKind::USE {
+        let indent = IndentLevel::from_element(left);
+        return Some(make::tokens::whitespace(&format!("\n{}", indent)));
+    }
     Some(make::tokens::single_space())
 }