Move target to AssistLabel

Target is used for assists sorting, so we need it before we compute
the action.
This commit is contained in:
Aleksey Kladov 2020-05-06 12:51:28 +02:00
parent ede8906844
commit 233f01c9ba
36 changed files with 288 additions and 252 deletions

View File

@ -94,9 +94,10 @@ impl<'a> AssistCtx<'a> {
self,
id: AssistId,
label: impl Into<String>,
target: TextRange,
f: impl FnOnce(&mut ActionBuilder),
) -> Option<Assist> {
let label = AssistLabel::new(id, label.into(), None);
let label = AssistLabel::new(id, label.into(), None, target);
let mut info = AssistInfo::new(label);
if self.should_compute_edit {
@ -152,9 +153,10 @@ impl<'a> AssistGroup<'a> {
&mut self,
id: AssistId,
label: impl Into<String>,
target: TextRange,
f: impl FnOnce(&mut ActionBuilder),
) {
let label = AssistLabel::new(id, label.into(), Some(self.group.clone()));
let label = AssistLabel::new(id, label.into(), Some(self.group.clone()), target);
let mut info = AssistInfo::new(label).with_group(self.group.clone());
if self.ctx.should_compute_edit {
@ -181,7 +183,6 @@ impl<'a> AssistGroup<'a> {
pub(crate) struct ActionBuilder<'a, 'b> {
edit: TextEditBuilder,
cursor_position: Option<TextSize>,
target: Option<TextRange>,
file: AssistFile,
ctx: &'a AssistCtx<'b>,
}
@ -191,7 +192,6 @@ impl<'a, 'b> ActionBuilder<'a, 'b> {
Self {
edit: TextEditBuilder::default(),
cursor_position: None,
target: None,
file: AssistFile::default(),
ctx,
}
@ -237,14 +237,6 @@ impl<'a, 'b> ActionBuilder<'a, 'b> {
self.cursor_position = Some(offset)
}
/// Specify that the assist should be active withing the `target` range.
///
/// Target ranges are used to sort assists: the smaller the target range,
/// the more specific assist is, and so it should be sorted first.
pub(crate) fn target(&mut self, target: TextRange) {
self.target = Some(target)
}
/// Get access to the raw `TextEditBuilder`.
pub(crate) fn text_edit_builder(&mut self) -> &mut TextEditBuilder {
&mut self.edit
@ -267,7 +259,6 @@ impl<'a, 'b> ActionBuilder<'a, 'b> {
AssistAction {
edit: self.edit.finish(),
cursor_position: self.cursor_position,
target: self.target,
file: self.file,
}
}

View File

@ -48,9 +48,8 @@ pub(crate) fn add_custom_impl(ctx: AssistCtx) -> Option<Assist> {
let label =
format!("Add custom impl '{}' for '{}'", trait_token.text().as_str(), annotated_name);
ctx.add_assist(AssistId("add_custom_impl"), label, |edit| {
edit.target(attr.syntax().text_range());
let target = attr.syntax().text_range();
ctx.add_assist(AssistId("add_custom_impl"), label, target, |edit| {
let new_attr_input = input
.syntax()
.descendants_with_tokens()

View File

@ -27,7 +27,8 @@ use crate::{Assist, AssistCtx, AssistId};
pub(crate) fn add_derive(ctx: AssistCtx) -> Option<Assist> {
let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?;
let node_start = derive_insertion_offset(&nominal)?;
ctx.add_assist(AssistId("add_derive"), "Add `#[derive]`", |edit| {
let target = nominal.syntax().text_range();
ctx.add_assist(AssistId("add_derive"), "Add `#[derive]`", target, |edit| {
let derive_attr = nominal
.attrs()
.filter_map(|x| x.as_simple_call())
@ -41,7 +42,6 @@ pub(crate) fn add_derive(ctx: AssistCtx) -> Option<Assist> {
}
Some(tt) => tt.syntax().text_range().end() - TextSize::of(')'),
};
edit.target(nominal.syntax().text_range());
edit.set_cursor(offset)
})
}

View File

@ -62,8 +62,8 @@ pub(crate) fn add_explicit_type(ctx: AssistCtx) -> Option<Assist> {
ctx.add_assist(
AssistId("add_explicit_type"),
format!("Insert explicit type '{}'", new_type_string),
pat_range,
|edit| {
edit.target(pat_range);
if let Some(ascribed_ty) = ascribed_ty {
edit.replace(ascribed_ty.syntax().text_range(), new_type_string);
} else {

View File

@ -47,9 +47,11 @@ pub(crate) fn add_from_impl_for_enum(ctx: AssistCtx) -> Option<Assist> {
return None;
}
let target = variant.syntax().text_range();
ctx.add_assist(
AssistId("add_from_impl_for_enum"),
"Add From impl for this enum variant",
target,
|edit| {
let start_offset = variant.parent_enum().syntax().text_range().end();
let mut buf = String::new();

View File

@ -57,9 +57,9 @@ pub(crate) fn add_function(ctx: AssistCtx) -> Option<Assist> {
let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?;
ctx.add_assist(AssistId("add_function"), "Add function", |edit| {
edit.target(call.syntax().text_range());
let target = call.syntax().text_range();
// TODO: assert here?
ctx.add_assist(AssistId("add_function"), "Add function", target, |edit| {
if let Some(function_template) = function_builder.render() {
edit.set_file(function_template.file);
edit.set_cursor(function_template.cursor_offset);

View File

@ -28,33 +28,40 @@ use crate::{Assist, AssistCtx, AssistId};
pub(crate) fn add_impl(ctx: AssistCtx) -> Option<Assist> {
let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?;
let name = nominal.name()?;
ctx.add_assist(AssistId("add_impl"), format!("Implement {}", name.text().as_str()), |edit| {
edit.target(nominal.syntax().text_range());
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 target = nominal.syntax().text_range();
ctx.add_assist(
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());
let generic_params = lifetime_params.chain(type_params).sep_by(", ");
format_to!(buf, "<{}>", generic_params)
}
buf.push_str(" {\n");
edit.set_cursor(start_offset + TextSize::of(&buf));
buf.push_str("\n}");
edit.insert(start_offset, buf);
})
let generic_params = lifetime_params.chain(type_params).sep_by(", ");
format_to!(buf, "<{}>", generic_params)
}
buf.push_str(" {\n");
edit.set_cursor(start_offset + TextSize::of(&buf));
buf.push_str("\n}");
edit.insert(start_offset, buf);
},
)
}
#[cfg(test)]

View File

@ -107,10 +107,10 @@ fn add_missing_impl_members_inner(
label: &'static str,
) -> Option<Assist> {
let _p = ra_prof::profile("add_missing_impl_members_inner");
let impl_node = ctx.find_node_at_offset::<ast::ImplDef>()?;
let impl_item_list = impl_node.item_list()?;
let impl_def = ctx.find_node_at_offset::<ast::ImplDef>()?;
let impl_item_list = impl_def.item_list()?;
let trait_ = resolve_target_trait(&ctx.sema, &impl_node)?;
let trait_ = resolve_target_trait(&ctx.sema, &impl_def)?;
let def_name = |item: &ast::AssocItem| -> Option<SmolStr> {
match item {
@ -121,7 +121,7 @@ fn add_missing_impl_members_inner(
.map(|it| it.text().clone())
};
let missing_items = get_missing_assoc_items(&ctx.sema, &impl_node)
let missing_items = get_missing_assoc_items(&ctx.sema, &impl_def)
.iter()
.map(|i| match i {
hir::AssocItem::Function(i) => ast::AssocItem::FnDef(i.source(ctx.db).value),
@ -143,13 +143,13 @@ fn add_missing_impl_members_inner(
}
let sema = ctx.sema;
ctx.add_assist(AssistId(assist_id), label, |edit| {
let target = impl_def.syntax().text_range();
ctx.add_assist(AssistId(assist_id), label, target, |edit| {
let n_existing_items = impl_item_list.assoc_items().count();
let source_scope = sema.scope_for_def(trait_);
let target_scope = sema.scope(impl_item_list.syntax());
let ast_transform = QualifyPaths::new(&target_scope, &source_scope)
.or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_node));
.or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_def));
let items = missing_items
.into_iter()
.map(|it| ast_transform::apply(&*ast_transform, it))

View File

@ -41,9 +41,8 @@ pub(crate) fn add_new(ctx: AssistCtx) -> Option<Assist> {
// Return early if we've found an existing new fn
let impl_def = find_struct_impl(&ctx, &strukt)?;
ctx.add_assist(AssistId("add_new"), "Add default constructor", |edit| {
edit.target(strukt.syntax().text_range());
let target = strukt.syntax().text_range();
ctx.add_assist(AssistId("add_new"), "Add default constructor", target, |edit| {
let mut buf = String::with_capacity(512);
if impl_def.is_some() {

View File

@ -39,8 +39,7 @@ pub(crate) fn apply_demorgan(ctx: AssistCtx) -> Option<Assist> {
let rhs_range = rhs.syntax().text_range();
let not_rhs = invert_boolean_expression(rhs);
ctx.add_assist(AssistId("apply_demorgan"), "Apply De Morgan's law", |edit| {
edit.target(op_range);
ctx.add_assist(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()));

View File

@ -48,8 +48,7 @@ pub(crate) fn auto_import(ctx: AssistCtx) -> Option<Assist> {
let range = ctx.sema.original_range(&auto_import_assets.syntax_under_caret).range;
let mut group = ctx.add_assist_group(auto_import_assets.get_import_group_message());
for import in proposed_imports {
group.add_assist(AssistId("auto_import"), format!("Import `{}`", &import), |edit| {
edit.target(range);
group.add_assist(AssistId("auto_import"), format!("Import `{}`", &import), range, |edit| {
insert_use_statement(&auto_import_assets.syntax_under_caret, &import, edit);
});
}

View File

@ -66,11 +66,15 @@ fn add_vis(ctx: AssistCtx) -> Option<Assist> {
return None;
};
ctx.add_assist(AssistId("change_visibility"), "Change visibility to pub(crate)", |edit| {
edit.target(target);
edit.insert(offset, "pub(crate) ");
edit.set_cursor(offset);
})
ctx.add_assist(
AssistId("change_visibility"),
"Change visibility to pub(crate)",
target,
|edit| {
edit.insert(offset, "pub(crate) ");
edit.set_cursor(offset);
},
)
}
fn vis_offset(node: &SyntaxNode) -> TextSize {
@ -86,22 +90,28 @@ fn vis_offset(node: &SyntaxNode) -> TextSize {
fn change_vis(ctx: AssistCtx, vis: ast::Visibility) -> Option<Assist> {
if vis.syntax().text() == "pub" {
let target = vis.syntax().text_range();
return ctx.add_assist(
AssistId("change_visibility"),
"Change Visibility to pub(crate)",
target,
|edit| {
edit.target(vis.syntax().text_range());
edit.replace(vis.syntax().text_range(), "pub(crate)");
edit.set_cursor(vis.syntax().text_range().start())
},
);
}
if vis.syntax().text() == "pub(crate)" {
return ctx.add_assist(AssistId("change_visibility"), "Change visibility to pub", |edit| {
edit.target(vis.syntax().text_range());
edit.replace(vis.syntax().text_range(), "pub");
edit.set_cursor(vis.syntax().text_range().start());
});
let target = vis.syntax().text_range();
return ctx.add_assist(
AssistId("change_visibility"),
"Change visibility to pub",
target,
|edit| {
edit.replace(vis.syntax().text_range(), "pub");
edit.set_cursor(vis.syntax().text_range().start());
},
);
}
None
}

View File

@ -95,89 +95,94 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option<Assist> {
then_block.syntax().last_child_or_token().filter(|t| t.kind() == R_CURLY)?;
let cursor_position = ctx.frange.range.start();
ctx.add_assist(AssistId("convert_to_guarded_return"), "Convert to guarded return", |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);
let e = make::expr_if(make::condition(cond, None), then_branch);
if_indent_level.increase_indent(e)
};
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)
let target = if_expr.syntax().text_range();
ctx.add_assist(
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);
let e = make::expr_if(make::condition(cond, None), then_branch);
if_indent_level.increase_indent(e)
};
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 = if_indent_level.increase_indent(let_stmt);
replace(let_stmt.syntax(), &then_block, &parent_block, &if_expr)
}
};
edit.replace_ast(parent_block, ast::BlockExpr::cast(new_block).unwrap());
edit.set_cursor(cursor_position);
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 = IndentLevel::from(1).decrease_indent(then_block.clone());
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 = if_indent_level.increase_indent(let_stmt);
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.target(if_expr.syntax().text_range());
edit.replace_ast(parent_block, ast::BlockExpr::cast(new_block).unwrap());
edit.set_cursor(cursor_position);
fn replace(
new_expr: &SyntaxNode,
then_block: &ast::BlockExpr,
parent_block: &ast::BlockExpr,
if_expr: &ast::IfExpr,
) -> SyntaxNode {
let then_block_items = IndentLevel::from(1).decrease_indent(then_block.clone());
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

@ -92,10 +92,9 @@ pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option<Assist> {
return None;
}
ctx.add_assist(AssistId("fill_match_arms"), "Fill match arms", |edit| {
let target = match_expr.syntax().text_range();
ctx.add_assist(AssistId("fill_match_arms"), "Fill match arms", target, |edit| {
let new_arm_list = match_arm_list.remove_placeholder().append_arms(missing_arms);
edit.target(match_expr.syntax().text_range());
edit.set_cursor(expr.syntax().text_range().start());
edit.replace_ast(match_arm_list, new_arm_list);
})

View File

@ -33,8 +33,7 @@ pub(crate) fn flip_binexpr(ctx: AssistCtx) -> Option<Assist> {
return None;
}
ctx.add_assist(AssistId("flip_binexpr"), "Flip binary expression", |edit| {
edit.target(op_range);
ctx.add_assist(AssistId("flip_binexpr"), "Flip binary expression", op_range, |edit| {
if let FlipAction::FlipAndReplaceOp(new_op) = action {
edit.replace(op_range, new_op);
}

View File

@ -28,8 +28,7 @@ pub(crate) fn flip_comma(ctx: AssistCtx) -> Option<Assist> {
return None;
}
ctx.add_assist(AssistId("flip_comma"), "Flip comma", |edit| {
edit.target(comma.text_range());
ctx.add_assist(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());
})

View File

@ -32,8 +32,8 @@ pub(crate) fn flip_trait_bound(ctx: AssistCtx) -> Option<Assist> {
non_trivia_sibling(plus.clone().into(), Direction::Next)?,
);
ctx.add_assist(AssistId("flip_trait_bound"), "Flip trait bounds", |edit| {
edit.target(plus.text_range());
let target = plus.text_range();
ctx.add_assist(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());
})

View File

@ -106,9 +106,11 @@ pub(crate) fn inline_local_variable(ctx: AssistCtx) -> Option<Assist> {
let init_str = initializer_expr.syntax().text().to_string();
let init_in_paren = format!("({})", &init_str);
let target = bind_pat.syntax().text_range();
ctx.add_assist(
AssistId("inline_local_variable"),
"Inline variable",
target,
move |edit: &mut ActionBuilder| {
edit.delete(delete_range);
for (desc, should_wrap) in refs.iter().zip(wrap_in_parens) {

View File

@ -42,7 +42,8 @@ pub(crate) fn introduce_variable(ctx: AssistCtx) -> Option<Assist> {
if indent.kind() != WHITESPACE {
return None;
}
ctx.add_assist(AssistId("introduce_variable"), "Extract into variable", move |edit| {
let target = expr.syntax().text_range();
ctx.add_assist(AssistId("introduce_variable"), "Extract into variable", target, move |edit| {
let mut buf = String::new();
let cursor_offset = if wrap_in_block {
@ -79,7 +80,6 @@ pub(crate) fn introduce_variable(ctx: AssistCtx) -> Option<Assist> {
buf.push_str(text);
}
edit.target(expr.syntax().text_range());
edit.replace(expr.syntax().text_range(), "var_name".to_string());
edit.insert(anchor_stmt.text_range().start(), buf);
if wrap_in_block {

View File

@ -47,8 +47,7 @@ pub(crate) fn invert_if(ctx: AssistCtx) -> Option<Assist> {
let else_node = else_block.syntax();
let else_range = else_node.text_range();
let then_range = then_node.text_range();
return ctx.add_assist(AssistId("invert_if"), "Invert if", |edit| {
edit.target(if_range);
return ctx.add_assist(AssistId("invert_if"), "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

@ -52,7 +52,8 @@ pub(crate) fn merge_imports(ctx: AssistCtx) -> Option<Assist> {
}
};
ctx.add_assist(AssistId("merge_imports"), "Merge imports", |edit| {
let target = tree.syntax().text_range();
ctx.add_assist(AssistId("merge_imports"), "Merge imports", target, |edit| {
edit.rewrite(rewriter);
// FIXME: we only need because our diff is imprecise
edit.set_cursor(offset);

View File

@ -70,7 +70,7 @@ pub(crate) fn merge_match_arms(ctx: AssistCtx) -> Option<Assist> {
return None;
}
ctx.add_assist(AssistId("merge_match_arms"), "Merge match arms", |edit| {
ctx.add_assist(AssistId("merge_match_arms"), "Merge match arms", current_text_range, |edit| {
let pats = if arms_to_merge.iter().any(contains_placeholder) {
"_".into()
} else {
@ -87,7 +87,6 @@ pub(crate) fn merge_match_arms(ctx: AssistCtx) -> Option<Assist> {
let start = arms_to_merge.first().unwrap().syntax().text_range().start();
let end = arms_to_merge.last().unwrap().syntax().text_range().end();
edit.target(current_text_range);
edit.set_cursor(match cursor_pos {
CursorPos::InExpr(back_offset) => start + TextSize::of(&arm) - back_offset,
CursorPos::InPat(offset) => offset,

View File

@ -49,30 +49,37 @@ pub(crate) fn move_bounds_to_where_clause(ctx: AssistCtx) -> Option<Assist> {
}
};
ctx.add_assist(AssistId("move_bounds_to_where_clause"), "Move to where clause", |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 target = type_param_list.syntax().text_range();
ctx.add_assist(
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)
});
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);
edit.target(type_param_list.syntax().text_range());
})
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

@ -40,8 +40,8 @@ pub(crate) fn move_guard_to_arm_body(ctx: AssistCtx) -> Option<Assist> {
let arm_expr = match_arm.expr()?;
let buf = format!("if {} {{ {} }}", guard_conditions.syntax().text(), arm_expr.syntax().text());
ctx.add_assist(AssistId("move_guard_to_arm_body"), "Move guard to arm body", |edit| {
edit.target(guard.syntax().text_range());
let target = guard.syntax().text_range();
ctx.add_assist(AssistId("move_guard_to_arm_body"), "Move guard to arm body", target, |edit| {
let offseting_amount = match space_before_guard.and_then(|it| it.into_token()) {
Some(tok) => {
if ast::Whitespace::cast(tok.clone()).is_some() {
@ -108,11 +108,12 @@ pub(crate) fn move_arm_cond_to_match_guard(ctx: AssistCtx) -> Option<Assist> {
let buf = format!(" if {}", cond.syntax().text());
let target = if_expr.syntax().text_range();
ctx.add_assist(
AssistId("move_arm_cond_to_match_guard"),
"Move condition to match guard",
target,
|edit| {
edit.target(if_expr.syntax().text_range());
let then_only_expr = then_block.statements().next().is_none();
match &then_block.expr() {

View File

@ -25,8 +25,8 @@ use crate::{Assist, AssistCtx, AssistId};
pub(crate) fn make_raw_string(ctx: AssistCtx) -> Option<Assist> {
let token = ctx.find_token_at_offset(STRING).and_then(ast::String::cast)?;
let value = token.value()?;
ctx.add_assist(AssistId("make_raw_string"), "Rewrite as raw string", |edit| {
edit.target(token.syntax().text_range());
let target = token.syntax().text_range();
ctx.add_assist(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() {
@ -54,8 +54,8 @@ pub(crate) fn make_raw_string(ctx: AssistCtx) -> Option<Assist> {
pub(crate) fn make_usual_string(ctx: AssistCtx) -> Option<Assist> {
let token = ctx.find_token_at_offset(RAW_STRING).and_then(ast::RawString::cast)?;
let value = token.value()?;
ctx.add_assist(AssistId("make_usual_string"), "Rewrite as regular string", |edit| {
edit.target(token.syntax().text_range());
let target = token.syntax().text_range();
ctx.add_assist(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));
@ -79,8 +79,8 @@ pub(crate) fn make_usual_string(ctx: AssistCtx) -> Option<Assist> {
// ```
pub(crate) fn add_hash(ctx: AssistCtx) -> Option<Assist> {
let token = ctx.find_token_at_offset(RAW_STRING)?;
ctx.add_assist(AssistId("add_hash"), "Add # to raw string", |edit| {
edit.target(token.text_range());
let target = token.text_range();
ctx.add_assist(AssistId("add_hash"), "Add # to raw string", target, |edit| {
edit.insert(token.text_range().start() + TextSize::of('r'), "#");
edit.insert(token.text_range().end(), "#");
})
@ -108,8 +108,8 @@ pub(crate) fn remove_hash(ctx: AssistCtx) -> Option<Assist> {
// no hash to remove
return None;
}
ctx.add_assist(AssistId("remove_hash"), "Remove hash from raw string", |edit| {
edit.target(token.text_range());
let target = token.text_range();
ctx.add_assist(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

View File

@ -57,8 +57,8 @@ pub(crate) fn remove_dbg(ctx: AssistCtx) -> Option<Assist> {
text.slice(without_parens).to_string()
};
ctx.add_assist(AssistId("remove_dbg"), "Remove dbg!()", |edit| {
edit.target(macro_call.syntax().text_range());
let target = macro_call.syntax().text_range();
ctx.add_assist(AssistId("remove_dbg"), "Remove dbg!()", target, |edit| {
edit.replace(macro_range, macro_content);
edit.set_cursor(cursor_pos);
})

View File

@ -25,7 +25,8 @@ pub(crate) fn remove_mut(ctx: AssistCtx) -> Option<Assist> {
_ => mut_token.text_range().end(),
};
ctx.add_assist(AssistId("remove_mut"), "Remove `mut` keyword", |edit| {
let target = mut_token.text_range();
ctx.add_assist(AssistId("remove_mut"), "Remove `mut` keyword", target, |edit| {
edit.set_cursor(delete_from);
edit.delete(TextRange::new(delete_from, delete_to));
})

View File

@ -50,11 +50,11 @@ fn reorder<R: AstNode>(ctx: AssistCtx) -> Option<Assist> {
return None;
}
ctx.add_assist(AssistId("reorder_fields"), "Reorder record fields", |edit| {
let target = record.syntax().text_range();
ctx.add_assist(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());
}
edit.target(record.syntax().text_range())
})
}

View File

@ -44,30 +44,35 @@ pub(crate) fn replace_if_let_with_match(ctx: AssistCtx) -> Option<Assist> {
};
let sema = ctx.sema;
ctx.add_assist(AssistId("replace_if_let_with_match"), "Replace with match", move |edit| {
let match_expr = {
let then_arm = {
let then_expr = unwrap_trivial_block(then_block);
make::match_arm(vec![pat.clone()], then_expr)
let target = if_expr.syntax().text_range();
ctx.add_assist(
AssistId("replace_if_let_with_match"),
"Replace with match",
target,
move |edit| {
let match_expr = {
let then_arm = {
let then_expr = unwrap_trivial_block(then_block);
make::match_arm(vec![pat.clone()], then_expr)
};
let else_arm = {
let pattern = sema
.type_of_pat(&pat)
.and_then(|ty| TryEnum::from_ty(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)
};
make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm]))
};
let else_arm = {
let pattern = sema
.type_of_pat(&pat)
.and_then(|ty| TryEnum::from_ty(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)
};
make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm]))
};
let match_expr = IndentLevel::from_node(if_expr.syntax()).increase_indent(match_expr);
let match_expr = IndentLevel::from_node(if_expr.syntax()).increase_indent(match_expr);
edit.target(if_expr.syntax().text_range());
edit.set_cursor(if_expr.syntax().text_range().start());
edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr);
})
edit.set_cursor(if_expr.syntax().text_range().start());
edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr);
},
)
}
#[cfg(test)]

View File

@ -47,7 +47,8 @@ pub(crate) fn replace_let_with_if_let(ctx: AssistCtx) -> Option<Assist> {
let ty = ctx.sema.type_of_expr(&init)?;
let happy_variant = TryEnum::from_ty(ctx.sema, &ty).map(|it| it.happy_case());
ctx.add_assist(AssistId("replace_let_with_if_let"), "Replace with if-let", |edit| {
let target = let_kw.text_range();
ctx.add_assist(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(
@ -67,7 +68,6 @@ pub(crate) fn replace_let_with_if_let(ctx: AssistCtx) -> Option<Assist> {
let stmt = stmt.replace_descendant(placeholder.into(), original_pat);
edit.replace_ast(ast::Stmt::from(let_stmt), ast::Stmt::from(stmt));
edit.target(let_kw.text_range());
edit.set_cursor(target_offset);
})
}

View File

@ -33,9 +33,11 @@ pub(crate) fn replace_qualified_name_with_use(ctx: AssistCtx) -> Option<Assist>
return None;
}
let target = path.syntax().text_range();
ctx.add_assist(
AssistId("replace_qualified_name_with_use"),
"Replace qualified path with use",
target,
|edit| {
let path_to_import = hir_path.mod_path().clone();
insert_use_statement(path.syntax(), &path_to_import, edit);

View File

@ -38,26 +38,32 @@ pub(crate) fn replace_unwrap_with_match(ctx: AssistCtx) -> Option<Assist> {
let caller = method_call.expr()?;
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();
ctx.add_assist(
AssistId("replace_unwrap_with_match"),
"Replace unwrap with match",
target,
|edit| {
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();
ctx.add_assist(AssistId("replace_unwrap_with_match"), "Replace unwrap with match", |edit| {
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::unreachable_macro_call().into();
let err_arm =
make::match_arm(iter::once(make::placeholder_pat().into()), unreachable_call);
let unreachable_call = make::unreachable_macro_call().into();
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);
let match_expr =
IndentLevel::from_node(method_call.syntax()).increase_indent(match_expr);
let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]);
let match_expr = make::expr_match(caller.clone(), match_arm_list);
let match_expr = IndentLevel::from_node(method_call.syntax()).increase_indent(match_expr);
edit.target(method_call.syntax().text_range());
edit.set_cursor(caller.syntax().text_range().start());
edit.replace_ast::<ast::Expr>(method_call.into(), match_expr);
})
edit.set_cursor(caller.syntax().text_range().start());
edit.replace_ast::<ast::Expr>(method_call.into(), match_expr);
},
)
}
#[cfg(test)]

View File

@ -28,8 +28,8 @@ pub(crate) fn split_import(ctx: AssistCtx) -> Option<Assist> {
}
let cursor = ctx.frange.range.start();
ctx.add_assist(AssistId("split_import"), "Split import", |edit| {
edit.target(colon_colon.text_range());
let target = colon_colon.text_range();
ctx.add_assist(AssistId("split_import"), "Split import", target, |edit| {
edit.replace_ast(use_tree, new_tree);
edit.set_cursor(cursor);
})

View File

@ -57,9 +57,9 @@ pub(crate) fn unwrap_block(ctx: AssistCtx) -> Option<Assist> {
}
};
ctx.add_assist(AssistId("unwrap_block"), "Unwrap block", |edit| {
let target = expr_to_unwrap.syntax().text_range();
ctx.add_assist(AssistId("unwrap_block"), "Unwrap block", target, |edit| {
edit.set_cursor(expr.syntax().text_range().start());
edit.target(expr_to_unwrap.syntax().text_range());
let pat_start: &[_] = &[' ', '{', '\n'];
let expr_to_unwrap = expr_to_unwrap.to_string();

View File

@ -36,16 +36,24 @@ pub struct AssistLabel {
/// Short description of the assist, as shown in the UI.
pub label: String,
pub group: Option<GroupLabel>,
/// Target ranges are used to sort assists: the smaller the target range,
/// the more specific assist is, and so it should be sorted first.
pub target: TextRange,
}
#[derive(Clone, Debug)]
pub struct GroupLabel(pub String);
impl AssistLabel {
pub(crate) fn new(id: AssistId, label: String, group: Option<GroupLabel>) -> AssistLabel {
pub(crate) fn new(
id: AssistId,
label: String,
group: Option<GroupLabel>,
target: TextRange,
) -> AssistLabel {
// FIXME: make fields private, so that this invariant can't be broken
assert!(label.starts_with(|c: char| c.is_uppercase()));
AssistLabel { id, label, group }
AssistLabel { id, label, group, target }
}
}
@ -53,8 +61,6 @@ impl AssistLabel {
pub struct AssistAction {
pub edit: TextEdit,
pub cursor_position: Option<TextSize>,
// FIXME: This belongs to `AssistLabel`
pub target: Option<TextRange>,
pub file: AssistFile,
}
@ -104,7 +110,7 @@ pub fn resolved_assists(db: &RootDatabase, range: FileRange) -> Vec<ResolvedAssi
.flat_map(|it| it.0)
.map(|it| it.into_resolved().unwrap())
.collect::<Vec<_>>();
a.sort_by_key(|it| it.action.target.map_or(TextSize::from(!0u32), |it| it.len()));
a.sort_by_key(|it| it.label.target.len());
a
}

View File

@ -118,8 +118,7 @@ fn check(assist: Handler, before: &str, expected: ExpectedResult) {
assert_eq_text!(after, &actual);
}
(Some(assist), ExpectedResult::Target(target)) => {
let action = assist.0[0].action.clone().unwrap();
let range = action.target.expect("expected target on action");
let range = assist.0[0].label.target;
assert_eq_text!(&text_without_caret[range], target);
}
(Some(_), ExpectedResult::NotApplicable) => panic!("assist should not be applicable!"),