diff options
author | Aleksey Kladov <[email protected]> | 2019-01-08 19:17:36 +0000 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2019-01-08 19:17:36 +0000 |
commit | 1967884d6836219ee78a754ca5c66ac781351559 (patch) | |
tree | 7594f37cd0a5200eb097b9d472c61f0223d01d05 /crates/ra_ide_api_light/src/assists | |
parent | 4f4f7933b1b7ff34f8633b1686b18b2d1b994c47 (diff) |
rename ra_editor -> ra_ide_api_light
Diffstat (limited to 'crates/ra_ide_api_light/src/assists')
7 files changed, 589 insertions, 0 deletions
diff --git a/crates/ra_ide_api_light/src/assists/add_derive.rs b/crates/ra_ide_api_light/src/assists/add_derive.rs new file mode 100644 index 000000000..6e964d011 --- /dev/null +++ b/crates/ra_ide_api_light/src/assists/add_derive.rs | |||
@@ -0,0 +1,84 @@ | |||
1 | use ra_syntax::{ | ||
2 | ast::{self, AstNode, AttrsOwner}, | ||
3 | SyntaxKind::{WHITESPACE, COMMENT}, | ||
4 | TextUnit, | ||
5 | }; | ||
6 | |||
7 | use crate::assists::{AssistCtx, Assist}; | ||
8 | |||
9 | pub fn add_derive(ctx: AssistCtx) -> Option<Assist> { | ||
10 | let nominal = ctx.node_at_offset::<ast::NominalDef>()?; | ||
11 | let node_start = derive_insertion_offset(nominal)?; | ||
12 | ctx.build("add `#[derive]`", |edit| { | ||
13 | let derive_attr = nominal | ||
14 | .attrs() | ||
15 | .filter_map(|x| x.as_call()) | ||
16 | .filter(|(name, _arg)| name == "derive") | ||
17 | .map(|(_name, arg)| arg) | ||
18 | .next(); | ||
19 | let offset = match derive_attr { | ||
20 | None => { | ||
21 | edit.insert(node_start, "#[derive()]\n"); | ||
22 | node_start + TextUnit::of_str("#[derive(") | ||
23 | } | ||
24 | Some(tt) => tt.syntax().range().end() - TextUnit::of_char(')'), | ||
25 | }; | ||
26 | edit.set_cursor(offset) | ||
27 | }) | ||
28 | } | ||
29 | |||
30 | // Insert `derive` after doc comments. | ||
31 | fn derive_insertion_offset(nominal: &ast::NominalDef) -> Option<TextUnit> { | ||
32 | let non_ws_child = nominal | ||
33 | .syntax() | ||
34 | .children() | ||
35 | .find(|it| it.kind() != COMMENT && it.kind() != WHITESPACE)?; | ||
36 | Some(non_ws_child.range().start()) | ||
37 | } | ||
38 | |||
39 | #[cfg(test)] | ||
40 | mod tests { | ||
41 | use super::*; | ||
42 | use crate::assists::check_assist; | ||
43 | |||
44 | #[test] | ||
45 | fn add_derive_new() { | ||
46 | check_assist( | ||
47 | add_derive, | ||
48 | "struct Foo { a: i32, <|>}", | ||
49 | "#[derive(<|>)]\nstruct Foo { a: i32, }", | ||
50 | ); | ||
51 | check_assist( | ||
52 | add_derive, | ||
53 | "struct Foo { <|> a: i32, }", | ||
54 | "#[derive(<|>)]\nstruct Foo { a: i32, }", | ||
55 | ); | ||
56 | } | ||
57 | |||
58 | #[test] | ||
59 | fn add_derive_existing() { | ||
60 | check_assist( | ||
61 | add_derive, | ||
62 | "#[derive(Clone)]\nstruct Foo { a: i32<|>, }", | ||
63 | "#[derive(Clone<|>)]\nstruct Foo { a: i32, }", | ||
64 | ); | ||
65 | } | ||
66 | |||
67 | #[test] | ||
68 | fn add_derive_new_with_doc_comment() { | ||
69 | check_assist( | ||
70 | add_derive, | ||
71 | " | ||
72 | /// `Foo` is a pretty important struct. | ||
73 | /// It does stuff. | ||
74 | struct Foo { a: i32<|>, } | ||
75 | ", | ||
76 | " | ||
77 | /// `Foo` is a pretty important struct. | ||
78 | /// It does stuff. | ||
79 | #[derive(<|>)] | ||
80 | struct Foo { a: i32, } | ||
81 | ", | ||
82 | ); | ||
83 | } | ||
84 | } | ||
diff --git a/crates/ra_ide_api_light/src/assists/add_impl.rs b/crates/ra_ide_api_light/src/assists/add_impl.rs new file mode 100644 index 000000000..2eda7cae2 --- /dev/null +++ b/crates/ra_ide_api_light/src/assists/add_impl.rs | |||
@@ -0,0 +1,66 @@ | |||
1 | use join_to_string::join; | ||
2 | use ra_syntax::{ | ||
3 | ast::{self, AstNode, AstToken, NameOwner, TypeParamsOwner}, | ||
4 | TextUnit, | ||
5 | }; | ||
6 | |||
7 | use crate::assists::{AssistCtx, Assist}; | ||
8 | |||
9 | pub fn add_impl(ctx: AssistCtx) -> Option<Assist> { | ||
10 | let nominal = ctx.node_at_offset::<ast::NominalDef>()?; | ||
11 | let name = nominal.name()?; | ||
12 | ctx.build("add impl", |edit| { | ||
13 | let type_params = nominal.type_param_list(); | ||
14 | let start_offset = nominal.syntax().range().end(); | ||
15 | let mut buf = String::new(); | ||
16 | buf.push_str("\n\nimpl"); | ||
17 | if let Some(type_params) = type_params { | ||
18 | type_params.syntax().text().push_to(&mut buf); | ||
19 | } | ||
20 | buf.push_str(" "); | ||
21 | buf.push_str(name.text().as_str()); | ||
22 | if let Some(type_params) = type_params { | ||
23 | let lifetime_params = type_params | ||
24 | .lifetime_params() | ||
25 | .filter_map(|it| it.lifetime()) | ||
26 | .map(|it| it.text()); | ||
27 | let type_params = type_params | ||
28 | .type_params() | ||
29 | .filter_map(|it| it.name()) | ||
30 | .map(|it| it.text()); | ||
31 | join(lifetime_params.chain(type_params)) | ||
32 | .surround_with("<", ">") | ||
33 | .to_buf(&mut buf); | ||
34 | } | ||
35 | buf.push_str(" {\n"); | ||
36 | edit.set_cursor(start_offset + TextUnit::of_str(&buf)); | ||
37 | buf.push_str("\n}"); | ||
38 | edit.insert(start_offset, buf); | ||
39 | }) | ||
40 | } | ||
41 | |||
42 | #[cfg(test)] | ||
43 | mod tests { | ||
44 | use super::*; | ||
45 | use crate::assists::check_assist; | ||
46 | |||
47 | #[test] | ||
48 | fn test_add_impl() { | ||
49 | check_assist( | ||
50 | add_impl, | ||
51 | "struct Foo {<|>}\n", | ||
52 | "struct Foo {}\n\nimpl Foo {\n<|>\n}\n", | ||
53 | ); | ||
54 | check_assist( | ||
55 | add_impl, | ||
56 | "struct Foo<T: Clone> {<|>}", | ||
57 | "struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n<|>\n}", | ||
58 | ); | ||
59 | check_assist( | ||
60 | add_impl, | ||
61 | "struct Foo<'a, T: Foo<'a>> {<|>}", | ||
62 | "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n<|>\n}", | ||
63 | ); | ||
64 | } | ||
65 | |||
66 | } | ||
diff --git a/crates/ra_ide_api_light/src/assists/change_visibility.rs b/crates/ra_ide_api_light/src/assists/change_visibility.rs new file mode 100644 index 000000000..89729e2c2 --- /dev/null +++ b/crates/ra_ide_api_light/src/assists/change_visibility.rs | |||
@@ -0,0 +1,116 @@ | |||
1 | use ra_syntax::{ | ||
2 | AstNode, | ||
3 | ast::{self, VisibilityOwner, NameOwner}, | ||
4 | SyntaxKind::{VISIBILITY, FN_KW, MOD_KW, STRUCT_KW, ENUM_KW, TRAIT_KW, FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF, IDENT}, | ||
5 | }; | ||
6 | |||
7 | use crate::assists::{AssistCtx, Assist}; | ||
8 | |||
9 | pub fn change_visibility(ctx: AssistCtx) -> Option<Assist> { | ||
10 | if let Some(vis) = ctx.node_at_offset::<ast::Visibility>() { | ||
11 | return change_vis(ctx, vis); | ||
12 | } | ||
13 | add_vis(ctx) | ||
14 | } | ||
15 | |||
16 | fn add_vis(ctx: AssistCtx) -> Option<Assist> { | ||
17 | let item_keyword = ctx.leaf_at_offset().find(|leaf| match leaf.kind() { | ||
18 | FN_KW | MOD_KW | STRUCT_KW | ENUM_KW | TRAIT_KW => true, | ||
19 | _ => false, | ||
20 | }); | ||
21 | |||
22 | let offset = if let Some(keyword) = item_keyword { | ||
23 | let parent = keyword.parent()?; | ||
24 | let def_kws = vec![FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF]; | ||
25 | // Parent is not a definition, can't add visibility | ||
26 | if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) { | ||
27 | return None; | ||
28 | } | ||
29 | // Already have visibility, do nothing | ||
30 | if parent.children().any(|child| child.kind() == VISIBILITY) { | ||
31 | return None; | ||
32 | } | ||
33 | parent.range().start() | ||
34 | } else { | ||
35 | let ident = ctx.leaf_at_offset().find(|leaf| leaf.kind() == IDENT)?; | ||
36 | let field = ident.ancestors().find_map(ast::NamedFieldDef::cast)?; | ||
37 | if field.name()?.syntax().range() != ident.range() && field.visibility().is_some() { | ||
38 | return None; | ||
39 | } | ||
40 | field.syntax().range().start() | ||
41 | }; | ||
42 | |||
43 | ctx.build("make pub(crate)", |edit| { | ||
44 | edit.insert(offset, "pub(crate) "); | ||
45 | edit.set_cursor(offset); | ||
46 | }) | ||
47 | } | ||
48 | |||
49 | fn change_vis(ctx: AssistCtx, vis: &ast::Visibility) -> Option<Assist> { | ||
50 | if vis.syntax().text() != "pub" { | ||
51 | return None; | ||
52 | } | ||
53 | ctx.build("chage to pub(crate)", |edit| { | ||
54 | edit.replace(vis.syntax().range(), "pub(crate)"); | ||
55 | edit.set_cursor(vis.syntax().range().start()); | ||
56 | }) | ||
57 | } | ||
58 | |||
59 | #[cfg(test)] | ||
60 | mod tests { | ||
61 | use super::*; | ||
62 | use crate::assists::check_assist; | ||
63 | |||
64 | #[test] | ||
65 | fn change_visibility_adds_pub_crate_to_items() { | ||
66 | check_assist( | ||
67 | change_visibility, | ||
68 | "<|>fn foo() {}", | ||
69 | "<|>pub(crate) fn foo() {}", | ||
70 | ); | ||
71 | check_assist( | ||
72 | change_visibility, | ||
73 | "f<|>n foo() {}", | ||
74 | "<|>pub(crate) fn foo() {}", | ||
75 | ); | ||
76 | check_assist( | ||
77 | change_visibility, | ||
78 | "<|>struct Foo {}", | ||
79 | "<|>pub(crate) struct Foo {}", | ||
80 | ); | ||
81 | check_assist( | ||
82 | change_visibility, | ||
83 | "<|>mod foo {}", | ||
84 | "<|>pub(crate) mod foo {}", | ||
85 | ); | ||
86 | check_assist( | ||
87 | change_visibility, | ||
88 | "<|>trait Foo {}", | ||
89 | "<|>pub(crate) trait Foo {}", | ||
90 | ); | ||
91 | check_assist(change_visibility, "m<|>od {}", "<|>pub(crate) mod {}"); | ||
92 | check_assist( | ||
93 | change_visibility, | ||
94 | "unsafe f<|>n foo() {}", | ||
95 | "<|>pub(crate) unsafe fn foo() {}", | ||
96 | ); | ||
97 | } | ||
98 | |||
99 | #[test] | ||
100 | fn change_visibility_works_with_struct_fields() { | ||
101 | check_assist( | ||
102 | change_visibility, | ||
103 | "struct S { <|>field: u32 }", | ||
104 | "struct S { <|>pub(crate) field: u32 }", | ||
105 | ) | ||
106 | } | ||
107 | |||
108 | #[test] | ||
109 | fn change_visibility_pub_to_pub_crate() { | ||
110 | check_assist( | ||
111 | change_visibility, | ||
112 | "<|>pub fn foo() {}", | ||
113 | "<|>pub(crate) fn foo() {}", | ||
114 | ) | ||
115 | } | ||
116 | } | ||
diff --git a/crates/ra_ide_api_light/src/assists/flip_comma.rs b/crates/ra_ide_api_light/src/assists/flip_comma.rs new file mode 100644 index 000000000..a343413cc --- /dev/null +++ b/crates/ra_ide_api_light/src/assists/flip_comma.rs | |||
@@ -0,0 +1,31 @@ | |||
1 | use ra_syntax::{ | ||
2 | Direction, | ||
3 | SyntaxKind::COMMA, | ||
4 | }; | ||
5 | |||
6 | use crate::assists::{non_trivia_sibling, AssistCtx, Assist}; | ||
7 | |||
8 | pub fn flip_comma(ctx: AssistCtx) -> Option<Assist> { | ||
9 | let comma = ctx.leaf_at_offset().find(|leaf| leaf.kind() == COMMA)?; | ||
10 | let prev = non_trivia_sibling(comma, Direction::Prev)?; | ||
11 | let next = non_trivia_sibling(comma, Direction::Next)?; | ||
12 | ctx.build("flip comma", |edit| { | ||
13 | edit.replace(prev.range(), next.text()); | ||
14 | edit.replace(next.range(), prev.text()); | ||
15 | }) | ||
16 | } | ||
17 | |||
18 | #[cfg(test)] | ||
19 | mod tests { | ||
20 | use super::*; | ||
21 | use crate::assists::check_assist; | ||
22 | |||
23 | #[test] | ||
24 | fn flip_comma_works_for_function_parameters() { | ||
25 | check_assist( | ||
26 | flip_comma, | ||
27 | "fn foo(x: i32,<|> y: Result<(), ()>) {}", | ||
28 | "fn foo(y: Result<(), ()>,<|> x: i32) {}", | ||
29 | ) | ||
30 | } | ||
31 | } | ||
diff --git a/crates/ra_ide_api_light/src/assists/introduce_variable.rs b/crates/ra_ide_api_light/src/assists/introduce_variable.rs new file mode 100644 index 000000000..523ec7034 --- /dev/null +++ b/crates/ra_ide_api_light/src/assists/introduce_variable.rs | |||
@@ -0,0 +1,144 @@ | |||
1 | use ra_syntax::{ | ||
2 | ast::{self, AstNode}, | ||
3 | SyntaxKind::WHITESPACE, | ||
4 | SyntaxNode, TextUnit, | ||
5 | }; | ||
6 | |||
7 | use crate::assists::{AssistCtx, Assist}; | ||
8 | |||
9 | pub fn introduce_variable<'a>(ctx: AssistCtx) -> Option<Assist> { | ||
10 | let node = ctx.covering_node(); | ||
11 | let expr = node.ancestors().filter_map(ast::Expr::cast).next()?; | ||
12 | |||
13 | let anchor_stmt = anchor_stmt(expr)?; | ||
14 | let indent = anchor_stmt.prev_sibling()?; | ||
15 | if indent.kind() != WHITESPACE { | ||
16 | return None; | ||
17 | } | ||
18 | ctx.build("introduce variable", move |edit| { | ||
19 | let mut buf = String::new(); | ||
20 | |||
21 | buf.push_str("let var_name = "); | ||
22 | expr.syntax().text().push_to(&mut buf); | ||
23 | let is_full_stmt = if let Some(expr_stmt) = ast::ExprStmt::cast(anchor_stmt) { | ||
24 | Some(expr.syntax()) == expr_stmt.expr().map(|e| e.syntax()) | ||
25 | } else { | ||
26 | false | ||
27 | }; | ||
28 | if is_full_stmt { | ||
29 | edit.replace(expr.syntax().range(), buf); | ||
30 | } else { | ||
31 | buf.push_str(";"); | ||
32 | indent.text().push_to(&mut buf); | ||
33 | edit.replace(expr.syntax().range(), "var_name".to_string()); | ||
34 | edit.insert(anchor_stmt.range().start(), buf); | ||
35 | } | ||
36 | edit.set_cursor(anchor_stmt.range().start() + TextUnit::of_str("let ")); | ||
37 | }) | ||
38 | } | ||
39 | |||
40 | /// Statement or last in the block expression, which will follow | ||
41 | /// the freshly introduced var. | ||
42 | fn anchor_stmt(expr: &ast::Expr) -> Option<&SyntaxNode> { | ||
43 | expr.syntax().ancestors().find(|&node| { | ||
44 | if ast::Stmt::cast(node).is_some() { | ||
45 | return true; | ||
46 | } | ||
47 | if let Some(expr) = node | ||
48 | .parent() | ||
49 | .and_then(ast::Block::cast) | ||
50 | .and_then(|it| it.expr()) | ||
51 | { | ||
52 | if expr.syntax() == node { | ||
53 | return true; | ||
54 | } | ||
55 | } | ||
56 | false | ||
57 | }) | ||
58 | } | ||
59 | |||
60 | #[cfg(test)] | ||
61 | mod tests { | ||
62 | use super::*; | ||
63 | use crate::assists::check_assist_range; | ||
64 | |||
65 | #[test] | ||
66 | fn test_introduce_var_simple() { | ||
67 | check_assist_range( | ||
68 | introduce_variable, | ||
69 | " | ||
70 | fn foo() { | ||
71 | foo(<|>1 + 1<|>); | ||
72 | }", | ||
73 | " | ||
74 | fn foo() { | ||
75 | let <|>var_name = 1 + 1; | ||
76 | foo(var_name); | ||
77 | }", | ||
78 | ); | ||
79 | } | ||
80 | |||
81 | #[test] | ||
82 | fn test_introduce_var_expr_stmt() { | ||
83 | check_assist_range( | ||
84 | introduce_variable, | ||
85 | " | ||
86 | fn foo() { | ||
87 | <|>1 + 1<|>; | ||
88 | }", | ||
89 | " | ||
90 | fn foo() { | ||
91 | let <|>var_name = 1 + 1; | ||
92 | }", | ||
93 | ); | ||
94 | } | ||
95 | |||
96 | #[test] | ||
97 | fn test_introduce_var_part_of_expr_stmt() { | ||
98 | check_assist_range( | ||
99 | introduce_variable, | ||
100 | " | ||
101 | fn foo() { | ||
102 | <|>1<|> + 1; | ||
103 | }", | ||
104 | " | ||
105 | fn foo() { | ||
106 | let <|>var_name = 1; | ||
107 | var_name + 1; | ||
108 | }", | ||
109 | ); | ||
110 | } | ||
111 | |||
112 | #[test] | ||
113 | fn test_introduce_var_last_expr() { | ||
114 | check_assist_range( | ||
115 | introduce_variable, | ||
116 | " | ||
117 | fn foo() { | ||
118 | bar(<|>1 + 1<|>) | ||
119 | }", | ||
120 | " | ||
121 | fn foo() { | ||
122 | let <|>var_name = 1 + 1; | ||
123 | bar(var_name) | ||
124 | }", | ||
125 | ); | ||
126 | } | ||
127 | |||
128 | #[test] | ||
129 | fn test_introduce_var_last_full_expr() { | ||
130 | check_assist_range( | ||
131 | introduce_variable, | ||
132 | " | ||
133 | fn foo() { | ||
134 | <|>bar(1 + 1)<|> | ||
135 | }", | ||
136 | " | ||
137 | fn foo() { | ||
138 | let <|>var_name = bar(1 + 1); | ||
139 | var_name | ||
140 | }", | ||
141 | ); | ||
142 | } | ||
143 | |||
144 | } | ||
diff --git a/crates/ra_ide_api_light/src/assists/replace_if_let_with_match.rs b/crates/ra_ide_api_light/src/assists/replace_if_let_with_match.rs new file mode 100644 index 000000000..30c371480 --- /dev/null +++ b/crates/ra_ide_api_light/src/assists/replace_if_let_with_match.rs | |||
@@ -0,0 +1,92 @@ | |||
1 | use ra_syntax::{ | ||
2 | AstNode, SyntaxKind::{L_CURLY, R_CURLY, WHITESPACE}, | ||
3 | ast, | ||
4 | }; | ||
5 | |||
6 | use crate::assists::{AssistCtx, Assist}; | ||
7 | |||
8 | pub fn replace_if_let_with_match(ctx: AssistCtx) -> Option<Assist> { | ||
9 | let if_expr: &ast::IfExpr = ctx.node_at_offset()?; | ||
10 | let cond = if_expr.condition()?; | ||
11 | let pat = cond.pat()?; | ||
12 | let expr = cond.expr()?; | ||
13 | let then_block = if_expr.then_branch()?; | ||
14 | let else_block = if_expr.else_branch()?; | ||
15 | |||
16 | ctx.build("replace with match", |edit| { | ||
17 | let match_expr = build_match_expr(expr, pat, then_block, else_block); | ||
18 | edit.replace_node_and_indent(if_expr.syntax(), match_expr); | ||
19 | edit.set_cursor(if_expr.syntax().range().start()) | ||
20 | }) | ||
21 | } | ||
22 | |||
23 | fn build_match_expr( | ||
24 | expr: &ast::Expr, | ||
25 | pat1: &ast::Pat, | ||
26 | arm1: &ast::Block, | ||
27 | arm2: &ast::Block, | ||
28 | ) -> String { | ||
29 | let mut buf = String::new(); | ||
30 | buf.push_str(&format!("match {} {{\n", expr.syntax().text())); | ||
31 | buf.push_str(&format!( | ||
32 | " {} => {}\n", | ||
33 | pat1.syntax().text(), | ||
34 | format_arm(arm1) | ||
35 | )); | ||
36 | buf.push_str(&format!(" _ => {}\n", format_arm(arm2))); | ||
37 | buf.push_str("}"); | ||
38 | buf | ||
39 | } | ||
40 | |||
41 | fn format_arm(block: &ast::Block) -> String { | ||
42 | match extract_expression(block) { | ||
43 | None => block.syntax().text().to_string(), | ||
44 | Some(e) => format!("{},", e.syntax().text()), | ||
45 | } | ||
46 | } | ||
47 | |||
48 | fn extract_expression(block: &ast::Block) -> Option<&ast::Expr> { | ||
49 | let expr = block.expr()?; | ||
50 | let non_trivial_children = block.syntax().children().filter(|it| { | ||
51 | !(it == &expr.syntax() | ||
52 | || it.kind() == L_CURLY | ||
53 | || it.kind() == R_CURLY | ||
54 | || it.kind() == WHITESPACE) | ||
55 | }); | ||
56 | if non_trivial_children.count() > 0 { | ||
57 | return None; | ||
58 | } | ||
59 | Some(expr) | ||
60 | } | ||
61 | |||
62 | #[cfg(test)] | ||
63 | mod tests { | ||
64 | use super::*; | ||
65 | use crate::assists::check_assist; | ||
66 | |||
67 | #[test] | ||
68 | fn test_replace_if_let_with_match_unwraps_simple_expressions() { | ||
69 | check_assist( | ||
70 | replace_if_let_with_match, | ||
71 | " | ||
72 | impl VariantData { | ||
73 | pub fn is_struct(&self) -> bool { | ||
74 | if <|>let VariantData::Struct(..) = *self { | ||
75 | true | ||
76 | } else { | ||
77 | false | ||
78 | } | ||
79 | } | ||
80 | } ", | ||
81 | " | ||
82 | impl VariantData { | ||
83 | pub fn is_struct(&self) -> bool { | ||
84 | <|>match *self { | ||
85 | VariantData::Struct(..) => true, | ||
86 | _ => false, | ||
87 | } | ||
88 | } | ||
89 | } ", | ||
90 | ) | ||
91 | } | ||
92 | } | ||
diff --git a/crates/ra_ide_api_light/src/assists/split_import.rs b/crates/ra_ide_api_light/src/assists/split_import.rs new file mode 100644 index 000000000..e4015f07d --- /dev/null +++ b/crates/ra_ide_api_light/src/assists/split_import.rs | |||
@@ -0,0 +1,56 @@ | |||
1 | use ra_syntax::{ | ||
2 | TextUnit, AstNode, SyntaxKind::COLONCOLON, | ||
3 | ast, | ||
4 | algo::generate, | ||
5 | }; | ||
6 | |||
7 | use crate::assists::{AssistCtx, Assist}; | ||
8 | |||
9 | pub fn split_import(ctx: AssistCtx) -> Option<Assist> { | ||
10 | let colon_colon = ctx | ||
11 | .leaf_at_offset() | ||
12 | .find(|leaf| leaf.kind() == COLONCOLON)?; | ||
13 | let path = colon_colon.parent().and_then(ast::Path::cast)?; | ||
14 | let top_path = generate(Some(path), |it| it.parent_path()).last()?; | ||
15 | |||
16 | let use_tree = top_path.syntax().ancestors().find_map(ast::UseTree::cast); | ||
17 | if use_tree.is_none() { | ||
18 | return None; | ||
19 | } | ||
20 | |||
21 | let l_curly = colon_colon.range().end(); | ||
22 | let r_curly = match top_path.syntax().parent().and_then(ast::UseTree::cast) { | ||
23 | Some(tree) => tree.syntax().range().end(), | ||
24 | None => top_path.syntax().range().end(), | ||
25 | }; | ||
26 | |||
27 | ctx.build("split import", |edit| { | ||
28 | edit.insert(l_curly, "{"); | ||
29 | edit.insert(r_curly, "}"); | ||
30 | edit.set_cursor(l_curly + TextUnit::of_str("{")); | ||
31 | }) | ||
32 | } | ||
33 | |||
34 | #[cfg(test)] | ||
35 | mod tests { | ||
36 | use super::*; | ||
37 | use crate::assists::check_assist; | ||
38 | |||
39 | #[test] | ||
40 | fn test_split_import() { | ||
41 | check_assist( | ||
42 | split_import, | ||
43 | "use crate::<|>db::RootDatabase;", | ||
44 | "use crate::{<|>db::RootDatabase};", | ||
45 | ) | ||
46 | } | ||
47 | |||
48 | #[test] | ||
49 | fn split_import_works_with_trees() { | ||
50 | check_assist( | ||
51 | split_import, | ||
52 | "use algo:<|>:visitor::{Visitor, visit}", | ||
53 | "use algo::{<|>visitor::{Visitor, visit}}", | ||
54 | ) | ||
55 | } | ||
56 | } | ||