Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide-db/src/imports/insert_use.rs')
| -rw-r--r-- | crates/ide-db/src/imports/insert_use.rs | 308 |
1 files changed, 84 insertions, 224 deletions
diff --git a/crates/ide-db/src/imports/insert_use.rs b/crates/ide-db/src/imports/insert_use.rs index 7402ec8f5a..4b0373271c 100644 --- a/crates/ide-db/src/imports/insert_use.rs +++ b/crates/ide-db/src/imports/insert_use.rs @@ -6,10 +6,11 @@ use std::cmp::Ordering; use hir::Semantics; use syntax::{ - Direction, NodeOrToken, SyntaxKind, SyntaxNode, algo, - ast::{self, AstNode, HasAttrs, HasModuleItem, HasVisibility, PathSegmentKind, make}, + NodeOrToken, SyntaxKind, SyntaxNode, + ast::{ + self, AstNode, HasAttrs, HasModuleItem, HasVisibility, PathSegmentKind, edit::IndentLevel, + }, syntax_editor::{Position, SyntaxEditor}, - ted, }; use crate::{ @@ -147,24 +148,6 @@ impl ImportScope { ImportScopeKind::Block(block) => block.syntax(), } } - - pub fn clone_for_update(&self) -> Self { - Self { - kind: match &self.kind { - ImportScopeKind::File(file) => ImportScopeKind::File(file.clone_for_update()), - ImportScopeKind::Module(item_list) => { - ImportScopeKind::Module(item_list.clone_for_update()) - } - ImportScopeKind::Block(block) => ImportScopeKind::Block(block.clone_for_update()), - }, - required_cfgs: self.required_cfgs.iter().map(|attr| attr.clone_for_update()).collect(), - } - } -} - -/// 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(scope: &ImportScope, path: ast::Path, cfg: &InsertUseConfig) { - insert_use_with_alias_option(scope, path, cfg, None); } /// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur. @@ -177,11 +160,43 @@ pub fn insert_use_with_editor( insert_use_with_alias_option_with_editor(scope, path, cfg, None, syntax_editor); } -pub fn insert_use_as_alias( +pub fn insert_uses_with_editor( + scope: &ImportScope, + paths: impl IntoIterator<Item = ast::Path>, + cfg: &InsertUseConfig, + syntax_editor: &SyntaxEditor, +) { + let paths = paths.into_iter().collect::<Vec<_>>(); + if paths.len() > 1 + && scope.as_syntax_node().parent().is_none() + && scope.required_cfgs.is_empty() + && !scope.as_syntax_node().children().any(|node| ast::Use::cast(node).is_some()) + { + let make = syntax_editor.make(); + let elements = paths + .into_iter() + .flat_map(|path| { + let use_tree = make.use_tree(path, None, None, false); + let use_item = make.use_(None, None, use_tree); + [use_item.syntax().clone().into(), make.whitespace("\n").into()] + }) + .chain([make.whitespace("\n").into()]) + .collect(); + syntax_editor.insert_all(Position::first_child_of(scope.as_syntax_node()), elements); + return; + } + + for path in paths { + insert_use_with_editor(scope, path, cfg, syntax_editor); + } +} + +pub fn insert_use_as_alias_with_editor( scope: &ImportScope, path: ast::Path, cfg: &InsertUseConfig, edition: span::Edition, + editor: &SyntaxEditor, ) { let text: &str = "use foo as _"; let parse = syntax::SourceFile::parse(text, edition); @@ -193,71 +208,7 @@ pub fn insert_use_as_alias( .expect("Failed to make ast node `Rename`"); let alias = node.rename(); - insert_use_with_alias_option(scope, path, cfg, alias); -} - -fn insert_use_with_alias_option( - scope: &ImportScope, - path: ast::Path, - cfg: &InsertUseConfig, - alias: Option<ast::Rename>, -) { - let _p = tracing::info_span!("insert_use_with_alias_option").entered(); - let mut mb = match cfg.granularity { - ImportGranularity::Crate => Some(MergeBehavior::Crate), - ImportGranularity::Module => Some(MergeBehavior::Module), - ImportGranularity::One => Some(MergeBehavior::One), - ImportGranularity::Item => None, - }; - if !cfg.enforce_granularity { - let file_granularity = guess_granularity_from_scope(scope); - mb = match file_granularity { - ImportGranularityGuess::Unknown => mb, - ImportGranularityGuess::Item => None, - ImportGranularityGuess::Module => Some(MergeBehavior::Module), - // We use the user's setting to infer if this is module or item. - ImportGranularityGuess::ModuleOrItem => match mb { - Some(MergeBehavior::Module) | None => mb, - // There isn't really a way to decide between module or item here, so we just pick one. - // FIXME: Maybe it is possible to infer based on semantic analysis? - Some(MergeBehavior::One | MergeBehavior::Crate) => Some(MergeBehavior::Module), - }, - ImportGranularityGuess::Crate => Some(MergeBehavior::Crate), - ImportGranularityGuess::CrateOrModule => match mb { - Some(MergeBehavior::Crate | MergeBehavior::Module) => mb, - Some(MergeBehavior::One) | None => Some(MergeBehavior::Crate), - }, - ImportGranularityGuess::One => Some(MergeBehavior::One), - }; - } - - let mut use_tree = make::use_tree(path, None, alias, false); - if mb == Some(MergeBehavior::One) && use_tree.path().is_some() { - use_tree = use_tree.clone_for_update(); - use_tree.wrap_in_tree_list(); - } - let use_item = make::use_(None, None, use_tree).clone_for_update(); - for attr in - scope.required_cfgs.iter().map(|attr| attr.syntax().clone_subtree().clone_for_update()) - { - ted::insert(ted::Position::first_child_of(use_item.syntax()), attr); - } - - // merge into existing imports if possible - if let Some(mb) = mb { - let filter = |it: &_| !(cfg.skip_glob_imports && ast::Use::is_simple_glob(it)); - for existing_use in - scope.as_syntax_node().children().filter_map(ast::Use::cast).filter(filter) - { - if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) { - 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 - insert_use_(scope, use_item, cfg.group); + insert_use_with_alias_option_with_editor(scope, path, cfg, alias, editor); } fn insert_use_with_alias_option_with_editor( @@ -301,10 +252,7 @@ fn insert_use_with_alias_option_with_editor( if mb == Some(MergeBehavior::One) && use_tree.path().is_some() { use_tree.wrap_in_tree_list(); } - let use_item = make::use_(None, None, use_tree); - for attr in scope.required_cfgs.iter().map(|attr| attr.syntax().clone()) { - syntax_editor.insert(Position::first_child_of(use_item.syntax()), attr); - } + let use_item = make.use_(scope.required_cfgs.iter().cloned().rev(), None, use_tree); // merge into existing imports if possible if let Some(mb) = mb { @@ -469,123 +417,6 @@ fn guess_granularity_from_scope(scope: &ImportScope) -> ImportGranularityGuess { } } -fn insert_use_(scope: &ImportScope, use_item: ast::Use, group_imports: bool) { - let scope_syntax = scope.as_syntax_node(); - let insert_use_tree = - use_item.use_tree().expect("`use_item` should have a use tree for `insert_path`"); - let group = ImportGroup::new(&insert_use_tree); - let path_node_iter = scope_syntax - .children() - .filter_map(|node| ast::Use::cast(node.clone()).zip(Some(node))) - .flat_map(|(use_, node)| { - let tree = use_.use_tree()?; - Some((tree, node)) - }); - - if group_imports { - // Iterator that discards anything that's not in the required grouping - // This implementation allows the user to rearrange their import groups as this only takes the first group that fits - let group_iter = path_node_iter - .clone() - .skip_while(|(use_tree, ..)| ImportGroup::new(use_tree) != group) - .take_while(|(use_tree, ..)| ImportGroup::new(use_tree) == group); - - // 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: Option<(_, SyntaxNode)> = group_iter - .inspect(|(.., node)| last = Some(node.clone())) - .find(|(use_tree, _)| use_tree_cmp(&insert_use_tree, use_tree) != Ordering::Greater); - - if let Some((.., node)) = post_insert { - cov_mark::hit!(insert_group); - // insert our import before that element - return ted::insert(ted::Position::before(node), use_item.syntax()); - } - if let Some(node) = last { - cov_mark::hit!(insert_group_last); - // there is no element after our new import, so append it to the end of the group - 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(|(use_tree, ..)| ImportGroup::new(use_tree) > group); - if let Some((.., node)) = post_group { - cov_mark::hit!(insert_group_new_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 { - cov_mark::hit!(insert_group_no_group); - ted::insert(ted::Position::after(&node), use_item.syntax()); - ted::insert(ted::Position::after(node), make::tokens::single_newline()); - return; - } - } else { - // There exists a group, so append to the end of it - if let Some((_, node)) = path_node_iter.last() { - cov_mark::hit!(insert_no_grouping_last); - ted::insert(ted::Position::after(node), use_item.syntax()); - return; - } - } - - let l_curly = match &scope.kind { - ImportScopeKind::File(_) => None, - // don't insert the imports before the item list/block expr's opening curly brace - ImportScopeKind::Module(item_list) => item_list.l_curly_token(), - // don't insert the imports before the item list's opening curly brace - ImportScopeKind::Block(block) => block.l_curly_token(), - }; - // there are no imports in this file at all - // so put the import after all inner module attributes and possible license header comments - if let Some(last_inner_element) = scope_syntax - .children_with_tokens() - // skip the curly brace - .skip(l_curly.is_some() as usize) - .take_while(|child| match child { - NodeOrToken::Node(node) => { - is_inner_attribute(node.clone()) && ast::Item::cast(node.clone()).is_none() - } - NodeOrToken::Token(token) => { - [SyntaxKind::WHITESPACE, SyntaxKind::COMMENT, SyntaxKind::SHEBANG] - .contains(&token.kind()) - } - }) - .filter(|child| child.as_token().is_none_or(|t| t.kind() != SyntaxKind::WHITESPACE)) - .last() - { - cov_mark::hit!(insert_empty_inner_attr); - ted::insert(ted::Position::after(&last_inner_element), use_item.syntax()); - ted::insert(ted::Position::after(last_inner_element), make::tokens::single_newline()); - } else { - match l_curly { - Some(b) => { - cov_mark::hit!(insert_empty_module); - ted::insert(ted::Position::after(&b), make::tokens::single_newline()); - ted::insert(ted::Position::after(&b), use_item.syntax()); - } - None => { - cov_mark::hit!(insert_empty_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()); - } - } - } -} - fn insert_use_with_editor_( scope: &ImportScope, use_item: ast::Use, @@ -623,12 +454,18 @@ fn insert_use_with_editor_( if let Some((.., node)) = post_insert { cov_mark::hit!(insert_group); // insert our import before that element - return syntax_editor.insert(Position::before(node), use_item.syntax()); + return syntax_editor.insert_all( + Position::before(node), + vec![use_item.syntax().clone().into(), make.whitespace("\n").into()], + ); } if let Some(node) = last { cov_mark::hit!(insert_group_last); // there is no element after our new import, so append it to the end of the group - return syntax_editor.insert(Position::after(node), use_item.syntax()); + return syntax_editor.insert_all( + Position::after(node), + vec![make.whitespace("\n").into(), use_item.syntax().clone().into()], + ); } // the group we were looking for actually doesn't exist, so insert @@ -640,24 +477,29 @@ fn insert_use_with_editor_( .find(|(use_tree, ..)| ImportGroup::new(use_tree) > group); if let Some((.., node)) = post_group { cov_mark::hit!(insert_group_new_group); - syntax_editor.insert(Position::before(&node), use_item.syntax()); - if let Some(node) = algo::non_trivia_sibling(node.into(), Direction::Prev) { - syntax_editor.insert(Position::after(node), make.whitespace("\n")); - } + syntax_editor.insert_all( + Position::before(&node), + vec![use_item.syntax().clone().into(), make.whitespace("\n\n").into()], + ); return; } // there is no such group, so append after the last one if let Some(node) = last { cov_mark::hit!(insert_group_no_group); - syntax_editor.insert(Position::after(&node), use_item.syntax()); - syntax_editor.insert(Position::after(node), make.whitespace("\n")); + syntax_editor.insert_all( + Position::after(&node), + vec![make.whitespace("\n\n").into(), use_item.syntax().clone().into()], + ); return; } } else { // There exists a group, so append to the end of it if let Some((_, node)) = path_node_iter.last() { cov_mark::hit!(insert_no_grouping_last); - syntax_editor.insert(Position::after(node), use_item.syntax()); + syntax_editor.insert_all( + Position::after(node), + vec![make.whitespace("\n").into(), use_item.syntax().clone().into()], + ); return; } } @@ -688,20 +530,38 @@ fn insert_use_with_editor_( .last() { cov_mark::hit!(insert_empty_inner_attr); - syntax_editor.insert(Position::after(&last_inner_element), use_item.syntax()); - syntax_editor.insert(Position::after(last_inner_element), make.whitespace("\n")); + let indent = if l_curly.is_some() { + IndentLevel::from_node(scope_syntax) + 1 + } else { + IndentLevel::zero() + }; + syntax_editor.insert_all( + Position::after(&last_inner_element), + vec![ + make.whitespace(&format!("\n\n{indent}")).into(), + use_item.syntax().clone().into(), + ], + ); } else { match l_curly { Some(b) => { cov_mark::hit!(insert_empty_module); - syntax_editor.insert(Position::after(&b), make.whitespace("\n")); - syntax_editor.insert_with_whitespace(Position::after(&b), use_item.syntax()); + let indent = IndentLevel::from_node(scope_syntax) + 1; + syntax_editor.insert_all( + Position::after(&b), + vec![ + make.whitespace(&format!("\n{indent}")).into(), + use_item.syntax().clone().into(), + make.whitespace("\n").into(), + ], + ); } None => { cov_mark::hit!(insert_empty_file); - syntax_editor - .insert(Position::first_child_of(scope_syntax), make.whitespace("\n\n")); - syntax_editor.insert(Position::first_child_of(scope_syntax), use_item.syntax()); + syntax_editor.insert_all( + Position::first_child_of(scope_syntax), + vec![use_item.syntax().clone().into(), make.whitespace("\n\n").into()], + ); } } } |