From fa20a5064be85349d2d05abcd66f5662d3aecb0c Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Tue, 20 Apr 2021 02:05:22 +0200 Subject: Remove SyntaxRewriter usage in insert_use in favor of ted --- crates/ide_assists/src/handlers/auto_import.rs | 8 +- .../handlers/extract_struct_from_enum_variant.rs | 85 +++---- .../handlers/replace_qualified_name_with_use.rs | 28 +-- crates/ide_completion/src/item.rs | 8 +- crates/ide_db/src/helpers/insert_use.rs | 262 ++++++++------------- crates/ide_db/src/helpers/insert_use/tests.rs | 19 +- crates/syntax/src/ast/make.rs | 1 + crates/syntax/src/ted.rs | 17 +- 8 files changed, 185 insertions(+), 243 deletions(-) (limited to 'crates') diff --git a/crates/ide_assists/src/handlers/auto_import.rs b/crates/ide_assists/src/handlers/auto_import.rs index 49aa70f74..6db2d2edd 100644 --- a/crates/ide_assists/src/handlers/auto_import.rs +++ b/crates/ide_assists/src/handlers/auto_import.rs @@ -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); }, ); } diff --git a/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs index a8d6355bd..26e1c66ab 100644 --- a/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs +++ b/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs @@ -13,9 +13,9 @@ use ide_db::{ }; 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, 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(()) } diff --git a/crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs b/crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs index 36d2e0331..2f2306fcc 100644 --- a/crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs +++ b/crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs @@ -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 @@ Debug ", ); } + #[test] fn test_replace_add_use_no_anchor_with_item_below() { check_assist( diff --git a/crates/ide_completion/src/item.rs b/crates/ide_completion/src/item.rs index 16991b688..99edb9499 100644 --- a/crates/ide_completion/src/item.rs +++ b/crates/ide_completion/src/item.rs @@ -377,11 +377,11 @@ impl ImportEdit { pub fn to_text_edit(&self, cfg: InsertUseConfig) -> Option { 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()) } diff --git a/crates/ide_db/src/helpers/insert_use.rs b/crates/ide_db/src/helpers/insert_use.rs index be3a22725..498d76f72 100644 --- a/crates/ide_db/src/helpers/insert_use.rs +++ b/crates/ide_db/src/helpers/insert_use.rs @@ -4,13 +4,9 @@ use std::{cmp::Ordering, iter::successors}; 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 @@ impl ImportScope { } } - 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, 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, 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 = { - 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, vis1: Option) -> 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 @@ impl 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, 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; diff --git a/crates/ide_db/src/helpers/insert_use/tests.rs b/crates/ide_db/src/helpers/insert_use/tests.rs index 3d151e629..a3464d606 100644 --- a/crates/ide_db/src/helpers/insert_use/tests.rs +++ b/crates/ide_db/src/helpers/insert_use/tests.rs @@ -51,17 +51,16 @@ use std::bar::G;", #[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 @@ use std::bar::ZZ;", #[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); } diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs index 94d4f2cf0..882e9fa09 100644 --- a/crates/syntax/src/ast/make.rs +++ b/crates/syntax/src/ast/make.rs @@ -598,6 +598,7 @@ pub mod tokens { 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") diff --git a/crates/syntax/src/ted.rs b/crates/syntax/src/ted.rs index 450f2e447..91a06101f 100644 --- a/crates/syntax/src/ted.rs +++ b/crates/syntax/src/ted.rs @@ -7,7 +7,7 @@ use std::{mem, ops::RangeInclusive}; 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 + Clone), child: impl Elem fn ws_before(position: &Position, new: &SyntaxElement) -> Option { 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