aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_editor/src/assists
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_editor/src/assists')
-rw-r--r--crates/ra_editor/src/assists/add_derive.rs97
-rw-r--r--crates/ra_editor/src/assists/add_impl.rs78
-rw-r--r--crates/ra_editor/src/assists/change_visibility.rs90
-rw-r--r--crates/ra_editor/src/assists/flip_comma.rs45
-rw-r--r--crates/ra_editor/src/assists/introduce_variable.rs156
5 files changed, 466 insertions, 0 deletions
diff --git a/crates/ra_editor/src/assists/add_derive.rs b/crates/ra_editor/src/assists/add_derive.rs
new file mode 100644
index 000000000..33d9d2c31
--- /dev/null
+++ b/crates/ra_editor/src/assists/add_derive.rs
@@ -0,0 +1,97 @@
1use ra_text_edit::TextEditBuilder;
2use ra_syntax::{
3 ast::{self, AstNode, AttrsOwner},
4 SourceFileNode,
5 SyntaxKind::{WHITESPACE, COMMENT},
6 TextUnit,
7};
8
9use crate::{
10 find_node_at_offset,
11 assists::LocalEdit,
12};
13
14pub fn add_derive<'a>(
15 file: &'a SourceFileNode,
16 offset: TextUnit,
17) -> Option<impl FnOnce() -> LocalEdit + 'a> {
18 let nominal = find_node_at_offset::<ast::NominalDef>(file.syntax(), offset)?;
19 let node_start = derive_insertion_offset(nominal)?;
20 return Some(move || {
21 let derive_attr = nominal
22 .attrs()
23 .filter_map(|x| x.as_call())
24 .filter(|(name, _arg)| name == "derive")
25 .map(|(_name, arg)| arg)
26 .next();
27 let mut edit = TextEditBuilder::new();
28 let offset = match derive_attr {
29 None => {
30 edit.insert(node_start, "#[derive()]\n".to_string());
31 node_start + TextUnit::of_str("#[derive(")
32 }
33 Some(tt) => tt.syntax().range().end() - TextUnit::of_char(')'),
34 };
35 LocalEdit {
36 label: "add `#[derive]`".to_string(),
37 edit: edit.finish(),
38 cursor_position: Some(offset),
39 }
40 });
41
42 // Insert `derive` after doc comments.
43 fn derive_insertion_offset(nominal: ast::NominalDef) -> Option<TextUnit> {
44 let non_ws_child = nominal
45 .syntax()
46 .children()
47 .find(|it| it.kind() != COMMENT && it.kind() != WHITESPACE)?;
48 Some(non_ws_child.range().start())
49 }
50}
51
52#[cfg(test)]
53mod tests {
54 use super::*;
55 use crate::test_utils::check_action;
56
57 #[test]
58 fn add_derive_new() {
59 check_action(
60 "struct Foo { a: i32, <|>}",
61 "#[derive(<|>)]\nstruct Foo { a: i32, }",
62 |file, off| add_derive(file, off).map(|f| f()),
63 );
64 check_action(
65 "struct Foo { <|> a: i32, }",
66 "#[derive(<|>)]\nstruct Foo { a: i32, }",
67 |file, off| add_derive(file, off).map(|f| f()),
68 );
69 }
70
71 #[test]
72 fn add_derive_existing() {
73 check_action(
74 "#[derive(Clone)]\nstruct Foo { a: i32<|>, }",
75 "#[derive(Clone<|>)]\nstruct Foo { a: i32, }",
76 |file, off| add_derive(file, off).map(|f| f()),
77 );
78 }
79
80 #[test]
81 fn add_derive_new_with_doc_comment() {
82 check_action(
83 "
84/// `Foo` is a pretty important struct.
85/// It does stuff.
86struct Foo { a: i32<|>, }
87 ",
88 "
89/// `Foo` is a pretty important struct.
90/// It does stuff.
91#[derive(<|>)]
92struct Foo { a: i32, }
93 ",
94 |file, off| add_derive(file, off).map(|f| f()),
95 );
96 }
97}
diff --git a/crates/ra_editor/src/assists/add_impl.rs b/crates/ra_editor/src/assists/add_impl.rs
new file mode 100644
index 000000000..50e00688e
--- /dev/null
+++ b/crates/ra_editor/src/assists/add_impl.rs
@@ -0,0 +1,78 @@
1use join_to_string::join;
2use ra_text_edit::TextEditBuilder;
3use ra_syntax::{
4 ast::{self, AstNode, NameOwner, TypeParamsOwner},
5 SourceFileNode,
6 TextUnit,
7};
8
9use crate::{find_node_at_offset, assists::LocalEdit};
10
11pub fn add_impl<'a>(
12 file: &'a SourceFileNode,
13 offset: TextUnit,
14) -> Option<impl FnOnce() -> LocalEdit + 'a> {
15 let nominal = find_node_at_offset::<ast::NominalDef>(file.syntax(), offset)?;
16 let name = nominal.name()?;
17
18 Some(move || {
19 let type_params = nominal.type_param_list();
20 let mut edit = TextEditBuilder::new();
21 let start_offset = nominal.syntax().range().end();
22 let mut buf = String::new();
23 buf.push_str("\n\nimpl");
24 if let Some(type_params) = type_params {
25 type_params.syntax().text().push_to(&mut buf);
26 }
27 buf.push_str(" ");
28 buf.push_str(name.text().as_str());
29 if let Some(type_params) = type_params {
30 let lifetime_params = type_params
31 .lifetime_params()
32 .filter_map(|it| it.lifetime())
33 .map(|it| it.text());
34 let type_params = type_params
35 .type_params()
36 .filter_map(|it| it.name())
37 .map(|it| it.text());
38 join(lifetime_params.chain(type_params))
39 .surround_with("<", ">")
40 .to_buf(&mut buf);
41 }
42 buf.push_str(" {\n");
43 let offset = start_offset + TextUnit::of_str(&buf);
44 buf.push_str("\n}");
45 edit.insert(start_offset, buf);
46 LocalEdit {
47 label: "add impl".to_string(),
48 edit: edit.finish(),
49 cursor_position: Some(offset),
50 }
51 })
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57 use crate::test_utils::check_action;
58
59 #[test]
60 fn test_add_impl() {
61 check_action(
62 "struct Foo {<|>}\n",
63 "struct Foo {}\n\nimpl Foo {\n<|>\n}\n",
64 |file, off| add_impl(file, off).map(|f| f()),
65 );
66 check_action(
67 "struct Foo<T: Clone> {<|>}",
68 "struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n<|>\n}",
69 |file, off| add_impl(file, off).map(|f| f()),
70 );
71 check_action(
72 "struct Foo<'a, T: Foo<'a>> {<|>}",
73 "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n<|>\n}",
74 |file, off| add_impl(file, off).map(|f| f()),
75 );
76 }
77
78}
diff --git a/crates/ra_editor/src/assists/change_visibility.rs b/crates/ra_editor/src/assists/change_visibility.rs
new file mode 100644
index 000000000..98c218f32
--- /dev/null
+++ b/crates/ra_editor/src/assists/change_visibility.rs
@@ -0,0 +1,90 @@
1use ra_text_edit::TextEditBuilder;
2use ra_syntax::{
3 SourceFileNode,
4 algo::find_leaf_at_offset,
5 SyntaxKind::{VISIBILITY, FN_KW, MOD_KW, STRUCT_KW, ENUM_KW, TRAIT_KW, FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF},
6 TextUnit,
7};
8
9use crate::assists::LocalEdit;
10
11pub fn change_visibility<'a>(
12 file: &'a SourceFileNode,
13 offset: TextUnit,
14) -> Option<impl FnOnce() -> LocalEdit + 'a> {
15 let syntax = file.syntax();
16
17 let keyword = find_leaf_at_offset(syntax, offset).find(|leaf| match leaf.kind() {
18 FN_KW | MOD_KW | STRUCT_KW | ENUM_KW | TRAIT_KW => true,
19 _ => false,
20 })?;
21 let parent = keyword.parent()?;
22 let def_kws = vec![FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF];
23 let node_start = parent.range().start();
24 Some(move || {
25 let mut edit = TextEditBuilder::new();
26
27 if !def_kws.iter().any(|&def_kw| def_kw == parent.kind())
28 || parent.children().any(|child| child.kind() == VISIBILITY)
29 {
30 return LocalEdit {
31 label: "make pub crate".to_string(),
32 edit: edit.finish(),
33 cursor_position: Some(offset),
34 };
35 }
36
37 edit.insert(node_start, "pub(crate) ".to_string());
38 LocalEdit {
39 label: "make pub crate".to_string(),
40 edit: edit.finish(),
41 cursor_position: Some(node_start),
42 }
43 })
44}
45
46#[cfg(test)]
47mod tests {
48 use super::*;
49 use crate::test_utils::check_action;
50
51 #[test]
52 fn test_change_visibility() {
53 check_action(
54 "<|>fn foo() {}",
55 "<|>pub(crate) fn foo() {}",
56 |file, off| change_visibility(file, off).map(|f| f()),
57 );
58 check_action(
59 "f<|>n foo() {}",
60 "<|>pub(crate) fn foo() {}",
61 |file, off| change_visibility(file, off).map(|f| f()),
62 );
63 check_action(
64 "<|>struct Foo {}",
65 "<|>pub(crate) struct Foo {}",
66 |file, off| change_visibility(file, off).map(|f| f()),
67 );
68 check_action("<|>mod foo {}", "<|>pub(crate) mod foo {}", |file, off| {
69 change_visibility(file, off).map(|f| f())
70 });
71 check_action(
72 "<|>trait Foo {}",
73 "<|>pub(crate) trait Foo {}",
74 |file, off| change_visibility(file, off).map(|f| f()),
75 );
76 check_action("m<|>od {}", "<|>pub(crate) mod {}", |file, off| {
77 change_visibility(file, off).map(|f| f())
78 });
79 check_action(
80 "pub(crate) f<|>n foo() {}",
81 "pub(crate) f<|>n foo() {}",
82 |file, off| change_visibility(file, off).map(|f| f()),
83 );
84 check_action(
85 "unsafe f<|>n foo() {}",
86 "<|>pub(crate) unsafe fn foo() {}",
87 |file, off| change_visibility(file, off).map(|f| f()),
88 );
89 }
90}
diff --git a/crates/ra_editor/src/assists/flip_comma.rs b/crates/ra_editor/src/assists/flip_comma.rs
new file mode 100644
index 000000000..d8727db0d
--- /dev/null
+++ b/crates/ra_editor/src/assists/flip_comma.rs
@@ -0,0 +1,45 @@
1use ra_text_edit::TextEditBuilder;
2use ra_syntax::{
3 algo::find_leaf_at_offset,
4 Direction, SourceFileNode,
5 SyntaxKind::COMMA,
6 TextUnit,
7};
8
9use crate::assists::{LocalEdit, non_trivia_sibling};
10
11pub fn flip_comma<'a>(
12 file: &'a SourceFileNode,
13 offset: TextUnit,
14) -> Option<impl FnOnce() -> LocalEdit + 'a> {
15 let syntax = file.syntax();
16
17 let comma = find_leaf_at_offset(syntax, offset).find(|leaf| leaf.kind() == COMMA)?;
18 let prev = non_trivia_sibling(comma, Direction::Prev)?;
19 let next = non_trivia_sibling(comma, Direction::Next)?;
20 Some(move || {
21 let mut edit = TextEditBuilder::new();
22 edit.replace(prev.range(), next.text().to_string());
23 edit.replace(next.range(), prev.text().to_string());
24 LocalEdit {
25 label: "flip comma".to_string(),
26 edit: edit.finish(),
27 cursor_position: None,
28 }
29 })
30}
31
32#[cfg(test)]
33mod tests {
34 use super::*;
35 use crate::test_utils::check_action;
36
37 #[test]
38 fn test_swap_comma() {
39 check_action(
40 "fn foo(x: i32,<|> y: Result<(), ()>) {}",
41 "fn foo(y: Result<(), ()>,<|> x: i32) {}",
42 |file, off| flip_comma(file, off).map(|f| f()),
43 )
44 }
45}
diff --git a/crates/ra_editor/src/assists/introduce_variable.rs b/crates/ra_editor/src/assists/introduce_variable.rs
new file mode 100644
index 000000000..17ab521fa
--- /dev/null
+++ b/crates/ra_editor/src/assists/introduce_variable.rs
@@ -0,0 +1,156 @@
1use ra_text_edit::TextEditBuilder;
2use ra_syntax::{
3 algo::{find_covering_node},
4 ast::{self, AstNode},
5 SourceFileNode,
6 SyntaxKind::{WHITESPACE},
7 SyntaxNodeRef, TextRange, TextUnit,
8};
9
10use crate::assists::LocalEdit;
11
12pub fn introduce_variable<'a>(
13 file: &'a SourceFileNode,
14 range: TextRange,
15) -> Option<impl FnOnce() -> LocalEdit + 'a> {
16 let node = find_covering_node(file.syntax(), range);
17 let expr = node.ancestors().filter_map(ast::Expr::cast).next()?;
18
19 let anchor_stmt = anchor_stmt(expr)?;
20 let indent = anchor_stmt.prev_sibling()?;
21 if indent.kind() != WHITESPACE {
22 return None;
23 }
24 return Some(move || {
25 let mut buf = String::new();
26 let mut edit = TextEditBuilder::new();
27
28 buf.push_str("let var_name = ");
29 expr.syntax().text().push_to(&mut buf);
30 let is_full_stmt = if let Some(expr_stmt) = ast::ExprStmt::cast(anchor_stmt) {
31 Some(expr.syntax()) == expr_stmt.expr().map(|e| e.syntax())
32 } else {
33 false
34 };
35 if is_full_stmt {
36 edit.replace(expr.syntax().range(), buf);
37 } else {
38 buf.push_str(";");
39 indent.text().push_to(&mut buf);
40 edit.replace(expr.syntax().range(), "var_name".to_string());
41 edit.insert(anchor_stmt.range().start(), buf);
42 }
43 let cursor_position = anchor_stmt.range().start() + TextUnit::of_str("let ");
44 LocalEdit {
45 label: "introduce variable".to_string(),
46 edit: edit.finish(),
47 cursor_position: Some(cursor_position),
48 }
49 });
50
51 /// Statement or last in the block expression, which will follow
52 /// the freshly introduced var.
53 fn anchor_stmt(expr: ast::Expr) -> Option<SyntaxNodeRef> {
54 expr.syntax().ancestors().find(|&node| {
55 if ast::Stmt::cast(node).is_some() {
56 return true;
57 }
58 if let Some(expr) = node
59 .parent()
60 .and_then(ast::Block::cast)
61 .and_then(|it| it.expr())
62 {
63 if expr.syntax() == node {
64 return true;
65 }
66 }
67 false
68 })
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75 use crate::test_utils::check_action_range;
76
77 #[test]
78 fn test_introduce_var_simple() {
79 check_action_range(
80 "
81fn foo() {
82 foo(<|>1 + 1<|>);
83}",
84 "
85fn foo() {
86 let <|>var_name = 1 + 1;
87 foo(var_name);
88}",
89 |file, range| introduce_variable(file, range).map(|f| f()),
90 );
91 }
92
93 #[test]
94 fn test_introduce_var_expr_stmt() {
95 check_action_range(
96 "
97fn foo() {
98 <|>1 + 1<|>;
99}",
100 "
101fn foo() {
102 let <|>var_name = 1 + 1;
103}",
104 |file, range| introduce_variable(file, range).map(|f| f()),
105 );
106 }
107
108 #[test]
109 fn test_introduce_var_part_of_expr_stmt() {
110 check_action_range(
111 "
112fn foo() {
113 <|>1<|> + 1;
114}",
115 "
116fn foo() {
117 let <|>var_name = 1;
118 var_name + 1;
119}",
120 |file, range| introduce_variable(file, range).map(|f| f()),
121 );
122 }
123
124 #[test]
125 fn test_introduce_var_last_expr() {
126 check_action_range(
127 "
128fn foo() {
129 bar(<|>1 + 1<|>)
130}",
131 "
132fn foo() {
133 let <|>var_name = 1 + 1;
134 bar(var_name)
135}",
136 |file, range| introduce_variable(file, range).map(|f| f()),
137 );
138 }
139
140 #[test]
141 fn test_introduce_var_last_full_expr() {
142 check_action_range(
143 "
144fn foo() {
145 <|>bar(1 + 1)<|>
146}",
147 "
148fn foo() {
149 let <|>var_name = bar(1 + 1);
150 var_name
151}",
152 |file, range| introduce_variable(file, range).map(|f| f()),
153 );
154 }
155
156}