From 0c5fd8f7cbf04eda763e55bc9a38dad5f7ec917d Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sun, 3 Feb 2019 21:26:35 +0300 Subject: move assists to a separate crate --- crates/ra_ide_api_light/src/assists.rs | 215 ---------- crates/ra_ide_api_light/src/assists/add_derive.rs | 84 ---- crates/ra_ide_api_light/src/assists/add_impl.rs | 66 ---- .../src/assists/change_visibility.rs | 165 -------- crates/ra_ide_api_light/src/assists/flip_comma.rs | 31 -- .../src/assists/introduce_variable.rs | 431 --------------------- .../src/assists/replace_if_let_with_match.rs | 81 ---- .../ra_ide_api_light/src/assists/split_import.rs | 56 --- crates/ra_ide_api_light/src/formatting.rs | 10 +- crates/ra_ide_api_light/src/lib.rs | 11 +- crates/ra_ide_api_light/src/test_utils.rs | 31 +- 11 files changed, 17 insertions(+), 1164 deletions(-) delete mode 100644 crates/ra_ide_api_light/src/assists.rs delete mode 100644 crates/ra_ide_api_light/src/assists/add_derive.rs delete mode 100644 crates/ra_ide_api_light/src/assists/add_impl.rs delete mode 100644 crates/ra_ide_api_light/src/assists/change_visibility.rs delete mode 100644 crates/ra_ide_api_light/src/assists/flip_comma.rs delete mode 100644 crates/ra_ide_api_light/src/assists/introduce_variable.rs delete mode 100644 crates/ra_ide_api_light/src/assists/replace_if_let_with_match.rs delete mode 100644 crates/ra_ide_api_light/src/assists/split_import.rs (limited to 'crates/ra_ide_api_light/src') diff --git a/crates/ra_ide_api_light/src/assists.rs b/crates/ra_ide_api_light/src/assists.rs deleted file mode 100644 index e578805f1..000000000 --- a/crates/ra_ide_api_light/src/assists.rs +++ /dev/null @@ -1,215 +0,0 @@ -//! This modules contains various "assists": suggestions for source code edits -//! which are likely to occur at a given cursor position. For example, if the -//! cursor is on the `,`, a possible assist is swapping the elements around the -//! comma. - -mod flip_comma; -mod add_derive; -mod add_impl; -mod introduce_variable; -mod change_visibility; -mod split_import; -mod replace_if_let_with_match; - -use ra_text_edit::{TextEdit, TextEditBuilder}; -use ra_syntax::{ - Direction, SyntaxNode, TextUnit, TextRange, SourceFile, AstNode, - algo::{find_leaf_at_offset, find_node_at_offset, find_covering_node, LeafAtOffset}, -}; -use itertools::Itertools; - -use crate::formatting::leading_indent; - -pub use self::{ - flip_comma::flip_comma, - add_derive::add_derive, - add_impl::add_impl, - introduce_variable::introduce_variable, - change_visibility::change_visibility, - split_import::split_import, - replace_if_let_with_match::replace_if_let_with_match, -}; - -/// Return all the assists applicable at the given position. -pub fn assists(file: &SourceFile, range: TextRange) -> Vec { - let ctx = AssistCtx::new(file, range); - [ - flip_comma, - add_derive, - add_impl, - introduce_variable, - change_visibility, - split_import, - replace_if_let_with_match, - ] - .iter() - .filter_map(|&assist| ctx.clone().apply(assist)) - .collect() -} - -#[derive(Debug)] -pub struct LocalEdit { - pub label: String, - pub edit: TextEdit, - pub cursor_position: Option, -} - -fn non_trivia_sibling(node: &SyntaxNode, direction: Direction) -> Option<&SyntaxNode> { - node.siblings(direction) - .skip(1) - .find(|node| !node.kind().is_trivia()) -} - -/// `AssistCtx` allows to apply an assist or check if it could be applied. -/// -/// Assists use a somewhat overengineered approach, given the current needs. The -/// assists workflow consists of two phases. In the first phase, a user asks for -/// the list of available assists. In the second phase, the user picks a -/// particular assist and it gets applied. -/// -/// There are two peculiarities here: -/// -/// * first, we ideally avoid computing more things then necessary to answer -/// "is assist applicable" in the first phase. -/// * second, when we are applying assist, we don't have a guarantee that there -/// weren't any changes between the point when user asked for assists and when -/// they applied a particular assist. So, when applying assist, we need to do -/// all the checks from scratch. -/// -/// To avoid repeating the same code twice for both "check" and "apply" -/// functions, we use an approach reminiscent of that of Django's function based -/// views dealing with forms. Each assist receives a runtime parameter, -/// `should_compute_edit`. It first check if an edit is applicable (potentially -/// computing info required to compute the actual edit). If it is applicable, -/// and `should_compute_edit` is `true`, it then computes the actual edit. -/// -/// So, to implement the original assists workflow, we can first apply each edit -/// with `should_compute_edit = false`, and then applying the selected edit -/// again, with `should_compute_edit = true` this time. -/// -/// Note, however, that we don't actually use such two-phase logic at the -/// moment, because the LSP API is pretty awkward in this place, and it's much -/// easier to just compute the edit eagerly :-) -#[derive(Debug, Clone)] -pub struct AssistCtx<'a> { - source_file: &'a SourceFile, - range: TextRange, - should_compute_edit: bool, -} - -#[derive(Debug)] -pub enum Assist { - Applicable, - Edit(LocalEdit), -} - -#[derive(Default)] -pub struct AssistBuilder { - edit: TextEditBuilder, - cursor_position: Option, -} - -impl<'a> AssistCtx<'a> { - pub fn new(source_file: &'a SourceFile, range: TextRange) -> AssistCtx { - AssistCtx { - source_file, - range, - should_compute_edit: false, - } - } - - pub fn apply(mut self, assist: fn(AssistCtx) -> Option) -> Option { - self.should_compute_edit = true; - match assist(self) { - None => None, - Some(Assist::Edit(e)) => Some(e), - Some(Assist::Applicable) => unreachable!(), - } - } - - pub fn check(mut self, assist: fn(AssistCtx) -> Option) -> bool { - self.should_compute_edit = false; - match assist(self) { - None => false, - Some(Assist::Edit(_)) => unreachable!(), - Some(Assist::Applicable) => true, - } - } - - fn build(self, label: impl Into, f: impl FnOnce(&mut AssistBuilder)) -> Option { - if !self.should_compute_edit { - return Some(Assist::Applicable); - } - let mut edit = AssistBuilder::default(); - f(&mut edit); - Some(edit.build(label)) - } - - pub(crate) fn leaf_at_offset(&self) -> LeafAtOffset<&'a SyntaxNode> { - find_leaf_at_offset(self.source_file.syntax(), self.range.start()) - } - pub(crate) fn node_at_offset(&self) -> Option<&'a N> { - find_node_at_offset(self.source_file.syntax(), self.range.start()) - } - pub(crate) fn covering_node(&self) -> &'a SyntaxNode { - find_covering_node(self.source_file.syntax(), self.range) - } -} - -impl AssistBuilder { - fn replace(&mut self, range: TextRange, replace_with: impl Into) { - self.edit.replace(range, replace_with.into()) - } - pub fn replace_node_and_indent(&mut self, node: &SyntaxNode, replace_with: impl Into) { - let mut replace_with = replace_with.into(); - if let Some(indent) = leading_indent(node) { - replace_with = reindent(&replace_with, indent) - } - self.replace(node.range(), replace_with) - } - #[allow(unused)] - fn delete(&mut self, range: TextRange) { - self.edit.delete(range) - } - fn insert(&mut self, offset: TextUnit, text: impl Into) { - self.edit.insert(offset, text.into()) - } - fn set_cursor(&mut self, offset: TextUnit) { - self.cursor_position = Some(offset) - } - pub fn build(self, label: impl Into) -> Assist { - Assist::Edit(LocalEdit { - label: label.into(), - cursor_position: self.cursor_position, - edit: self.edit.finish(), - }) - } -} - -fn reindent(text: &str, indent: &str) -> String { - let indent = format!("\n{}", indent); - text.lines().intersperse(&indent).collect() -} - -#[cfg(test)] -fn check_assist(assist: fn(AssistCtx) -> Option, before: &str, after: &str) { - crate::test_utils::check_action(before, after, |file, off| { - let range = TextRange::offset_len(off, 0.into()); - AssistCtx::new(file, range).apply(assist) - }) -} - -#[cfg(test)] -fn check_assist_not_applicable(assist: fn(AssistCtx) -> Option, text: &str) { - crate::test_utils::check_action_not_applicable(text, |file, off| { - let range = TextRange::offset_len(off, 0.into()); - AssistCtx::new(file, range).apply(assist) - }) -} - -#[cfg(test)] -fn check_assist_range(assist: fn(AssistCtx) -> Option, before: &str, after: &str) { - crate::test_utils::check_action_range(before, after, |file, range| { - AssistCtx::new(file, range).apply(assist) - }) -} diff --git a/crates/ra_ide_api_light/src/assists/add_derive.rs b/crates/ra_ide_api_light/src/assists/add_derive.rs deleted file mode 100644 index 6e964d011..000000000 --- a/crates/ra_ide_api_light/src/assists/add_derive.rs +++ /dev/null @@ -1,84 +0,0 @@ -use ra_syntax::{ - ast::{self, AstNode, AttrsOwner}, - SyntaxKind::{WHITESPACE, COMMENT}, - TextUnit, -}; - -use crate::assists::{AssistCtx, Assist}; - -pub fn add_derive(ctx: AssistCtx) -> Option { - let nominal = ctx.node_at_offset::()?; - let node_start = derive_insertion_offset(nominal)?; - ctx.build("add `#[derive]`", |edit| { - let derive_attr = nominal - .attrs() - .filter_map(|x| x.as_call()) - .filter(|(name, _arg)| name == "derive") - .map(|(_name, arg)| arg) - .next(); - let offset = match derive_attr { - None => { - edit.insert(node_start, "#[derive()]\n"); - node_start + TextUnit::of_str("#[derive(") - } - Some(tt) => tt.syntax().range().end() - TextUnit::of_char(')'), - }; - edit.set_cursor(offset) - }) -} - -// Insert `derive` after doc comments. -fn derive_insertion_offset(nominal: &ast::NominalDef) -> Option { - let non_ws_child = nominal - .syntax() - .children() - .find(|it| it.kind() != COMMENT && it.kind() != WHITESPACE)?; - Some(non_ws_child.range().start()) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::assists::check_assist; - - #[test] - fn add_derive_new() { - check_assist( - add_derive, - "struct Foo { a: i32, <|>}", - "#[derive(<|>)]\nstruct Foo { a: i32, }", - ); - check_assist( - add_derive, - "struct Foo { <|> a: i32, }", - "#[derive(<|>)]\nstruct Foo { a: i32, }", - ); - } - - #[test] - fn add_derive_existing() { - check_assist( - add_derive, - "#[derive(Clone)]\nstruct Foo { a: i32<|>, }", - "#[derive(Clone<|>)]\nstruct Foo { a: i32, }", - ); - } - - #[test] - fn add_derive_new_with_doc_comment() { - check_assist( - add_derive, - " -/// `Foo` is a pretty important struct. -/// It does stuff. -struct Foo { a: i32<|>, } - ", - " -/// `Foo` is a pretty important struct. -/// It does stuff. -#[derive(<|>)] -struct Foo { a: i32, } - ", - ); - } -} diff --git a/crates/ra_ide_api_light/src/assists/add_impl.rs b/crates/ra_ide_api_light/src/assists/add_impl.rs deleted file mode 100644 index 2eda7cae2..000000000 --- a/crates/ra_ide_api_light/src/assists/add_impl.rs +++ /dev/null @@ -1,66 +0,0 @@ -use join_to_string::join; -use ra_syntax::{ - ast::{self, AstNode, AstToken, NameOwner, TypeParamsOwner}, - TextUnit, -}; - -use crate::assists::{AssistCtx, Assist}; - -pub fn add_impl(ctx: AssistCtx) -> Option { - let nominal = ctx.node_at_offset::()?; - let name = nominal.name()?; - ctx.build("add impl", |edit| { - let type_params = nominal.type_param_list(); - let start_offset = nominal.syntax().range().end(); - let mut buf = String::new(); - buf.push_str("\n\nimpl"); - if let Some(type_params) = type_params { - type_params.syntax().text().push_to(&mut buf); - } - buf.push_str(" "); - buf.push_str(name.text().as_str()); - if let Some(type_params) = type_params { - let lifetime_params = type_params - .lifetime_params() - .filter_map(|it| it.lifetime()) - .map(|it| it.text()); - let type_params = type_params - .type_params() - .filter_map(|it| it.name()) - .map(|it| it.text()); - join(lifetime_params.chain(type_params)) - .surround_with("<", ">") - .to_buf(&mut buf); - } - buf.push_str(" {\n"); - edit.set_cursor(start_offset + TextUnit::of_str(&buf)); - buf.push_str("\n}"); - edit.insert(start_offset, buf); - }) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::assists::check_assist; - - #[test] - fn test_add_impl() { - check_assist( - add_impl, - "struct Foo {<|>}\n", - "struct Foo {}\n\nimpl Foo {\n<|>\n}\n", - ); - check_assist( - add_impl, - "struct Foo {<|>}", - "struct Foo {}\n\nimpl Foo {\n<|>\n}", - ); - check_assist( - add_impl, - "struct Foo<'a, T: Foo<'a>> {<|>}", - "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n<|>\n}", - ); - } - -} diff --git a/crates/ra_ide_api_light/src/assists/change_visibility.rs b/crates/ra_ide_api_light/src/assists/change_visibility.rs deleted file mode 100644 index 6e8bc2632..000000000 --- a/crates/ra_ide_api_light/src/assists/change_visibility.rs +++ /dev/null @@ -1,165 +0,0 @@ -use ra_syntax::{ - AstNode, SyntaxNode, TextUnit, - ast::{self, VisibilityOwner, NameOwner}, - SyntaxKind::{VISIBILITY, FN_KW, MOD_KW, STRUCT_KW, ENUM_KW, TRAIT_KW, FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF, IDENT, WHITESPACE, COMMENT, ATTR}, -}; - -use crate::assists::{AssistCtx, Assist}; - -pub fn change_visibility(ctx: AssistCtx) -> Option { - if let Some(vis) = ctx.node_at_offset::() { - return change_vis(ctx, vis); - } - add_vis(ctx) -} - -fn add_vis(ctx: AssistCtx) -> Option { - let item_keyword = ctx.leaf_at_offset().find(|leaf| match leaf.kind() { - FN_KW | MOD_KW | STRUCT_KW | ENUM_KW | TRAIT_KW => true, - _ => false, - }); - - let offset = if let Some(keyword) = item_keyword { - let parent = keyword.parent()?; - let def_kws = vec![FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF]; - // Parent is not a definition, can't add visibility - if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) { - return None; - } - // Already have visibility, do nothing - if parent.children().any(|child| child.kind() == VISIBILITY) { - return None; - } - vis_offset(parent) - } else { - let ident = ctx.leaf_at_offset().find(|leaf| leaf.kind() == IDENT)?; - let field = ident.ancestors().find_map(ast::NamedFieldDef::cast)?; - if field.name()?.syntax().range() != ident.range() && field.visibility().is_some() { - return None; - } - vis_offset(field.syntax()) - }; - - ctx.build("make pub(crate)", |edit| { - edit.insert(offset, "pub(crate) "); - edit.set_cursor(offset); - }) -} - -fn vis_offset(node: &SyntaxNode) -> TextUnit { - node.children() - .skip_while(|it| match it.kind() { - WHITESPACE | COMMENT | ATTR => true, - _ => false, - }) - .next() - .map(|it| it.range().start()) - .unwrap_or(node.range().start()) -} - -fn change_vis(ctx: AssistCtx, vis: &ast::Visibility) -> Option { - if vis.syntax().text() == "pub" { - return ctx.build("chage to pub(crate)", |edit| { - edit.replace(vis.syntax().range(), "pub(crate)"); - edit.set_cursor(vis.syntax().range().start()); - }); - } - if vis.syntax().text() == "pub(crate)" { - return ctx.build("chage to pub", |edit| { - edit.replace(vis.syntax().range(), "pub"); - edit.set_cursor(vis.syntax().range().start()); - }); - } - None -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::assists::check_assist; - - #[test] - fn change_visibility_adds_pub_crate_to_items() { - check_assist( - change_visibility, - "<|>fn foo() {}", - "<|>pub(crate) fn foo() {}", - ); - check_assist( - change_visibility, - "f<|>n foo() {}", - "<|>pub(crate) fn foo() {}", - ); - check_assist( - change_visibility, - "<|>struct Foo {}", - "<|>pub(crate) struct Foo {}", - ); - check_assist( - change_visibility, - "<|>mod foo {}", - "<|>pub(crate) mod foo {}", - ); - check_assist( - change_visibility, - "<|>trait Foo {}", - "<|>pub(crate) trait Foo {}", - ); - check_assist(change_visibility, "m<|>od {}", "<|>pub(crate) mod {}"); - check_assist( - change_visibility, - "unsafe f<|>n foo() {}", - "<|>pub(crate) unsafe fn foo() {}", - ); - } - - #[test] - fn change_visibility_works_with_struct_fields() { - check_assist( - change_visibility, - "struct S { <|>field: u32 }", - "struct S { <|>pub(crate) field: u32 }", - ) - } - - #[test] - fn change_visibility_pub_to_pub_crate() { - check_assist( - change_visibility, - "<|>pub fn foo() {}", - "<|>pub(crate) fn foo() {}", - ) - } - - #[test] - fn change_visibility_pub_crate_to_pub() { - check_assist( - change_visibility, - "<|>pub(crate) fn foo() {}", - "<|>pub fn foo() {}", - ) - } - - #[test] - fn change_visibility_handles_comment_attrs() { - check_assist( - change_visibility, - " - /// docs - - // comments - - #[derive(Debug)] - <|>struct Foo; - ", - " - /// docs - - // comments - - #[derive(Debug)] - <|>pub(crate) struct Foo; - ", - ) - } -} diff --git a/crates/ra_ide_api_light/src/assists/flip_comma.rs b/crates/ra_ide_api_light/src/assists/flip_comma.rs deleted file mode 100644 index a343413cc..000000000 --- a/crates/ra_ide_api_light/src/assists/flip_comma.rs +++ /dev/null @@ -1,31 +0,0 @@ -use ra_syntax::{ - Direction, - SyntaxKind::COMMA, -}; - -use crate::assists::{non_trivia_sibling, AssistCtx, Assist}; - -pub fn flip_comma(ctx: AssistCtx) -> Option { - let comma = ctx.leaf_at_offset().find(|leaf| leaf.kind() == COMMA)?; - let prev = non_trivia_sibling(comma, Direction::Prev)?; - let next = non_trivia_sibling(comma, Direction::Next)?; - ctx.build("flip comma", |edit| { - edit.replace(prev.range(), next.text()); - edit.replace(next.range(), prev.text()); - }) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::assists::check_assist; - - #[test] - fn flip_comma_works_for_function_parameters() { - check_assist( - flip_comma, - "fn foo(x: i32,<|> y: Result<(), ()>) {}", - "fn foo(y: Result<(), ()>,<|> x: i32) {}", - ) - } -} diff --git a/crates/ra_ide_api_light/src/assists/introduce_variable.rs b/crates/ra_ide_api_light/src/assists/introduce_variable.rs deleted file mode 100644 index ed13bddc4..000000000 --- a/crates/ra_ide_api_light/src/assists/introduce_variable.rs +++ /dev/null @@ -1,431 +0,0 @@ -use ra_syntax::{ - ast::{self, AstNode}, - SyntaxKind::{ - WHITESPACE, MATCH_ARM, LAMBDA_EXPR, PATH_EXPR, BREAK_EXPR, LOOP_EXPR, RETURN_EXPR, COMMENT - }, SyntaxNode, TextUnit, -}; - -use crate::assists::{AssistCtx, Assist}; - -pub fn introduce_variable<'a>(ctx: AssistCtx) -> Option { - let node = ctx.covering_node(); - if !valid_covering_node(node) { - return None; - } - let expr = node.ancestors().filter_map(valid_target_expr).next()?; - let (anchor_stmt, wrap_in_block) = anchor_stmt(expr)?; - let indent = anchor_stmt.prev_sibling()?; - if indent.kind() != WHITESPACE { - return None; - } - ctx.build("introduce variable", move |edit| { - let mut buf = String::new(); - - let cursor_offset = if wrap_in_block { - buf.push_str("{ let var_name = "); - TextUnit::of_str("{ let ") - } else { - buf.push_str("let var_name = "); - TextUnit::of_str("let ") - }; - - expr.syntax().text().push_to(&mut buf); - let full_stmt = ast::ExprStmt::cast(anchor_stmt); - let is_full_stmt = if let Some(expr_stmt) = full_stmt { - Some(expr.syntax()) == expr_stmt.expr().map(|e| e.syntax()) - } else { - false - }; - if is_full_stmt { - if !full_stmt.unwrap().has_semi() { - buf.push_str(";"); - } - edit.replace(expr.syntax().range(), buf); - } else { - buf.push_str(";"); - indent.text().push_to(&mut buf); - edit.replace(expr.syntax().range(), "var_name".to_string()); - edit.insert(anchor_stmt.range().start(), buf); - if wrap_in_block { - edit.insert(anchor_stmt.range().end(), " }"); - } - } - edit.set_cursor(anchor_stmt.range().start() + cursor_offset); - }) -} - -fn valid_covering_node(node: &SyntaxNode) -> bool { - node.kind() != COMMENT -} -/// Check wether the node is a valid expression which can be extracted to a variable. -/// In general that's true for any expression, but in some cases that would produce invalid code. -fn valid_target_expr(node: &SyntaxNode) -> Option<&ast::Expr> { - return match node.kind() { - PATH_EXPR => None, - BREAK_EXPR => ast::BreakExpr::cast(node).and_then(|e| e.expr()), - RETURN_EXPR => ast::ReturnExpr::cast(node).and_then(|e| e.expr()), - LOOP_EXPR => ast::ReturnExpr::cast(node).and_then(|e| e.expr()), - _ => ast::Expr::cast(node), - }; -} - -/// Returns the syntax node which will follow the freshly introduced var -/// and a boolean indicating whether we have to wrap it within a { } block -/// to produce correct code. -/// It can be a statement, the last in a block expression or a wanna be block -/// expression like a lamba or match arm. -fn anchor_stmt(expr: &ast::Expr) -> Option<(&SyntaxNode, bool)> { - expr.syntax().ancestors().find_map(|node| { - if ast::Stmt::cast(node).is_some() { - return Some((node, false)); - } - - if let Some(expr) = node - .parent() - .and_then(ast::Block::cast) - .and_then(|it| it.expr()) - { - if expr.syntax() == node { - return Some((node, false)); - } - } - - if let Some(parent) = node.parent() { - if parent.kind() == MATCH_ARM || parent.kind() == LAMBDA_EXPR { - return Some((node, true)); - } - } - - None - }) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::assists::{ check_assist, check_assist_not_applicable, check_assist_range }; - - #[test] - fn test_introduce_var_simple() { - check_assist_range( - introduce_variable, - " -fn foo() { - foo(<|>1 + 1<|>); -}", - " -fn foo() { - let <|>var_name = 1 + 1; - foo(var_name); -}", - ); - } - - #[test] - fn test_introduce_var_expr_stmt() { - check_assist_range( - introduce_variable, - " -fn foo() { - <|>1 + 1<|>; -}", - " -fn foo() { - let <|>var_name = 1 + 1; -}", - ); - } - - #[test] - fn test_introduce_var_part_of_expr_stmt() { - check_assist_range( - introduce_variable, - " -fn foo() { - <|>1<|> + 1; -}", - " -fn foo() { - let <|>var_name = 1; - var_name + 1; -}", - ); - } - - #[test] - fn test_introduce_var_last_expr() { - check_assist_range( - introduce_variable, - " -fn foo() { - bar(<|>1 + 1<|>) -}", - " -fn foo() { - let <|>var_name = 1 + 1; - bar(var_name) -}", - ); - } - - #[test] - fn test_introduce_var_last_full_expr() { - check_assist_range( - introduce_variable, - " -fn foo() { - <|>bar(1 + 1)<|> -}", - " -fn foo() { - let <|>var_name = bar(1 + 1); - var_name -}", - ); - } - - #[test] - fn test_introduce_var_block_expr_second_to_last() { - check_assist_range( - introduce_variable, - " -fn foo() { - <|>{ let x = 0; x }<|> - something_else(); -}", - " -fn foo() { - let <|>var_name = { let x = 0; x }; - something_else(); -}", - ); - } - - #[test] - fn test_introduce_var_in_match_arm_no_block() { - check_assist_range( - introduce_variable, - " -fn main() { - let x = true; - let tuple = match x { - true => (<|>2 + 2<|>, true) - _ => (0, false) - }; -} -", - " -fn main() { - let x = true; - let tuple = match x { - true => { let <|>var_name = 2 + 2; (var_name, true) } - _ => (0, false) - }; -} -", - ); - } - - #[test] - fn test_introduce_var_in_match_arm_with_block() { - check_assist_range( - introduce_variable, - " -fn main() { - let x = true; - let tuple = match x { - true => { - let y = 1; - (<|>2 + y<|>, true) - } - _ => (0, false) - }; -} -", - " -fn main() { - let x = true; - let tuple = match x { - true => { - let y = 1; - let <|>var_name = 2 + y; - (var_name, true) - } - _ => (0, false) - }; -} -", - ); - } - - #[test] - fn test_introduce_var_in_closure_no_block() { - check_assist_range( - introduce_variable, - " -fn main() { - let lambda = |x: u32| <|>x * 2<|>; -} -", - " -fn main() { - let lambda = |x: u32| { let <|>var_name = x * 2; var_name }; -} -", - ); - } - - #[test] - fn test_introduce_var_in_closure_with_block() { - check_assist_range( - introduce_variable, - " -fn main() { - let lambda = |x: u32| { <|>x * 2<|> }; -} -", - " -fn main() { - let lambda = |x: u32| { let <|>var_name = x * 2; var_name }; -} -", - ); - } - - #[test] - fn test_introduce_var_path_simple() { - check_assist( - introduce_variable, - " -fn main() { - let o = S<|>ome(true); -} -", - " -fn main() { - let <|>var_name = Some(true); - let o = var_name; -} -", - ); - } - - #[test] - fn test_introduce_var_path_method() { - check_assist( - introduce_variable, - " -fn main() { - let v = b<|>ar.foo(); -} -", - " -fn main() { - let <|>var_name = bar.foo(); - let v = var_name; -} -", - ); - } - - #[test] - fn test_introduce_var_return() { - check_assist( - introduce_variable, - " -fn foo() -> u32 { - r<|>eturn 2 + 2; -} -", - " -fn foo() -> u32 { - let <|>var_name = 2 + 2; - return var_name; -} -", - ); - } - - #[test] - fn test_introduce_var_break() { - check_assist( - introduce_variable, - " -fn main() { - let result = loop { - b<|>reak 2 + 2; - }; -} -", - " -fn main() { - let result = loop { - let <|>var_name = 2 + 2; - break var_name; - }; -} -", - ); - } - - #[test] - fn test_introduce_var_for_cast() { - check_assist( - introduce_variable, - " -fn main() { - let v = 0f32 a<|>s u32; -} -", - " -fn main() { - let <|>var_name = 0f32 as u32; - let v = var_name; -} -", - ); - } - - #[test] - fn test_introduce_var_for_return_not_applicable() { - check_assist_not_applicable( - introduce_variable, - " -fn foo() { - r<|>eturn; -} -", - ); - } - - #[test] - fn test_introduce_var_for_break_not_applicable() { - check_assist_not_applicable( - introduce_variable, - " -fn main() { - loop { - b<|>reak; - }; -} -", - ); - } - - #[test] - fn test_introduce_var_in_comment_not_applicable() { - check_assist_not_applicable( - introduce_variable, - " -fn main() { - let x = true; - let tuple = match x { - // c<|>omment - true => (2 + 2, true) - _ => (0, false) - }; -} -", - ); - } -} diff --git a/crates/ra_ide_api_light/src/assists/replace_if_let_with_match.rs b/crates/ra_ide_api_light/src/assists/replace_if_let_with_match.rs deleted file mode 100644 index 71880b919..000000000 --- a/crates/ra_ide_api_light/src/assists/replace_if_let_with_match.rs +++ /dev/null @@ -1,81 +0,0 @@ -use ra_syntax::{AstNode, ast}; - -use crate::{ - assists::{AssistCtx, Assist}, - formatting::extract_trivial_expression, -}; - -pub fn replace_if_let_with_match(ctx: AssistCtx) -> Option { - let if_expr: &ast::IfExpr = ctx.node_at_offset()?; - let cond = if_expr.condition()?; - let pat = cond.pat()?; - let expr = cond.expr()?; - let then_block = if_expr.then_branch()?; - let else_block = match if_expr.else_branch()? { - ast::ElseBranchFlavor::Block(it) => it, - ast::ElseBranchFlavor::IfExpr(_) => return None, - }; - - ctx.build("replace with match", |edit| { - let match_expr = build_match_expr(expr, pat, then_block, else_block); - edit.replace_node_and_indent(if_expr.syntax(), match_expr); - edit.set_cursor(if_expr.syntax().range().start()) - }) -} - -fn build_match_expr( - expr: &ast::Expr, - pat1: &ast::Pat, - arm1: &ast::Block, - arm2: &ast::Block, -) -> String { - let mut buf = String::new(); - buf.push_str(&format!("match {} {{\n", expr.syntax().text())); - buf.push_str(&format!( - " {} => {}\n", - pat1.syntax().text(), - format_arm(arm1) - )); - buf.push_str(&format!(" _ => {}\n", format_arm(arm2))); - buf.push_str("}"); - buf -} - -fn format_arm(block: &ast::Block) -> String { - match extract_trivial_expression(block) { - None => block.syntax().text().to_string(), - Some(e) => format!("{},", e.syntax().text()), - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::assists::check_assist; - - #[test] - fn test_replace_if_let_with_match_unwraps_simple_expressions() { - check_assist( - replace_if_let_with_match, - " -impl VariantData { - pub fn is_struct(&self) -> bool { - if <|>let VariantData::Struct(..) = *self { - true - } else { - false - } - } -} ", - " -impl VariantData { - pub fn is_struct(&self) -> bool { - <|>match *self { - VariantData::Struct(..) => true, - _ => false, - } - } -} ", - ) - } -} diff --git a/crates/ra_ide_api_light/src/assists/split_import.rs b/crates/ra_ide_api_light/src/assists/split_import.rs deleted file mode 100644 index e4015f07d..000000000 --- a/crates/ra_ide_api_light/src/assists/split_import.rs +++ /dev/null @@ -1,56 +0,0 @@ -use ra_syntax::{ - TextUnit, AstNode, SyntaxKind::COLONCOLON, - ast, - algo::generate, -}; - -use crate::assists::{AssistCtx, Assist}; - -pub fn split_import(ctx: AssistCtx) -> Option { - let colon_colon = ctx - .leaf_at_offset() - .find(|leaf| leaf.kind() == COLONCOLON)?; - let path = colon_colon.parent().and_then(ast::Path::cast)?; - let top_path = generate(Some(path), |it| it.parent_path()).last()?; - - let use_tree = top_path.syntax().ancestors().find_map(ast::UseTree::cast); - if use_tree.is_none() { - return None; - } - - let l_curly = colon_colon.range().end(); - let r_curly = match top_path.syntax().parent().and_then(ast::UseTree::cast) { - Some(tree) => tree.syntax().range().end(), - None => top_path.syntax().range().end(), - }; - - ctx.build("split import", |edit| { - edit.insert(l_curly, "{"); - edit.insert(r_curly, "}"); - edit.set_cursor(l_curly + TextUnit::of_str("{")); - }) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::assists::check_assist; - - #[test] - fn test_split_import() { - check_assist( - split_import, - "use crate::<|>db::RootDatabase;", - "use crate::{<|>db::RootDatabase};", - ) - } - - #[test] - fn split_import_works_with_trees() { - check_assist( - split_import, - "use algo:<|>:visitor::{Visitor, visit}", - "use algo::{<|>visitor::{Visitor, visit}}", - ) - } -} diff --git a/crates/ra_ide_api_light/src/formatting.rs b/crates/ra_ide_api_light/src/formatting.rs index 1f34b85d6..46ffa7d96 100644 --- a/crates/ra_ide_api_light/src/formatting.rs +++ b/crates/ra_ide_api_light/src/formatting.rs @@ -1,3 +1,4 @@ +use itertools::Itertools; use ra_syntax::{ AstNode, SyntaxNode, SyntaxKind::*, @@ -5,8 +6,13 @@ use ra_syntax::{ algo::generate, }; +pub fn reindent(text: &str, indent: &str) -> String { + let indent = format!("\n{}", indent); + text.lines().intersperse(&indent).collect() +} + /// If the node is on the beginning of the line, calculate indent. -pub(crate) fn leading_indent(node: &SyntaxNode) -> Option<&str> { +pub fn leading_indent(node: &SyntaxNode) -> Option<&str> { for leaf in prev_leaves(node) { if let Some(ws) = ast::Whitespace::cast(leaf) { let ws_text = ws.text(); @@ -32,7 +38,7 @@ fn prev_leaf(node: &SyntaxNode) -> Option<&SyntaxNode> { .last() } -pub(crate) fn extract_trivial_expression(block: &ast::Block) -> Option<&ast::Expr> { +pub fn extract_trivial_expression(block: &ast::Block) -> Option<&ast::Expr> { let expr = block.expr()?; if expr.syntax().text().contains('\n') { return None; diff --git a/crates/ra_ide_api_light/src/lib.rs b/crates/ra_ide_api_light/src/lib.rs index 9dd72701d..17044270c 100644 --- a/crates/ra_ide_api_light/src/lib.rs +++ b/crates/ra_ide_api_light/src/lib.rs @@ -3,7 +3,7 @@ //! This usually means functions which take syntax tree as an input and produce //! an edit or some auxiliary info. -pub mod assists; +pub mod formatting; mod extend_selection; mod folding_ranges; mod line_index; @@ -14,10 +14,15 @@ mod test_utils; mod join_lines; mod typing; mod diagnostics; -pub(crate) mod formatting; + +#[derive(Debug)] +pub struct LocalEdit { + pub label: String, + pub edit: ra_text_edit::TextEdit, + pub cursor_position: Option, +} pub use self::{ - assists::LocalEdit, extend_selection::extend_selection, folding_ranges::{folding_ranges, Fold, FoldKind}, line_index::{LineCol, LineIndex}, diff --git a/crates/ra_ide_api_light/src/test_utils.rs b/crates/ra_ide_api_light/src/test_utils.rs index 22ded2435..bfac0fce3 100644 --- a/crates/ra_ide_api_light/src/test_utils.rs +++ b/crates/ra_ide_api_light/src/test_utils.rs @@ -1,4 +1,4 @@ -use ra_syntax::{SourceFile, TextRange, TextUnit}; +use ra_syntax::{SourceFile, TextUnit}; use crate::LocalEdit; pub use test_utils::*; @@ -22,32 +22,3 @@ pub fn check_action Option>( let actual = add_cursor(&actual, actual_cursor_pos); assert_eq_text!(after, &actual); } - -pub fn check_action_not_applicable Option>( - text: &str, - f: F, -) { - let (text_cursor_pos, text) = extract_offset(text); - let file = SourceFile::parse(&text); - assert!( - f(&file, text_cursor_pos).is_none(), - "code action is applicable but it shouldn't" - ); -} - -pub fn check_action_range Option>( - before: &str, - after: &str, - f: F, -) { - let (range, before) = extract_range(before); - let file = SourceFile::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); -} -- cgit v1.2.3