diff options
-rw-r--r-- | crates/libanalysis/src/imp.rs | 4 | ||||
-rw-r--r-- | crates/libanalysis/src/lib.rs | 4 | ||||
-rw-r--r-- | crates/libeditor/src/code_actions.rs | 48 | ||||
-rw-r--r-- | crates/libeditor/src/lib.rs | 1 | ||||
-rw-r--r-- | crates/libeditor/src/test_utils.rs | 19 | ||||
-rw-r--r-- | crates/server/src/main_loop/handlers.rs | 6 |
6 files changed, 72 insertions, 10 deletions
diff --git a/crates/libanalysis/src/imp.rs b/crates/libanalysis/src/imp.rs index e3ccffbf0..47b0d79ff 100644 --- a/crates/libanalysis/src/imp.rs +++ b/crates/libanalysis/src/imp.rs | |||
@@ -256,12 +256,14 @@ impl AnalysisImpl { | |||
256 | res | 256 | res |
257 | } | 257 | } |
258 | 258 | ||
259 | pub fn assists(&self, file_id: FileId, offset: TextUnit) -> Vec<SourceChange> { | 259 | pub fn assists(&self, file_id: FileId, range: TextRange) -> Vec<SourceChange> { |
260 | let file = self.file_syntax(file_id); | 260 | let file = self.file_syntax(file_id); |
261 | let offset = range.start(); | ||
261 | let actions = vec![ | 262 | let actions = vec![ |
262 | ("flip comma", libeditor::flip_comma(&file, offset).map(|f| f())), | 263 | ("flip comma", libeditor::flip_comma(&file, offset).map(|f| f())), |
263 | ("add `#[derive]`", libeditor::add_derive(&file, offset).map(|f| f())), | 264 | ("add `#[derive]`", libeditor::add_derive(&file, offset).map(|f| f())), |
264 | ("add impl", libeditor::add_impl(&file, offset).map(|f| f())), | 265 | ("add impl", libeditor::add_impl(&file, offset).map(|f| f())), |
266 | ("introduce variable", libeditor::introduce_variable(&file, range).map(|f| f())), | ||
265 | ]; | 267 | ]; |
266 | actions.into_iter() | 268 | actions.into_iter() |
267 | .filter_map(|(name, local_edit)| { | 269 | .filter_map(|(name, local_edit)| { |
diff --git a/crates/libanalysis/src/lib.rs b/crates/libanalysis/src/lib.rs index a8152939b..4e63813f9 100644 --- a/crates/libanalysis/src/lib.rs +++ b/crates/libanalysis/src/lib.rs | |||
@@ -209,8 +209,8 @@ impl Analysis { | |||
209 | let file = self.file_syntax(file_id); | 209 | let file = self.file_syntax(file_id); |
210 | libeditor::scope_completion(&file, offset) | 210 | libeditor::scope_completion(&file, offset) |
211 | } | 211 | } |
212 | pub fn assists(&self, file_id: FileId, offset: TextUnit) -> Vec<SourceChange> { | 212 | pub fn assists(&self, file_id: FileId, range: TextRange) -> Vec<SourceChange> { |
213 | self.imp.assists(file_id, offset) | 213 | self.imp.assists(file_id, range) |
214 | } | 214 | } |
215 | pub fn diagnostics(&self, file_id: FileId) -> Vec<Diagnostic> { | 215 | pub fn diagnostics(&self, file_id: FileId) -> Vec<Diagnostic> { |
216 | self.imp.diagnostics(file_id) | 216 | self.imp.diagnostics(file_id) |
diff --git a/crates/libeditor/src/code_actions.rs b/crates/libeditor/src/code_actions.rs index 522b605ed..ebe70681b 100644 --- a/crates/libeditor/src/code_actions.rs +++ b/crates/libeditor/src/code_actions.rs | |||
@@ -1,13 +1,15 @@ | |||
1 | use join_to_string::join; | 1 | use join_to_string::join; |
2 | 2 | ||
3 | use libsyntax2::{ | 3 | use libsyntax2::{ |
4 | File, TextUnit, | 4 | File, TextUnit, TextRange, |
5 | ast::{self, AstNode, AttrsOwner, TypeParamsOwner, NameOwner}, | 5 | ast::{self, AstNode, AttrsOwner, TypeParamsOwner, NameOwner}, |
6 | SyntaxKind::COMMA, | 6 | SyntaxKind::{COMMA, WHITESPACE}, |
7 | SyntaxNodeRef, | 7 | SyntaxNodeRef, |
8 | algo::{ | 8 | algo::{ |
9 | Direction, siblings, | 9 | Direction, siblings, |
10 | find_leaf_at_offset, | 10 | find_leaf_at_offset, |
11 | find_covering_node, | ||
12 | ancestors, | ||
11 | }, | 13 | }, |
12 | }; | 14 | }; |
13 | 15 | ||
@@ -97,6 +99,31 @@ pub fn add_impl<'a>(file: &'a File, offset: TextUnit) -> Option<impl FnOnce() -> | |||
97 | }) | 99 | }) |
98 | } | 100 | } |
99 | 101 | ||
102 | pub fn introduce_variable<'a>(file: &'a File, range: TextRange) -> Option<impl FnOnce() -> LocalEdit + 'a> { | ||
103 | let node = find_covering_node(file.syntax(), range); | ||
104 | let expr = ancestors(node).filter_map(ast::Expr::cast).next()?; | ||
105 | let anchor_stmt = ancestors(expr.syntax()).filter_map(ast::Stmt::cast).next()?; | ||
106 | let indent = anchor_stmt.syntax().prev_sibling()?; | ||
107 | if indent.kind() != WHITESPACE { | ||
108 | return None; | ||
109 | } | ||
110 | Some(move || { | ||
111 | let mut buf = String::new(); | ||
112 | buf.push_str("let var_name = "); | ||
113 | expr.syntax().text().push_to(&mut buf); | ||
114 | buf.push_str(";"); | ||
115 | indent.text().push_to(&mut buf); | ||
116 | |||
117 | let mut edit = EditBuilder::new(); | ||
118 | edit.replace(expr.syntax().range(), "var_name".to_string()); | ||
119 | edit.insert(anchor_stmt.syntax().range().start(), buf); | ||
120 | LocalEdit { | ||
121 | edit: edit.finish(), | ||
122 | cursor_position: Some(anchor_stmt.syntax().range().start() + TextUnit::of_str("let ")), | ||
123 | } | ||
124 | }) | ||
125 | } | ||
126 | |||
100 | fn non_trivia_sibling(node: SyntaxNodeRef, direction: Direction) -> Option<SyntaxNodeRef> { | 127 | fn non_trivia_sibling(node: SyntaxNodeRef, direction: Direction) -> Option<SyntaxNodeRef> { |
101 | siblings(node, direction) | 128 | siblings(node, direction) |
102 | .skip(1) | 129 | .skip(1) |
@@ -106,7 +133,7 @@ fn non_trivia_sibling(node: SyntaxNodeRef, direction: Direction) -> Option<Synta | |||
106 | #[cfg(test)] | 133 | #[cfg(test)] |
107 | mod tests { | 134 | mod tests { |
108 | use super::*; | 135 | use super::*; |
109 | use test_utils::check_action; | 136 | use test_utils::{check_action, check_action_range}; |
110 | 137 | ||
111 | #[test] | 138 | #[test] |
112 | fn test_swap_comma() { | 139 | fn test_swap_comma() { |
@@ -155,4 +182,19 @@ mod tests { | |||
155 | ); | 182 | ); |
156 | } | 183 | } |
157 | 184 | ||
185 | #[test] | ||
186 | fn test_intrdoduce_var() { | ||
187 | check_action_range( | ||
188 | " | ||
189 | fn foo() { | ||
190 | foo(<|>1 + 1<|>); | ||
191 | }", " | ||
192 | fn foo() { | ||
193 | let <|>var_name = 1 + 1; | ||
194 | foo(var_name); | ||
195 | }", | ||
196 | |file, range| introduce_variable(file, range).map(|f| f()), | ||
197 | ); | ||
198 | } | ||
199 | |||
158 | } | 200 | } |
diff --git a/crates/libeditor/src/lib.rs b/crates/libeditor/src/lib.rs index 4700ef328..b3cf2ef55 100644 --- a/crates/libeditor/src/lib.rs +++ b/crates/libeditor/src/lib.rs | |||
@@ -32,6 +32,7 @@ pub use self::{ | |||
32 | code_actions::{ | 32 | code_actions::{ |
33 | LocalEdit, | 33 | LocalEdit, |
34 | flip_comma, add_derive, add_impl, | 34 | flip_comma, add_derive, add_impl, |
35 | introduce_variable, | ||
35 | }, | 36 | }, |
36 | typing::{join_lines, on_eq_typed}, | 37 | typing::{join_lines, on_eq_typed}, |
37 | completion::{scope_completion, CompletionItem}, | 38 | completion::{scope_completion, CompletionItem}, |
diff --git a/crates/libeditor/src/test_utils.rs b/crates/libeditor/src/test_utils.rs index 037319cd0..9c1279991 100644 --- a/crates/libeditor/src/test_utils.rs +++ b/crates/libeditor/src/test_utils.rs | |||
@@ -1,4 +1,4 @@ | |||
1 | use libsyntax2::{File, TextUnit}; | 1 | use libsyntax2::{File, TextUnit, TextRange}; |
2 | pub use _test_utils::*; | 2 | pub use _test_utils::*; |
3 | use LocalEdit; | 3 | use LocalEdit; |
4 | 4 | ||
@@ -18,3 +18,20 @@ pub fn check_action<F: Fn(&File, TextUnit) -> Option<LocalEdit>> ( | |||
18 | let actual = add_cursor(&actual, actual_cursor_pos); | 18 | let actual = add_cursor(&actual, actual_cursor_pos); |
19 | assert_eq_text!(after, &actual); | 19 | assert_eq_text!(after, &actual); |
20 | } | 20 | } |
21 | |||
22 | pub fn check_action_range<F: Fn(&File, TextRange) -> Option<LocalEdit>> ( | ||
23 | before: &str, | ||
24 | after: &str, | ||
25 | f: F, | ||
26 | ) { | ||
27 | let (range, before) = extract_range(before); | ||
28 | let file = File::parse(&before); | ||
29 | let result = f(&file, range).expect("code action is not applicable"); | ||
30 | let actual = result.edit.apply(&before); | ||
31 | let actual_cursor_pos = match result.cursor_position { | ||
32 | None => result.edit.apply_to_offset(range.start()).unwrap(), | ||
33 | Some(off) => off, | ||
34 | }; | ||
35 | let actual = add_cursor(&actual, actual_cursor_pos); | ||
36 | assert_eq_text!(after, &actual); | ||
37 | } | ||
diff --git a/crates/server/src/main_loop/handlers.rs b/crates/server/src/main_loop/handlers.rs index 323d4e95e..3e02227d5 100644 --- a/crates/server/src/main_loop/handlers.rs +++ b/crates/server/src/main_loop/handlers.rs | |||
@@ -372,12 +372,12 @@ pub fn handle_code_action( | |||
372 | ) -> Result<Option<Vec<Command>>> { | 372 | ) -> Result<Option<Vec<Command>>> { |
373 | let file_id = params.text_document.try_conv_with(&world)?; | 373 | let file_id = params.text_document.try_conv_with(&world)?; |
374 | let line_index = world.analysis().file_line_index(file_id); | 374 | let line_index = world.analysis().file_line_index(file_id); |
375 | let offset = params.range.conv_with(&line_index).start(); | 375 | let range = params.range.conv_with(&line_index); |
376 | 376 | ||
377 | let assists = world.analysis().assists(file_id, offset).into_iter(); | 377 | let assists = world.analysis().assists(file_id, range).into_iter(); |
378 | let fixes = world.analysis().diagnostics(file_id).into_iter() | 378 | let fixes = world.analysis().diagnostics(file_id).into_iter() |
379 | .filter_map(|d| Some((d.range, d.fix?))) | 379 | .filter_map(|d| Some((d.range, d.fix?))) |
380 | .filter(|(range, _fix)| contains_offset_nonstrict(*range, offset)) | 380 | .filter(|(range, _fix)| contains_offset_nonstrict(*range, range.start())) |
381 | .map(|(_range, fix)| fix); | 381 | .map(|(_range, fix)| fix); |
382 | 382 | ||
383 | let mut res = Vec::new(); | 383 | let mut res = Vec::new(); |