introduce variable

This commit is contained in:
Aleksey Kladov 2018-09-06 00:59:07 +03:00
parent 47e8b80e9b
commit bb64edf8ba
6 changed files with 72 additions and 10 deletions

View File

@ -256,12 +256,14 @@ pub fn diagnostics(&self, file_id: FileId) -> Vec<Diagnostic> {
res
}
pub fn assists(&self, file_id: FileId, offset: TextUnit) -> Vec<SourceChange> {
pub fn assists(&self, file_id: FileId, range: TextRange) -> Vec<SourceChange> {
let file = self.file_syntax(file_id);
let offset = range.start();
let actions = vec![
("flip comma", libeditor::flip_comma(&file, offset).map(|f| f())),
("add `#[derive]`", libeditor::add_derive(&file, offset).map(|f| f())),
("add impl", libeditor::add_impl(&file, offset).map(|f| f())),
("introduce variable", libeditor::introduce_variable(&file, range).map(|f| f())),
];
actions.into_iter()
.filter_map(|(name, local_edit)| {

View File

@ -209,8 +209,8 @@ pub fn completions(&self, file_id: FileId, offset: TextUnit) -> Option<Vec<Compl
let file = self.file_syntax(file_id);
libeditor::scope_completion(&file, offset)
}
pub fn assists(&self, file_id: FileId, offset: TextUnit) -> Vec<SourceChange> {
self.imp.assists(file_id, offset)
pub fn assists(&self, file_id: FileId, range: TextRange) -> Vec<SourceChange> {
self.imp.assists(file_id, range)
}
pub fn diagnostics(&self, file_id: FileId) -> Vec<Diagnostic> {
self.imp.diagnostics(file_id)

View File

@ -1,13 +1,15 @@
use join_to_string::join;
use libsyntax2::{
File, TextUnit,
File, TextUnit, TextRange,
ast::{self, AstNode, AttrsOwner, TypeParamsOwner, NameOwner},
SyntaxKind::COMMA,
SyntaxKind::{COMMA, WHITESPACE},
SyntaxNodeRef,
algo::{
Direction, siblings,
find_leaf_at_offset,
find_covering_node,
ancestors,
},
};
@ -97,6 +99,31 @@ pub fn add_impl<'a>(file: &'a File, offset: TextUnit) -> Option<impl FnOnce() ->
})
}
pub fn introduce_variable<'a>(file: &'a File, range: TextRange) -> Option<impl FnOnce() -> LocalEdit + 'a> {
let node = find_covering_node(file.syntax(), range);
let expr = ancestors(node).filter_map(ast::Expr::cast).next()?;
let anchor_stmt = ancestors(expr.syntax()).filter_map(ast::Stmt::cast).next()?;
let indent = anchor_stmt.syntax().prev_sibling()?;
if indent.kind() != WHITESPACE {
return None;
}
Some(move || {
let mut buf = String::new();
buf.push_str("let var_name = ");
expr.syntax().text().push_to(&mut buf);
buf.push_str(";");
indent.text().push_to(&mut buf);
let mut edit = EditBuilder::new();
edit.replace(expr.syntax().range(), "var_name".to_string());
edit.insert(anchor_stmt.syntax().range().start(), buf);
LocalEdit {
edit: edit.finish(),
cursor_position: Some(anchor_stmt.syntax().range().start() + TextUnit::of_str("let ")),
}
})
}
fn non_trivia_sibling(node: SyntaxNodeRef, direction: Direction) -> Option<SyntaxNodeRef> {
siblings(node, direction)
.skip(1)
@ -106,7 +133,7 @@ fn non_trivia_sibling(node: SyntaxNodeRef, direction: Direction) -> Option<Synta
#[cfg(test)]
mod tests {
use super::*;
use test_utils::check_action;
use test_utils::{check_action, check_action_range};
#[test]
fn test_swap_comma() {
@ -155,4 +182,19 @@ fn test_add_impl() {
);
}
#[test]
fn test_intrdoduce_var() {
check_action_range(
"
fn foo() {
foo(<|>1 + 1<|>);
}", "
fn foo() {
let <|>var_name = 1 + 1;
foo(var_name);
}",
|file, range| introduce_variable(file, range).map(|f| f()),
);
}
}

View File

@ -32,6 +32,7 @@
code_actions::{
LocalEdit,
flip_comma, add_derive, add_impl,
introduce_variable,
},
typing::{join_lines, on_eq_typed},
completion::{scope_completion, CompletionItem},

View File

@ -1,4 +1,4 @@
use libsyntax2::{File, TextUnit};
use libsyntax2::{File, TextUnit, TextRange};
pub use _test_utils::*;
use LocalEdit;
@ -18,3 +18,20 @@ pub fn check_action<F: Fn(&File, TextUnit) -> Option<LocalEdit>> (
let actual = add_cursor(&actual, actual_cursor_pos);
assert_eq_text!(after, &actual);
}
pub fn check_action_range<F: Fn(&File, TextRange) -> Option<LocalEdit>> (
before: &str,
after: &str,
f: F,
) {
let (range, before) = extract_range(before);
let file = File::parse(&before);
let result = f(&file, range).expect("code action is not applicable");
let actual = result.edit.apply(&before);
let actual_cursor_pos = match result.cursor_position {
None => result.edit.apply_to_offset(range.start()).unwrap(),
Some(off) => off,
};
let actual = add_cursor(&actual, actual_cursor_pos);
assert_eq_text!(after, &actual);
}

View File

@ -372,12 +372,12 @@ pub fn handle_code_action(
) -> Result<Option<Vec<Command>>> {
let file_id = params.text_document.try_conv_with(&world)?;
let line_index = world.analysis().file_line_index(file_id);
let offset = params.range.conv_with(&line_index).start();
let range = params.range.conv_with(&line_index);
let assists = world.analysis().assists(file_id, offset).into_iter();
let assists = world.analysis().assists(file_id, range).into_iter();
let fixes = world.analysis().diagnostics(file_id).into_iter()
.filter_map(|d| Some((d.range, d.fix?)))
.filter(|(range, _fix)| contains_offset_nonstrict(*range, offset))
.filter(|(range, _fix)| contains_offset_nonstrict(*range, range.start()))
.map(|(_range, fix)| fix);
let mut res = Vec::new();