aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide_api_light/src/assists
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide_api_light/src/assists')
-rw-r--r--crates/ra_ide_api_light/src/assists/add_derive.rs84
-rw-r--r--crates/ra_ide_api_light/src/assists/add_impl.rs66
-rw-r--r--crates/ra_ide_api_light/src/assists/change_visibility.rs116
-rw-r--r--crates/ra_ide_api_light/src/assists/flip_comma.rs31
-rw-r--r--crates/ra_ide_api_light/src/assists/introduce_variable.rs144
-rw-r--r--crates/ra_ide_api_light/src/assists/replace_if_let_with_match.rs92
-rw-r--r--crates/ra_ide_api_light/src/assists/split_import.rs56
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 @@
1use ra_syntax::{
2 ast::{self, AstNode, AttrsOwner},
3 SyntaxKind::{WHITESPACE, COMMENT},
4 TextUnit,
5};
6
7use crate::assists::{AssistCtx, Assist};
8
9pub 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.
31fn 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)]
40mod 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.
74struct Foo { a: i32<|>, }
75 ",
76 "
77/// `Foo` is a pretty important struct.
78/// It does stuff.
79#[derive(<|>)]
80struct 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 @@
1use join_to_string::join;
2use ra_syntax::{
3 ast::{self, AstNode, AstToken, NameOwner, TypeParamsOwner},
4 TextUnit,
5};
6
7use crate::assists::{AssistCtx, Assist};
8
9pub 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)]
43mod 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 @@
1use 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
7use crate::assists::{AssistCtx, Assist};
8
9pub 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
16fn 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
49fn 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)]
60mod 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 @@
1use ra_syntax::{
2 Direction,
3 SyntaxKind::COMMA,
4};
5
6use crate::assists::{non_trivia_sibling, AssistCtx, Assist};
7
8pub 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)]
19mod 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 @@
1use ra_syntax::{
2 ast::{self, AstNode},
3 SyntaxKind::WHITESPACE,
4 SyntaxNode, TextUnit,
5};
6
7use crate::assists::{AssistCtx, Assist};
8
9pub 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.
42fn 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)]
61mod 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 "
70fn foo() {
71 foo(<|>1 + 1<|>);
72}",
73 "
74fn 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 "
86fn foo() {
87 <|>1 + 1<|>;
88}",
89 "
90fn 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 "
101fn foo() {
102 <|>1<|> + 1;
103}",
104 "
105fn 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 "
117fn foo() {
118 bar(<|>1 + 1<|>)
119}",
120 "
121fn 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 "
133fn foo() {
134 <|>bar(1 + 1)<|>
135}",
136 "
137fn 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 @@
1use ra_syntax::{
2 AstNode, SyntaxKind::{L_CURLY, R_CURLY, WHITESPACE},
3 ast,
4};
5
6use crate::assists::{AssistCtx, Assist};
7
8pub 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
23fn 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
41fn 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
48fn 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)]
63mod 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 "
72impl VariantData {
73 pub fn is_struct(&self) -> bool {
74 if <|>let VariantData::Struct(..) = *self {
75 true
76 } else {
77 false
78 }
79 }
80} ",
81 "
82impl 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 @@
1use ra_syntax::{
2 TextUnit, AstNode, SyntaxKind::COLONCOLON,
3 ast,
4 algo::generate,
5};
6
7use crate::assists::{AssistCtx, Assist};
8
9pub 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)]
35mod 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}