aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2019-01-03 15:59:17 +0000
committerAleksey Kladov <[email protected]>2019-01-03 15:59:17 +0000
commita4635a199bc446bd103aa5821e57dc19b8a15751 (patch)
tree5dcdd940d8627f021062245cb79589a790b60e04
parentaea2183799e7975d3d9000cec9bb9a3c001a3d4e (diff)
more enterprisey assists API
-rw-r--r--crates/ra_analysis/src/imp.rs16
-rw-r--r--crates/ra_editor/src/assists.rs155
-rw-r--r--crates/ra_editor/src/assists/add_derive.rs61
-rw-r--r--crates/ra_editor/src/assists/add_impl.rs36
-rw-r--r--crates/ra_editor/src/assists/change_visibility.rs84
-rw-r--r--crates/ra_editor/src/assists/flip_comma.rs36
-rw-r--r--crates/ra_editor/src/assists/introduce_variable.rs84
-rw-r--r--crates/ra_editor/src/diagnostics.rs6
-rw-r--r--crates/ra_editor/src/typing.rs8
-rw-r--r--crates/ra_syntax/src/yellow/syntax_text.rs6
-rw-r--r--crates/ra_text_edit/src/text_edit.rs5
11 files changed, 287 insertions, 210 deletions
diff --git a/crates/ra_analysis/src/imp.rs b/crates/ra_analysis/src/imp.rs
index 136e7f7dc..771dad475 100644
--- a/crates/ra_analysis/src/imp.rs
+++ b/crates/ra_analysis/src/imp.rs
@@ -333,19 +333,9 @@ impl db::RootDatabase {
333 333
334 pub(crate) fn assists(&self, frange: FileRange) -> Vec<SourceChange> { 334 pub(crate) fn assists(&self, frange: FileRange) -> Vec<SourceChange> {
335 let file = self.source_file(frange.file_id); 335 let file = self.source_file(frange.file_id);
336 let offset = frange.range.start(); 336 assists::assists(&file, frange.range)
337 let actions = vec![
338 assists::flip_comma(&file, offset).map(|f| f()),
339 assists::add_derive(&file, offset).map(|f| f()),
340 assists::add_impl(&file, offset).map(|f| f()),
341 assists::change_visibility(&file, offset).map(|f| f()),
342 assists::introduce_variable(&file, frange.range).map(|f| f()),
343 ];
344 actions
345 .into_iter() 337 .into_iter()
346 .filter_map(|local_edit| { 338 .map(|local_edit| SourceChange::from_local_edit(frange.file_id, local_edit))
347 Some(SourceChange::from_local_edit(frange.file_id, local_edit?))
348 })
349 .collect() 339 .collect()
350 } 340 }
351 341
@@ -440,7 +430,7 @@ impl db::RootDatabase {
440 .map(|(file_id, text_range)| SourceFileEdit { 430 .map(|(file_id, text_range)| SourceFileEdit {
441 file_id: *file_id, 431 file_id: *file_id,
442 edit: { 432 edit: {
443 let mut builder = ra_text_edit::TextEditBuilder::new(); 433 let mut builder = ra_text_edit::TextEditBuilder::default();
444 builder.replace(*text_range, new_name.into()); 434 builder.replace(*text_range, new_name.into());
445 builder.finish() 435 builder.finish()
446 }, 436 },
diff --git a/crates/ra_editor/src/assists.rs b/crates/ra_editor/src/assists.rs
index b6e6dd628..cc40ee4c8 100644
--- a/crates/ra_editor/src/assists.rs
+++ b/crates/ra_editor/src/assists.rs
@@ -9,8 +9,13 @@ mod add_impl;
9mod introduce_variable; 9mod introduce_variable;
10mod change_visibility; 10mod change_visibility;
11 11
12use ra_text_edit::TextEdit; 12use ra_text_edit::{TextEdit, TextEditBuilder};
13use ra_syntax::{Direction, SyntaxNodeRef, TextUnit}; 13use ra_syntax::{
14 Direction, SyntaxNodeRef, TextUnit, TextRange,SourceFileNode, AstNode,
15 algo::{find_leaf_at_offset, find_covering_node, LeafAtOffset},
16};
17
18use crate::find_node_at_offset;
14 19
15pub use self::{ 20pub use self::{
16 flip_comma::flip_comma, 21 flip_comma::flip_comma,
@@ -20,6 +25,21 @@ pub use self::{
20 change_visibility::change_visibility, 25 change_visibility::change_visibility,
21}; 26};
22 27
28/// Return all the assists applicable at the given position.
29pub fn assists(file: &SourceFileNode, range: TextRange) -> Vec<LocalEdit> {
30 let ctx = AssistCtx::new(file, range);
31 [
32 flip_comma,
33 add_derive,
34 add_impl,
35 introduce_variable,
36 change_visibility,
37 ]
38 .iter()
39 .filter_map(|&assist| ctx.clone().apply(assist))
40 .collect()
41}
42
23#[derive(Debug)] 43#[derive(Debug)]
24pub struct LocalEdit { 44pub struct LocalEdit {
25 pub label: String, 45 pub label: String,
@@ -32,3 +52,134 @@ fn non_trivia_sibling(node: SyntaxNodeRef, direction: Direction) -> Option<Synta
32 .skip(1) 52 .skip(1)
33 .find(|node| !node.kind().is_trivia()) 53 .find(|node| !node.kind().is_trivia())
34} 54}
55
56/// `AssistCtx` allows to apply an assist or check if it could be applied.
57///
58/// Assists use a somewhat overengeneered approach, given the current needs. The
59/// assists workflow consists of two phases. In the first phase, a user asks for
60/// the list of available assists. In the second phase, the user picks a
61/// particular assist and it gets applied.
62///
63/// There are two peculiarities here:
64///
65/// * first, we ideally avoid computing more things then neccessary to answer
66/// "is assist applicable" in the first phase.
67/// * second, when we are appling assist, we don't have a gurantee that there
68/// weren't any changes between the point when user asked for assists and when
69/// they applied a particular assist. So, when applying assist, we need to do
70/// all the checks from scratch.
71///
72/// To avoid repeating the same code twice for both "check" and "apply"
73/// functions, we use an approach remeniscent of that of Django's function based
74/// views dealing with forms. Each assist receives a runtime parameter,
75/// `should_compute_edit`. It first check if an edit is applicable (potentially
76/// computing info required to compute the actual edit). If it is applicable,
77/// and `should_compute_edit` is `true`, it then computes the actual edit.
78///
79/// So, to implement the original assists workflow, we can first apply each edit
80/// with `should_compute_edit = false`, and then applying the selected edit
81/// again, with `should_compute_edit = true` this time.
82///
83/// Note, however, that we don't actually use such two-phase logic at the
84/// moment, because the LSP API is pretty awkward in this place, and it's much
85/// easier to just compute the edit eagarly :-)
86#[derive(Debug, Clone)]
87pub struct AssistCtx<'a> {
88 source_file: &'a SourceFileNode,
89 range: TextRange,
90 should_compute_edit: bool,
91}
92
93#[derive(Debug)]
94pub enum Assist {
95 Applicable,
96 Edit(LocalEdit),
97}
98
99#[derive(Default)]
100struct AssistBuilder {
101 edit: TextEditBuilder,
102 cursor_position: Option<TextUnit>,
103}
104
105impl<'a> AssistCtx<'a> {
106 pub fn new(source_file: &'a SourceFileNode, range: TextRange) -> AssistCtx {
107 AssistCtx {
108 source_file,
109 range,
110 should_compute_edit: false,
111 }
112 }
113
114 pub fn apply(mut self, assist: fn(AssistCtx) -> Option<Assist>) -> Option<LocalEdit> {
115 self.should_compute_edit = true;
116 match assist(self) {
117 None => None,
118 Some(Assist::Edit(e)) => Some(e),
119 Some(Assist::Applicable) => unreachable!(),
120 }
121 }
122
123 pub fn check(mut self, assist: fn(AssistCtx) -> Option<Assist>) -> bool {
124 self.should_compute_edit = false;
125 match assist(self) {
126 None => false,
127 Some(Assist::Edit(_)) => unreachable!(),
128 Some(Assist::Applicable) => true,
129 }
130 }
131
132 fn build(self, label: impl Into<String>, f: impl FnOnce(&mut AssistBuilder)) -> Option<Assist> {
133 if !self.should_compute_edit {
134 return Some(Assist::Applicable);
135 }
136 let mut edit = AssistBuilder::default();
137 f(&mut edit);
138 Some(Assist::Edit(LocalEdit {
139 label: label.into(),
140 edit: edit.edit.finish(),
141 cursor_position: edit.cursor_position,
142 }))
143 }
144
145 pub(crate) fn leaf_at_offset(&self) -> LeafAtOffset<SyntaxNodeRef<'a>> {
146 find_leaf_at_offset(self.source_file.syntax(), self.range.start())
147 }
148 pub(crate) fn node_at_offset<N: AstNode<'a>>(&self) -> Option<N> {
149 find_node_at_offset(self.source_file.syntax(), self.range.start())
150 }
151 pub(crate) fn covering_node(&self) -> SyntaxNodeRef<'a> {
152 find_covering_node(self.source_file.syntax(), self.range)
153 }
154}
155
156impl AssistBuilder {
157 fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) {
158 self.edit.replace(range, replace_with.into())
159 }
160 #[allow(unused)]
161 fn delete(&mut self, range: TextRange) {
162 self.edit.delete(range)
163 }
164 fn insert(&mut self, offset: TextUnit, text: impl Into<String>) {
165 self.edit.insert(offset, text.into())
166 }
167 fn set_cursor(&mut self, offset: TextUnit) {
168 self.cursor_position = Some(offset)
169 }
170}
171
172#[cfg(test)]
173fn check_assist(assist: fn(AssistCtx) -> Option<Assist>, before: &str, after: &str) {
174 crate::test_utils::check_action(before, after, |file, off| {
175 let range = TextRange::offset_len(off, 0.into());
176 AssistCtx::new(file, range).apply(assist)
177 })
178}
179
180#[cfg(test)]
181fn check_assist_range(assist: fn(AssistCtx) -> Option<Assist>, before: &str, after: &str) {
182 crate::test_utils::check_action_range(before, after, |file, range| {
183 AssistCtx::new(file, range).apply(assist)
184 })
185}
diff --git a/crates/ra_editor/src/assists/add_derive.rs b/crates/ra_editor/src/assists/add_derive.rs
index 33d9d2c31..1e2cd4f30 100644
--- a/crates/ra_editor/src/assists/add_derive.rs
+++ b/crates/ra_editor/src/assists/add_derive.rs
@@ -1,85 +1,73 @@
1use ra_text_edit::TextEditBuilder;
2use ra_syntax::{ 1use ra_syntax::{
3 ast::{self, AstNode, AttrsOwner}, 2 ast::{self, AstNode, AttrsOwner},
4 SourceFileNode,
5 SyntaxKind::{WHITESPACE, COMMENT}, 3 SyntaxKind::{WHITESPACE, COMMENT},
6 TextUnit, 4 TextUnit,
7}; 5};
8 6
9use crate::{ 7use crate::assists::{AssistCtx, Assist};
10 find_node_at_offset,
11 assists::LocalEdit,
12};
13 8
14pub fn add_derive<'a>( 9pub fn add_derive(ctx: AssistCtx) -> Option<Assist> {
15 file: &'a SourceFileNode, 10 let nominal = ctx.node_at_offset::<ast::NominalDef>()?;
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)?; 11 let node_start = derive_insertion_offset(nominal)?;
20 return Some(move || { 12 ctx.build("add `#[derive]`", |edit| {
21 let derive_attr = nominal 13 let derive_attr = nominal
22 .attrs() 14 .attrs()
23 .filter_map(|x| x.as_call()) 15 .filter_map(|x| x.as_call())
24 .filter(|(name, _arg)| name == "derive") 16 .filter(|(name, _arg)| name == "derive")
25 .map(|(_name, arg)| arg) 17 .map(|(_name, arg)| arg)
26 .next(); 18 .next();
27 let mut edit = TextEditBuilder::new();
28 let offset = match derive_attr { 19 let offset = match derive_attr {
29 None => { 20 None => {
30 edit.insert(node_start, "#[derive()]\n".to_string()); 21 edit.insert(node_start, "#[derive()]\n");
31 node_start + TextUnit::of_str("#[derive(") 22 node_start + TextUnit::of_str("#[derive(")
32 } 23 }
33 Some(tt) => tt.syntax().range().end() - TextUnit::of_char(')'), 24 Some(tt) => tt.syntax().range().end() - TextUnit::of_char(')'),
34 }; 25 };
35 LocalEdit { 26 edit.set_cursor(offset)
36 label: "add `#[derive]`".to_string(), 27 })
37 edit: edit.finish(), 28}
38 cursor_position: Some(offset),
39 }
40 });
41 29
42 // Insert `derive` after doc comments. 30// Insert `derive` after doc comments.
43 fn derive_insertion_offset(nominal: ast::NominalDef) -> Option<TextUnit> { 31fn derive_insertion_offset(nominal: ast::NominalDef) -> Option<TextUnit> {
44 let non_ws_child = nominal 32 let non_ws_child = nominal
45 .syntax() 33 .syntax()
46 .children() 34 .children()
47 .find(|it| it.kind() != COMMENT && it.kind() != WHITESPACE)?; 35 .find(|it| it.kind() != COMMENT && it.kind() != WHITESPACE)?;
48 Some(non_ws_child.range().start()) 36 Some(non_ws_child.range().start())
49 }
50} 37}
51 38
52#[cfg(test)] 39#[cfg(test)]
53mod tests { 40mod tests {
54 use super::*; 41 use super::*;
55 use crate::test_utils::check_action; 42 use crate::assists::check_assist;
56 43
57 #[test] 44 #[test]
58 fn add_derive_new() { 45 fn add_derive_new() {
59 check_action( 46 check_assist(
47 add_derive,
60 "struct Foo { a: i32, <|>}", 48 "struct Foo { a: i32, <|>}",
61 "#[derive(<|>)]\nstruct Foo { a: i32, }", 49 "#[derive(<|>)]\nstruct Foo { a: i32, }",
62 |file, off| add_derive(file, off).map(|f| f()),
63 ); 50 );
64 check_action( 51 check_assist(
52 add_derive,
65 "struct Foo { <|> a: i32, }", 53 "struct Foo { <|> a: i32, }",
66 "#[derive(<|>)]\nstruct Foo { a: i32, }", 54 "#[derive(<|>)]\nstruct Foo { a: i32, }",
67 |file, off| add_derive(file, off).map(|f| f()),
68 ); 55 );
69 } 56 }
70 57
71 #[test] 58 #[test]
72 fn add_derive_existing() { 59 fn add_derive_existing() {
73 check_action( 60 check_assist(
61 add_derive,
74 "#[derive(Clone)]\nstruct Foo { a: i32<|>, }", 62 "#[derive(Clone)]\nstruct Foo { a: i32<|>, }",
75 "#[derive(Clone<|>)]\nstruct Foo { a: i32, }", 63 "#[derive(Clone<|>)]\nstruct Foo { a: i32, }",
76 |file, off| add_derive(file, off).map(|f| f()),
77 ); 64 );
78 } 65 }
79 66
80 #[test] 67 #[test]
81 fn add_derive_new_with_doc_comment() { 68 fn add_derive_new_with_doc_comment() {
82 check_action( 69 check_assist(
70 add_derive,
83 " 71 "
84/// `Foo` is a pretty important struct. 72/// `Foo` is a pretty important struct.
85/// It does stuff. 73/// It does stuff.
@@ -91,7 +79,6 @@ struct Foo { a: i32<|>, }
91#[derive(<|>)] 79#[derive(<|>)]
92struct Foo { a: i32, } 80struct Foo { a: i32, }
93 ", 81 ",
94 |file, off| add_derive(file, off).map(|f| f()),
95 ); 82 );
96 } 83 }
97} 84}
diff --git a/crates/ra_editor/src/assists/add_impl.rs b/crates/ra_editor/src/assists/add_impl.rs
index 50e00688e..9353e2717 100644
--- a/crates/ra_editor/src/assists/add_impl.rs
+++ b/crates/ra_editor/src/assists/add_impl.rs
@@ -1,23 +1,16 @@
1use join_to_string::join; 1use join_to_string::join;
2use ra_text_edit::TextEditBuilder;
3use ra_syntax::{ 2use ra_syntax::{
4 ast::{self, AstNode, NameOwner, TypeParamsOwner}, 3 ast::{self, AstNode, NameOwner, TypeParamsOwner},
5 SourceFileNode,
6 TextUnit, 4 TextUnit,
7}; 5};
8 6
9use crate::{find_node_at_offset, assists::LocalEdit}; 7use crate::assists::{AssistCtx, Assist};
10 8
11pub fn add_impl<'a>( 9pub fn add_impl(ctx: AssistCtx) -> Option<Assist> {
12 file: &'a SourceFileNode, 10 let nominal = ctx.node_at_offset::<ast::NominalDef>()?;
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()?; 11 let name = nominal.name()?;
17 12 ctx.build("add impl", |edit| {
18 Some(move || {
19 let type_params = nominal.type_param_list(); 13 let type_params = nominal.type_param_list();
20 let mut edit = TextEditBuilder::new();
21 let start_offset = nominal.syntax().range().end(); 14 let start_offset = nominal.syntax().range().end();
22 let mut buf = String::new(); 15 let mut buf = String::new();
23 buf.push_str("\n\nimpl"); 16 buf.push_str("\n\nimpl");
@@ -40,38 +33,33 @@ pub fn add_impl<'a>(
40 .to_buf(&mut buf); 33 .to_buf(&mut buf);
41 } 34 }
42 buf.push_str(" {\n"); 35 buf.push_str(" {\n");
43 let offset = start_offset + TextUnit::of_str(&buf); 36 edit.set_cursor(start_offset + TextUnit::of_str(&buf));
44 buf.push_str("\n}"); 37 buf.push_str("\n}");
45 edit.insert(start_offset, buf); 38 edit.insert(start_offset, buf);
46 LocalEdit {
47 label: "add impl".to_string(),
48 edit: edit.finish(),
49 cursor_position: Some(offset),
50 }
51 }) 39 })
52} 40}
53 41
54#[cfg(test)] 42#[cfg(test)]
55mod tests { 43mod tests {
56 use super::*; 44 use super::*;
57 use crate::test_utils::check_action; 45 use crate::assists::check_assist;
58 46
59 #[test] 47 #[test]
60 fn test_add_impl() { 48 fn test_add_impl() {
61 check_action( 49 check_assist(
50 add_impl,
62 "struct Foo {<|>}\n", 51 "struct Foo {<|>}\n",
63 "struct Foo {}\n\nimpl Foo {\n<|>\n}\n", 52 "struct Foo {}\n\nimpl Foo {\n<|>\n}\n",
64 |file, off| add_impl(file, off).map(|f| f()),
65 ); 53 );
66 check_action( 54 check_assist(
55 add_impl,
67 "struct Foo<T: Clone> {<|>}", 56 "struct Foo<T: Clone> {<|>}",
68 "struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n<|>\n}", 57 "struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n<|>\n}",
69 |file, off| add_impl(file, off).map(|f| f()),
70 ); 58 );
71 check_action( 59 check_assist(
60 add_impl,
72 "struct Foo<'a, T: Foo<'a>> {<|>}", 61 "struct Foo<'a, T: Foo<'a>> {<|>}",
73 "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n<|>\n}", 62 "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 ); 63 );
76 } 64 }
77 65
diff --git a/crates/ra_editor/src/assists/change_visibility.rs b/crates/ra_editor/src/assists/change_visibility.rs
index 98c218f32..379e88d3c 100644
--- a/crates/ra_editor/src/assists/change_visibility.rs
+++ b/crates/ra_editor/src/assists/change_visibility.rs
@@ -1,90 +1,74 @@
1use ra_text_edit::TextEditBuilder;
2use ra_syntax::{ 1use 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}, 2 SyntaxKind::{VISIBILITY, FN_KW, MOD_KW, STRUCT_KW, ENUM_KW, TRAIT_KW, FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF},
6 TextUnit,
7}; 3};
8 4
9use crate::assists::LocalEdit; 5use crate::assists::{AssistCtx, Assist};
10 6
11pub fn change_visibility<'a>( 7pub fn change_visibility(ctx: AssistCtx) -> Option<Assist> {
12 file: &'a SourceFileNode, 8 let keyword = ctx.leaf_at_offset().find(|leaf| match leaf.kind() {
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, 9 FN_KW | MOD_KW | STRUCT_KW | ENUM_KW | TRAIT_KW => true,
19 _ => false, 10 _ => false,
20 })?; 11 })?;
21 let parent = keyword.parent()?; 12 let parent = keyword.parent()?;
22 let def_kws = vec![FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF]; 13 let def_kws = vec![FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF];
23 let node_start = parent.range().start(); 14 // Parent is not a definition, can't add visibility
24 Some(move || { 15 if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) {
25 let mut edit = TextEditBuilder::new(); 16 return None;
26 17 }
27 if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) 18 // Already have visibility, do nothing
28 || parent.children().any(|child| child.kind() == VISIBILITY) 19 if parent.children().any(|child| child.kind() == VISIBILITY) {
29 { 20 return None;
30 return LocalEdit { 21 }
31 label: "make pub crate".to_string(),
32 edit: edit.finish(),
33 cursor_position: Some(offset),
34 };
35 }
36 22
37 edit.insert(node_start, "pub(crate) ".to_string()); 23 let node_start = parent.range().start();
38 LocalEdit { 24 ctx.build("make pub crate", |edit| {
39 label: "make pub crate".to_string(), 25 edit.insert(node_start, "pub(crate) ");
40 edit: edit.finish(), 26 edit.set_cursor(node_start);
41 cursor_position: Some(node_start),
42 }
43 }) 27 })
44} 28}
45 29
46#[cfg(test)] 30#[cfg(test)]
47mod tests { 31mod tests {
48 use super::*; 32 use super::*;
49 use crate::test_utils::check_action; 33 use crate::assists::check_assist;
50 34
51 #[test] 35 #[test]
52 fn test_change_visibility() { 36 fn test_change_visibility() {
53 check_action( 37 check_assist(
38 change_visibility,
54 "<|>fn foo() {}", 39 "<|>fn foo() {}",
55 "<|>pub(crate) fn foo() {}", 40 "<|>pub(crate) fn foo() {}",
56 |file, off| change_visibility(file, off).map(|f| f()),
57 ); 41 );
58 check_action( 42 check_assist(
43 change_visibility,
59 "f<|>n foo() {}", 44 "f<|>n foo() {}",
60 "<|>pub(crate) fn foo() {}", 45 "<|>pub(crate) fn foo() {}",
61 |file, off| change_visibility(file, off).map(|f| f()),
62 ); 46 );
63 check_action( 47 check_assist(
48 change_visibility,
64 "<|>struct Foo {}", 49 "<|>struct Foo {}",
65 "<|>pub(crate) struct Foo {}", 50 "<|>pub(crate) struct Foo {}",
66 |file, off| change_visibility(file, off).map(|f| f()),
67 ); 51 );
68 check_action("<|>mod foo {}", "<|>pub(crate) mod foo {}", |file, off| { 52 check_assist(
69 change_visibility(file, off).map(|f| f()) 53 change_visibility,
70 }); 54 "<|>mod foo {}",
71 check_action( 55 "<|>pub(crate) mod foo {}",
56 );
57 check_assist(
58 change_visibility,
72 "<|>trait Foo {}", 59 "<|>trait Foo {}",
73 "<|>pub(crate) trait Foo {}", 60 "<|>pub(crate) trait Foo {}",
74 |file, off| change_visibility(file, off).map(|f| f()),
75 ); 61 );
76 check_action("m<|>od {}", "<|>pub(crate) mod {}", |file, off| { 62 check_assist(change_visibility, "m<|>od {}", "<|>pub(crate) mod {}");
77 change_visibility(file, off).map(|f| f()) 63 check_assist(
78 }); 64 change_visibility,
79 check_action(
80 "pub(crate) f<|>n foo() {}", 65 "pub(crate) f<|>n foo() {}",
81 "pub(crate) f<|>n foo() {}", 66 "pub(crate) f<|>n foo() {}",
82 |file, off| change_visibility(file, off).map(|f| f()),
83 ); 67 );
84 check_action( 68 check_assist(
69 change_visibility,
85 "unsafe f<|>n foo() {}", 70 "unsafe f<|>n foo() {}",
86 "<|>pub(crate) unsafe fn foo() {}", 71 "<|>pub(crate) unsafe fn foo() {}",
87 |file, off| change_visibility(file, off).map(|f| f()),
88 ); 72 );
89 } 73 }
90} 74}
diff --git a/crates/ra_editor/src/assists/flip_comma.rs b/crates/ra_editor/src/assists/flip_comma.rs
index d8727db0d..a343413cc 100644
--- a/crates/ra_editor/src/assists/flip_comma.rs
+++ b/crates/ra_editor/src/assists/flip_comma.rs
@@ -1,45 +1,31 @@
1use ra_text_edit::TextEditBuilder;
2use ra_syntax::{ 1use ra_syntax::{
3 algo::find_leaf_at_offset, 2 Direction,
4 Direction, SourceFileNode,
5 SyntaxKind::COMMA, 3 SyntaxKind::COMMA,
6 TextUnit,
7}; 4};
8 5
9use crate::assists::{LocalEdit, non_trivia_sibling}; 6use crate::assists::{non_trivia_sibling, AssistCtx, Assist};
10 7
11pub fn flip_comma<'a>( 8pub fn flip_comma(ctx: AssistCtx) -> Option<Assist> {
12 file: &'a SourceFileNode, 9 let comma = ctx.leaf_at_offset().find(|leaf| leaf.kind() == COMMA)?;
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)?; 10 let prev = non_trivia_sibling(comma, Direction::Prev)?;
19 let next = non_trivia_sibling(comma, Direction::Next)?; 11 let next = non_trivia_sibling(comma, Direction::Next)?;
20 Some(move || { 12 ctx.build("flip comma", |edit| {
21 let mut edit = TextEditBuilder::new(); 13 edit.replace(prev.range(), next.text());
22 edit.replace(prev.range(), next.text().to_string()); 14 edit.replace(next.range(), prev.text());
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 }) 15 })
30} 16}
31 17
32#[cfg(test)] 18#[cfg(test)]
33mod tests { 19mod tests {
34 use super::*; 20 use super::*;
35 use crate::test_utils::check_action; 21 use crate::assists::check_assist;
36 22
37 #[test] 23 #[test]
38 fn test_swap_comma() { 24 fn flip_comma_works_for_function_parameters() {
39 check_action( 25 check_assist(
26 flip_comma,
40 "fn foo(x: i32,<|> y: Result<(), ()>) {}", 27 "fn foo(x: i32,<|> y: Result<(), ()>) {}",
41 "fn foo(y: Result<(), ()>,<|> x: i32) {}", 28 "fn foo(y: Result<(), ()>,<|> x: i32) {}",
42 |file, off| flip_comma(file, off).map(|f| f()),
43 ) 29 )
44 } 30 }
45} 31}
diff --git a/crates/ra_editor/src/assists/introduce_variable.rs b/crates/ra_editor/src/assists/introduce_variable.rs
index 17ab521fa..782861023 100644
--- a/crates/ra_editor/src/assists/introduce_variable.rs
+++ b/crates/ra_editor/src/assists/introduce_variable.rs
@@ -1,19 +1,13 @@
1use ra_text_edit::TextEditBuilder;
2use ra_syntax::{ 1use ra_syntax::{
3 algo::{find_covering_node},
4 ast::{self, AstNode}, 2 ast::{self, AstNode},
5 SourceFileNode, 3 SyntaxKind::WHITESPACE,
6 SyntaxKind::{WHITESPACE}, 4 SyntaxNodeRef, TextUnit,
7 SyntaxNodeRef, TextRange, TextUnit,
8}; 5};
9 6
10use crate::assists::LocalEdit; 7use crate::assists::{AssistCtx, Assist};
11 8
12pub fn introduce_variable<'a>( 9pub fn introduce_variable<'a>(ctx: AssistCtx) -> Option<Assist> {
13 file: &'a SourceFileNode, 10 let node = ctx.covering_node();
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()?; 11 let expr = node.ancestors().filter_map(ast::Expr::cast).next()?;
18 12
19 let anchor_stmt = anchor_stmt(expr)?; 13 let anchor_stmt = anchor_stmt(expr)?;
@@ -21,9 +15,8 @@ pub fn introduce_variable<'a>(
21 if indent.kind() != WHITESPACE { 15 if indent.kind() != WHITESPACE {
22 return None; 16 return None;
23 } 17 }
24 return Some(move || { 18 ctx.build("introduce variable", move |edit| {
25 let mut buf = String::new(); 19 let mut buf = String::new();
26 let mut edit = TextEditBuilder::new();
27 20
28 buf.push_str("let var_name = "); 21 buf.push_str("let var_name = ");
29 expr.syntax().text().push_to(&mut buf); 22 expr.syntax().text().push_to(&mut buf);
@@ -40,43 +33,39 @@ pub fn introduce_variable<'a>(
40 edit.replace(expr.syntax().range(), "var_name".to_string()); 33 edit.replace(expr.syntax().range(), "var_name".to_string());
41 edit.insert(anchor_stmt.range().start(), buf); 34 edit.insert(anchor_stmt.range().start(), buf);
42 } 35 }
43 let cursor_position = anchor_stmt.range().start() + TextUnit::of_str("let "); 36 edit.set_cursor(anchor_stmt.range().start() + TextUnit::of_str("let "));
44 LocalEdit { 37 })
45 label: "introduce variable".to_string(), 38}
46 edit: edit.finish(),
47 cursor_position: Some(cursor_position),
48 }
49 });
50 39
51 /// Statement or last in the block expression, which will follow 40/// Statement or last in the block expression, which will follow
52 /// the freshly introduced var. 41/// the freshly introduced var.
53 fn anchor_stmt(expr: ast::Expr) -> Option<SyntaxNodeRef> { 42fn anchor_stmt(expr: ast::Expr) -> Option<SyntaxNodeRef> {
54 expr.syntax().ancestors().find(|&node| { 43 expr.syntax().ancestors().find(|&node| {
55 if ast::Stmt::cast(node).is_some() { 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 {
56 return true; 53 return true;
57 } 54 }
58 if let Some(expr) = node 55 }
59 .parent() 56 false
60 .and_then(ast::Block::cast) 57 })
61 .and_then(|it| it.expr())
62 {
63 if expr.syntax() == node {
64 return true;
65 }
66 }
67 false
68 })
69 }
70} 58}
71 59
72#[cfg(test)] 60#[cfg(test)]
73mod tests { 61mod tests {
74 use super::*; 62 use super::*;
75 use crate::test_utils::check_action_range; 63 use crate::assists::check_assist_range;
76 64
77 #[test] 65 #[test]
78 fn test_introduce_var_simple() { 66 fn test_introduce_var_simple() {
79 check_action_range( 67 check_assist_range(
68 introduce_variable,
80 " 69 "
81fn foo() { 70fn foo() {
82 foo(<|>1 + 1<|>); 71 foo(<|>1 + 1<|>);
@@ -86,13 +75,13 @@ fn foo() {
86 let <|>var_name = 1 + 1; 75 let <|>var_name = 1 + 1;
87 foo(var_name); 76 foo(var_name);
88}", 77}",
89 |file, range| introduce_variable(file, range).map(|f| f()),
90 ); 78 );
91 } 79 }
92 80
93 #[test] 81 #[test]
94 fn test_introduce_var_expr_stmt() { 82 fn test_introduce_var_expr_stmt() {
95 check_action_range( 83 check_assist_range(
84 introduce_variable,
96 " 85 "
97fn foo() { 86fn foo() {
98 <|>1 + 1<|>; 87 <|>1 + 1<|>;
@@ -101,13 +90,13 @@ fn foo() {
101fn foo() { 90fn foo() {
102 let <|>var_name = 1 + 1; 91 let <|>var_name = 1 + 1;
103}", 92}",
104 |file, range| introduce_variable(file, range).map(|f| f()),
105 ); 93 );
106 } 94 }
107 95
108 #[test] 96 #[test]
109 fn test_introduce_var_part_of_expr_stmt() { 97 fn test_introduce_var_part_of_expr_stmt() {
110 check_action_range( 98 check_assist_range(
99 introduce_variable,
111 " 100 "
112fn foo() { 101fn foo() {
113 <|>1<|> + 1; 102 <|>1<|> + 1;
@@ -117,13 +106,13 @@ fn foo() {
117 let <|>var_name = 1; 106 let <|>var_name = 1;
118 var_name + 1; 107 var_name + 1;
119}", 108}",
120 |file, range| introduce_variable(file, range).map(|f| f()),
121 ); 109 );
122 } 110 }
123 111
124 #[test] 112 #[test]
125 fn test_introduce_var_last_expr() { 113 fn test_introduce_var_last_expr() {
126 check_action_range( 114 check_assist_range(
115 introduce_variable,
127 " 116 "
128fn foo() { 117fn foo() {
129 bar(<|>1 + 1<|>) 118 bar(<|>1 + 1<|>)
@@ -133,13 +122,13 @@ fn foo() {
133 let <|>var_name = 1 + 1; 122 let <|>var_name = 1 + 1;
134 bar(var_name) 123 bar(var_name)
135}", 124}",
136 |file, range| introduce_variable(file, range).map(|f| f()),
137 ); 125 );
138 } 126 }
139 127
140 #[test] 128 #[test]
141 fn test_introduce_var_last_full_expr() { 129 fn test_introduce_var_last_full_expr() {
142 check_action_range( 130 check_assist_range(
131 introduce_variable,
143 " 132 "
144fn foo() { 133fn foo() {
145 <|>bar(1 + 1)<|> 134 <|>bar(1 + 1)<|>
@@ -149,7 +138,6 @@ fn foo() {
149 let <|>var_name = bar(1 + 1); 138 let <|>var_name = bar(1 + 1);
150 var_name 139 var_name
151}", 140}",
152 |file, range| introduce_variable(file, range).map(|f| f()),
153 ); 141 );
154 } 142 }
155 143
diff --git a/crates/ra_editor/src/diagnostics.rs b/crates/ra_editor/src/diagnostics.rs
index 1b336cfe2..199b0e502 100644
--- a/crates/ra_editor/src/diagnostics.rs
+++ b/crates/ra_editor/src/diagnostics.rs
@@ -57,7 +57,7 @@ fn check_unnecessary_braces_in_use_statement(
57 text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(single_use_tree) 57 text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(single_use_tree)
58 .unwrap_or_else(|| { 58 .unwrap_or_else(|| {
59 let to_replace = single_use_tree.syntax().text().to_string(); 59 let to_replace = single_use_tree.syntax().text().to_string();
60 let mut edit_builder = TextEditBuilder::new(); 60 let mut edit_builder = TextEditBuilder::default();
61 edit_builder.delete(range); 61 edit_builder.delete(range);
62 edit_builder.insert(range.start(), to_replace); 62 edit_builder.insert(range.start(), to_replace);
63 edit_builder.finish() 63 edit_builder.finish()
@@ -93,7 +93,7 @@ fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(
93 let start = use_tree_list_node.prev_sibling()?.range().start(); 93 let start = use_tree_list_node.prev_sibling()?.range().start();
94 let end = use_tree_list_node.range().end(); 94 let end = use_tree_list_node.range().end();
95 let range = TextRange::from_to(start, end); 95 let range = TextRange::from_to(start, end);
96 let mut edit_builder = TextEditBuilder::new(); 96 let mut edit_builder = TextEditBuilder::default();
97 edit_builder.delete(range); 97 edit_builder.delete(range);
98 return Some(edit_builder.finish()); 98 return Some(edit_builder.finish());
99 } 99 }
@@ -111,7 +111,7 @@ fn check_struct_shorthand_initialization(
111 let field_name = name_ref.syntax().text().to_string(); 111 let field_name = name_ref.syntax().text().to_string();
112 let field_expr = expr.syntax().text().to_string(); 112 let field_expr = expr.syntax().text().to_string();
113 if field_name == field_expr { 113 if field_name == field_expr {
114 let mut edit_builder = TextEditBuilder::new(); 114 let mut edit_builder = TextEditBuilder::default();
115 edit_builder.delete(named_field.syntax().range()); 115 edit_builder.delete(named_field.syntax().range());
116 edit_builder.insert(named_field.syntax().range().start(), field_name); 116 edit_builder.insert(named_field.syntax().range().start(), field_name);
117 let edit = edit_builder.finish(); 117 let edit = edit_builder.finish();
diff --git a/crates/ra_editor/src/typing.rs b/crates/ra_editor/src/typing.rs
index 21d068a7b..dd3d0f260 100644
--- a/crates/ra_editor/src/typing.rs
+++ b/crates/ra_editor/src/typing.rs
@@ -21,7 +21,7 @@ pub fn join_lines(file: &SourceFileNode, range: TextRange) -> LocalEdit {
21 None => { 21 None => {
22 return LocalEdit { 22 return LocalEdit {
23 label: "join lines".to_string(), 23 label: "join lines".to_string(),
24 edit: TextEditBuilder::new().finish(), 24 edit: TextEditBuilder::default().finish(),
25 cursor_position: None, 25 cursor_position: None,
26 }; 26 };
27 } 27 }
@@ -33,7 +33,7 @@ pub fn join_lines(file: &SourceFileNode, range: TextRange) -> LocalEdit {
33 }; 33 };
34 34
35 let node = find_covering_node(file.syntax(), range); 35 let node = find_covering_node(file.syntax(), range);
36 let mut edit = TextEditBuilder::new(); 36 let mut edit = TextEditBuilder::default();
37 for node in node.descendants() { 37 for node in node.descendants() {
38 let text = match node.leaf_text() { 38 let text = match node.leaf_text() {
39 Some(text) => text, 39 Some(text) => text,
@@ -76,7 +76,7 @@ pub fn on_enter(file: &SourceFileNode, offset: TextUnit) -> Option<LocalEdit> {
76 let indent = node_indent(file, comment.syntax())?; 76 let indent = node_indent(file, comment.syntax())?;
77 let inserted = format!("\n{}{} ", indent, prefix); 77 let inserted = format!("\n{}{} ", indent, prefix);
78 let cursor_position = offset + TextUnit::of_str(&inserted); 78 let cursor_position = offset + TextUnit::of_str(&inserted);
79 let mut edit = TextEditBuilder::new(); 79 let mut edit = TextEditBuilder::default();
80 edit.insert(offset, inserted); 80 edit.insert(offset, inserted);
81 Some(LocalEdit { 81 Some(LocalEdit {
82 label: "on enter".to_string(), 82 label: "on enter".to_string(),
@@ -127,7 +127,7 @@ pub fn on_eq_typed(file: &SourceFileNode, offset: TextUnit) -> Option<LocalEdit>
127 return None; 127 return None;
128 } 128 }
129 let offset = let_stmt.syntax().range().end(); 129 let offset = let_stmt.syntax().range().end();
130 let mut edit = TextEditBuilder::new(); 130 let mut edit = TextEditBuilder::default();
131 edit.insert(offset, ";".to_string()); 131 edit.insert(offset, ";".to_string());
132 Some(LocalEdit { 132 Some(LocalEdit {
133 label: "add semicolon".to_string(), 133 label: "add semicolon".to_string(),
diff --git a/crates/ra_syntax/src/yellow/syntax_text.rs b/crates/ra_syntax/src/yellow/syntax_text.rs
index 46bde9a08..783dca214 100644
--- a/crates/ra_syntax/src/yellow/syntax_text.rs
+++ b/crates/ra_syntax/src/yellow/syntax_text.rs
@@ -119,3 +119,9 @@ impl SyntaxTextSlice for ops::Range<TextUnit> {
119 TextRange::from_to(self.start, self.end).restrict(range) 119 TextRange::from_to(self.start, self.end).restrict(range)
120 } 120 }
121} 121}
122
123impl From<SyntaxText<'_>> for String {
124 fn from(text: SyntaxText) -> String {
125 text.to_string()
126 }
127}
diff --git a/crates/ra_text_edit/src/text_edit.rs b/crates/ra_text_edit/src/text_edit.rs
index 0881f3e1c..a288a990d 100644
--- a/crates/ra_text_edit/src/text_edit.rs
+++ b/crates/ra_text_edit/src/text_edit.rs
@@ -7,15 +7,12 @@ pub struct TextEdit {
7 atoms: Vec<AtomTextEdit>, 7 atoms: Vec<AtomTextEdit>,
8} 8}
9 9
10#[derive(Debug)] 10#[derive(Debug, Default)]
11pub struct TextEditBuilder { 11pub struct TextEditBuilder {
12 atoms: Vec<AtomTextEdit>, 12 atoms: Vec<AtomTextEdit>,
13} 13}
14 14
15impl TextEditBuilder { 15impl TextEditBuilder {
16 pub fn new() -> TextEditBuilder {
17 TextEditBuilder { atoms: Vec::new() }
18 }
19 pub fn replace(&mut self, range: TextRange, replace_with: String) { 16 pub fn replace(&mut self, range: TextRange, replace_with: String) {
20 self.atoms.push(AtomTextEdit::replace(range, replace_with)) 17 self.atoms.push(AtomTextEdit::replace(range, replace_with))
21 } 18 }