aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src
diff options
context:
space:
mode:
authorAndrea Pretto <[email protected]>2019-02-06 14:34:37 +0000
committerAndrea Pretto <[email protected]>2019-02-09 10:29:59 +0000
commit5580cf239d3b1a93718bec3acac5ff2183d418e4 (patch)
tree4e4147f0458d4bb4c293273efc0c7af4ea7ba3d9 /crates/ra_assists/src
parent34398a8756b56c323d3b4b2ef32fbca32d88a105 (diff)
auto_import assist
Diffstat (limited to 'crates/ra_assists/src')
-rw-r--r--crates/ra_assists/src/auto_import.rs780
-rw-r--r--crates/ra_assists/src/lib.rs3
2 files changed, 783 insertions, 0 deletions
diff --git a/crates/ra_assists/src/auto_import.rs b/crates/ra_assists/src/auto_import.rs
new file mode 100644
index 000000000..4cac5a926
--- /dev/null
+++ b/crates/ra_assists/src/auto_import.rs
@@ -0,0 +1,780 @@
1use hir::db::HirDatabase;
2use ra_syntax::{
3 ast, AstNode, SyntaxNode, Direction, TextRange,
4 SyntaxKind::{ PATH, PATH_SEGMENT, COLONCOLON, COMMA }
5};
6use crate::assist_ctx::{AssistCtx, Assist, AssistBuilder};
7use itertools::{ Itertools, EitherOrBoth };
8
9// TODO: refactor this before merge
10mod formatting {
11 use ra_syntax::{
12 AstNode, SyntaxNode,
13 ast::{self, AstToken},
14 algo::generate,
15};
16
17 /// If the node is on the beginning of the line, calculate indent.
18 pub fn leading_indent(node: &SyntaxNode) -> Option<&str> {
19 for leaf in prev_leaves(node) {
20 if let Some(ws) = ast::Whitespace::cast(leaf) {
21 let ws_text = ws.text();
22 if let Some(pos) = ws_text.rfind('\n') {
23 return Some(&ws_text[pos + 1..]);
24 }
25 }
26 if leaf.leaf_text().unwrap().contains('\n') {
27 break;
28 }
29 }
30 None
31 }
32
33 fn prev_leaves(node: &SyntaxNode) -> impl Iterator<Item = &SyntaxNode> {
34 generate(prev_leaf(node), |&node| prev_leaf(node))
35 }
36
37 fn prev_leaf(node: &SyntaxNode) -> Option<&SyntaxNode> {
38 generate(node.ancestors().find_map(SyntaxNode::prev_sibling), |it| {
39 it.last_child()
40 })
41 .last()
42 }
43}
44
45fn collect_path_segments(path: &ast::Path) -> Option<Vec<&ast::PathSegment>> {
46 let mut v = Vec::new();
47 collect_path_segments_raw(&mut v, path)?;
48 return Some(v);
49}
50
51fn collect_path_segments_raw<'b, 'a: 'b>(
52 segments: &'b mut Vec<&'a ast::PathSegment>,
53 mut path: &'a ast::Path,
54) -> Option<usize> {
55 let oldlen = segments.len();
56 loop {
57 let mut children = path.syntax().children();
58 let (first, second, third) = (
59 children.next().map(|n| (n, n.kind())),
60 children.next().map(|n| (n, n.kind())),
61 children.next().map(|n| (n, n.kind())),
62 );
63 match (first, second, third) {
64 (Some((subpath, PATH)), Some((_, COLONCOLON)), Some((segment, PATH_SEGMENT))) => {
65 path = ast::Path::cast(subpath)?;
66 segments.push(ast::PathSegment::cast(segment)?);
67 }
68 (Some((segment, PATH_SEGMENT)), _, _) => {
69 segments.push(ast::PathSegment::cast(segment)?);
70 break;
71 }
72 (_, _, _) => return None,
73 }
74 }
75 // We need to reverse only the new added segments
76 let only_new_segments = segments.split_at_mut(oldlen).1;
77 only_new_segments.reverse();
78 return Some(segments.len() - oldlen);
79}
80
81fn fmt_segments(segments: &[&ast::PathSegment]) -> String {
82 let mut buf = String::new();
83 fmt_segments_raw(segments, &mut buf);
84 return buf;
85}
86
87fn fmt_segments_raw(segments: &[&ast::PathSegment], buf: &mut String) {
88 let mut first = true;
89 for s in segments {
90 if !first {
91 buf.push_str("::");
92 }
93 match s.kind() {
94 Some(ast::PathSegmentKind::Name(nameref)) => buf.push_str(nameref.text()),
95 Some(ast::PathSegmentKind::SelfKw) => buf.push_str("self"),
96 Some(ast::PathSegmentKind::SuperKw) => buf.push_str("super"),
97 Some(ast::PathSegmentKind::CrateKw) => buf.push_str("crate"),
98 None => {}
99 }
100 first = false;
101 }
102}
103
104#[derive(Copy, Clone)]
105enum PathSegmentsMatch {
106 // Patch matches exactly
107 Full,
108 // None of the segments matched. It's a more explicit Partial(0)
109 Empty,
110 // When some of the segments matched
111 Partial(usize),
112 // When all the segments of the right path are matched against the left path,
113 // but the left path is longer.
114 PartialLeft(usize),
115 // When all the segments of the left path are matched against the right path,
116 // but the right path is longer.
117 PartialRight(usize),
118 // In all the three cases above we keep track of how many segments matched
119}
120
121fn compare_path_segments(
122 left: &[&ast::PathSegment],
123 right: &[&ast::PathSegment],
124) -> PathSegmentsMatch {
125 let mut matching = 0;
126 for either_or_both in left.iter().zip_longest(right.iter()) {
127 match either_or_both {
128 EitherOrBoth::Both(left, right) => {
129 if compare_path_segment(left, right) {
130 matching += 1
131 } else {
132 return if matching == 0 {
133 PathSegmentsMatch::Empty
134 } else {
135 PathSegmentsMatch::Partial(matching)
136 };
137 }
138 }
139 EitherOrBoth::Left(_) => {
140 return PathSegmentsMatch::PartialLeft(matching);
141 }
142 EitherOrBoth::Right(_) => {
143 return PathSegmentsMatch::PartialRight(matching);
144 }
145 }
146 }
147 return PathSegmentsMatch::Full;
148}
149
150fn compare_path_segment(a: &ast::PathSegment, b: &ast::PathSegment) -> bool {
151 if let (Some(ka), Some(kb)) = (a.kind(), b.kind()) {
152 return match (ka, kb) {
153 (ast::PathSegmentKind::Name(nameref_a), ast::PathSegmentKind::Name(nameref_b)) => {
154 nameref_a.text() == nameref_b.text()
155 }
156 (ast::PathSegmentKind::SelfKw, ast::PathSegmentKind::SelfKw) => true,
157 (ast::PathSegmentKind::SuperKw, ast::PathSegmentKind::SuperKw) => true,
158 (ast::PathSegmentKind::CrateKw, ast::PathSegmentKind::CrateKw) => true,
159 (_, _) => false,
160 };
161 } else {
162 false
163 }
164}
165
166fn compare_path_segment_with_name(a: &ast::PathSegment, b: &ast::Name) -> bool {
167 if let Some(ka) = a.kind() {
168 return match (ka, b) {
169 (ast::PathSegmentKind::Name(nameref_a), _) => nameref_a.text() == b.text(),
170 (_, _) => false,
171 };
172 } else {
173 false
174 }
175}
176
177#[derive(Copy, Clone)]
178enum ImportAction<'a> {
179 Nothing,
180 // Add a brand new use statement.
181 AddNewUse(
182 Option<&'a SyntaxNode>, // anchor node
183 bool, // true if we want to add the new statement after the anchor
184 ),
185
186 // In the following actions we keep track of how may segments matched,
187 // so we can choose the best action to take.
188
189 // To split an existing use statement creating a nested import.
190 AddNestedImport(
191 usize,
192 &'a ast::Path, // the complete path we want to split
193 Option<&'a ast::PathSegment>, // the first segment of path we want to add into the new nested list
194 bool, // true if we want to add 'self' in addition to the segment
195 ),
196 // To add the target path to an existing nested import tree list.
197 AddInTreeList(
198 usize,
199 &'a ast::UseTreeList,
200 bool, // true if we want to add 'self'
201 ),
202}
203
204impl<'a> ImportAction<'a> {
205 fn better<'b>(left: &'b ImportAction<'a>, right: &'b ImportAction<'a>) -> &'b ImportAction<'a> {
206 if left.is_better(right) {
207 left
208 } else {
209 right
210 }
211 }
212
213 fn is_better(&self, other: &ImportAction) -> bool {
214 match (self, other) {
215 (ImportAction::Nothing, _) => true,
216 (ImportAction::AddInTreeList(..), ImportAction::Nothing) => false,
217 (ImportAction::AddNestedImport(n, ..), ImportAction::AddInTreeList(m, ..)) => n > m,
218 (ImportAction::AddInTreeList(n, ..), ImportAction::AddNestedImport(m, ..)) => n > m,
219 (ImportAction::AddInTreeList(..), _) => true,
220 (ImportAction::AddNestedImport(..), ImportAction::Nothing) => false,
221 (ImportAction::AddNestedImport(..), _) => true,
222 (ImportAction::AddNewUse(..), _) => false,
223 }
224 }
225}
226
227// Find out the best ImportAction to import target path against current_use_tree.
228// If current_use_tree has a nested import the function gets called recursively on every UseTree inside a UseTreeList.
229fn walk_use_tree_for_best_action<'b, 'c, 'a: 'b + 'c>(
230 current_path_segments: &'b mut Vec<&'a ast::PathSegment>, // buffer containing path segments
231 current_parent_use_tree_list: Option<&'a ast::UseTreeList>, // will be Some value if we are in a nested import
232 current_use_tree: &'a ast::UseTree, // the use tree we are currently examinating
233 target: &'c [&'a ast::PathSegment], // the path we want to import
234) -> ImportAction<'a> {
235 // We save the number of segments in the buffer so we can restore the correct segments
236 // before returning. Recursive call will add segments so we need to delete them.
237 let prev_len = current_path_segments.len();
238
239 let tree_list = current_use_tree.use_tree_list();
240 let alias = current_use_tree.alias();
241
242 let path = match current_use_tree.path() {
243 Some(path) => path,
244 None => {
245 // If the use item don't have a path, it means it's broken (syntax error)
246 return ImportAction::AddNewUse(
247 current_use_tree
248 .syntax()
249 .ancestors()
250 .find_map(ast::UseItem::cast)
251 .map(AstNode::syntax),
252 true,
253 );
254 }
255 };
256
257 // This can happen only if current_use_tree is a direct child of a UseItem
258 if let Some(name) = alias.and_then(ast::NameOwner::name) {
259 if compare_path_segment_with_name(target[0], name) {
260 return ImportAction::Nothing;
261 }
262 }
263
264 collect_path_segments_raw(current_path_segments, path);
265
266 // We compare only the new segments added in the line just above.
267 // The first prev_len segments were already compared in 'parent' recursive calls.
268 let c = compare_path_segments(
269 target.split_at(prev_len).1,
270 current_path_segments.split_at(prev_len).1,
271 );
272
273 let mut action = match c {
274 PathSegmentsMatch::Full => {
275 // e.g: target is std::fmt and we can have
276 // 1- use std::fmt;
277 // 2- use std::fmt:{ ... }
278 if let Some(list) = tree_list {
279 // In case 2 we need to add self to the nested list
280 // unless it's already there
281 let has_self = list.use_trees().map(ast::UseTree::path).any(|p| {
282 p.and_then(ast::Path::segment)
283 .and_then(ast::PathSegment::kind)
284 .filter(|k| *k == ast::PathSegmentKind::SelfKw)
285 .is_some()
286 });
287
288 if has_self {
289 ImportAction::Nothing
290 } else {
291 ImportAction::AddInTreeList(current_path_segments.len(), list, true)
292 }
293 } else {
294 // Case 1
295 ImportAction::Nothing
296 }
297 }
298 PathSegmentsMatch::Empty => ImportAction::AddNewUse(
299 // e.g: target is std::fmt and we can have
300 // use foo::bar
301 // We add a brand new use statement
302 current_use_tree
303 .syntax()
304 .ancestors()
305 .find_map(ast::UseItem::cast)
306 .map(AstNode::syntax),
307 true,
308 ),
309 PathSegmentsMatch::Partial(n) => {
310 // e.g: target is std::fmt and we have
311 // use std::io;
312 // We need to split.
313 let segments_to_split = current_path_segments.split_at(prev_len + n).1;
314 ImportAction::AddNestedImport(prev_len + n, path, Some(segments_to_split[0]), false)
315 }
316 PathSegmentsMatch::PartialLeft(n) => {
317 // e.g: target is std::fmt and we can have
318 // 1- use std;
319 // 2- use std::{ ... };
320
321 // fallback action
322 let mut better_action = ImportAction::AddNewUse(
323 current_use_tree
324 .syntax()
325 .ancestors()
326 .find_map(ast::UseItem::cast)
327 .map(AstNode::syntax),
328 true,
329 );
330 if let Some(list) = tree_list {
331 // Case 2, check recursively if the path is already imported in the nested list
332 for u in list.use_trees() {
333 let child_action =
334 walk_use_tree_for_best_action(current_path_segments, Some(list), u, target);
335 if child_action.is_better(&better_action) {
336 better_action = child_action;
337 if let ImportAction::Nothing = better_action {
338 return better_action;
339 }
340 }
341 }
342 } else {
343 // Case 1, split
344 better_action = ImportAction::AddNestedImport(prev_len + n, path, None, true)
345 }
346 better_action
347 }
348 PathSegmentsMatch::PartialRight(n) => {
349 // e.g: target std::fmt and we can have
350 // use std::fmt::Debug;
351 let segments_to_split = current_path_segments.split_at(prev_len + n).1;
352 ImportAction::AddNestedImport(prev_len + n, path, Some(segments_to_split[0]), true)
353 }
354 };
355
356 // If we are inside a UseTreeList adding a use statement become adding to the existing
357 // tree list.
358 action = match (current_parent_use_tree_list, action) {
359 (Some(use_tree_list), ImportAction::AddNewUse(..)) => {
360 ImportAction::AddInTreeList(prev_len, use_tree_list, false)
361 }
362 (_, _) => action,
363 };
364
365 // We remove the segments added
366 current_path_segments.truncate(prev_len);
367 return action;
368}
369
370fn best_action_for_target<'b, 'a: 'b>(
371 container: &'a SyntaxNode,
372 path: &'a ast::Path,
373 target: &'b [&'a ast::PathSegment],
374) -> ImportAction<'a> {
375 let mut storage = Vec::with_capacity(16); // this should be the only allocation
376 let best_action = container
377 .children()
378 .filter_map(ast::UseItem::cast)
379 .filter_map(ast::UseItem::use_tree)
380 .map(|u| walk_use_tree_for_best_action(&mut storage, None, u, target))
381 .fold(None, |best, a| {
382 best.and_then(|best| Some(*ImportAction::better(&best, &a)))
383 .or(Some(a))
384 });
385
386 match best_action {
387 Some(action) => return action,
388 None => {
389 // We have no action we no use item was found in container so we find
390 // another item and we use it as anchor.
391 // If there are not items, we choose the target path itself as anchor.
392 let anchor = container
393 .children()
394 .find_map(ast::ModuleItem::cast)
395 .map(AstNode::syntax)
396 .or(Some(path.syntax()));
397
398 return ImportAction::AddNewUse(anchor, false);
399 }
400 }
401}
402
403fn make_assist(action: &ImportAction, target: &[&ast::PathSegment], edit: &mut AssistBuilder) {
404 match action {
405 ImportAction::AddNewUse(anchor, after) => {
406 make_assist_add_new_use(anchor, *after, target, edit)
407 }
408 ImportAction::AddInTreeList(n, tree_list_node, add_self) => {
409 // We know that the fist n segments already exists in the use statement we want
410 // to modify, so we want to add only the last target.len() - n segments.
411 let segments_to_add = target.split_at(*n).1;
412 make_assist_add_in_tree_list(tree_list_node, segments_to_add, *add_self, edit)
413 }
414 ImportAction::AddNestedImport(n, path, first_segment_to_split, add_self) => {
415 let segments_to_add = target.split_at(*n).1;
416 make_assist_add_nested_import(
417 path,
418 first_segment_to_split,
419 segments_to_add,
420 *add_self,
421 edit,
422 )
423 }
424 _ => {}
425 }
426}
427
428fn make_assist_add_new_use(
429 anchor: &Option<&SyntaxNode>,
430 after: bool,
431 target: &[&ast::PathSegment],
432 edit: &mut AssistBuilder,
433) {
434 if let Some(anchor) = anchor {
435 let indent = formatting::leading_indent(anchor);
436 let mut buf = String::new();
437 if after {
438 buf.push_str("\n");
439 if let Some(spaces) = indent {
440 buf.push_str(spaces);
441 }
442 }
443 buf.push_str("use ");
444 fmt_segments_raw(target, &mut buf);
445 buf.push_str(";");
446 if !after {
447 buf.push_str("\n\n");
448 if let Some(spaces) = indent {
449 buf.push_str(spaces);
450 }
451 }
452 let position = if after {
453 anchor.range().end()
454 } else {
455 anchor.range().start()
456 };
457 edit.insert(position, buf);
458 }
459}
460
461fn make_assist_add_in_tree_list(
462 tree_list: &ast::UseTreeList,
463 target: &[&ast::PathSegment],
464 add_self: bool,
465 edit: &mut AssistBuilder,
466) {
467 let last = tree_list.use_trees().last();
468 if let Some(last) = last {
469 let mut buf = String::new();
470 let comma = last
471 .syntax()
472 .siblings(Direction::Next)
473 .find(|n| n.kind() == COMMA);
474 let offset = if let Some(comma) = comma {
475 comma.range().end()
476 } else {
477 buf.push_str(",");
478 last.syntax().range().end()
479 };
480 if add_self {
481 buf.push_str(" self")
482 } else {
483 buf.push_str(" ");
484 }
485 fmt_segments_raw(target, &mut buf);
486 edit.insert(offset, buf);
487 } else {
488
489 }
490}
491
492fn make_assist_add_nested_import(
493 path: &ast::Path,
494 first_segment_to_split: &Option<&ast::PathSegment>,
495 target: &[&ast::PathSegment],
496 add_self: bool,
497 edit: &mut AssistBuilder,
498) {
499 let use_tree = path.syntax().ancestors().find_map(ast::UseTree::cast);
500 if let Some(use_tree) = use_tree {
501 let (start, add_colon_colon) = if let Some(first_segment_to_split) = first_segment_to_split
502 {
503 (first_segment_to_split.syntax().range().start(), false)
504 } else {
505 (use_tree.syntax().range().end(), true)
506 };
507 let end = use_tree.syntax().range().end();
508
509 let mut buf = String::new();
510 if add_colon_colon {
511 buf.push_str("::");
512 }
513 buf.push_str("{ ");
514 if add_self {
515 buf.push_str("self, ");
516 }
517 fmt_segments_raw(target, &mut buf);
518 if !target.is_empty() {
519 buf.push_str(", ");
520 }
521 edit.insert(start, buf);
522 edit.insert(end, "}");
523 }
524}
525
526pub(crate) fn auto_import(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
527 let node = ctx.covering_node();
528 let current_file = node.ancestors().find_map(ast::SourceFile::cast)?;
529
530 let path = node.ancestors().find_map(ast::Path::cast)?;
531 // We don't want to mess with use statements
532 if path
533 .syntax()
534 .ancestors()
535 .find_map(ast::UseItem::cast)
536 .is_some()
537 {
538 return None;
539 }
540
541 let segments = collect_path_segments(path)?;
542 if segments.len() < 2 {
543 return None;
544 }
545
546 ctx.build(
547 format!("import {} in the current file", fmt_segments(&segments)),
548 |edit| {
549 let action = best_action_for_target(current_file.syntax(), path, &segments);
550 make_assist(&action, segments.as_slice(), edit);
551 if let Some(last_segment) = path.segment() {
552 // Here we are assuming the assist will provide a correct use statement
553 // so we can delete the path qualifier
554 edit.delete(TextRange::from_to(
555 path.syntax().range().start(),
556 last_segment.syntax().range().start(),
557 ));
558 }
559 },
560 )
561}
562
563#[cfg(test)]
564mod tests {
565 use super::*;
566 use crate::helpers::{ check_assist, check_assist_not_applicable };
567
568 #[test]
569 fn test_auto_import_file_add_use_no_anchor() {
570 check_assist(
571 auto_import,
572 "
573std::fmt::Debug<|>
574 ",
575 "
576use std::fmt::Debug;
577
578Debug<|>
579 ",
580 );
581 }
582
583 #[test]
584 fn test_auto_import_file_add_use() {
585 check_assist(
586 auto_import,
587 "
588use stdx;
589
590impl std::fmt::Debug<|> for Foo {
591}
592 ",
593 "
594use stdx;
595use std::fmt::Debug;
596
597impl Debug<|> for Foo {
598}
599 ",
600 );
601 }
602
603 #[test]
604 fn test_auto_import_file_add_use_other_anchor() {
605 check_assist(
606 auto_import,
607 "
608impl std::fmt::Debug<|> for Foo {
609}
610 ",
611 "
612use std::fmt::Debug;
613
614impl Debug<|> for Foo {
615}
616 ",
617 );
618 }
619
620 #[test]
621 fn test_auto_import_file_add_use_other_anchor_indent() {
622 check_assist(
623 auto_import,
624 "
625 impl std::fmt::Debug<|> for Foo {
626 }
627 ",
628 "
629 use std::fmt::Debug;
630
631 impl Debug<|> for Foo {
632 }
633 ",
634 );
635 }
636
637 #[test]
638 fn test_auto_import_file_split_different() {
639 check_assist(
640 auto_import,
641 "
642use std::fmt;
643
644impl std::io<|> for Foo {
645}
646 ",
647 "
648use std::{ io, fmt};
649
650impl io<|> for Foo {
651}
652 ",
653 );
654 }
655
656 #[test]
657 fn test_auto_import_file_split_self_for_use() {
658 check_assist(
659 auto_import,
660 "
661use std::fmt;
662
663impl std::fmt::Debug<|> for Foo {
664}
665 ",
666 "
667use std::fmt::{ self, Debug, };
668
669impl Debug<|> for Foo {
670}
671 ",
672 );
673 }
674
675 #[test]
676 fn test_auto_import_file_split_self_for_target() {
677 check_assist(
678 auto_import,
679 "
680use std::fmt::Debug;
681
682impl std::fmt<|> for Foo {
683}
684 ",
685 "
686use std::fmt::{ self, Debug};
687
688impl fmt<|> for Foo {
689}
690 ",
691 );
692 }
693
694 #[test]
695 fn test_auto_import_file_add_to_nested_self_nested() {
696 check_assist(
697 auto_import,
698 "
699use std::fmt::{Debug, nested::{Display}};
700
701impl std::fmt::nested<|> for Foo {
702}
703",
704 "
705use std::fmt::{Debug, nested::{Display, self}};
706
707impl nested<|> for Foo {
708}
709",
710 );
711 }
712
713 #[test]
714 fn test_auto_import_file_add_to_nested_self_already_included() {
715 check_assist(
716 auto_import,
717 "
718use std::fmt::{Debug, nested::{self, Display}};
719
720impl std::fmt::nested<|> for Foo {
721}
722",
723 "
724use std::fmt::{Debug, nested::{self, Display}};
725
726impl nested<|> for Foo {
727}
728",
729 );
730 }
731
732 #[test]
733 fn test_auto_import_file_add_to_nested_nested() {
734 check_assist(
735 auto_import,
736 "
737use std::fmt::{Debug, nested::{Display}};
738
739impl std::fmt::nested::Debug<|> for Foo {
740}
741",
742 "
743use std::fmt::{Debug, nested::{Display, Debug}};
744
745impl Debug<|> for Foo {
746}
747",
748 );
749 }
750
751 #[test]
752 fn test_auto_import_file_alias() {
753 check_assist(
754 auto_import,
755 "
756use std::fmt as foo;
757
758impl foo::Debug<|> for Foo {
759}
760",
761 "
762use std::fmt as foo;
763
764impl Debug<|> for Foo {
765}
766",
767 );
768 }
769
770 #[test]
771 fn test_auto_import_not_applicable_one_segment() {
772 check_assist_not_applicable(
773 auto_import,
774 "
775impl foo<|> for Foo {
776}
777",
778 );
779 }
780}
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs
index 7928b4983..af578893e 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -84,6 +84,8 @@ mod introduce_variable;
84mod replace_if_let_with_match; 84mod replace_if_let_with_match;
85mod split_import; 85mod split_import;
86mod remove_dbg; 86mod remove_dbg;
87mod auto_import;
88
87fn all_assists<DB: HirDatabase>() -> &'static [fn(AssistCtx<DB>) -> Option<Assist>] { 89fn all_assists<DB: HirDatabase>() -> &'static [fn(AssistCtx<DB>) -> Option<Assist>] {
88 &[ 90 &[
89 add_derive::add_derive, 91 add_derive::add_derive,
@@ -95,6 +97,7 @@ fn all_assists<DB: HirDatabase>() -> &'static [fn(AssistCtx<DB>) -> Option<Assis
95 replace_if_let_with_match::replace_if_let_with_match, 97 replace_if_let_with_match::replace_if_let_with_match,
96 split_import::split_import, 98 split_import::split_import,
97 remove_dbg::remove_dbg, 99 remove_dbg::remove_dbg,
100 auto_import::auto_import,
98 ] 101 ]
99} 102}
100 103