mirror of
https://github.com/rust-lang/rust-analyzer
synced 2025-01-13 13:48:50 +00:00
introduce variable
This commit is contained in:
parent
47e8b80e9b
commit
bb64edf8ba
6 changed files with 72 additions and 10 deletions
|
@ -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)| {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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},
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
Loading…
Reference in a new issue