]> git.lizzy.rs Git - rust.git/blob - crates/ide_db/src/helpers/insert_use.rs
Split out merge_imports module from helpers::insert_use
[rust.git] / crates / ide_db / src / helpers / insert_use.rs
1 //! Handle syntactic aspects of inserting a new `use`.
2 use std::cmp::Ordering;
3
4 use hir::Semantics;
5 use syntax::{
6     algo,
7     ast::{self, make, AstNode, PathSegmentKind},
8     ted, AstToken, Direction, NodeOrToken, SyntaxNode, SyntaxToken,
9 };
10
11 use crate::{
12     helpers::merge_imports::{try_merge_imports, use_tree_path_cmp, MergeBehavior},
13     RootDatabase,
14 };
15
16 pub use hir::PrefixKind;
17
18 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
19 pub struct InsertUseConfig {
20     pub merge: Option<MergeBehavior>,
21     pub prefix_kind: PrefixKind,
22     pub group: bool,
23 }
24
25 #[derive(Debug, Clone)]
26 pub enum ImportScope {
27     File(ast::SourceFile),
28     Module(ast::ItemList),
29 }
30
31 impl ImportScope {
32     pub fn from(syntax: SyntaxNode) -> Option<Self> {
33         if let Some(module) = ast::Module::cast(syntax.clone()) {
34             module.item_list().map(ImportScope::Module)
35         } else if let this @ Some(_) = ast::SourceFile::cast(syntax.clone()) {
36             this.map(ImportScope::File)
37         } else {
38             ast::ItemList::cast(syntax).map(ImportScope::Module)
39         }
40     }
41
42     /// Determines the containing syntax node in which to insert a `use` statement affecting `position`.
43     pub fn find_insert_use_container_with_macros(
44         position: &SyntaxNode,
45         sema: &Semantics<'_, RootDatabase>,
46     ) -> Option<Self> {
47         sema.ancestors_with_macros(position.clone()).find_map(Self::from)
48     }
49
50     /// Determines the containing syntax node in which to insert a `use` statement affecting `position`.
51     pub fn find_insert_use_container(position: &SyntaxNode) -> Option<Self> {
52         std::iter::successors(Some(position.clone()), SyntaxNode::parent).find_map(Self::from)
53     }
54
55     pub fn as_syntax_node(&self) -> &SyntaxNode {
56         match self {
57             ImportScope::File(file) => file.syntax(),
58             ImportScope::Module(item_list) => item_list.syntax(),
59         }
60     }
61
62     pub fn clone_for_update(&self) -> Self {
63         match self {
64             ImportScope::File(file) => ImportScope::File(file.clone_for_update()),
65             ImportScope::Module(item_list) => ImportScope::Module(item_list.clone_for_update()),
66         }
67     }
68 }
69
70 /// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur.
71 pub fn insert_use<'a>(scope: &ImportScope, path: ast::Path, cfg: InsertUseConfig) {
72     let _p = profile::span("insert_use");
73     let use_item =
74         make::use_(None, make::use_tree(path.clone(), None, None, false)).clone_for_update();
75     // merge into existing imports if possible
76     if let Some(mb) = cfg.merge {
77         for existing_use in scope.as_syntax_node().children().filter_map(ast::Use::cast) {
78             if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) {
79                 ted::replace(existing_use.syntax(), merged.syntax());
80                 return;
81             }
82         }
83     }
84
85     // either we weren't allowed to merge or there is no import that fits the merge conditions
86     // so look for the place we have to insert to
87     insert_use_(scope, path, cfg.group, use_item);
88 }
89
90 #[derive(Eq, PartialEq, PartialOrd, Ord)]
91 enum ImportGroup {
92     // the order here defines the order of new group inserts
93     Std,
94     ExternCrate,
95     ThisCrate,
96     ThisModule,
97     SuperModule,
98 }
99
100 impl ImportGroup {
101     fn new(path: &ast::Path) -> ImportGroup {
102         let default = ImportGroup::ExternCrate;
103
104         let first_segment = match path.first_segment() {
105             Some(it) => it,
106             None => return default,
107         };
108
109         let kind = first_segment.kind().unwrap_or(PathSegmentKind::SelfKw);
110         match kind {
111             PathSegmentKind::SelfKw => ImportGroup::ThisModule,
112             PathSegmentKind::SuperKw => ImportGroup::SuperModule,
113             PathSegmentKind::CrateKw => ImportGroup::ThisCrate,
114             PathSegmentKind::Name(name) => match name.text().as_str() {
115                 "std" => ImportGroup::Std,
116                 "core" => ImportGroup::Std,
117                 _ => ImportGroup::ExternCrate,
118             },
119             PathSegmentKind::Type { .. } => unreachable!(),
120         }
121     }
122 }
123
124 fn insert_use_(
125     scope: &ImportScope,
126     insert_path: ast::Path,
127     group_imports: bool,
128     use_item: ast::Use,
129 ) {
130     let scope_syntax = scope.as_syntax_node();
131     let group = ImportGroup::new(&insert_path);
132     let path_node_iter = scope_syntax
133         .children()
134         .filter_map(|node| ast::Use::cast(node.clone()).zip(Some(node)))
135         .flat_map(|(use_, node)| {
136             let tree = use_.use_tree()?;
137             let path = tree.path()?;
138             let has_tl = tree.use_tree_list().is_some();
139             Some((path, has_tl, node))
140         });
141
142     if !group_imports {
143         if let Some((_, _, node)) = path_node_iter.last() {
144             cov_mark::hit!(insert_no_grouping_last);
145             ted::insert(ted::Position::after(node), use_item.syntax());
146         } else {
147             cov_mark::hit!(insert_no_grouping_last2);
148             ted::insert(ted::Position::first_child_of(scope_syntax), make::tokens::blank_line());
149             ted::insert(ted::Position::first_child_of(scope_syntax), use_item.syntax());
150         }
151         return;
152     }
153
154     // Iterator that discards anything thats not in the required grouping
155     // This implementation allows the user to rearrange their import groups as this only takes the first group that fits
156     let group_iter = path_node_iter
157         .clone()
158         .skip_while(|(path, ..)| ImportGroup::new(path) != group)
159         .take_while(|(path, ..)| ImportGroup::new(path) == group);
160
161     // 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
162     let mut last = None;
163     // find the element that would come directly after our new import
164     let post_insert: Option<(_, _, SyntaxNode)> = group_iter
165         .inspect(|(.., node)| last = Some(node.clone()))
166         .find(|&(ref path, has_tl, _)| {
167             use_tree_path_cmp(&insert_path, false, path, has_tl) != Ordering::Greater
168         });
169
170     if let Some((.., node)) = post_insert {
171         cov_mark::hit!(insert_group);
172         // insert our import before that element
173         return ted::insert(ted::Position::before(node), use_item.syntax());
174     }
175     if let Some(node) = last {
176         cov_mark::hit!(insert_group_last);
177         // there is no element after our new import, so append it to the end of the group
178         return ted::insert(ted::Position::after(node), use_item.syntax());
179     }
180
181     // the group we were looking for actually doesn't exist, so insert
182
183     let mut last = None;
184     // find the group that comes after where we want to insert
185     let post_group = path_node_iter
186         .inspect(|(.., node)| last = Some(node.clone()))
187         .find(|(p, ..)| ImportGroup::new(p) > group);
188     if let Some((.., node)) = post_group {
189         cov_mark::hit!(insert_group_new_group);
190         ted::insert(ted::Position::before(&node), use_item.syntax());
191         if let Some(node) = algo::non_trivia_sibling(node.into(), Direction::Prev) {
192             ted::insert(ted::Position::after(node), make::tokens::single_newline());
193         }
194         return;
195     }
196     // there is no such group, so append after the last one
197     if let Some(node) = last {
198         cov_mark::hit!(insert_group_no_group);
199         ted::insert(ted::Position::after(&node), use_item.syntax());
200         ted::insert(ted::Position::after(node), make::tokens::single_newline());
201         return;
202     }
203     // there are no imports in this file at all
204     if let Some(last_inner_element) = scope_syntax
205         .children_with_tokens()
206         .filter(|child| match child {
207             NodeOrToken::Node(node) => is_inner_attribute(node.clone()),
208             NodeOrToken::Token(token) => is_inner_comment(token.clone()),
209         })
210         .last()
211     {
212         cov_mark::hit!(insert_group_empty_inner_attr);
213         ted::insert(ted::Position::after(&last_inner_element), use_item.syntax());
214         ted::insert(ted::Position::after(last_inner_element), make::tokens::single_newline());
215         return;
216     }
217     match scope {
218         ImportScope::File(_) => {
219             cov_mark::hit!(insert_group_empty_file);
220             ted::insert(ted::Position::first_child_of(scope_syntax), make::tokens::blank_line());
221             ted::insert(ted::Position::first_child_of(scope_syntax), use_item.syntax())
222         }
223         // don't insert the imports before the item list's opening curly brace
224         ImportScope::Module(item_list) => match item_list.l_curly_token() {
225             Some(b) => {
226                 cov_mark::hit!(insert_group_empty_module);
227                 ted::insert(ted::Position::after(&b), make::tokens::single_newline());
228                 ted::insert(ted::Position::after(&b), use_item.syntax());
229             }
230             None => {
231                 // This should never happens, broken module syntax node
232                 ted::insert(
233                     ted::Position::first_child_of(scope_syntax),
234                     make::tokens::blank_line(),
235                 );
236                 ted::insert(ted::Position::first_child_of(scope_syntax), use_item.syntax());
237             }
238         },
239     }
240 }
241
242 fn is_inner_attribute(node: SyntaxNode) -> bool {
243     ast::Attr::cast(node).map(|attr| attr.kind()) == Some(ast::AttrKind::Inner)
244 }
245
246 fn is_inner_comment(token: SyntaxToken) -> bool {
247     ast::Comment::cast(token).and_then(|comment| comment.kind().doc)
248         == Some(ast::CommentPlacement::Inner)
249 }
250 #[cfg(test)]
251 mod tests;