From 1967884d6836219ee78a754ca5c66ac781351559 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 8 Jan 2019 22:17:36 +0300 Subject: rename ra_editor -> ra_ide_api_light --- 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 | 116 +++++++++++++++++ crates/ra_ide_api_light/src/assists/flip_comma.rs | 31 +++++ .../src/assists/introduce_variable.rs | 144 +++++++++++++++++++++ .../src/assists/replace_if_let_with_match.rs | 92 +++++++++++++ .../ra_ide_api_light/src/assists/split_import.rs | 56 ++++++++ 7 files changed, 589 insertions(+) create mode 100644 crates/ra_ide_api_light/src/assists/add_derive.rs create mode 100644 crates/ra_ide_api_light/src/assists/add_impl.rs create mode 100644 crates/ra_ide_api_light/src/assists/change_visibility.rs create mode 100644 crates/ra_ide_api_light/src/assists/flip_comma.rs create mode 100644 crates/ra_ide_api_light/src/assists/introduce_variable.rs create mode 100644 crates/ra_ide_api_light/src/assists/replace_if_let_with_match.rs create mode 100644 crates/ra_ide_api_light/src/assists/split_import.rs (limited to 'crates/ra_ide_api_light/src/assists') diff --git a/crates/ra_ide_api_light/src/assists/add_derive.rs b/crates/ra_ide_api_light/src/assists/add_derive.rs new file mode 100644 index 000000000..6e964d011 --- /dev/null +++ b/crates/ra_ide_api_light/src/assists/add_derive.rs @@ -0,0 +1,84 @@ +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 new file mode 100644 index 000000000..2eda7cae2 --- /dev/null +++ b/crates/ra_ide_api_light/src/assists/add_impl.rs @@ -0,0 +1,66 @@ +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 new file mode 100644 index 000000000..89729e2c2 --- /dev/null +++ b/crates/ra_ide_api_light/src/assists/change_visibility.rs @@ -0,0 +1,116 @@ +use ra_syntax::{ + AstNode, + 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}, +}; + +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; + } + parent.range().start() + } 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; + } + field.syntax().range().start() + }; + + ctx.build("make pub(crate)", |edit| { + edit.insert(offset, "pub(crate) "); + edit.set_cursor(offset); + }) +} + +fn change_vis(ctx: AssistCtx, vis: &ast::Visibility) -> Option { + if vis.syntax().text() != "pub" { + return None; + } + ctx.build("chage to pub(crate)", |edit| { + edit.replace(vis.syntax().range(), "pub(crate)"); + edit.set_cursor(vis.syntax().range().start()); + }) +} + +#[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() {}", + ) + } +} diff --git a/crates/ra_ide_api_light/src/assists/flip_comma.rs b/crates/ra_ide_api_light/src/assists/flip_comma.rs new file mode 100644 index 000000000..a343413cc --- /dev/null +++ b/crates/ra_ide_api_light/src/assists/flip_comma.rs @@ -0,0 +1,31 @@ +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 new file mode 100644 index 000000000..523ec7034 --- /dev/null +++ b/crates/ra_ide_api_light/src/assists/introduce_variable.rs @@ -0,0 +1,144 @@ +use ra_syntax::{ + ast::{self, AstNode}, + SyntaxKind::WHITESPACE, + SyntaxNode, TextUnit, +}; + +use crate::assists::{AssistCtx, Assist}; + +pub fn introduce_variable<'a>(ctx: AssistCtx) -> Option { + let node = ctx.covering_node(); + let expr = node.ancestors().filter_map(ast::Expr::cast).next()?; + + let anchor_stmt = 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(); + + buf.push_str("let var_name = "); + expr.syntax().text().push_to(&mut buf); + let is_full_stmt = if let Some(expr_stmt) = ast::ExprStmt::cast(anchor_stmt) { + Some(expr.syntax()) == expr_stmt.expr().map(|e| e.syntax()) + } else { + false + }; + if is_full_stmt { + 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); + } + edit.set_cursor(anchor_stmt.range().start() + TextUnit::of_str("let ")); + }) +} + +/// Statement or last in the block expression, which will follow +/// the freshly introduced var. +fn anchor_stmt(expr: &ast::Expr) -> Option<&SyntaxNode> { + expr.syntax().ancestors().find(|&node| { + if ast::Stmt::cast(node).is_some() { + return true; + } + if let Some(expr) = node + .parent() + .and_then(ast::Block::cast) + .and_then(|it| it.expr()) + { + if expr.syntax() == node { + return true; + } + } + false + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::assists::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 +}", + ); + } + +} 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 new file mode 100644 index 000000000..30c371480 --- /dev/null +++ b/crates/ra_ide_api_light/src/assists/replace_if_let_with_match.rs @@ -0,0 +1,92 @@ +use ra_syntax::{ + AstNode, SyntaxKind::{L_CURLY, R_CURLY, WHITESPACE}, + ast, +}; + +use crate::assists::{AssistCtx, Assist}; + +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 = if_expr.else_branch()?; + + 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_expression(block) { + None => block.syntax().text().to_string(), + Some(e) => format!("{},", e.syntax().text()), + } +} + +fn extract_expression(block: &ast::Block) -> Option<&ast::Expr> { + let expr = block.expr()?; + let non_trivial_children = block.syntax().children().filter(|it| { + !(it == &expr.syntax() + || it.kind() == L_CURLY + || it.kind() == R_CURLY + || it.kind() == WHITESPACE) + }); + if non_trivial_children.count() > 0 { + return None; + } + Some(expr) +} + +#[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 new file mode 100644 index 000000000..e4015f07d --- /dev/null +++ b/crates/ra_ide_api_light/src/assists/split_import.rs @@ -0,0 +1,56 @@ +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}}", + ) + } +} -- cgit v1.2.3