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, self,
id: AssistId, id: AssistId,
label: impl Into<String>, label: impl Into<String>,
target: TextRange,
f: impl FnOnce(&mut ActionBuilder), f: impl FnOnce(&mut ActionBuilder),
) -> Option<Assist> { ) -> 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); let mut info = AssistInfo::new(label);
if self.should_compute_edit { if self.should_compute_edit {
@ -152,9 +153,10 @@ impl<'a> AssistGroup<'a> {
&mut self, &mut self,
id: AssistId, id: AssistId,
label: impl Into<String>, label: impl Into<String>,
target: TextRange,
f: impl FnOnce(&mut ActionBuilder), 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()); let mut info = AssistInfo::new(label).with_group(self.group.clone());
if self.ctx.should_compute_edit { if self.ctx.should_compute_edit {
@ -181,7 +183,6 @@ impl<'a> AssistGroup<'a> {
pub(crate) struct ActionBuilder<'a, 'b> { pub(crate) struct ActionBuilder<'a, 'b> {
edit: TextEditBuilder, edit: TextEditBuilder,
cursor_position: Option<TextSize>, cursor_position: Option<TextSize>,
target: Option<TextRange>,
file: AssistFile, file: AssistFile,
ctx: &'a AssistCtx<'b>, ctx: &'a AssistCtx<'b>,
} }
@ -191,7 +192,6 @@ impl<'a, 'b> ActionBuilder<'a, 'b> {
Self { Self {
edit: TextEditBuilder::default(), edit: TextEditBuilder::default(),
cursor_position: None, cursor_position: None,
target: None,
file: AssistFile::default(), file: AssistFile::default(),
ctx, ctx,
} }
@ -237,14 +237,6 @@ impl<'a, 'b> ActionBuilder<'a, 'b> {
self.cursor_position = Some(offset) 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`. /// Get access to the raw `TextEditBuilder`.
pub(crate) fn text_edit_builder(&mut self) -> &mut TextEditBuilder { pub(crate) fn text_edit_builder(&mut self) -> &mut TextEditBuilder {
&mut self.edit &mut self.edit
@ -267,7 +259,6 @@ impl<'a, 'b> ActionBuilder<'a, 'b> {
AssistAction { AssistAction {
edit: self.edit.finish(), edit: self.edit.finish(),
cursor_position: self.cursor_position, cursor_position: self.cursor_position,
target: self.target,
file: self.file, file: self.file,
} }
} }

View File

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

View File

@ -27,7 +27,8 @@ use crate::{Assist, AssistCtx, AssistId};
pub(crate) fn add_derive(ctx: AssistCtx) -> Option<Assist> { pub(crate) fn add_derive(ctx: AssistCtx) -> Option<Assist> {
let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?; let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?;
let node_start = derive_insertion_offset(&nominal)?; 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 let derive_attr = nominal
.attrs() .attrs()
.filter_map(|x| x.as_simple_call()) .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(')'), Some(tt) => tt.syntax().text_range().end() - TextSize::of(')'),
}; };
edit.target(nominal.syntax().text_range());
edit.set_cursor(offset) edit.set_cursor(offset)
}) })
} }

View File

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

View File

@ -47,9 +47,11 @@ pub(crate) fn add_from_impl_for_enum(ctx: AssistCtx) -> Option<Assist> {
return None; return None;
} }
let target = variant.syntax().text_range();
ctx.add_assist( ctx.add_assist(
AssistId("add_from_impl_for_enum"), AssistId("add_from_impl_for_enum"),
"Add From impl for this enum variant", "Add From impl for this enum variant",
target,
|edit| { |edit| {
let start_offset = variant.parent_enum().syntax().text_range().end(); let start_offset = variant.parent_enum().syntax().text_range().end();
let mut buf = String::new(); 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)?; let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?;
ctx.add_assist(AssistId("add_function"), "Add function", |edit| { let target = call.syntax().text_range();
edit.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() { if let Some(function_template) = function_builder.render() {
edit.set_file(function_template.file); edit.set_file(function_template.file);
edit.set_cursor(function_template.cursor_offset); edit.set_cursor(function_template.cursor_offset);

View File

@ -28,8 +28,12 @@ use crate::{Assist, AssistCtx, AssistId};
pub(crate) fn add_impl(ctx: AssistCtx) -> Option<Assist> { pub(crate) fn add_impl(ctx: AssistCtx) -> Option<Assist> {
let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?; let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?;
let name = nominal.name()?; let name = nominal.name()?;
ctx.add_assist(AssistId("add_impl"), format!("Implement {}", name.text().as_str()), |edit| { let target = nominal.syntax().text_range();
edit.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 type_params = nominal.type_param_list();
let start_offset = nominal.syntax().text_range().end(); let start_offset = nominal.syntax().text_range().end();
let mut buf = String::new(); let mut buf = String::new();
@ -44,8 +48,10 @@ pub(crate) fn add_impl(ctx: AssistCtx) -> Option<Assist> {
.lifetime_params() .lifetime_params()
.filter_map(|it| it.lifetime_token()) .filter_map(|it| it.lifetime_token())
.map(|it| it.text().clone()); .map(|it| it.text().clone());
let type_params = let type_params = type_params
type_params.type_params().filter_map(|it| it.name()).map(|it| it.text().clone()); .type_params()
.filter_map(|it| it.name())
.map(|it| it.text().clone());
let generic_params = lifetime_params.chain(type_params).sep_by(", "); let generic_params = lifetime_params.chain(type_params).sep_by(", ");
format_to!(buf, "<{}>", generic_params) format_to!(buf, "<{}>", generic_params)
@ -54,7 +60,8 @@ pub(crate) fn add_impl(ctx: AssistCtx) -> Option<Assist> {
edit.set_cursor(start_offset + TextSize::of(&buf)); edit.set_cursor(start_offset + TextSize::of(&buf));
buf.push_str("\n}"); buf.push_str("\n}");
edit.insert(start_offset, buf); edit.insert(start_offset, buf);
}) },
)
} }
#[cfg(test)] #[cfg(test)]

View File

@ -107,10 +107,10 @@ fn add_missing_impl_members_inner(
label: &'static str, label: &'static str,
) -> Option<Assist> { ) -> Option<Assist> {
let _p = ra_prof::profile("add_missing_impl_members_inner"); let _p = ra_prof::profile("add_missing_impl_members_inner");
let impl_node = ctx.find_node_at_offset::<ast::ImplDef>()?; let impl_def = ctx.find_node_at_offset::<ast::ImplDef>()?;
let impl_item_list = impl_node.item_list()?; 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> { let def_name = |item: &ast::AssocItem| -> Option<SmolStr> {
match item { match item {
@ -121,7 +121,7 @@ fn add_missing_impl_members_inner(
.map(|it| it.text().clone()) .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() .iter()
.map(|i| match i { .map(|i| match i {
hir::AssocItem::Function(i) => ast::AssocItem::FnDef(i.source(ctx.db).value), 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; let sema = ctx.sema;
let target = impl_def.syntax().text_range();
ctx.add_assist(AssistId(assist_id), label, |edit| { ctx.add_assist(AssistId(assist_id), label, target, |edit| {
let n_existing_items = impl_item_list.assoc_items().count(); let n_existing_items = impl_item_list.assoc_items().count();
let source_scope = sema.scope_for_def(trait_); let source_scope = sema.scope_for_def(trait_);
let target_scope = sema.scope(impl_item_list.syntax()); let target_scope = sema.scope(impl_item_list.syntax());
let ast_transform = QualifyPaths::new(&target_scope, &source_scope) 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 let items = missing_items
.into_iter() .into_iter()
.map(|it| ast_transform::apply(&*ast_transform, it)) .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 // Return early if we've found an existing new fn
let impl_def = find_struct_impl(&ctx, &strukt)?; let impl_def = find_struct_impl(&ctx, &strukt)?;
ctx.add_assist(AssistId("add_new"), "Add default constructor", |edit| { let target = strukt.syntax().text_range();
edit.target(strukt.syntax().text_range()); ctx.add_assist(AssistId("add_new"), "Add default constructor", target, |edit| {
let mut buf = String::with_capacity(512); let mut buf = String::with_capacity(512);
if impl_def.is_some() { 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 rhs_range = rhs.syntax().text_range();
let not_rhs = invert_boolean_expression(rhs); let not_rhs = invert_boolean_expression(rhs);
ctx.add_assist(AssistId("apply_demorgan"), "Apply De Morgan's law", |edit| { ctx.add_assist(AssistId("apply_demorgan"), "Apply De Morgan's law", op_range, |edit| {
edit.target(op_range);
edit.replace(op_range, opposite_op); edit.replace(op_range, opposite_op);
edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text())); edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text()));
edit.replace(rhs_range, format!("{})", not_rhs.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 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()); let mut group = ctx.add_assist_group(auto_import_assets.get_import_group_message());
for import in proposed_imports { for import in proposed_imports {
group.add_assist(AssistId("auto_import"), format!("Import `{}`", &import), |edit| { group.add_assist(AssistId("auto_import"), format!("Import `{}`", &import), range, |edit| {
edit.target(range);
insert_use_statement(&auto_import_assets.syntax_under_caret, &import, 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; return None;
}; };
ctx.add_assist(AssistId("change_visibility"), "Change visibility to pub(crate)", |edit| { ctx.add_assist(
edit.target(target); AssistId("change_visibility"),
"Change visibility to pub(crate)",
target,
|edit| {
edit.insert(offset, "pub(crate) "); edit.insert(offset, "pub(crate) ");
edit.set_cursor(offset); edit.set_cursor(offset);
}) },
)
} }
fn vis_offset(node: &SyntaxNode) -> TextSize { 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> { fn change_vis(ctx: AssistCtx, vis: ast::Visibility) -> Option<Assist> {
if vis.syntax().text() == "pub" { if vis.syntax().text() == "pub" {
let target = vis.syntax().text_range();
return ctx.add_assist( return ctx.add_assist(
AssistId("change_visibility"), AssistId("change_visibility"),
"Change Visibility to pub(crate)", "Change Visibility to pub(crate)",
target,
|edit| { |edit| {
edit.target(vis.syntax().text_range());
edit.replace(vis.syntax().text_range(), "pub(crate)"); edit.replace(vis.syntax().text_range(), "pub(crate)");
edit.set_cursor(vis.syntax().text_range().start()) edit.set_cursor(vis.syntax().text_range().start())
}, },
); );
} }
if vis.syntax().text() == "pub(crate)" { if vis.syntax().text() == "pub(crate)" {
return ctx.add_assist(AssistId("change_visibility"), "Change visibility to pub", |edit| { let target = vis.syntax().text_range();
edit.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.replace(vis.syntax().text_range(), "pub");
edit.set_cursor(vis.syntax().text_range().start()); edit.set_cursor(vis.syntax().text_range().start());
}); },
);
} }
None None
} }

View File

@ -95,7 +95,12 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option<Assist> {
then_block.syntax().last_child_or_token().filter(|t| t.kind() == R_CURLY)?; then_block.syntax().last_child_or_token().filter(|t| t.kind() == R_CURLY)?;
let cursor_position = ctx.frange.range.start(); let cursor_position = ctx.frange.range.start();
ctx.add_assist(AssistId("convert_to_guarded_return"), "Convert to guarded return", |edit| { 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 if_indent_level = IndentLevel::from_node(&if_expr.syntax());
let new_block = match if_let_pat { let new_block = match if_let_pat {
None => { None => {
@ -143,7 +148,6 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option<Assist> {
replace(let_stmt.syntax(), &then_block, &parent_block, &if_expr) replace(let_stmt.syntax(), &then_block, &parent_block, &if_expr)
} }
}; };
edit.target(if_expr.syntax().text_range());
edit.replace_ast(parent_block, ast::BlockExpr::cast(new_block).unwrap()); edit.replace_ast(parent_block, ast::BlockExpr::cast(new_block).unwrap());
edit.set_cursor(cursor_position); edit.set_cursor(cursor_position);
@ -177,7 +181,8 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option<Assist> {
&mut then_statements, &mut then_statements,
) )
} }
}) },
)
} }
#[cfg(test)] #[cfg(test)]

View File

@ -92,10 +92,9 @@ pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option<Assist> {
return None; 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); 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.set_cursor(expr.syntax().text_range().start());
edit.replace_ast(match_arm_list, new_arm_list); 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; return None;
} }
ctx.add_assist(AssistId("flip_binexpr"), "Flip binary expression", |edit| { ctx.add_assist(AssistId("flip_binexpr"), "Flip binary expression", op_range, |edit| {
edit.target(op_range);
if let FlipAction::FlipAndReplaceOp(new_op) = action { if let FlipAction::FlipAndReplaceOp(new_op) = action {
edit.replace(op_range, new_op); edit.replace(op_range, new_op);
} }

View File

@ -28,8 +28,7 @@ pub(crate) fn flip_comma(ctx: AssistCtx) -> Option<Assist> {
return None; return None;
} }
ctx.add_assist(AssistId("flip_comma"), "Flip comma", |edit| { ctx.add_assist(AssistId("flip_comma"), "Flip comma", comma.text_range(), |edit| {
edit.target(comma.text_range());
edit.replace(prev.text_range(), next.to_string()); edit.replace(prev.text_range(), next.to_string());
edit.replace(next.text_range(), prev.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)?, non_trivia_sibling(plus.clone().into(), Direction::Next)?,
); );
ctx.add_assist(AssistId("flip_trait_bound"), "Flip trait bounds", |edit| { let target = plus.text_range();
edit.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(before.text_range(), after.to_string());
edit.replace(after.text_range(), before.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_str = initializer_expr.syntax().text().to_string();
let init_in_paren = format!("({})", &init_str); let init_in_paren = format!("({})", &init_str);
let target = bind_pat.syntax().text_range();
ctx.add_assist( ctx.add_assist(
AssistId("inline_local_variable"), AssistId("inline_local_variable"),
"Inline variable", "Inline variable",
target,
move |edit: &mut ActionBuilder| { move |edit: &mut ActionBuilder| {
edit.delete(delete_range); edit.delete(delete_range);
for (desc, should_wrap) in refs.iter().zip(wrap_in_parens) { 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 { if indent.kind() != WHITESPACE {
return None; 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 mut buf = String::new();
let cursor_offset = if wrap_in_block { let cursor_offset = if wrap_in_block {
@ -79,7 +80,6 @@ pub(crate) fn introduce_variable(ctx: AssistCtx) -> Option<Assist> {
buf.push_str(text); buf.push_str(text);
} }
edit.target(expr.syntax().text_range());
edit.replace(expr.syntax().text_range(), "var_name".to_string()); edit.replace(expr.syntax().text_range(), "var_name".to_string());
edit.insert(anchor_stmt.text_range().start(), buf); edit.insert(anchor_stmt.text_range().start(), buf);
if wrap_in_block { 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_node = else_block.syntax();
let else_range = else_node.text_range(); let else_range = else_node.text_range();
let then_range = then_node.text_range(); let then_range = then_node.text_range();
return ctx.add_assist(AssistId("invert_if"), "Invert if", |edit| { return ctx.add_assist(AssistId("invert_if"), "Invert if", if_range, |edit| {
edit.target(if_range);
edit.replace(cond_range, flip_cond.syntax().text()); edit.replace(cond_range, flip_cond.syntax().text());
edit.replace(else_range, then_node.text()); edit.replace(else_range, then_node.text());
edit.replace(then_range, else_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); edit.rewrite(rewriter);
// FIXME: we only need because our diff is imprecise // FIXME: we only need because our diff is imprecise
edit.set_cursor(offset); edit.set_cursor(offset);

View File

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

View File

@ -49,7 +49,12 @@ 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 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 let new_params = type_param_list
.type_params() .type_params()
.filter(|it| it.type_bound_list().is_some()) .filter(|it| it.type_bound_list().is_some())
@ -67,12 +72,14 @@ pub(crate) fn move_bounds_to_where_clause(ctx: AssistCtx) -> Option<Assist> {
}; };
let to_insert = match anchor.prev_sibling_or_token() { let to_insert = match anchor.prev_sibling_or_token() {
Some(ref elem) if elem.kind() == WHITESPACE => format!("{} ", where_clause.syntax()), Some(ref elem) if elem.kind() == WHITESPACE => {
format!("{} ", where_clause.syntax())
}
_ => format!(" {}", where_clause.syntax()), _ => format!(" {}", where_clause.syntax()),
}; };
edit.insert(anchor.text_range().start(), to_insert); edit.insert(anchor.text_range().start(), to_insert);
edit.target(type_param_list.syntax().text_range()); },
}) )
} }
fn build_predicate(param: ast::TypeParam) -> Option<ast::WherePred> { 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 arm_expr = match_arm.expr()?;
let buf = format!("if {} {{ {} }}", guard_conditions.syntax().text(), arm_expr.syntax().text()); 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| { let target = guard.syntax().text_range();
edit.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()) { let offseting_amount = match space_before_guard.and_then(|it| it.into_token()) {
Some(tok) => { Some(tok) => {
if ast::Whitespace::cast(tok.clone()).is_some() { 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 buf = format!(" if {}", cond.syntax().text());
let target = if_expr.syntax().text_range();
ctx.add_assist( ctx.add_assist(
AssistId("move_arm_cond_to_match_guard"), AssistId("move_arm_cond_to_match_guard"),
"Move condition to match guard", "Move condition to match guard",
target,
|edit| { |edit| {
edit.target(if_expr.syntax().text_range());
let then_only_expr = then_block.statements().next().is_none(); let then_only_expr = then_block.statements().next().is_none();
match &then_block.expr() { 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> { pub(crate) fn make_raw_string(ctx: AssistCtx) -> Option<Assist> {
let token = ctx.find_token_at_offset(STRING).and_then(ast::String::cast)?; let token = ctx.find_token_at_offset(STRING).and_then(ast::String::cast)?;
let value = token.value()?; let value = token.value()?;
ctx.add_assist(AssistId("make_raw_string"), "Rewrite as raw string", |edit| { let target = token.syntax().text_range();
edit.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 max_hash_streak = count_hashes(&value);
let mut hashes = String::with_capacity(max_hash_streak + 1); let mut hashes = String::with_capacity(max_hash_streak + 1);
for _ in 0..hashes.capacity() { 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> { 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 token = ctx.find_token_at_offset(RAW_STRING).and_then(ast::RawString::cast)?;
let value = token.value()?; let value = token.value()?;
ctx.add_assist(AssistId("make_usual_string"), "Rewrite as regular string", |edit| { let target = token.syntax().text_range();
edit.target(token.syntax().text_range()); ctx.add_assist(AssistId("make_usual_string"), "Rewrite as regular string", target, |edit| {
// parse inside string to escape `"` // parse inside string to escape `"`
let escaped = value.escape_default().to_string(); let escaped = value.escape_default().to_string();
edit.replace(token.syntax().text_range(), format!("\"{}\"", escaped)); 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> { pub(crate) fn add_hash(ctx: AssistCtx) -> Option<Assist> {
let token = ctx.find_token_at_offset(RAW_STRING)?; let token = ctx.find_token_at_offset(RAW_STRING)?;
ctx.add_assist(AssistId("add_hash"), "Add # to raw string", |edit| { let target = token.text_range();
edit.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().start() + TextSize::of('r'), "#");
edit.insert(token.text_range().end(), "#"); edit.insert(token.text_range().end(), "#");
}) })
@ -108,8 +108,8 @@ pub(crate) fn remove_hash(ctx: AssistCtx) -> Option<Assist> {
// no hash to remove // no hash to remove
return None; return None;
} }
ctx.add_assist(AssistId("remove_hash"), "Remove hash from raw string", |edit| { let target = token.text_range();
edit.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 = &text[2..text.len() - 1];
let result = if result.starts_with('\"') { let result = if result.starts_with('\"') {
// FIXME: this logic is wrong, not only the last has has to handled specially // 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() text.slice(without_parens).to_string()
}; };
ctx.add_assist(AssistId("remove_dbg"), "Remove dbg!()", |edit| { let target = macro_call.syntax().text_range();
edit.target(macro_call.syntax().text_range()); ctx.add_assist(AssistId("remove_dbg"), "Remove dbg!()", target, |edit| {
edit.replace(macro_range, macro_content); edit.replace(macro_range, macro_content);
edit.set_cursor(cursor_pos); 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(), _ => 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.set_cursor(delete_from);
edit.delete(TextRange::new(delete_from, delete_to)); edit.delete(TextRange::new(delete_from, delete_to));
}) })

View File

@ -50,11 +50,11 @@ fn reorder<R: AstNode>(ctx: AssistCtx) -> Option<Assist> {
return None; 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) { for (old, new) in fields.iter().zip(&sorted_fields) {
algo::diff(old, new).into_text_edit(edit.text_edit_builder()); algo::diff(old, new).into_text_edit(edit.text_edit_builder());
} }
edit.target(record.syntax().text_range())
}) })
} }

View File

@ -44,7 +44,12 @@ pub(crate) fn replace_if_let_with_match(ctx: AssistCtx) -> Option<Assist> {
}; };
let sema = ctx.sema; let sema = ctx.sema;
ctx.add_assist(AssistId("replace_if_let_with_match"), "Replace with match", move |edit| { 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 match_expr = {
let then_arm = { let then_arm = {
let then_expr = unwrap_trivial_block(then_block); let then_expr = unwrap_trivial_block(then_block);
@ -64,10 +69,10 @@ pub(crate) fn replace_if_let_with_match(ctx: AssistCtx) -> Option<Assist> {
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.set_cursor(if_expr.syntax().text_range().start());
edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr); edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr);
}) },
)
} }
#[cfg(test)] #[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 ty = ctx.sema.type_of_expr(&init)?;
let happy_variant = TryEnum::from_ty(ctx.sema, &ty).map(|it| it.happy_case()); 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 { let with_placeholder: ast::Pat = match happy_variant {
None => make::placeholder_pat().into(), None => make::placeholder_pat().into(),
Some(var_name) => make::tuple_struct_pat( 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); 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));
edit.target(let_kw.text_range());
edit.set_cursor(target_offset); 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; return None;
} }
let target = path.syntax().text_range();
ctx.add_assist( ctx.add_assist(
AssistId("replace_qualified_name_with_use"), AssistId("replace_qualified_name_with_use"),
"Replace qualified path with use", "Replace qualified path with use",
target,
|edit| { |edit| {
let path_to_import = hir_path.mod_path().clone(); let path_to_import = hir_path.mod_path().clone();
insert_use_statement(path.syntax(), &path_to_import, edit); insert_use_statement(path.syntax(), &path_to_import, edit);

View File

@ -38,8 +38,12 @@ pub(crate) fn replace_unwrap_with_match(ctx: AssistCtx) -> Option<Assist> {
let caller = method_call.expr()?; let caller = method_call.expr()?;
let ty = ctx.sema.type_of_expr(&caller)?; let ty = ctx.sema.type_of_expr(&caller)?;
let happy_variant = TryEnum::from_ty(ctx.sema, &ty)?.happy_case(); 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", |edit| { 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 ok_path = make::path_unqualified(make::path_segment(make::name_ref(happy_variant)));
let it = make::bind_pat(make::name("a")).into(); let it = make::bind_pat(make::name("a")).into();
let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into(); let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into();
@ -48,16 +52,18 @@ pub(crate) fn replace_unwrap_with_match(ctx: AssistCtx) -> Option<Assist> {
let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path)); let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path));
let unreachable_call = make::unreachable_macro_call().into(); let unreachable_call = make::unreachable_macro_call().into();
let err_arm = make::match_arm(iter::once(make::placeholder_pat().into()), unreachable_call); 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_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 = make::expr_match(caller.clone(), match_arm_list);
let match_expr = IndentLevel::from_node(method_call.syntax()).increase_indent(match_expr); 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.set_cursor(caller.syntax().text_range().start());
edit.replace_ast::<ast::Expr>(method_call.into(), match_expr); edit.replace_ast::<ast::Expr>(method_call.into(), match_expr);
}) },
)
} }
#[cfg(test)] #[cfg(test)]

View File

@ -28,8 +28,8 @@ pub(crate) fn split_import(ctx: AssistCtx) -> Option<Assist> {
} }
let cursor = ctx.frange.range.start(); let cursor = ctx.frange.range.start();
ctx.add_assist(AssistId("split_import"), "Split import", |edit| { let target = colon_colon.text_range();
edit.target(colon_colon.text_range()); ctx.add_assist(AssistId("split_import"), "Split import", target, |edit| {
edit.replace_ast(use_tree, new_tree); edit.replace_ast(use_tree, new_tree);
edit.set_cursor(cursor); 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.set_cursor(expr.syntax().text_range().start());
edit.target(expr_to_unwrap.syntax().text_range());
let pat_start: &[_] = &[' ', '{', '\n']; let pat_start: &[_] = &[' ', '{', '\n'];
let expr_to_unwrap = expr_to_unwrap.to_string(); 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. /// Short description of the assist, as shown in the UI.
pub label: String, pub label: String,
pub group: Option<GroupLabel>, 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)] #[derive(Clone, Debug)]
pub struct GroupLabel(pub String); pub struct GroupLabel(pub String);
impl AssistLabel { 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 // FIXME: make fields private, so that this invariant can't be broken
assert!(label.starts_with(|c: char| c.is_uppercase())); 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 struct AssistAction {
pub edit: TextEdit, pub edit: TextEdit,
pub cursor_position: Option<TextSize>, pub cursor_position: Option<TextSize>,
// FIXME: This belongs to `AssistLabel`
pub target: Option<TextRange>,
pub file: AssistFile, pub file: AssistFile,
} }
@ -104,7 +110,7 @@ pub fn resolved_assists(db: &RootDatabase, range: FileRange) -> Vec<ResolvedAssi
.flat_map(|it| it.0) .flat_map(|it| it.0)
.map(|it| it.into_resolved().unwrap()) .map(|it| it.into_resolved().unwrap())
.collect::<Vec<_>>(); .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 a
} }

View File

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