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 @@ impl AnalysisImpl {
res 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 file = self.file_syntax(file_id);
let offset = range.start();
let actions = vec![ let actions = vec![
("flip comma", libeditor::flip_comma(&file, offset).map(|f| f())), ("flip comma", libeditor::flip_comma(&file, offset).map(|f| f())),
("add `#[derive]`", libeditor::add_derive(&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())), ("add impl", libeditor::add_impl(&file, offset).map(|f| f())),
("introduce variable", libeditor::introduce_variable(&file, range).map(|f| f())),
]; ];
actions.into_iter() actions.into_iter()
.filter_map(|(name, local_edit)| { .filter_map(|(name, local_edit)| {

View file

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

View file

@ -1,13 +1,15 @@
use join_to_string::join; use join_to_string::join;
use libsyntax2::{ use libsyntax2::{
File, TextUnit, File, TextUnit, TextRange,
ast::{self, AstNode, AttrsOwner, TypeParamsOwner, NameOwner}, ast::{self, AstNode, AttrsOwner, TypeParamsOwner, NameOwner},
SyntaxKind::COMMA, SyntaxKind::{COMMA, WHITESPACE},
SyntaxNodeRef, SyntaxNodeRef,
algo::{ algo::{
Direction, siblings, Direction, siblings,
find_leaf_at_offset, 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> { fn non_trivia_sibling(node: SyntaxNodeRef, direction: Direction) -> Option<SyntaxNodeRef> {
siblings(node, direction) siblings(node, direction)
.skip(1) .skip(1)
@ -106,7 +133,7 @@ fn non_trivia_sibling(node: SyntaxNodeRef, direction: Direction) -> Option<Synta
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use test_utils::check_action; use test_utils::{check_action, check_action_range};
#[test] #[test]
fn test_swap_comma() { fn test_swap_comma() {
@ -155,4 +182,19 @@ mod tests {
); );
} }
#[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 @@ pub use self::{
code_actions::{ code_actions::{
LocalEdit, LocalEdit,
flip_comma, add_derive, add_impl, flip_comma, add_derive, add_impl,
introduce_variable,
}, },
typing::{join_lines, on_eq_typed}, typing::{join_lines, on_eq_typed},
completion::{scope_completion, CompletionItem}, completion::{scope_completion, CompletionItem},

View file

@ -1,4 +1,4 @@
use libsyntax2::{File, TextUnit}; use libsyntax2::{File, TextUnit, TextRange};
pub use _test_utils::*; pub use _test_utils::*;
use LocalEdit; use LocalEdit;
@ -18,3 +18,20 @@ pub fn check_action<F: Fn(&File, TextUnit) -> Option<LocalEdit>> (
let actual = add_cursor(&actual, actual_cursor_pos); let actual = add_cursor(&actual, actual_cursor_pos);
assert_eq_text!(after, &actual); 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>>> { ) -> Result<Option<Vec<Command>>> {
let file_id = params.text_document.try_conv_with(&world)?; let file_id = params.text_document.try_conv_with(&world)?;
let line_index = world.analysis().file_line_index(file_id); 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() let fixes = world.analysis().diagnostics(file_id).into_iter()
.filter_map(|d| Some((d.range, d.fix?))) .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); .map(|(_range, fix)| fix);
let mut res = Vec::new(); let mut res = Vec::new();