aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/libanalysis/src/imp.rs4
-rw-r--r--crates/libanalysis/src/lib.rs4
-rw-r--r--crates/libeditor/src/code_actions.rs48
-rw-r--r--crates/libeditor/src/lib.rs1
-rw-r--r--crates/libeditor/src/test_utils.rs19
-rw-r--r--crates/server/src/main_loop/handlers.rs6
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 @@
1use join_to_string::join; 1use join_to_string::join;
2 2
3use libsyntax2::{ 3use 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
102pub 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
100fn non_trivia_sibling(node: SyntaxNodeRef, direction: Direction) -> Option<SyntaxNodeRef> { 127fn 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)]
107mod tests { 134mod 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 "
189fn foo() {
190 foo(<|>1 + 1<|>);
191}", "
192fn 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 @@
1use libsyntax2::{File, TextUnit}; 1use libsyntax2::{File, TextUnit, TextRange};
2pub use _test_utils::*; 2pub use _test_utils::*;
3use LocalEdit; 3use 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
22pub 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();