diff options
Diffstat (limited to 'crates/assists/src/utils/insert_use.rs')
-rw-r--r-- | crates/assists/src/utils/insert_use.rs | 112 |
1 files changed, 86 insertions, 26 deletions
diff --git a/crates/assists/src/utils/insert_use.rs b/crates/assists/src/utils/insert_use.rs index 84a0dffdd..423782a0e 100644 --- a/crates/assists/src/utils/insert_use.rs +++ b/crates/assists/src/utils/insert_use.rs | |||
@@ -1,6 +1,8 @@ | |||
1 | //! Handle syntactic aspects of inserting a new `use`. | 1 | //! Handle syntactic aspects of inserting a new `use`. |
2 | use std::{cmp::Ordering, iter::successors}; | 2 | use std::{cmp::Ordering, iter::successors}; |
3 | 3 | ||
4 | use hir::Semantics; | ||
5 | use ide_db::RootDatabase; | ||
4 | use itertools::{EitherOrBoth, Itertools}; | 6 | use itertools::{EitherOrBoth, Itertools}; |
5 | use syntax::{ | 7 | use syntax::{ |
6 | algo::SyntaxRewriter, | 8 | algo::SyntaxRewriter, |
@@ -9,12 +11,12 @@ use syntax::{ | |||
9 | edit::{AstNodeEdit, IndentLevel}, | 11 | edit::{AstNodeEdit, IndentLevel}, |
10 | make, AstNode, PathSegmentKind, VisibilityOwner, | 12 | make, AstNode, PathSegmentKind, VisibilityOwner, |
11 | }, | 13 | }, |
12 | InsertPosition, SyntaxElement, SyntaxNode, | 14 | AstToken, InsertPosition, NodeOrToken, SyntaxElement, SyntaxNode, SyntaxToken, |
13 | }; | 15 | }; |
14 | use test_utils::mark; | 16 | use test_utils::mark; |
15 | 17 | ||
16 | #[derive(Debug)] | 18 | #[derive(Debug, Clone)] |
17 | pub(crate) enum ImportScope { | 19 | pub enum ImportScope { |
18 | File(ast::SourceFile), | 20 | File(ast::SourceFile), |
19 | Module(ast::ItemList), | 21 | Module(ast::ItemList), |
20 | } | 22 | } |
@@ -31,14 +33,14 @@ impl ImportScope { | |||
31 | } | 33 | } |
32 | 34 | ||
33 | /// Determines the containing syntax node in which to insert a `use` statement affecting `position`. | 35 | /// Determines the containing syntax node in which to insert a `use` statement affecting `position`. |
34 | pub(crate) fn find_insert_use_container( | 36 | pub fn find_insert_use_container( |
35 | position: &SyntaxNode, | 37 | position: &SyntaxNode, |
36 | ctx: &crate::assist_context::AssistContext, | 38 | sema: &Semantics<'_, RootDatabase>, |
37 | ) -> Option<Self> { | 39 | ) -> Option<Self> { |
38 | ctx.sema.ancestors_with_macros(position.clone()).find_map(Self::from) | 40 | sema.ancestors_with_macros(position.clone()).find_map(Self::from) |
39 | } | 41 | } |
40 | 42 | ||
41 | pub(crate) fn as_syntax_node(&self) -> &SyntaxNode { | 43 | pub fn as_syntax_node(&self) -> &SyntaxNode { |
42 | match self { | 44 | match self { |
43 | ImportScope::File(file) => file.syntax(), | 45 | ImportScope::File(file) => file.syntax(), |
44 | ImportScope::Module(item_list) => item_list.syntax(), | 46 | ImportScope::Module(item_list) => item_list.syntax(), |
@@ -63,29 +65,32 @@ impl ImportScope { | |||
63 | } | 65 | } |
64 | } | 66 | } |
65 | 67 | ||
66 | fn insert_pos_after_inner_attribute(&self) -> (InsertPosition<SyntaxElement>, AddBlankLine) { | 68 | fn insert_pos_after_last_inner_element(&self) -> (InsertPosition<SyntaxElement>, AddBlankLine) { |
67 | // check if the scope has inner attributes, we dont want to insert in front of them | 69 | self.as_syntax_node() |
68 | match self | 70 | .children_with_tokens() |
69 | .as_syntax_node() | 71 | .filter(|child| match child { |
70 | .children() | 72 | NodeOrToken::Node(node) => is_inner_attribute(node.clone()), |
71 | // no flat_map here cause we want to short circuit the iterator | 73 | NodeOrToken::Token(token) => is_inner_comment(token.clone()), |
72 | .map(ast::Attr::cast) | ||
73 | .take_while(|attr| { | ||
74 | attr.as_ref().map(|attr| attr.kind() == ast::AttrKind::Inner).unwrap_or(false) | ||
75 | }) | 74 | }) |
76 | .last() | 75 | .last() |
77 | .flatten() | 76 | .map(|last_inner_element| { |
78 | { | 77 | (InsertPosition::After(last_inner_element.into()), AddBlankLine::BeforeTwice) |
79 | Some(attr) => { | 78 | }) |
80 | (InsertPosition::After(attr.syntax().clone().into()), AddBlankLine::BeforeTwice) | 79 | .unwrap_or_else(|| self.first_insert_pos()) |
81 | } | ||
82 | None => self.first_insert_pos(), | ||
83 | } | ||
84 | } | 80 | } |
85 | } | 81 | } |
86 | 82 | ||
83 | fn is_inner_attribute(node: SyntaxNode) -> bool { | ||
84 | ast::Attr::cast(node).map(|attr| attr.kind()) == Some(ast::AttrKind::Inner) | ||
85 | } | ||
86 | |||
87 | fn is_inner_comment(token: SyntaxToken) -> bool { | ||
88 | ast::Comment::cast(token).and_then(|comment| comment.kind().doc) | ||
89 | == Some(ast::CommentPlacement::Inner) | ||
90 | } | ||
91 | |||
87 | /// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur. | 92 | /// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur. |
88 | pub(crate) fn insert_use<'a>( | 93 | pub fn insert_use<'a>( |
89 | scope: &ImportScope, | 94 | scope: &ImportScope, |
90 | path: ast::Path, | 95 | path: ast::Path, |
91 | merge: Option<MergeBehaviour>, | 96 | merge: Option<MergeBehaviour>, |
@@ -558,7 +563,7 @@ fn find_insert_position( | |||
558 | (InsertPosition::After(node.into()), AddBlankLine::BeforeTwice) | 563 | (InsertPosition::After(node.into()), AddBlankLine::BeforeTwice) |
559 | } | 564 | } |
560 | // there are no imports in this file at all | 565 | // there are no imports in this file at all |
561 | None => scope.insert_pos_after_inner_attribute(), | 566 | None => scope.insert_pos_after_last_inner_element(), |
562 | }, | 567 | }, |
563 | } | 568 | } |
564 | } | 569 | } |
@@ -830,12 +835,67 @@ use foo::bar;", | |||
830 | "foo::bar", | 835 | "foo::bar", |
831 | r"#![allow(unused_imports)] | 836 | r"#![allow(unused_imports)] |
832 | 837 | ||
838 | #![no_std] | ||
833 | fn main() {}", | 839 | fn main() {}", |
834 | r"#![allow(unused_imports)] | 840 | r"#![allow(unused_imports)] |
835 | 841 | ||
836 | use foo::bar; | 842 | #![no_std] |
837 | 843 | ||
844 | use foo::bar; | ||
838 | fn main() {}", | 845 | fn main() {}", |
846 | ); | ||
847 | } | ||
848 | |||
849 | #[test] | ||
850 | fn inserts_after_single_line_inner_comments() { | ||
851 | check_none( | ||
852 | "foo::bar::Baz", | ||
853 | "//! Single line inner comments do not allow any code before them.", | ||
854 | r#"//! Single line inner comments do not allow any code before them. | ||
855 | |||
856 | use foo::bar::Baz;"#, | ||
857 | ); | ||
858 | } | ||
859 | |||
860 | #[test] | ||
861 | fn inserts_after_multiline_inner_comments() { | ||
862 | check_none( | ||
863 | "foo::bar::Baz", | ||
864 | r#"/*! Multiline inner comments do not allow any code before them. */ | ||
865 | |||
866 | /*! Still an inner comment, cannot place any code before. */ | ||
867 | fn main() {}"#, | ||
868 | r#"/*! Multiline inner comments do not allow any code before them. */ | ||
869 | |||
870 | /*! Still an inner comment, cannot place any code before. */ | ||
871 | |||
872 | use foo::bar::Baz; | ||
873 | fn main() {}"#, | ||
874 | ) | ||
875 | } | ||
876 | |||
877 | #[test] | ||
878 | fn inserts_after_all_inner_items() { | ||
879 | check_none( | ||
880 | "foo::bar::Baz", | ||
881 | r#"#![allow(unused_imports)] | ||
882 | /*! Multiline line comment 2 */ | ||
883 | |||
884 | |||
885 | //! Single line comment 1 | ||
886 | #![no_std] | ||
887 | //! Single line comment 2 | ||
888 | fn main() {}"#, | ||
889 | r#"#![allow(unused_imports)] | ||
890 | /*! Multiline line comment 2 */ | ||
891 | |||
892 | |||
893 | //! Single line comment 1 | ||
894 | #![no_std] | ||
895 | //! Single line comment 2 | ||
896 | |||
897 | use foo::bar::Baz; | ||
898 | fn main() {}"#, | ||
839 | ) | 899 | ) |
840 | } | 900 | } |
841 | 901 | ||