]> git.lizzy.rs Git - rust.git/commitdiff
Remove SyntaxRewriter usage in insert_use in favor of ted
authorLukas Wirth <lukastw97@gmail.com>
Tue, 20 Apr 2021 00:05:22 +0000 (02:05 +0200)
committerLukas Wirth <lukastw97@gmail.com>
Tue, 20 Apr 2021 00:09:12 +0000 (02:09 +0200)
crates/ide_assists/src/handlers/auto_import.rs
crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs
crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs
crates/ide_completion/src/item.rs
crates/ide_db/src/helpers/insert_use.rs
crates/ide_db/src/helpers/insert_use/tests.rs
crates/syntax/src/ast/make.rs
crates/syntax/src/ted.rs

index 49aa70f74e77cec079a89f1e439aeb7ba25941b0..6db2d2edd6e19fa4ae776ce668890e5bfbb156a2 100644 (file)
@@ -101,9 +101,11 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
             format!("Import `{}`", import.import_path),
             range,
             |builder| {
-                let rewriter =
-                    insert_use(&scope, mod_path_to_ast(&import.import_path), ctx.config.insert_use);
-                builder.rewrite(rewriter);
+                let scope = match scope.clone() {
+                    ImportScope::File(it) => ImportScope::File(builder.make_ast_mut(it)),
+                    ImportScope::Module(it) => ImportScope::Module(builder.make_ast_mut(it)),
+                };
+                insert_use(&scope, mod_path_to_ast(&import.import_path), ctx.config.insert_use);
             },
         );
     }
index a8d6355bdd2ff6f84b43e7245005e76c61dad17b..26e1c66ab471b1afdb6a4c7248c450d3c1fa11c7 100644 (file)
@@ -13,9 +13,9 @@
 };
 use rustc_hash::FxHashSet;
 use syntax::{
-    algo::{find_node_at_offset, SyntaxRewriter},
-    ast::{self, edit::IndentLevel, make, AstNode, NameOwner, VisibilityOwner},
-    SourceFile, SyntaxElement, SyntaxNode, T,
+    algo::find_node_at_offset,
+    ast::{self, make, AstNode, NameOwner, VisibilityOwner},
+    ted, SourceFile, SyntaxElement, SyntaxNode, T,
 };
 
 use crate::{AssistContext, AssistId, AssistKind, Assists};
@@ -62,14 +62,17 @@ pub(crate) fn extract_struct_from_enum_variant(
             let mut visited_modules_set = FxHashSet::default();
             let current_module = enum_hir.module(ctx.db());
             visited_modules_set.insert(current_module);
-            let mut def_rewriter = None;
+            let mut def_file_references = None;
             for (file_id, references) in usages {
-                let mut rewriter = SyntaxRewriter::default();
-                let source_file = ctx.sema.parse(file_id);
+                if file_id == ctx.frange.file_id {
+                    def_file_references = Some(references);
+                    continue;
+                }
+                builder.edit_file(file_id);
+                let source_file = builder.make_ast_mut(ctx.sema.parse(file_id));
                 for reference in references {
                     update_reference(
                         ctx,
-                        &mut rewriter,
                         reference,
                         &source_file,
                         &enum_module_def,
@@ -77,25 +80,27 @@ pub(crate) fn extract_struct_from_enum_variant(
                         &mut visited_modules_set,
                     );
                 }
-                if file_id == ctx.frange.file_id {
-                    def_rewriter = Some(rewriter);
-                    continue;
-                }
-                builder.edit_file(file_id);
-                builder.rewrite(rewriter);
             }
-            let mut rewriter = def_rewriter.unwrap_or_default();
-            update_variant(&mut rewriter, &variant);
+            builder.edit_file(ctx.frange.file_id);
+            let variant = builder.make_ast_mut(variant.clone());
+            let source_file = builder.make_ast_mut(ctx.sema.parse(ctx.frange.file_id));
+            for reference in def_file_references.into_iter().flatten() {
+                update_reference(
+                    ctx,
+                    reference,
+                    &source_file,
+                    &enum_module_def,
+                    &variant_hir_name,
+                    &mut visited_modules_set,
+                );
+            }
             extract_struct_def(
-                &mut rewriter,
-                &enum_ast,
                 variant_name.clone(),
                 &field_list,
                 &variant.parent_enum().syntax().clone().into(),
                 enum_ast.visibility(),
             );
-            builder.edit_file(ctx.frange.file_id);
-            builder.rewrite(rewriter);
+            update_variant(&variant);
         },
     )
 }
@@ -138,7 +143,6 @@ fn existing_definition(db: &RootDatabase, variant_name: &ast::Name, variant: &Va
 
 fn insert_import(
     ctx: &AssistContext,
-    rewriter: &mut SyntaxRewriter,
     scope_node: &SyntaxNode,
     module: &Module,
     enum_module_def: &ModuleDef,
@@ -151,14 +155,12 @@ fn insert_import(
         mod_path.pop_segment();
         mod_path.push_segment(variant_hir_name.clone());
         let scope = ImportScope::find_insert_use_container(scope_node, &ctx.sema)?;
-        *rewriter += insert_use(&scope, mod_path_to_ast(&mod_path), ctx.config.insert_use);
+        insert_use(&scope, mod_path_to_ast(&mod_path), ctx.config.insert_use);
     }
     Some(())
 }
 
 fn extract_struct_def(
-    rewriter: &mut SyntaxRewriter,
-    enum_: &ast::Enum,
     variant_name: ast::Name,
     field_list: &Either<ast::RecordFieldList, ast::TupleFieldList>,
     start_offset: &SyntaxElement,
@@ -180,33 +182,34 @@ fn extract_struct_def(
         .into(),
     };
 
-    rewriter.insert_before(
-        start_offset,
-        make::struct_(visibility, variant_name, None, field_list).syntax(),
+    ted::insert_raw(
+        ted::Position::before(start_offset),
+        make::struct_(visibility, variant_name, None, field_list).clone_for_update().syntax(),
     );
-    rewriter.insert_before(start_offset, &make::tokens::blank_line());
+    ted::insert_raw(ted::Position::before(start_offset), &make::tokens::blank_line());
 
-    if let indent_level @ 1..=usize::MAX = IndentLevel::from_node(enum_.syntax()).0 as usize {
-        rewriter
-            .insert_before(start_offset, &make::tokens::whitespace(&" ".repeat(4 * indent_level)));
-    }
+    // if let indent_level @ 1..=usize::MAX = IndentLevel::from_node(enum_.syntax()).0 as usize {
+    //     ted::insert(ted::Position::before(start_offset), &make::tokens::blank_line());
+    //     rewriter
+    //         .insert_before(start_offset, &make::tokens::whitespace(&" ".repeat(4 * indent_level)));
+    // }
     Some(())
 }
 
-fn update_variant(rewriter: &mut SyntaxRewriter, variant: &ast::Variant) -> Option<()> {
+fn update_variant(variant: &ast::Variant) -> Option<()> {
     let name = variant.name()?;
     let tuple_field = make::tuple_field(None, make::ty(&name.text()));
     let replacement = make::variant(
         name,
         Some(ast::FieldList::TupleFieldList(make::tuple_field_list(iter::once(tuple_field)))),
-    );
-    rewriter.replace(variant.syntax(), replacement.syntax());
+    )
+    .clone_for_update();
+    ted::replace(variant.syntax(), replacement.syntax());
     Some(())
 }
 
 fn update_reference(
     ctx: &AssistContext,
-    rewriter: &mut SyntaxRewriter,
     reference: FileReference,
     source_file: &SourceFile,
     enum_module_def: &ModuleDef,
@@ -230,14 +233,16 @@ fn update_reference(
 
     let module = ctx.sema.scope(&expr).module()?;
     if !visited_modules_set.contains(&module) {
-        if insert_import(ctx, rewriter, &expr, &module, enum_module_def, variant_hir_name).is_some()
-        {
+        if insert_import(ctx, &expr, &module, enum_module_def, variant_hir_name).is_some() {
             visited_modules_set.insert(module);
         }
     }
-    rewriter.insert_after(segment.syntax(), &make::token(T!['(']));
-    rewriter.insert_after(segment.syntax(), segment.syntax());
-    rewriter.insert_after(&expr, &make::token(T![')']));
+    ted::insert_raw(
+        ted::Position::before(segment.syntax()),
+        make::path_from_text(&format!("{}", segment)).clone_for_update().syntax(),
+    );
+    ted::insert_raw(ted::Position::before(segment.syntax()), make::token(T!['(']));
+    ted::insert_raw(ted::Position::after(&expr), make::token(T![')']));
     Some(())
 }
 
index 36d2e0331ec5fd0867d971f1e8262e688619f858..2f2306fcc2376f99ae2a85aa0e780ca4637d2b0e 100644 (file)
@@ -1,5 +1,5 @@
 use ide_db::helpers::insert_use::{insert_use, ImportScope};
-use syntax::{algo::SyntaxRewriter, ast, match_ast, AstNode, SyntaxNode};
+use syntax::{ast, match_ast, ted, AstNode, SyntaxNode};
 
 use crate::{AssistContext, AssistId, AssistKind, Assists};
 
@@ -40,18 +40,17 @@ pub(crate) fn replace_qualified_name_with_use(
         |builder| {
             // Now that we've brought the name into scope, re-qualify all paths that could be
             // affected (that is, all paths inside the node we added the `use` to).
-            let mut rewriter = SyntaxRewriter::default();
-            shorten_paths(&mut rewriter, syntax.clone(), &path);
+            let syntax = builder.make_mut(syntax.clone());
             if let Some(ref import_scope) = ImportScope::from(syntax.clone()) {
-                rewriter += insert_use(import_scope, path, ctx.config.insert_use);
-                builder.rewrite(rewriter);
+                insert_use(import_scope, path.clone(), ctx.config.insert_use);
             }
+            shorten_paths(syntax.clone(), &path.clone_for_update());
         },
     )
 }
 
 /// Adds replacements to `re` that shorten `path` in all descendants of `node`.
-fn shorten_paths(rewriter: &mut SyntaxRewriter<'static>, node: SyntaxNode, path: &ast::Path) {
+fn shorten_paths(node: SyntaxNode, path: &ast::Path) {
     for child in node.children() {
         match_ast! {
             match child {
@@ -62,32 +61,28 @@ fn shorten_paths(rewriter: &mut SyntaxRewriter<'static>, node: SyntaxNode, path:
                 ast::Module(_it) => continue,
 
                 ast::Path(p) => {
-                    match maybe_replace_path(rewriter, p.clone(), path.clone()) {
+                    match maybe_replace_path(p.clone(), path.clone()) {
                         Some(()) => {},
-                        None => shorten_paths(rewriter, p.syntax().clone(), path),
+                        None => shorten_paths(p.syntax().clone(), path),
                     }
                 },
-                _ => shorten_paths(rewriter, child, path),
+                _ => shorten_paths(child, path),
             }
         }
     }
 }
 
-fn maybe_replace_path(
-    rewriter: &mut SyntaxRewriter<'static>,
-    path: ast::Path,
-    target: ast::Path,
-) -> Option<()> {
+fn maybe_replace_path(path: ast::Path, target: ast::Path) -> Option<()> {
     if !path_eq(path.clone(), target) {
         return None;
     }
 
     // Shorten `path`, leaving only its last segment.
     if let Some(parent) = path.qualifier() {
-        rewriter.delete(parent.syntax());
+        ted::remove(parent.syntax());
     }
     if let Some(double_colon) = path.coloncolon_token() {
-        rewriter.delete(&double_colon);
+        ted::remove(&double_colon);
     }
 
     Some(())
@@ -150,6 +145,7 @@ fn test_replace_add_use_no_anchor() {
     ",
         );
     }
+
     #[test]
     fn test_replace_add_use_no_anchor_with_item_below() {
         check_assist(
index 16991b6880da1a11de12d2b2982cc521f2edd8e6..99edb94992020e2914582d3ac4d52a66c0be5dd4 100644 (file)
@@ -377,11 +377,11 @@ impl ImportEdit {
     pub fn to_text_edit(&self, cfg: InsertUseConfig) -> Option<TextEdit> {
         let _p = profile::span("ImportEdit::to_text_edit");
 
-        let rewriter =
-            insert_use::insert_use(&self.scope, mod_path_to_ast(&self.import.import_path), cfg);
-        let old_ast = rewriter.rewrite_root()?;
+        let new_ast = self.scope.clone_for_update();
+        insert_use::insert_use(&new_ast, mod_path_to_ast(&self.import.import_path), cfg);
         let mut import_insert = TextEdit::builder();
-        algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut import_insert);
+        algo::diff(self.scope.as_syntax_node(), new_ast.as_syntax_node())
+            .into_text_edit(&mut import_insert);
 
         Some(import_insert.finish())
     }
index be3a22725ab1d6d0bce229f189e2bb695fafebfc..498d76f722a087181debe75257a855c9a882e825 100644 (file)
@@ -4,13 +4,9 @@
 use hir::Semantics;
 use itertools::{EitherOrBoth, Itertools};
 use syntax::{
-    algo::SyntaxRewriter,
-    ast::{
-        self,
-        edit::{AstNodeEdit, IndentLevel},
-        make, AstNode, AttrsOwner, PathSegmentKind, VisibilityOwner,
-    },
-    AstToken, InsertPosition, NodeOrToken, SyntaxElement, SyntaxNode, SyntaxToken,
+    algo,
+    ast::{self, edit::AstNodeEdit, make, AstNode, AttrsOwner, PathSegmentKind, VisibilityOwner},
+    ted, AstToken, Direction, NodeOrToken, SyntaxNode, SyntaxToken,
 };
 
 use crate::RootDatabase;
@@ -56,127 +52,32 @@ pub fn as_syntax_node(&self) -> &SyntaxNode {
         }
     }
 
-    fn indent_level(&self) -> IndentLevel {
+    pub fn clone_for_update(&self) -> Self {
         match self {
-            ImportScope::File(file) => file.indent_level(),
-            ImportScope::Module(item_list) => item_list.indent_level() + 1,
+            ImportScope::File(file) => ImportScope::File(file.clone_for_update()),
+            ImportScope::Module(item_list) => ImportScope::Module(item_list.clone_for_update()),
         }
     }
-
-    fn first_insert_pos(&self) -> (InsertPosition<SyntaxElement>, AddBlankLine) {
-        match self {
-            ImportScope::File(_) => (InsertPosition::First, AddBlankLine::AfterTwice),
-            // don't insert the imports before the item list's opening curly brace
-            ImportScope::Module(item_list) => item_list
-                .l_curly_token()
-                .map(|b| (InsertPosition::After(b.into()), AddBlankLine::Around))
-                .unwrap_or((InsertPosition::First, AddBlankLine::AfterTwice)),
-        }
-    }
-
-    fn insert_pos_after_last_inner_element(&self) -> (InsertPosition<SyntaxElement>, AddBlankLine) {
-        self.as_syntax_node()
-            .children_with_tokens()
-            .filter(|child| match child {
-                NodeOrToken::Node(node) => is_inner_attribute(node.clone()),
-                NodeOrToken::Token(token) => is_inner_comment(token.clone()),
-            })
-            .last()
-            .map(|last_inner_element| {
-                (InsertPosition::After(last_inner_element), AddBlankLine::BeforeTwice)
-            })
-            .unwrap_or_else(|| self.first_insert_pos())
-    }
-}
-
-fn is_inner_attribute(node: SyntaxNode) -> bool {
-    ast::Attr::cast(node).map(|attr| attr.kind()) == Some(ast::AttrKind::Inner)
-}
-
-fn is_inner_comment(token: SyntaxToken) -> bool {
-    ast::Comment::cast(token).and_then(|comment| comment.kind().doc)
-        == Some(ast::CommentPlacement::Inner)
 }
 
 /// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur.
-pub fn insert_use<'a>(
-    scope: &ImportScope,
-    path: ast::Path,
-    cfg: InsertUseConfig,
-) -> SyntaxRewriter<'a> {
+pub fn insert_use<'a>(scope: &ImportScope, path: ast::Path, cfg: InsertUseConfig) {
     let _p = profile::span("insert_use");
-    let mut rewriter = SyntaxRewriter::default();
-    let use_item = make::use_(None, make::use_tree(path.clone(), None, None, false));
+    let use_item =
+        make::use_(None, make::use_tree(path.clone(), None, None, false)).clone_for_update();
     // merge into existing imports if possible
     if let Some(mb) = cfg.merge {
         for existing_use in scope.as_syntax_node().children().filter_map(ast::Use::cast) {
             if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) {
-                rewriter.replace(existing_use.syntax(), merged.syntax());
-                return rewriter;
+                ted::replace(existing_use.syntax(), merged.syntax());
+                return;
             }
         }
     }
 
     // either we weren't allowed to merge or there is no import that fits the merge conditions
     // so look for the place we have to insert to
-    let (insert_position, add_blank) = find_insert_position(scope, path, cfg.group);
-
-    let indent = if let ident_level @ 1..=usize::MAX = scope.indent_level().0 as usize {
-        Some(make::tokens::whitespace(&" ".repeat(4 * ident_level)).into())
-    } else {
-        None
-    };
-
-    let to_insert: Vec<SyntaxElement> = {
-        let mut buf = Vec::new();
-
-        match add_blank {
-            AddBlankLine::Before | AddBlankLine::Around => {
-                buf.push(make::tokens::single_newline().into())
-            }
-            AddBlankLine::BeforeTwice => buf.push(make::tokens::blank_line().into()),
-            _ => (),
-        }
-
-        if add_blank.has_before() {
-            if let Some(indent) = indent.clone() {
-                cov_mark::hit!(insert_use_indent_before);
-                buf.push(indent);
-            }
-        }
-
-        buf.push(use_item.syntax().clone().into());
-
-        match add_blank {
-            AddBlankLine::After | AddBlankLine::Around => {
-                buf.push(make::tokens::single_newline().into())
-            }
-            AddBlankLine::AfterTwice => buf.push(make::tokens::blank_line().into()),
-            _ => (),
-        }
-
-        // only add indentation *after* our stuff if there's another node directly after it
-        if add_blank.has_after() && matches!(insert_position, InsertPosition::Before(_)) {
-            if let Some(indent) = indent {
-                cov_mark::hit!(insert_use_indent_after);
-                buf.push(indent);
-            }
-        } else if add_blank.has_after() && matches!(insert_position, InsertPosition::After(_)) {
-            cov_mark::hit!(insert_use_no_indent_after);
-        }
-
-        buf
-    };
-
-    match insert_position {
-        InsertPosition::First => {
-            rewriter.insert_many_as_first_children(scope.as_syntax_node(), to_insert)
-        }
-        InsertPosition::Last => return rewriter, // actually unreachable
-        InsertPosition::Before(anchor) => rewriter.insert_many_before(&anchor, to_insert),
-        InsertPosition::After(anchor) => rewriter.insert_many_after(&anchor, to_insert),
-    }
-    rewriter
+    insert_use_(scope, path, cfg.group, use_item);
 }
 
 fn eq_visibility(vis0: Option<ast::Visibility>, vis1: Option<ast::Visibility>) -> bool {
@@ -235,7 +136,7 @@ pub fn try_merge_trees(
     } else {
         (lhs.split_prefix(&lhs_prefix), rhs.split_prefix(&rhs_prefix))
     };
-    recursive_merge(&lhs, &rhs, merge).map(|it| it.clone_for_update())
+    recursive_merge(&lhs, &rhs, merge)
 }
 
 /// Recursively "zips" together lhs and rhs.
@@ -334,7 +235,12 @@ fn recursive_merge(
             }
         }
     }
-    Some(lhs.with_use_tree_list(make::use_tree_list(use_trees)))
+
+    Some(if let Some(old) = lhs.use_tree_list() {
+        lhs.replace_descendant(old, make::use_tree_list(use_trees)).clone_for_update()
+    } else {
+        lhs.clone()
+    })
 }
 
 /// Traverses both paths until they differ, returning the common prefix of both.
@@ -520,32 +426,15 @@ fn new(path: &ast::Path) -> ImportGroup {
     }
 }
 
-#[derive(PartialEq, Eq)]
-enum AddBlankLine {
-    Before,
-    BeforeTwice,
-    Around,
-    After,
-    AfterTwice,
-}
-
-impl AddBlankLine {
-    fn has_before(&self) -> bool {
-        matches!(self, AddBlankLine::Before | AddBlankLine::BeforeTwice | AddBlankLine::Around)
-    }
-    fn has_after(&self) -> bool {
-        matches!(self, AddBlankLine::After | AddBlankLine::AfterTwice | AddBlankLine::Around)
-    }
-}
-
-fn find_insert_position(
+fn insert_use_(
     scope: &ImportScope,
     insert_path: ast::Path,
     group_imports: bool,
-) -> (InsertPosition<SyntaxElement>, AddBlankLine) {
+    use_item: ast::Use,
+) {
+    let scope_syntax = scope.as_syntax_node();
     let group = ImportGroup::new(&insert_path);
-    let path_node_iter = scope
-        .as_syntax_node()
+    let path_node_iter = scope_syntax
         .children()
         .filter_map(|node| ast::Use::cast(node.clone()).zip(Some(node)))
         .flat_map(|(use_, node)| {
@@ -557,9 +446,12 @@ fn find_insert_position(
 
     if !group_imports {
         if let Some((_, _, node)) = path_node_iter.last() {
-            return (InsertPosition::After(node.into()), AddBlankLine::Before);
+            ted::insert(ted::Position::after(node), use_item.syntax());
+        } else {
+            ted::insert(ted::Position::first_child_of(scope_syntax), make::tokens::blank_line());
+            ted::insert(ted::Position::first_child_of(scope_syntax), use_item.syntax());
         }
-        return (InsertPosition::First, AddBlankLine::AfterTwice);
+        return;
     }
 
     // Iterator that discards anything thats not in the required grouping
@@ -572,43 +464,83 @@ fn find_insert_position(
     // track the last element we iterated over, if this is still None after the iteration then that means we never iterated in the first place
     let mut last = None;
     // find the element that would come directly after our new import
-    let post_insert = group_iter.inspect(|(.., node)| last = Some(node.clone())).find(
-        |&(ref path, has_tl, _)| {
+    let post_insert: Option<(_, _, SyntaxNode)> = group_iter
+        .inspect(|(.., node)| last = Some(node.clone()))
+        .find(|&(ref path, has_tl, _)| {
             use_tree_path_cmp(&insert_path, false, path, has_tl) != Ordering::Greater
-        },
-    );
+        });
 
-    match post_insert {
+    if let Some((.., node)) = post_insert {
         // insert our import before that element
-        Some((.., node)) => (InsertPosition::Before(node.into()), AddBlankLine::After),
+        return ted::insert(ted::Position::before(node), use_item.syntax());
+    }
+    if let Some(node) = last {
         // there is no element after our new import, so append it to the end of the group
-        None => match last {
-            Some(node) => (InsertPosition::After(node.into()), AddBlankLine::Before),
-            // the group we were looking for actually doesnt exist, so insert
+        return ted::insert(ted::Position::after(node), use_item.syntax());
+    }
+
+    // the group we were looking for actually doesn't exist, so insert
+
+    let mut last = None;
+    // find the group that comes after where we want to insert
+    let post_group = path_node_iter
+        .inspect(|(.., node)| last = Some(node.clone()))
+        .find(|(p, ..)| ImportGroup::new(p) > group);
+    if let Some((.., node)) = post_group {
+        ted::insert(ted::Position::before(&node), use_item.syntax());
+        if let Some(node) = algo::non_trivia_sibling(node.into(), Direction::Prev) {
+            ted::insert(ted::Position::after(node), make::tokens::single_newline());
+        }
+        return;
+    }
+    // there is no such group, so append after the last one
+    if let Some(node) = last {
+        ted::insert(ted::Position::after(&node), use_item.syntax());
+        ted::insert(ted::Position::after(node), make::tokens::single_newline());
+        return;
+    }
+    // there are no imports in this file at all
+    if let Some(last_inner_element) = scope_syntax
+        .children_with_tokens()
+        .filter(|child| match child {
+            NodeOrToken::Node(node) => is_inner_attribute(node.clone()),
+            NodeOrToken::Token(token) => is_inner_comment(token.clone()),
+        })
+        .last()
+    {
+        ted::insert(ted::Position::after(&last_inner_element), use_item.syntax());
+        ted::insert(ted::Position::after(last_inner_element), make::tokens::single_newline());
+        return;
+    }
+    match scope {
+        ImportScope::File(_) => {
+            ted::insert(ted::Position::first_child_of(scope_syntax), make::tokens::blank_line());
+            ted::insert(ted::Position::first_child_of(scope_syntax), use_item.syntax())
+        }
+        // don't insert the imports before the item list's opening curly brace
+        ImportScope::Module(item_list) => match item_list.l_curly_token() {
+            Some(b) => {
+                ted::insert(ted::Position::after(&b), make::tokens::single_newline());
+                ted::insert(ted::Position::after(&b), use_item.syntax());
+            }
             None => {
-                // similar concept here to the `last` from above
-                let mut last = None;
-                // find the group that comes after where we want to insert
-                let post_group = path_node_iter
-                    .inspect(|(.., node)| last = Some(node.clone()))
-                    .find(|(p, ..)| ImportGroup::new(p) > group);
-                match post_group {
-                    Some((.., node)) => {
-                        (InsertPosition::Before(node.into()), AddBlankLine::AfterTwice)
-                    }
-                    // there is no such group, so append after the last one
-                    None => match last {
-                        Some(node) => {
-                            (InsertPosition::After(node.into()), AddBlankLine::BeforeTwice)
-                        }
-                        // there are no imports in this file at all
-                        None => scope.insert_pos_after_last_inner_element(),
-                    },
-                }
+                ted::insert(
+                    ted::Position::first_child_of(scope_syntax),
+                    make::tokens::blank_line(),
+                );
+                ted::insert(ted::Position::first_child_of(scope_syntax), use_item.syntax());
             }
         },
     }
 }
 
+fn is_inner_attribute(node: SyntaxNode) -> bool {
+    ast::Attr::cast(node).map(|attr| attr.kind()) == Some(ast::AttrKind::Inner)
+}
+
+fn is_inner_comment(token: SyntaxToken) -> bool {
+    ast::Comment::cast(token).and_then(|comment| comment.kind().doc)
+        == Some(ast::CommentPlacement::Inner)
+}
 #[cfg(test)]
 mod tests;
index 3d151e629d29f9ded330fe85d46f24175ecf26c2..a3464d606b6eeeff8dcd1f4f44769146156862af 100644 (file)
@@ -51,17 +51,16 @@ fn insert_start() {
 
 #[test]
 fn insert_start_indent() {
-    cov_mark::check!(insert_use_indent_after);
     check_none(
         "std::bar::AA",
         r"
     use std::bar::B;
-    use std::bar::D;",
+    use std::bar::C;",
         r"
     use std::bar::AA;
     use std::bar::B;
-    use std::bar::D;",
-    )
+    use std::bar::C;",
+    );
 }
 
 #[test]
@@ -120,7 +119,6 @@ fn insert_end() {
 
 #[test]
 fn insert_end_indent() {
-    cov_mark::check!(insert_use_indent_before);
     check_none(
         "std::bar::ZZ",
         r"
@@ -255,7 +253,6 @@ fn insert_empty_file() {
 
 #[test]
 fn insert_empty_module() {
-    cov_mark::check!(insert_use_no_indent_after);
     check(
         "foo::bar",
         "mod x {}",
@@ -615,7 +612,7 @@ fn check(
     if module {
         syntax = syntax.descendants().find_map(ast::Module::cast).unwrap().syntax().clone();
     }
-    let file = super::ImportScope::from(syntax).unwrap();
+    let file = super::ImportScope::from(syntax.clone_for_update()).unwrap();
     let path = ast::SourceFile::parse(&format!("use {};", path))
         .tree()
         .syntax()
@@ -623,12 +620,8 @@ fn check(
         .find_map(ast::Path::cast)
         .unwrap();
 
-    let rewriter = insert_use(
-        &file,
-        path,
-        InsertUseConfig { merge: mb, prefix_kind: PrefixKind::Plain, group },
-    );
-    let result = rewriter.rewrite(file.as_syntax_node()).to_string();
+    insert_use(&file, path, InsertUseConfig { merge: mb, prefix_kind: PrefixKind::Plain, group });
+    let result = file.as_syntax_node().to_string();
     assert_eq_text!(ra_fixture_after, &result);
 }
 
index 94d4f2cf0b986659f1cbfbea4212d87941fd38bf..882e9fa09039bcce98d9539f935e0ee4884c994e 100644 (file)
@@ -598,6 +598,7 @@ pub fn blank_line() -> SyntaxToken {
         SOURCE_FILE
             .tree()
             .syntax()
+            .clone_for_update()
             .descendants_with_tokens()
             .filter_map(|it| it.into_token())
             .find(|it| it.kind() == WHITESPACE && it.text() == "\n\n")
index 450f2e447ab7ee5b86d8d296560c183bcae931ab..91a06101f5984548d1558b94e1b17adc6185067f 100644 (file)
@@ -7,7 +7,7 @@
 use parser::T;
 
 use crate::{
-    ast::{edit::IndentLevel, make},
+    ast::{self, edit::IndentLevel, make, AstNode},
     SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken,
 };
 
@@ -147,6 +147,16 @@ pub fn append_child_raw(node: &(impl Into<SyntaxNode> + Clone), child: impl Elem
 fn ws_before(position: &Position, new: &SyntaxElement) -> Option<SyntaxToken> {
     let prev = match &position.repr {
         PositionRepr::FirstChild(_) => return None,
+        PositionRepr::After(it) if it.kind() == SyntaxKind::L_CURLY => {
+            if new.kind() == SyntaxKind::USE {
+                if let Some(item_list) = it.parent().and_then(ast::ItemList::cast) {
+                    let mut indent = IndentLevel::from_element(&item_list.syntax().clone().into());
+                    indent.0 += 1;
+                    return Some(make::tokens::whitespace(&format!("\n{}", indent)));
+                }
+            }
+            it
+        }
         PositionRepr::After(it) => it,
     };
     ws_between(prev, new)
@@ -173,7 +183,10 @@ fn ws_between(left: &SyntaxElement, right: &SyntaxElement) -> Option<SyntaxToken
     }
 
     if right.kind() == SyntaxKind::USE {
-        let indent = IndentLevel::from_element(left);
+        let mut indent = IndentLevel::from_element(left);
+        if left.kind() == SyntaxKind::USE {
+            indent.0 = IndentLevel::from_element(right).0.max(indent.0);
+        }
         return Some(make::tokens::whitespace(&format!("\n{}", indent)));
     }
     Some(make::tokens::single_space())