5116: Categorize assists r=matklad a=kjeremy

Categorize assists so that editors can use them. Follows the LSP spec pretty close (and some things may need adjustments) but this populates the Refactor menu in vscode and pushes quickfixes through again.

This is a prerequisite to filtering out assists that the client doesn't care about.

Fixes #4147

Co-authored-by: Jeremy Kolb <kjeremy@gmail.com>
Co-authored-by: kjeremy <kjeremy@gmail.com>
This commit is contained in:
bors[bot] 2020-07-03 12:44:09 +00:00 committed by GitHub
commit f51b0cfdd6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
43 changed files with 581 additions and 430 deletions

View file

@ -8,7 +8,7 @@ use stdx::SepBy;
use crate::{
assist_context::{AssistContext, Assists},
AssistId,
AssistId, AssistKind,
};
// Assist: add_custom_impl
@ -52,7 +52,7 @@ pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<
format!("Add custom impl `{}` for `{}`", trait_token.text().as_str(), annotated_name);
let target = attr.syntax().text_range();
acc.add(AssistId("add_custom_impl"), label, target, |builder| {
acc.add(AssistId("add_custom_impl", AssistKind::Refactor), label, target, |builder| {
let new_attr_input = input
.syntax()
.descendants_with_tokens()

View file

@ -4,7 +4,7 @@ use ra_syntax::{
TextSize,
};
use crate::{AssistContext, AssistId, Assists};
use crate::{AssistContext, AssistId, AssistKind, Assists};
// Assist: add_derive
//
@ -29,7 +29,7 @@ pub(crate) fn add_derive(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?;
let node_start = derive_insertion_offset(&nominal)?;
let target = nominal.syntax().text_range();
acc.add(AssistId("add_derive"), "Add `#[derive]`", target, |builder| {
acc.add(AssistId("add_derive", AssistKind::None), "Add `#[derive]`", target, |builder| {
let derive_attr = nominal
.attrs()
.filter_map(|x| x.as_simple_call())

View file

@ -4,7 +4,7 @@ use ra_syntax::{
TextRange,
};
use crate::{AssistContext, AssistId, Assists};
use crate::{AssistContext, AssistId, AssistKind, Assists};
// Assist: add_explicit_type
//
@ -59,7 +59,7 @@ pub(crate) fn add_explicit_type(acc: &mut Assists, ctx: &AssistContext) -> Optio
let inferred_type = ty.display_source_code(ctx.db(), module.into()).ok()?;
acc.add(
AssistId("add_explicit_type"),
AssistId("add_explicit_type", AssistKind::RefactorRewrite),
format!("Insert explicit type `{}`", inferred_type),
pat_range,
|builder| match ascribed_ty {

View file

@ -2,7 +2,7 @@ use ra_ide_db::RootDatabase;
use ra_syntax::ast::{self, AstNode, NameOwner};
use test_utils::mark;
use crate::{utils::FamousDefs, AssistContext, AssistId, Assists};
use crate::{utils::FamousDefs, AssistContext, AssistId, AssistKind, Assists};
// Assist: add_from_impl_for_enum
//
@ -45,7 +45,7 @@ pub(crate) fn add_from_impl_for_enum(acc: &mut Assists, ctx: &AssistContext) ->
let target = variant.syntax().text_range();
acc.add(
AssistId("add_from_impl_for_enum"),
AssistId("add_from_impl_for_enum", AssistKind::Refactor),
"Add From impl for this enum variant",
target,
|edit| {

View file

@ -13,7 +13,7 @@ use rustc_hash::{FxHashMap, FxHashSet};
use crate::{
assist_config::SnippetCap,
utils::{render_snippet, Cursor},
AssistContext, AssistId, Assists,
AssistContext, AssistId, AssistKind, Assists,
};
// Assist: add_function
@ -62,7 +62,7 @@ pub(crate) fn add_function(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?;
let target = call.syntax().text_range();
acc.add(AssistId("add_function"), "Add function", target, |builder| {
acc.add(AssistId("add_function", AssistKind::None), "Add function", target, |builder| {
let function_template = function_builder.render();
builder.edit_file(function_template.file);
let new_fn = function_template.to_string(ctx.config.snippet_cap);

View file

@ -1,7 +1,7 @@
use ra_syntax::ast::{self, AstNode, NameOwner, TypeParamsOwner};
use stdx::{format_to, SepBy};
use crate::{AssistContext, AssistId, Assists};
use crate::{AssistContext, AssistId, AssistKind, Assists};
// Assist: add_impl
//
@ -26,38 +26,45 @@ pub(crate) fn add_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?;
let name = nominal.name()?;
let target = nominal.syntax().text_range();
acc.add(AssistId("add_impl"), format!("Implement {}", name.text().as_str()), target, |edit| {
let type_params = nominal.type_param_list();
let start_offset = nominal.syntax().text_range().end();
let mut buf = String::new();
buf.push_str("\n\nimpl");
if let Some(type_params) = &type_params {
format_to!(buf, "{}", type_params.syntax());
}
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_token())
.map(|it| it.text().clone());
let type_params =
type_params.type_params().filter_map(|it| it.name()).map(|it| it.text().clone());
acc.add(
AssistId("add_impl", AssistKind::Refactor),
format!("Implement {}", name.text().as_str()),
target,
|edit| {
let type_params = nominal.type_param_list();
let start_offset = nominal.syntax().text_range().end();
let mut buf = String::new();
buf.push_str("\n\nimpl");
if let Some(type_params) = &type_params {
format_to!(buf, "{}", type_params.syntax());
}
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_token())
.map(|it| it.text().clone());
let type_params = type_params
.type_params()
.filter_map(|it| it.name())
.map(|it| it.text().clone());
let generic_params = lifetime_params.chain(type_params).sep_by(", ");
format_to!(buf, "<{}>", generic_params)
}
match ctx.config.snippet_cap {
Some(cap) => {
buf.push_str(" {\n $0\n}");
edit.insert_snippet(cap, start_offset, buf);
let generic_params = lifetime_params.chain(type_params).sep_by(", ");
format_to!(buf, "<{}>", generic_params)
}
None => {
buf.push_str(" {\n}");
edit.insert(start_offset, buf);
match ctx.config.snippet_cap {
Some(cap) => {
buf.push_str(" {\n $0\n}");
edit.insert_snippet(cap, start_offset, buf);
}
None => {
buf.push_str(" {\n}");
edit.insert(start_offset, buf);
}
}
}
})
},
)
}
#[cfg(test)]

View file

@ -12,7 +12,7 @@ use crate::{
assist_context::{AssistContext, Assists},
ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams},
utils::{get_missing_assoc_items, render_snippet, resolve_target_trait, Cursor},
AssistId,
AssistId, AssistKind,
};
#[derive(PartialEq)]
@ -147,7 +147,7 @@ fn add_missing_impl_members_inner(
}
let target = impl_def.syntax().text_range();
acc.add(AssistId(assist_id), label, target, |builder| {
acc.add(AssistId(assist_id, AssistKind::QuickFix), label, target, |builder| {
let n_existing_items = impl_item_list.assoc_items().count();
let source_scope = ctx.sema.scope_for_def(trait_);
let target_scope = ctx.sema.scope(impl_item_list.syntax());

View file

@ -7,7 +7,7 @@ use ra_syntax::{
};
use stdx::{format_to, SepBy};
use crate::{AssistContext, AssistId, Assists};
use crate::{AssistContext, AssistId, AssistKind, Assists};
// Assist: add_new
//
@ -42,7 +42,7 @@ pub(crate) fn add_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let impl_def = find_struct_impl(&ctx, &strukt)?;
let target = strukt.syntax().text_range();
acc.add(AssistId("add_new"), "Add default constructor", target, |builder| {
acc.add(AssistId("add_new", AssistKind::None), "Add default constructor", target, |builder| {
let mut buf = String::with_capacity(512);
if impl_def.is_some() {

View file

@ -4,7 +4,7 @@ use test_utils::mark;
use crate::{
assist_context::{AssistContext, Assists},
AssistId,
AssistId, AssistKind,
};
// Assist: add_turbo_fish
@ -45,12 +45,15 @@ pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext) -> Option<(
mark::hit!(add_turbo_fish_non_generic);
return None;
}
acc.add(AssistId("add_turbo_fish"), "Add `::<>`", ident.text_range(), |builder| {
match ctx.config.snippet_cap {
acc.add(
AssistId("add_turbo_fish", AssistKind::RefactorRewrite),
"Add `::<>`",
ident.text_range(),
|builder| match ctx.config.snippet_cap {
Some(cap) => builder.insert_snippet(cap, ident.text_range().end(), "::<${0:_}>"),
None => builder.insert(ident.text_range().end(), "::<_>"),
}
})
},
)
}
#[cfg(test)]

View file

@ -1,6 +1,6 @@
use ra_syntax::ast::{self, AstNode};
use crate::{utils::invert_boolean_expression, AssistContext, AssistId, Assists};
use crate::{utils::invert_boolean_expression, AssistContext, AssistId, AssistKind, Assists};
// Assist: apply_demorgan
//
@ -39,11 +39,16 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<(
let rhs_range = rhs.syntax().text_range();
let not_rhs = invert_boolean_expression(rhs);
acc.add(AssistId("apply_demorgan"), "Apply De Morgan's law", op_range, |edit| {
edit.replace(op_range, opposite_op);
edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text()));
edit.replace(rhs_range, format!("{})", not_rhs.syntax().text()));
})
acc.add(
AssistId("apply_demorgan", AssistKind::RefactorRewrite),
"Apply De Morgan's law",
op_range,
|edit| {
edit.replace(op_range, opposite_op);
edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text()));
edit.replace(rhs_range, format!("{})", not_rhs.syntax().text()));
},
)
}
// Return the opposite text for a given logical operator, if it makes sense

View file

@ -13,7 +13,9 @@ use ra_syntax::{
};
use rustc_hash::FxHashSet;
use crate::{utils::insert_use_statement, AssistContext, AssistId, Assists, GroupLabel};
use crate::{
utils::insert_use_statement, AssistContext, AssistId, AssistKind, Assists, GroupLabel,
};
// Assist: auto_import
//
@ -46,7 +48,7 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
for import in proposed_imports {
acc.add_group(
&group,
AssistId("auto_import"),
AssistId("auto_import", AssistKind::QuickFix),
format!("Import `{}`", &import),
range,
|builder| {

View file

@ -3,7 +3,7 @@ use ra_syntax::{
AstNode, SyntaxNode,
};
use crate::{AssistContext, AssistId, Assists};
use crate::{AssistContext, AssistId, AssistKind, Assists};
use test_utils::mark;
// Assist: change_return_type_to_result
@ -35,7 +35,7 @@ pub(crate) fn change_return_type_to_result(acc: &mut Assists, ctx: &AssistContex
let block_expr = &fn_def.body()?;
acc.add(
AssistId("change_return_type_to_result"),
AssistId("change_return_type_to_result", AssistKind::RefactorRewrite),
"Change return type to Result",
type_ref.syntax().text_range(),
|builder| {

View file

@ -6,7 +6,7 @@ use ra_syntax::{
};
use test_utils::mark;
use crate::{utils::vis_offset, AssistContext, AssistId, Assists};
use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists};
// Assist: change_visibility
//
@ -62,16 +62,21 @@ fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
return None;
};
acc.add(AssistId("change_visibility"), "Change visibility to pub(crate)", target, |edit| {
edit.insert(offset, "pub(crate) ");
})
acc.add(
AssistId("change_visibility", AssistKind::RefactorRewrite),
"Change visibility to pub(crate)",
target,
|edit| {
edit.insert(offset, "pub(crate) ");
},
)
}
fn change_vis(acc: &mut Assists, vis: ast::Visibility) -> Option<()> {
if vis.syntax().text() == "pub" {
let target = vis.syntax().text_range();
return acc.add(
AssistId("change_visibility"),
AssistId("change_visibility", AssistKind::RefactorRewrite),
"Change Visibility to pub(crate)",
target,
|edit| {
@ -82,7 +87,7 @@ fn change_vis(acc: &mut Assists, vis: ast::Visibility) -> Option<()> {
if vis.syntax().text() == "pub(crate)" {
let target = vis.syntax().text_range();
return acc.add(
AssistId("change_visibility"),
AssistId("change_visibility", AssistKind::RefactorRewrite),
"Change visibility to pub",
target,
|edit| {

View file

@ -15,7 +15,7 @@ use ra_syntax::{
use crate::{
assist_context::{AssistContext, Assists},
utils::invert_boolean_expression,
AssistId,
AssistId, AssistKind,
};
// Assist: convert_to_guarded_return
@ -99,86 +99,92 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext)
then_block.syntax().last_child_or_token().filter(|t| t.kind() == R_CURLY)?;
let target = if_expr.syntax().text_range();
acc.add(AssistId("convert_to_guarded_return"), "Convert to guarded return", target, |edit| {
let if_indent_level = IndentLevel::from_node(&if_expr.syntax());
let new_block = match if_let_pat {
None => {
// If.
let new_expr = {
let then_branch =
make::block_expr(once(make::expr_stmt(early_expression).into()), None);
let cond = invert_boolean_expression(cond_expr);
make::expr_if(make::condition(cond, None), then_branch).indent(if_indent_level)
};
replace(new_expr.syntax(), &then_block, &parent_block, &if_expr)
}
Some((path, bound_ident)) => {
// If-let.
let match_expr = {
let happy_arm = {
let pat = make::tuple_struct_pat(
path,
once(make::bind_pat(make::name("it")).into()),
);
let expr = {
let name_ref = make::name_ref("it");
let segment = make::path_segment(name_ref);
let path = make::path_unqualified(segment);
make::expr_path(path)
acc.add(
AssistId("convert_to_guarded_return", AssistKind::RefactorRewrite),
"Convert to guarded return",
target,
|edit| {
let if_indent_level = IndentLevel::from_node(&if_expr.syntax());
let new_block = match if_let_pat {
None => {
// If.
let new_expr = {
let then_branch =
make::block_expr(once(make::expr_stmt(early_expression).into()), None);
let cond = invert_boolean_expression(cond_expr);
make::expr_if(make::condition(cond, None), then_branch)
.indent(if_indent_level)
};
replace(new_expr.syntax(), &then_block, &parent_block, &if_expr)
}
Some((path, bound_ident)) => {
// If-let.
let match_expr = {
let happy_arm = {
let pat = make::tuple_struct_pat(
path,
once(make::bind_pat(make::name("it")).into()),
);
let expr = {
let name_ref = make::name_ref("it");
let segment = make::path_segment(name_ref);
let path = make::path_unqualified(segment);
make::expr_path(path)
};
make::match_arm(once(pat.into()), expr)
};
make::match_arm(once(pat.into()), expr)
let sad_arm = make::match_arm(
// FIXME: would be cool to use `None` or `Err(_)` if appropriate
once(make::placeholder_pat().into()),
early_expression,
);
make::expr_match(cond_expr, make::match_arm_list(vec![happy_arm, sad_arm]))
};
let sad_arm = make::match_arm(
// FIXME: would be cool to use `None` or `Err(_)` if appropriate
once(make::placeholder_pat().into()),
early_expression,
let let_stmt = make::let_stmt(
make::bind_pat(make::name(&bound_ident.syntax().to_string())).into(),
Some(match_expr),
);
let let_stmt = let_stmt.indent(if_indent_level);
replace(let_stmt.syntax(), &then_block, &parent_block, &if_expr)
}
};
edit.replace_ast(parent_block, ast::BlockExpr::cast(new_block).unwrap());
make::expr_match(cond_expr, make::match_arm_list(vec![happy_arm, sad_arm]))
};
let let_stmt = make::let_stmt(
make::bind_pat(make::name(&bound_ident.syntax().to_string())).into(),
Some(match_expr),
fn replace(
new_expr: &SyntaxNode,
then_block: &ast::BlockExpr,
parent_block: &ast::BlockExpr,
if_expr: &ast::IfExpr,
) -> SyntaxNode {
let then_block_items = then_block.dedent(IndentLevel(1));
let end_of_then = then_block_items.syntax().last_child_or_token().unwrap();
let end_of_then =
if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) {
end_of_then.prev_sibling_or_token().unwrap()
} else {
end_of_then
};
let mut then_statements = new_expr.children_with_tokens().chain(
then_block_items
.syntax()
.children_with_tokens()
.skip(1)
.take_while(|i| *i != end_of_then),
);
let let_stmt = let_stmt.indent(if_indent_level);
replace(let_stmt.syntax(), &then_block, &parent_block, &if_expr)
replace_children(
&parent_block.syntax(),
RangeInclusive::new(
if_expr.clone().syntax().clone().into(),
if_expr.syntax().clone().into(),
),
&mut then_statements,
)
}
};
edit.replace_ast(parent_block, ast::BlockExpr::cast(new_block).unwrap());
fn replace(
new_expr: &SyntaxNode,
then_block: &ast::BlockExpr,
parent_block: &ast::BlockExpr,
if_expr: &ast::IfExpr,
) -> SyntaxNode {
let then_block_items = then_block.dedent(IndentLevel(1));
let end_of_then = then_block_items.syntax().last_child_or_token().unwrap();
let end_of_then =
if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) {
end_of_then.prev_sibling_or_token().unwrap()
} else {
end_of_then
};
let mut then_statements = new_expr.children_with_tokens().chain(
then_block_items
.syntax()
.children_with_tokens()
.skip(1)
.take_while(|i| *i != end_of_then),
);
replace_children(
&parent_block.syntax(),
RangeInclusive::new(
if_expr.clone().syntax().clone().into(),
if_expr.syntax().clone().into(),
),
&mut then_statements,
)
}
})
},
)
}
#[cfg(test)]

View file

@ -10,7 +10,8 @@ use ra_syntax::{
use rustc_hash::FxHashSet;
use crate::{
assist_context::AssistBuilder, utils::insert_use_statement, AssistContext, AssistId, Assists,
assist_context::AssistBuilder, utils::insert_use_statement, AssistContext, AssistId,
AssistKind, Assists,
};
// Assist: extract_struct_from_enum_variant
@ -48,7 +49,7 @@ pub(crate) fn extract_struct_from_enum_variant(
let current_module = enum_hir.module(ctx.db());
let target = variant.syntax().text_range();
acc.add(
AssistId("extract_struct_from_enum_variant"),
AssistId("extract_struct_from_enum_variant", AssistKind::RefactorRewrite),
"Extract struct from enum variant",
target,
|builder| {

View file

@ -9,7 +9,7 @@ use ra_syntax::{
use stdx::format_to;
use test_utils::mark;
use crate::{AssistContext, AssistId, Assists};
use crate::{AssistContext, AssistId, AssistKind, Assists};
// Assist: extract_variable
//
@ -43,80 +43,85 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext) -> Option
return None;
}
let target = expr.syntax().text_range();
acc.add(AssistId("extract_variable"), "Extract into variable", target, move |edit| {
let field_shorthand = match expr.syntax().parent().and_then(ast::RecordField::cast) {
Some(field) => field.name_ref(),
None => None,
};
acc.add(
AssistId("extract_variable", AssistKind::RefactorExtract),
"Extract into variable",
target,
move |edit| {
let field_shorthand = match expr.syntax().parent().and_then(ast::RecordField::cast) {
Some(field) => field.name_ref(),
None => None,
};
let mut buf = String::new();
let mut buf = String::new();
let var_name = match &field_shorthand {
Some(it) => it.to_string(),
None => "var_name".to_string(),
};
let expr_range = match &field_shorthand {
Some(it) => it.syntax().text_range().cover(expr.syntax().text_range()),
None => expr.syntax().text_range(),
};
let var_name = match &field_shorthand {
Some(it) => it.to_string(),
None => "var_name".to_string(),
};
let expr_range = match &field_shorthand {
Some(it) => it.syntax().text_range().cover(expr.syntax().text_range()),
None => expr.syntax().text_range(),
};
if wrap_in_block {
format_to!(buf, "{{ let {} = ", var_name);
} else {
format_to!(buf, "let {} = ", var_name);
};
format_to!(buf, "{}", expr.syntax());
if wrap_in_block {
format_to!(buf, "{{ let {} = ", var_name);
} else {
format_to!(buf, "let {} = ", var_name);
};
format_to!(buf, "{}", expr.syntax());
let full_stmt = ast::ExprStmt::cast(anchor_stmt.clone());
let is_full_stmt = if let Some(expr_stmt) = &full_stmt {
Some(expr.syntax().clone()) == expr_stmt.expr().map(|e| e.syntax().clone())
} else {
false
};
if is_full_stmt {
mark::hit!(test_extract_var_expr_stmt);
if full_stmt.unwrap().semicolon_token().is_none() {
buf.push_str(";");
let full_stmt = ast::ExprStmt::cast(anchor_stmt.clone());
let is_full_stmt = if let Some(expr_stmt) = &full_stmt {
Some(expr.syntax().clone()) == expr_stmt.expr().map(|e| e.syntax().clone())
} else {
false
};
if is_full_stmt {
mark::hit!(test_extract_var_expr_stmt);
if full_stmt.unwrap().semicolon_token().is_none() {
buf.push_str(";");
}
match ctx.config.snippet_cap {
Some(cap) => {
let snip = buf
.replace(&format!("let {}", var_name), &format!("let $0{}", var_name));
edit.replace_snippet(cap, expr_range, snip)
}
None => edit.replace(expr_range, buf),
}
return;
}
buf.push_str(";");
// We want to maintain the indent level,
// but we do not want to duplicate possible
// extra newlines in the indent block
let text = indent.text();
if text.starts_with('\n') {
buf.push_str("\n");
buf.push_str(text.trim_start_matches('\n'));
} else {
buf.push_str(text);
}
edit.replace(expr_range, var_name.clone());
let offset = anchor_stmt.text_range().start();
match ctx.config.snippet_cap {
Some(cap) => {
let snip =
buf.replace(&format!("let {}", var_name), &format!("let $0{}", var_name));
edit.replace_snippet(cap, expr_range, snip)
edit.insert_snippet(cap, offset, snip)
}
None => edit.replace(expr_range, buf),
None => edit.insert(offset, buf),
}
return;
}
buf.push_str(";");
// We want to maintain the indent level,
// but we do not want to duplicate possible
// extra newlines in the indent block
let text = indent.text();
if text.starts_with('\n') {
buf.push_str("\n");
buf.push_str(text.trim_start_matches('\n'));
} else {
buf.push_str(text);
}
edit.replace(expr_range, var_name.clone());
let offset = anchor_stmt.text_range().start();
match ctx.config.snippet_cap {
Some(cap) => {
let snip =
buf.replace(&format!("let {}", var_name), &format!("let $0{}", var_name));
edit.insert_snippet(cap, offset, snip)
if wrap_in_block {
edit.insert(anchor_stmt.text_range().end(), " }");
}
None => edit.insert(offset, buf),
}
if wrap_in_block {
edit.insert(anchor_stmt.text_range().end(), " }");
}
})
},
)
}
/// Check whether the node is a valid expression which can be extracted to a variable.

View file

@ -8,7 +8,7 @@ use test_utils::mark;
use crate::{
utils::{render_snippet, Cursor, FamousDefs},
AssistContext, AssistId, Assists,
AssistContext, AssistId, AssistKind, Assists,
};
// Assist: fill_match_arms
@ -103,24 +103,29 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
}
let target = match_expr.syntax().text_range();
acc.add(AssistId("fill_match_arms"), "Fill match arms", target, |builder| {
let new_arm_list = match_arm_list.remove_placeholder();
let n_old_arms = new_arm_list.arms().count();
let new_arm_list = new_arm_list.append_arms(missing_arms);
let first_new_arm = new_arm_list.arms().nth(n_old_arms);
let old_range = match_arm_list.syntax().text_range();
match (first_new_arm, ctx.config.snippet_cap) {
(Some(first_new_arm), Some(cap)) => {
let snippet = render_snippet(
cap,
new_arm_list.syntax(),
Cursor::Before(first_new_arm.syntax()),
);
builder.replace_snippet(cap, old_range, snippet);
acc.add(
AssistId("fill_match_arms", AssistKind::QuickFix),
"Fill match arms",
target,
|builder| {
let new_arm_list = match_arm_list.remove_placeholder();
let n_old_arms = new_arm_list.arms().count();
let new_arm_list = new_arm_list.append_arms(missing_arms);
let first_new_arm = new_arm_list.arms().nth(n_old_arms);
let old_range = match_arm_list.syntax().text_range();
match (first_new_arm, ctx.config.snippet_cap) {
(Some(first_new_arm), Some(cap)) => {
let snippet = render_snippet(
cap,
new_arm_list.syntax(),
Cursor::Before(first_new_arm.syntax()),
);
builder.replace_snippet(cap, old_range, snippet);
}
_ => builder.replace(old_range, new_arm_list.to_string()),
}
_ => builder.replace(old_range, new_arm_list.to_string()),
}
})
},
)
}
fn is_variant_missing(existing_arms: &mut Vec<MatchArm>, var: &Pat) -> bool {

View file

@ -2,7 +2,7 @@ use hir::{db::HirDatabase, HasSource, HasVisibility, PathResolution};
use ra_db::FileId;
use ra_syntax::{ast, AstNode, TextRange, TextSize};
use crate::{utils::vis_offset, AssistContext, AssistId, Assists};
use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists};
// FIXME: this really should be a fix for diagnostic, rather than an assist.
@ -58,7 +58,7 @@ fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext) -> O
Some(name) => format!("Change visibility of {} to {}", name, missing_visibility),
};
acc.add(AssistId("fix_visibility"), assist_label, target, |builder| {
acc.add(AssistId("fix_visibility", AssistKind::QuickFix), assist_label, target, |builder| {
builder.edit_file(target_file);
match ctx.config.snippet_cap {
Some(cap) => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
@ -101,7 +101,7 @@ fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext) ->
let assist_label =
format!("Change visibility of {}.{} to {}", parent_name, target_name, missing_visibility);
acc.add(AssistId("fix_visibility"), assist_label, target, |builder| {
acc.add(AssistId("fix_visibility", AssistKind::QuickFix), assist_label, target, |builder| {
builder.edit_file(target_file);
match ctx.config.snippet_cap {
Some(cap) => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),

View file

@ -1,6 +1,6 @@
use ra_syntax::ast::{AstNode, BinExpr, BinOp};
use crate::{AssistContext, AssistId, Assists};
use crate::{AssistContext, AssistId, AssistKind, Assists};
// Assist: flip_binexpr
//
@ -33,13 +33,18 @@ pub(crate) fn flip_binexpr(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
return None;
}
acc.add(AssistId("flip_binexpr"), "Flip binary expression", op_range, |edit| {
if let FlipAction::FlipAndReplaceOp(new_op) = action {
edit.replace(op_range, new_op);
}
edit.replace(lhs.text_range(), rhs.text());
edit.replace(rhs.text_range(), lhs.text());
})
acc.add(
AssistId("flip_binexpr", AssistKind::RefactorRewrite),
"Flip binary expression",
op_range,
|edit| {
if let FlipAction::FlipAndReplaceOp(new_op) = action {
edit.replace(op_range, new_op);
}
edit.replace(lhs.text_range(), rhs.text());
edit.replace(rhs.text_range(), lhs.text());
},
)
}
enum FlipAction {

View file

@ -1,6 +1,6 @@
use ra_syntax::{algo::non_trivia_sibling, Direction, T};
use crate::{AssistContext, AssistId, Assists};
use crate::{AssistContext, AssistId, AssistKind, Assists};
// Assist: flip_comma
//
@ -28,10 +28,15 @@ pub(crate) fn flip_comma(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
return None;
}
acc.add(AssistId("flip_comma"), "Flip comma", comma.text_range(), |edit| {
edit.replace(prev.text_range(), next.to_string());
edit.replace(next.text_range(), prev.to_string());
})
acc.add(
AssistId("flip_comma", AssistKind::RefactorRewrite),
"Flip comma",
comma.text_range(),
|edit| {
edit.replace(prev.text_range(), next.to_string());
edit.replace(next.text_range(), prev.to_string());
},
)
}
#[cfg(test)]

View file

@ -4,7 +4,7 @@ use ra_syntax::{
Direction, T,
};
use crate::{AssistContext, AssistId, Assists};
use crate::{AssistContext, AssistId, AssistKind, Assists};
// Assist: flip_trait_bound
//
@ -33,10 +33,15 @@ pub(crate) fn flip_trait_bound(acc: &mut Assists, ctx: &AssistContext) -> Option
);
let target = plus.text_range();
acc.add(AssistId("flip_trait_bound"), "Flip trait bounds", target, |edit| {
edit.replace(before.text_range(), after.to_string());
edit.replace(after.text_range(), before.to_string());
})
acc.add(
AssistId("flip_trait_bound", AssistKind::RefactorRewrite),
"Flip trait bounds",
target,
|edit| {
edit.replace(before.text_range(), after.to_string());
edit.replace(after.text_range(), before.to_string());
},
)
}
#[cfg(test)]

View file

@ -7,7 +7,7 @@ use test_utils::mark;
use crate::{
assist_context::{AssistContext, Assists},
AssistId,
AssistId, AssistKind,
};
// Assist: inline_local_variable
@ -110,13 +110,19 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> O
let init_in_paren = format!("({})", &init_str);
let target = bind_pat.syntax().text_range();
acc.add(AssistId("inline_local_variable"), "Inline variable", target, move |builder| {
builder.delete(delete_range);
for (desc, should_wrap) in refs.iter().zip(wrap_in_parens) {
let replacement = if should_wrap { init_in_paren.clone() } else { init_str.clone() };
builder.replace(desc.file_range.range, replacement)
}
})
acc.add(
AssistId("inline_local_variable", AssistKind::RefactorInline),
"Inline variable",
target,
move |builder| {
builder.delete(delete_range);
for (desc, should_wrap) in refs.iter().zip(wrap_in_parens) {
let replacement =
if should_wrap { init_in_paren.clone() } else { init_str.clone() };
builder.replace(desc.file_range.range, replacement)
}
},
)
}
#[cfg(test)]

View file

@ -4,7 +4,7 @@ use ra_syntax::{
};
use rustc_hash::FxHashSet;
use crate::{assist_context::AssistBuilder, AssistContext, AssistId, Assists};
use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, Assists};
static ASSIST_NAME: &str = "introduce_named_lifetime";
static ASSIST_LABEL: &str = "Introduce named lifetime";
@ -83,7 +83,7 @@ fn generate_fn_def_assist(
_ => return None,
}
};
acc.add(AssistId(ASSIST_NAME), ASSIST_LABEL, lifetime_loc, |builder| {
acc.add(AssistId(ASSIST_NAME, AssistKind::Refactor), ASSIST_LABEL, lifetime_loc, |builder| {
add_lifetime_param(fn_def, builder, end_of_fn_ident, new_lifetime_param);
builder.replace(lifetime_loc, format!("'{}", new_lifetime_param));
loc_needing_lifetime.map(|loc| builder.insert(loc, format!("'{} ", new_lifetime_param)));
@ -98,7 +98,7 @@ fn generate_impl_def_assist(
) -> Option<()> {
let new_lifetime_param = generate_unique_lifetime_param_name(&impl_def.type_param_list())?;
let end_of_impl_kw = impl_def.impl_token()?.text_range().end();
acc.add(AssistId(ASSIST_NAME), ASSIST_LABEL, lifetime_loc, |builder| {
acc.add(AssistId(ASSIST_NAME, AssistKind::Refactor), ASSIST_LABEL, lifetime_loc, |builder| {
add_lifetime_param(impl_def, builder, end_of_impl_kw, new_lifetime_param);
builder.replace(lifetime_loc, format!("'{}", new_lifetime_param));
})

View file

@ -6,7 +6,7 @@ use ra_syntax::{
use crate::{
assist_context::{AssistContext, Assists},
utils::invert_boolean_expression,
AssistId,
AssistId, AssistKind,
};
// Assist: invert_if
@ -54,7 +54,7 @@ pub(crate) fn invert_if(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let else_node = else_block.syntax();
let else_range = else_node.text_range();
let then_range = then_node.text_range();
acc.add(AssistId("invert_if"), "Invert if", if_range, |edit| {
acc.add(AssistId("invert_if", AssistKind::RefactorRewrite), "Invert if", if_range, |edit| {
edit.replace(cond_range, flip_cond.syntax().text());
edit.replace(else_range, then_node.text());
edit.replace(then_range, else_node.text());

View file

@ -8,7 +8,7 @@ use ra_syntax::{
use crate::{
assist_context::{AssistContext, Assists},
AssistId,
AssistId, AssistKind,
};
// Assist: merge_imports
@ -56,9 +56,14 @@ pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext) -> Option<()
};
let target = tree.syntax().text_range();
acc.add(AssistId("merge_imports"), "Merge imports", target, |builder| {
builder.rewrite(rewriter);
})
acc.add(
AssistId("merge_imports", AssistKind::RefactorRewrite),
"Merge imports",
target,
|builder| {
builder.rewrite(rewriter);
},
)
}
fn next_prev() -> impl Iterator<Item = Direction> {

View file

@ -6,7 +6,7 @@ use ra_syntax::{
Direction,
};
use crate::{AssistContext, AssistId, Assists, TextRange};
use crate::{AssistContext, AssistId, AssistKind, Assists, TextRange};
// Assist: merge_match_arms
//
@ -59,25 +59,30 @@ pub(crate) fn merge_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option
return None;
}
acc.add(AssistId("merge_match_arms"), "Merge match arms", current_text_range, |edit| {
let pats = if arms_to_merge.iter().any(contains_placeholder) {
"_".into()
} else {
arms_to_merge
.iter()
.filter_map(ast::MatchArm::pat)
.map(|x| x.syntax().to_string())
.collect::<Vec<String>>()
.join(" | ")
};
acc.add(
AssistId("merge_match_arms", AssistKind::RefactorRewrite),
"Merge match arms",
current_text_range,
|edit| {
let pats = if arms_to_merge.iter().any(contains_placeholder) {
"_".into()
} else {
arms_to_merge
.iter()
.filter_map(ast::MatchArm::pat)
.map(|x| x.syntax().to_string())
.collect::<Vec<String>>()
.join(" | ")
};
let arm = format!("{} => {}", pats, current_expr.syntax().text());
let arm = format!("{} => {}", pats, current_expr.syntax().text());
let start = arms_to_merge.first().unwrap().syntax().text_range().start();
let end = arms_to_merge.last().unwrap().syntax().text_range().end();
let start = arms_to_merge.first().unwrap().syntax().text_range().start();
let end = arms_to_merge.last().unwrap().syntax().text_range().end();
edit.replace(TextRange::new(start, end), arm);
})
edit.replace(TextRange::new(start, end), arm);
},
)
}
fn contains_placeholder(a: &ast::MatchArm) -> bool {

View file

@ -5,7 +5,7 @@ use ra_syntax::{
T,
};
use crate::{AssistContext, AssistId, Assists};
use crate::{AssistContext, AssistId, AssistKind, Assists};
// Assist: move_bounds_to_where_clause
//
@ -50,29 +50,36 @@ pub(crate) fn move_bounds_to_where_clause(acc: &mut Assists, ctx: &AssistContext
};
let target = type_param_list.syntax().text_range();
acc.add(AssistId("move_bounds_to_where_clause"), "Move to where clause", target, |edit| {
let new_params = type_param_list
.type_params()
.filter(|it| it.type_bound_list().is_some())
.map(|type_param| {
let without_bounds = type_param.remove_bounds();
(type_param, without_bounds)
});
acc.add(
AssistId("move_bounds_to_where_clause", AssistKind::RefactorRewrite),
"Move to where clause",
target,
|edit| {
let new_params = type_param_list
.type_params()
.filter(|it| it.type_bound_list().is_some())
.map(|type_param| {
let without_bounds = type_param.remove_bounds();
(type_param, without_bounds)
});
let new_type_param_list = type_param_list.replace_descendants(new_params);
edit.replace_ast(type_param_list.clone(), new_type_param_list);
let new_type_param_list = type_param_list.replace_descendants(new_params);
edit.replace_ast(type_param_list.clone(), new_type_param_list);
let where_clause = {
let predicates = type_param_list.type_params().filter_map(build_predicate);
make::where_clause(predicates)
};
let where_clause = {
let predicates = type_param_list.type_params().filter_map(build_predicate);
make::where_clause(predicates)
};
let to_insert = match anchor.prev_sibling_or_token() {
Some(ref elem) if elem.kind() == WHITESPACE => format!("{} ", where_clause.syntax()),
_ => format!(" {}", where_clause.syntax()),
};
edit.insert(anchor.text_range().start(), to_insert);
})
let to_insert = match anchor.prev_sibling_or_token() {
Some(ref elem) if elem.kind() == WHITESPACE => {
format!("{} ", where_clause.syntax())
}
_ => format!(" {}", where_clause.syntax()),
};
edit.insert(anchor.text_range().start(), to_insert);
},
)
}
fn build_predicate(param: ast::TypeParam) -> Option<ast::WherePred> {

View file

@ -3,7 +3,7 @@ use ra_syntax::{
SyntaxKind::WHITESPACE,
};
use crate::{AssistContext, AssistId, Assists};
use crate::{AssistContext, AssistId, AssistKind, Assists};
// Assist: move_guard_to_arm_body
//
@ -40,17 +40,22 @@ pub(crate) fn move_guard_to_arm_body(acc: &mut Assists, ctx: &AssistContext) ->
let buf = format!("if {} {{ {} }}", guard_conditions.syntax().text(), arm_expr.syntax().text());
let target = guard.syntax().text_range();
acc.add(AssistId("move_guard_to_arm_body"), "Move guard to arm body", target, |edit| {
match space_before_guard {
Some(element) if element.kind() == WHITESPACE => {
edit.delete(element.text_range());
}
_ => (),
};
acc.add(
AssistId("move_guard_to_arm_body", AssistKind::RefactorRewrite),
"Move guard to arm body",
target,
|edit| {
match space_before_guard {
Some(element) if element.kind() == WHITESPACE => {
edit.delete(element.text_range());
}
_ => (),
};
edit.delete(guard.syntax().text_range());
edit.replace_node_and_indent(arm_expr.syntax(), buf);
})
edit.delete(guard.syntax().text_range());
edit.replace_node_and_indent(arm_expr.syntax(), buf);
},
)
}
// Assist: move_arm_cond_to_match_guard
@ -100,7 +105,7 @@ pub(crate) fn move_arm_cond_to_match_guard(acc: &mut Assists, ctx: &AssistContex
let target = if_expr.syntax().text_range();
acc.add(
AssistId("move_arm_cond_to_match_guard"),
AssistId("move_arm_cond_to_match_guard", AssistKind::RefactorRewrite),
"Move condition to match guard",
target,
|edit| {

View file

@ -5,7 +5,7 @@ use ra_syntax::{
TextSize,
};
use crate::{AssistContext, AssistId, Assists};
use crate::{AssistContext, AssistId, AssistKind, Assists};
// Assist: make_raw_string
//
@ -26,14 +26,22 @@ pub(crate) fn make_raw_string(acc: &mut Assists, ctx: &AssistContext) -> Option<
let token = ctx.find_token_at_offset(STRING).and_then(ast::String::cast)?;
let value = token.value()?;
let target = token.syntax().text_range();
acc.add(AssistId("make_raw_string"), "Rewrite as raw string", target, |edit| {
let max_hash_streak = count_hashes(&value);
let mut hashes = String::with_capacity(max_hash_streak + 1);
for _ in 0..hashes.capacity() {
hashes.push('#');
}
edit.replace(token.syntax().text_range(), format!("r{}\"{}\"{}", hashes, value, hashes));
})
acc.add(
AssistId("make_raw_string", AssistKind::RefactorRewrite),
"Rewrite as raw string",
target,
|edit| {
let max_hash_streak = count_hashes(&value);
let mut hashes = String::with_capacity(max_hash_streak + 1);
for _ in 0..hashes.capacity() {
hashes.push('#');
}
edit.replace(
token.syntax().text_range(),
format!("r{}\"{}\"{}", hashes, value, hashes),
);
},
)
}
// Assist: make_usual_string
@ -55,11 +63,16 @@ pub(crate) fn make_usual_string(acc: &mut Assists, ctx: &AssistContext) -> Optio
let token = ctx.find_token_at_offset(RAW_STRING).and_then(ast::RawString::cast)?;
let value = token.value()?;
let target = token.syntax().text_range();
acc.add(AssistId("make_usual_string"), "Rewrite as regular string", target, |edit| {
// parse inside string to escape `"`
let escaped = value.escape_default().to_string();
edit.replace(token.syntax().text_range(), format!("\"{}\"", escaped));
})
acc.add(
AssistId("make_usual_string", AssistKind::RefactorRewrite),
"Rewrite as regular string",
target,
|edit| {
// parse inside string to escape `"`
let escaped = value.escape_default().to_string();
edit.replace(token.syntax().text_range(), format!("\"{}\"", escaped));
},
)
}
// Assist: add_hash
@ -80,7 +93,7 @@ pub(crate) fn make_usual_string(acc: &mut Assists, ctx: &AssistContext) -> Optio
pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let token = ctx.find_token_at_offset(RAW_STRING)?;
let target = token.text_range();
acc.add(AssistId("add_hash"), "Add # to raw string", target, |edit| {
acc.add(AssistId("add_hash", AssistKind::Refactor), "Add # to raw string", target, |edit| {
edit.insert(token.text_range().start() + TextSize::of('r'), "#");
edit.insert(token.text_range().end(), "#");
})
@ -109,18 +122,23 @@ pub(crate) fn remove_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
return None;
}
let target = token.text_range();
acc.add(AssistId("remove_hash"), "Remove hash from raw string", target, |edit| {
let result = &text[2..text.len() - 1];
let result = if result.starts_with('\"') {
// FIXME: this logic is wrong, not only the last has has to handled specially
// no more hash, escape
let internal_str = &result[1..result.len() - 1];
format!("\"{}\"", internal_str.escape_default().to_string())
} else {
result.to_owned()
};
edit.replace(token.text_range(), format!("r{}", result));
})
acc.add(
AssistId("remove_hash", AssistKind::RefactorRewrite),
"Remove hash from raw string",
target,
|edit| {
let result = &text[2..text.len() - 1];
let result = if result.starts_with('\"') {
// FIXME: this logic is wrong, not only the last has has to handled specially
// no more hash, escape
let internal_str = &result[1..result.len() - 1];
format!("\"{}\"", internal_str.escape_default().to_string())
} else {
result.to_owned()
};
edit.replace(token.text_range(), format!("r{}", result));
},
)
}
fn count_hashes(s: &str) -> usize {

View file

@ -3,7 +3,7 @@ use ra_syntax::{
TextSize, T,
};
use crate::{AssistContext, AssistId, Assists};
use crate::{AssistContext, AssistId, AssistKind, Assists};
// Assist: remove_dbg
//
@ -38,7 +38,7 @@ pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
};
let target = macro_call.syntax().text_range();
acc.add(AssistId("remove_dbg"), "Remove dbg!()", target, |builder| {
acc.add(AssistId("remove_dbg", AssistKind::Refactor), "Remove dbg!()", target, |builder| {
builder.replace(macro_range, macro_content);
})
}

View file

@ -1,6 +1,6 @@
use ra_syntax::{SyntaxKind, TextRange, T};
use crate::{AssistContext, AssistId, Assists};
use crate::{AssistContext, AssistId, AssistKind, Assists};
// Assist: remove_mut
//
@ -26,7 +26,12 @@ pub(crate) fn remove_mut(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
};
let target = mut_token.text_range();
acc.add(AssistId("remove_mut"), "Remove `mut` keyword", target, |builder| {
builder.delete(TextRange::new(delete_from, delete_to));
})
acc.add(
AssistId("remove_mut", AssistKind::Refactor),
"Remove `mut` keyword",
target,
|builder| {
builder.delete(TextRange::new(delete_from, delete_to));
},
)
}

View file

@ -5,7 +5,7 @@ use hir::{Adt, ModuleDef, PathResolution, Semantics, Struct};
use ra_ide_db::RootDatabase;
use ra_syntax::{algo, ast, match_ast, AstNode, SyntaxKind, SyntaxKind::*, SyntaxNode};
use crate::{AssistContext, AssistId, Assists};
use crate::{AssistContext, AssistId, AssistKind, Assists};
// Assist: reorder_fields
//
@ -42,11 +42,16 @@ fn reorder<R: AstNode>(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
}
let target = record.syntax().text_range();
acc.add(AssistId("reorder_fields"), "Reorder record fields", target, |edit| {
for (old, new) in fields.iter().zip(&sorted_fields) {
algo::diff(old, new).into_text_edit(edit.text_edit_builder());
}
})
acc.add(
AssistId("reorder_fields", AssistKind::RefactorRewrite),
"Reorder record fields",
target,
|edit| {
for (old, new) in fields.iter().zip(&sorted_fields) {
algo::diff(old, new).into_text_edit(edit.text_edit_builder());
}
},
)
}
fn get_fields_kind(node: &SyntaxNode) -> Vec<SyntaxKind> {

View file

@ -8,7 +8,7 @@ use ra_syntax::{
AstNode,
};
use crate::{utils::TryEnum, AssistContext, AssistId, Assists};
use crate::{utils::TryEnum, AssistContext, AssistId, AssistKind, Assists};
// Assist: replace_if_let_with_match
//
@ -48,29 +48,35 @@ pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext)
};
let target = if_expr.syntax().text_range();
acc.add(AssistId("replace_if_let_with_match"), "Replace with match", target, move |edit| {
let match_expr = {
let then_arm = {
let then_block = then_block.reset_indent().indent(IndentLevel(1));
let then_expr = unwrap_trivial_block(then_block);
make::match_arm(vec![pat.clone()], then_expr)
acc.add(
AssistId("replace_if_let_with_match", AssistKind::RefactorRewrite),
"Replace with match",
target,
move |edit| {
let match_expr = {
let then_arm = {
let then_block = then_block.reset_indent().indent(IndentLevel(1));
let then_expr = unwrap_trivial_block(then_block);
make::match_arm(vec![pat.clone()], then_expr)
};
let else_arm = {
let pattern = ctx
.sema
.type_of_pat(&pat)
.and_then(|ty| TryEnum::from_ty(&ctx.sema, &ty))
.map(|it| it.sad_pattern())
.unwrap_or_else(|| make::placeholder_pat().into());
let else_expr = unwrap_trivial_block(else_block);
make::match_arm(vec![pattern], else_expr)
};
let match_expr =
make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm]));
match_expr.indent(IndentLevel::from_node(if_expr.syntax()))
};
let else_arm = {
let pattern = ctx
.sema
.type_of_pat(&pat)
.and_then(|ty| TryEnum::from_ty(&ctx.sema, &ty))
.map(|it| it.sad_pattern())
.unwrap_or_else(|| make::placeholder_pat().into());
let else_expr = unwrap_trivial_block(else_block);
make::match_arm(vec![pattern], else_expr)
};
let match_expr = make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm]));
match_expr.indent(IndentLevel::from_node(if_expr.syntax()))
};
edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr);
})
edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr);
},
)
}
#[cfg(test)]

View file

@ -9,7 +9,7 @@ use ra_syntax::{
AstNode, T,
};
use crate::{utils::TryEnum, AssistContext, AssistId, Assists};
use crate::{utils::TryEnum, AssistContext, AssistId, AssistKind, Assists};
// Assist: replace_let_with_if_let
//
@ -44,24 +44,31 @@ pub(crate) fn replace_let_with_if_let(acc: &mut Assists, ctx: &AssistContext) ->
let happy_variant = TryEnum::from_ty(&ctx.sema, &ty).map(|it| it.happy_case());
let target = let_kw.text_range();
acc.add(AssistId("replace_let_with_if_let"), "Replace with if-let", target, |edit| {
let with_placeholder: ast::Pat = match happy_variant {
None => make::placeholder_pat().into(),
Some(var_name) => make::tuple_struct_pat(
make::path_unqualified(make::path_segment(make::name_ref(var_name))),
once(make::placeholder_pat().into()),
)
.into(),
};
let block = make::block_expr(None, None).indent(IndentLevel::from_node(let_stmt.syntax()));
let if_ = make::expr_if(make::condition(init, Some(with_placeholder)), block);
let stmt = make::expr_stmt(if_);
acc.add(
AssistId("replace_let_with_if_let", AssistKind::RefactorRewrite),
"Replace with if-let",
target,
|edit| {
let with_placeholder: ast::Pat = match happy_variant {
None => make::placeholder_pat().into(),
Some(var_name) => make::tuple_struct_pat(
make::path_unqualified(make::path_segment(make::name_ref(var_name))),
once(make::placeholder_pat().into()),
)
.into(),
};
let block =
make::block_expr(None, None).indent(IndentLevel::from_node(let_stmt.syntax()));
let if_ = make::expr_if(make::condition(init, Some(with_placeholder)), block);
let stmt = make::expr_stmt(if_);
let placeholder = stmt.syntax().descendants().find_map(ast::PlaceholderPat::cast).unwrap();
let stmt = stmt.replace_descendant(placeholder.into(), original_pat);
let placeholder =
stmt.syntax().descendants().find_map(ast::PlaceholderPat::cast).unwrap();
let stmt = stmt.replace_descendant(placeholder.into(), original_pat);
edit.replace_ast(ast::Stmt::from(let_stmt), ast::Stmt::from(stmt));
})
edit.replace_ast(ast::Stmt::from(let_stmt), ast::Stmt::from(stmt));
},
)
}
#[cfg(test)]

View file

@ -3,7 +3,7 @@ use ra_syntax::{algo::SyntaxRewriter, ast, match_ast, AstNode, SmolStr, SyntaxNo
use crate::{
utils::{find_insert_use_container, insert_use_statement},
AssistContext, AssistId, Assists,
AssistContext, AssistId, AssistKind, Assists,
};
// Assist: replace_qualified_name_with_use
@ -37,7 +37,7 @@ pub(crate) fn replace_qualified_name_with_use(
let target = path.syntax().text_range();
acc.add(
AssistId("replace_qualified_name_with_use"),
AssistId("replace_qualified_name_with_use", AssistKind::RefactorRewrite),
"Replace qualified path with use",
target,
|builder| {

View file

@ -11,7 +11,7 @@ use ra_syntax::{
use crate::{
utils::{render_snippet, Cursor, TryEnum},
AssistContext, AssistId, Assists,
AssistContext, AssistId, AssistKind, Assists,
};
// Assist: replace_unwrap_with_match
@ -46,37 +46,43 @@ pub(crate) fn replace_unwrap_with_match(acc: &mut Assists, ctx: &AssistContext)
let ty = ctx.sema.type_of_expr(&caller)?;
let happy_variant = TryEnum::from_ty(&ctx.sema, &ty)?.happy_case();
let target = method_call.syntax().text_range();
acc.add(AssistId("replace_unwrap_with_match"), "Replace unwrap with match", target, |builder| {
let ok_path = make::path_unqualified(make::path_segment(make::name_ref(happy_variant)));
let it = make::bind_pat(make::name("a")).into();
let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into();
acc.add(
AssistId("replace_unwrap_with_match", AssistKind::RefactorRewrite),
"Replace unwrap with match",
target,
|builder| {
let ok_path = make::path_unqualified(make::path_segment(make::name_ref(happy_variant)));
let it = make::bind_pat(make::name("a")).into();
let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into();
let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a")));
let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path));
let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a")));
let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path));
let unreachable_call = make::expr_unreachable();
let err_arm = make::match_arm(iter::once(make::placeholder_pat().into()), unreachable_call);
let unreachable_call = make::expr_unreachable();
let err_arm =
make::match_arm(iter::once(make::placeholder_pat().into()), unreachable_call);
let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]);
let match_expr = make::expr_match(caller.clone(), match_arm_list)
.indent(IndentLevel::from_node(method_call.syntax()));
let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]);
let match_expr = make::expr_match(caller.clone(), match_arm_list)
.indent(IndentLevel::from_node(method_call.syntax()));
let range = method_call.syntax().text_range();
match ctx.config.snippet_cap {
Some(cap) => {
let err_arm = match_expr
.syntax()
.descendants()
.filter_map(ast::MatchArm::cast)
.last()
.unwrap();
let snippet =
render_snippet(cap, match_expr.syntax(), Cursor::Before(err_arm.syntax()));
builder.replace_snippet(cap, range, snippet)
let range = method_call.syntax().text_range();
match ctx.config.snippet_cap {
Some(cap) => {
let err_arm = match_expr
.syntax()
.descendants()
.filter_map(ast::MatchArm::cast)
.last()
.unwrap();
let snippet =
render_snippet(cap, match_expr.syntax(), Cursor::Before(err_arm.syntax()));
builder.replace_snippet(cap, range, snippet)
}
None => builder.replace(range, match_expr.to_string()),
}
None => builder.replace(range, match_expr.to_string()),
}
})
},
)
}
#[cfg(test)]

View file

@ -2,7 +2,7 @@ use std::iter::successors;
use ra_syntax::{ast, AstNode, T};
use crate::{AssistContext, AssistId, Assists};
use crate::{AssistContext, AssistId, AssistKind, Assists};
// Assist: split_import
//
@ -28,7 +28,7 @@ pub(crate) fn split_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
}
let target = colon_colon.text_range();
acc.add(AssistId("split_import"), "Split import", target, |edit| {
acc.add(AssistId("split_import", AssistKind::RefactorRewrite), "Split import", target, |edit| {
edit.replace_ast(use_tree, new_tree);
})
}

View file

@ -7,7 +7,7 @@ use ra_syntax::{
AstNode, TextRange, T,
};
use crate::{AssistContext, AssistId, Assists};
use crate::{AssistContext, AssistId, AssistKind, Assists};
// Assist: unwrap_block
//
@ -27,7 +27,7 @@ use crate::{AssistContext, AssistId, Assists};
// }
// ```
pub(crate) fn unwrap_block(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let assist_id = AssistId("unwrap_block");
let assist_id = AssistId("unwrap_block", AssistKind::RefactorRewrite);
let assist_label = "Unwrap block";
let l_curly_token = ctx.find_token_at_offset(T!['{'])?;

View file

@ -26,10 +26,20 @@ pub(crate) use crate::assist_context::{AssistContext, Assists};
pub use assist_config::AssistConfig;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AssistKind {
None,
QuickFix,
Refactor,
RefactorExtract,
RefactorInline,
RefactorRewrite,
}
/// Unique identifier of the assist, should not be shown to the user
/// directly.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AssistId(pub &'static str);
pub struct AssistId(pub &'static str, pub AssistKind);
#[derive(Clone, Debug)]
pub struct GroupLabel(pub String);

View file

@ -76,7 +76,7 @@ pub use crate::{
};
pub use hir::{Documentation, Semantics};
pub use ra_assists::{Assist, AssistConfig, AssistId, ResolvedAssist};
pub use ra_assists::{Assist, AssistConfig, AssistId, AssistKind, ResolvedAssist};
pub use ra_db::{
Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRoot,
SourceRootId,

View file

@ -112,8 +112,6 @@ fn code_action_capabilities(client_caps: &ClientCapabilities) -> CodeActionProvi
lsp_types::code_action_kind::REFACTOR_EXTRACT.to_string(),
lsp_types::code_action_kind::REFACTOR_INLINE.to_string(),
lsp_types::code_action_kind::REFACTOR_REWRITE.to_string(),
lsp_types::code_action_kind::SOURCE.to_string(),
lsp_types::code_action_kind::SOURCE_ORGANIZE_IMPORTS.to_string(),
]),
work_done_progress_options: Default::default(),
})

View file

@ -4,9 +4,9 @@ use std::path::{self, Path};
use itertools::Itertools;
use ra_db::{FileId, FileRange};
use ra_ide::{
Assist, CompletionItem, CompletionItemKind, Documentation, FileSystemEdit, Fold, FoldKind,
FunctionSignature, Highlight, HighlightModifier, HighlightTag, HighlightedRange, Indel,
InlayHint, InlayKind, InsertTextFormat, LineIndex, NavigationTarget, ReferenceAccess,
Assist, AssistKind, CompletionItem, CompletionItemKind, Documentation, FileSystemEdit, Fold,
FoldKind, FunctionSignature, Highlight, HighlightModifier, HighlightTag, HighlightedRange,
Indel, InlayHint, InlayKind, InsertTextFormat, LineIndex, NavigationTarget, ReferenceAccess,
ResolvedAssist, Runnable, Severity, SourceChange, SourceFileEdit, TextEdit,
};
use ra_syntax::{SyntaxKind, TextRange, TextSize};
@ -627,6 +627,18 @@ pub(crate) fn call_hierarchy_item(
Ok(lsp_types::CallHierarchyItem { name, kind, tags: None, detail, uri, range, selection_range })
}
pub(crate) fn code_action_kind(kind: AssistKind) -> String {
match kind {
AssistKind::None => lsp_types::code_action_kind::EMPTY,
AssistKind::QuickFix => lsp_types::code_action_kind::QUICKFIX,
AssistKind::Refactor => lsp_types::code_action_kind::REFACTOR,
AssistKind::RefactorExtract => lsp_types::code_action_kind::REFACTOR_EXTRACT,
AssistKind::RefactorInline => lsp_types::code_action_kind::REFACTOR_INLINE,
AssistKind::RefactorRewrite => lsp_types::code_action_kind::REFACTOR_REWRITE,
}
.to_string()
}
pub(crate) fn unresolved_code_action(
snap: &GlobalStateSnapshot,
assist: Assist,
@ -636,7 +648,7 @@ pub(crate) fn unresolved_code_action(
title: assist.label,
id: Some(format!("{}:{}", assist.id.0.to_owned(), index.to_string())),
group: assist.group.filter(|_| snap.config.client_caps.code_action_group).map(|gr| gr.0),
kind: Some(String::new()),
kind: Some(code_action_kind(assist.id.1)),
edit: None,
command: None,
};

View file

@ -66,7 +66,7 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient
return Promise.resolve(null);
});
},
// Using custom handling of CodeActions where each code action is resloved lazily
// Using custom handling of CodeActions where each code action is resolved lazily
// That's why we are not waiting for any command or edits
async provideCodeActions(document: vscode.TextDocument, range: vscode.Range, context: vscode.CodeActionContext, token: vscode.CancellationToken, _next: lc.ProvideCodeActionsSignature) {
const params: lc.CodeActionParams = {
@ -87,7 +87,8 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient
continue;
}
assert(isCodeActionWithoutEditsAndCommands(item), "We don't expect edits or commands here");
const action = new vscode.CodeAction(item.title);
const kind = client.protocol2CodeConverter.asCodeActionKind((item as any).kind);
const action = new vscode.CodeAction(item.title, kind);
const group = (item as any).group;
const id = (item as any).id;
const resolveParams: ra.ResolveCodeActionParams = {
@ -116,6 +117,7 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient
result[index] = items[0];
} else {
const action = new vscode.CodeAction(group);
action.kind = items[0].kind;
action.command = {
command: "rust-analyzer.applyActionGroup",
title: "",