From 5c9c0d3ae2735b4b32a44742bac800ca616fdde8 Mon Sep 17 00:00:00 2001 From: Andrea Pretto Date: Mon, 11 Feb 2019 18:07:21 +0100 Subject: ra_assists: assist "providers" can produce multiple assists --- crates/ra_assists/src/add_derive.rs | 8 ++- crates/ra_assists/src/add_impl.rs | 8 ++- crates/ra_assists/src/assist_ctx.rs | 41 ++++++++---- crates/ra_assists/src/auto_import.rs | 8 ++- crates/ra_assists/src/change_visibility.rs | 20 ++++-- crates/ra_assists/src/fill_match_arms.rs | 8 ++- crates/ra_assists/src/flip_comma.rs | 8 ++- crates/ra_assists/src/introduce_variable.rs | 8 ++- crates/ra_assists/src/lib.rs | 75 +++++++++++++++++----- crates/ra_assists/src/remove_dbg.rs | 8 ++- crates/ra_assists/src/replace_if_let_with_match.rs | 8 ++- crates/ra_assists/src/split_import.rs | 8 ++- 12 files changed, 144 insertions(+), 64 deletions(-) (limited to 'crates/ra_assists/src') diff --git a/crates/ra_assists/src/add_derive.rs b/crates/ra_assists/src/add_derive.rs index ea9707631..0556dd69c 100644 --- a/crates/ra_assists/src/add_derive.rs +++ b/crates/ra_assists/src/add_derive.rs @@ -7,10 +7,10 @@ use ra_syntax::{ use crate::{AssistCtx, Assist}; -pub(crate) fn add_derive(ctx: AssistCtx) -> Option { +pub(crate) fn add_derive(mut ctx: AssistCtx) -> Option { let nominal = ctx.node_at_offset::()?; let node_start = derive_insertion_offset(nominal)?; - ctx.build("add `#[derive]`", |edit| { + ctx.add_action("add `#[derive]`", |edit| { let derive_attr = nominal .attrs() .filter_map(|x| x.as_call()) @@ -26,7 +26,9 @@ pub(crate) fn add_derive(ctx: AssistCtx) -> Option { }; edit.target(nominal.syntax().range()); edit.set_cursor(offset) - }) + }); + + ctx.build() } // Insert `derive` after doc comments. diff --git a/crates/ra_assists/src/add_impl.rs b/crates/ra_assists/src/add_impl.rs index 32fc074a6..b40b9cc0c 100644 --- a/crates/ra_assists/src/add_impl.rs +++ b/crates/ra_assists/src/add_impl.rs @@ -7,10 +7,10 @@ use ra_syntax::{ use crate::{AssistCtx, Assist}; -pub(crate) fn add_impl(ctx: AssistCtx) -> Option { +pub(crate) fn add_impl(mut ctx: AssistCtx) -> Option { let nominal = ctx.node_at_offset::()?; let name = nominal.name()?; - ctx.build("add impl", |edit| { + ctx.add_action("add impl", |edit| { edit.target(nominal.syntax().range()); let type_params = nominal.type_param_list(); let start_offset = nominal.syntax().range().end(); @@ -32,7 +32,9 @@ pub(crate) fn add_impl(ctx: AssistCtx) -> Option { edit.set_cursor(start_offset + TextUnit::of_str(&buf)); buf.push_str("\n}"); edit.insert(start_offset, buf); - }) + }); + + ctx.build() } #[cfg(test)] diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_ctx.rs index e240c35d6..e9c4f0a23 100644 --- a/crates/ra_assists/src/assist_ctx.rs +++ b/crates/ra_assists/src/assist_ctx.rs @@ -9,9 +9,10 @@ use ra_fmt::{leading_indent, reindent}; use crate::{AssistLabel, AssistAction}; +#[derive(Clone, Debug)] pub(crate) enum Assist { - Unresolved(AssistLabel), - Resolved(AssistLabel, AssistAction), + Unresolved(Vec), + Resolved(Vec<(AssistLabel, AssistAction)>), } /// `AssistCtx` allows to apply an assist or check if it could be applied. @@ -50,6 +51,7 @@ pub(crate) struct AssistCtx<'a, DB> { pub(crate) frange: FileRange, source_file: &'a SourceFile, should_compute_edit: bool, + assist: Assist, } impl<'a, DB> Clone for AssistCtx<'a, DB> { @@ -59,6 +61,7 @@ impl<'a, DB> Clone for AssistCtx<'a, DB> { frange: self.frange, source_file: self.source_file, should_compute_edit: self.should_compute_edit, + assist: self.assist.clone(), } } } @@ -69,25 +72,35 @@ impl<'a, DB: HirDatabase> AssistCtx<'a, DB> { F: FnOnce(AssistCtx) -> T, { let source_file = &db.parse(frange.file_id); - let ctx = AssistCtx { db, frange, source_file, should_compute_edit }; + let assist = + if should_compute_edit { Assist::Resolved(vec![]) } else { Assist::Unresolved(vec![]) }; + + let ctx = AssistCtx { db, frange, source_file, should_compute_edit, assist }; f(ctx) } - pub(crate) fn build( - self, + pub(crate) fn add_action( + &mut self, label: impl Into, f: impl FnOnce(&mut AssistBuilder), - ) -> Option { + ) -> &mut Self { let label = AssistLabel { label: label.into() }; - if !self.should_compute_edit { - return Some(Assist::Unresolved(label)); + match &mut self.assist { + Assist::Unresolved(labels) => labels.push(label), + Assist::Resolved(labels_actions) => { + let action = { + let mut edit = AssistBuilder::default(); + f(&mut edit); + edit.build() + }; + labels_actions.push((label, action)); + } } - let action = { - let mut edit = AssistBuilder::default(); - f(&mut edit); - edit.build() - }; - Some(Assist::Resolved(label, action)) + self + } + + pub(crate) fn build(self) -> Option { + Some(self.assist) } pub(crate) fn leaf_at_offset(&self) -> LeafAtOffset<&'a SyntaxNode> { diff --git a/crates/ra_assists/src/auto_import.rs b/crates/ra_assists/src/auto_import.rs index 6a0c351f1..b251c9369 100644 --- a/crates/ra_assists/src/auto_import.rs +++ b/crates/ra_assists/src/auto_import.rs @@ -480,7 +480,7 @@ fn make_assist_add_nested_import( } } -pub(crate) fn auto_import(ctx: AssistCtx) -> Option { +pub(crate) fn auto_import(mut ctx: AssistCtx) -> Option { let node = ctx.covering_node(); let current_file = node.ancestors().find_map(ast::SourceFile::cast)?; @@ -495,7 +495,7 @@ pub(crate) fn auto_import(ctx: AssistCtx) -> Option { return None; } - ctx.build(format!("import {} in the current file", fmt_segments(&segments)), |edit| { + ctx.add_action(format!("import {} in the current file", fmt_segments(&segments)), |edit| { let action = best_action_for_target(current_file.syntax(), path, &segments); make_assist(&action, segments.as_slice(), edit); if let Some(last_segment) = path.segment() { @@ -506,7 +506,9 @@ pub(crate) fn auto_import(ctx: AssistCtx) -> Option { last_segment.syntax().range().start(), )); } - }) + }); + + ctx.build() } #[cfg(test)] diff --git a/crates/ra_assists/src/change_visibility.rs b/crates/ra_assists/src/change_visibility.rs index 6d9a4eec2..c2ba897a4 100644 --- a/crates/ra_assists/src/change_visibility.rs +++ b/crates/ra_assists/src/change_visibility.rs @@ -14,7 +14,7 @@ pub(crate) fn change_visibility(ctx: AssistCtx) -> Option) -> Option { +fn add_vis(mut ctx: AssistCtx) -> Option { let item_keyword = ctx.leaf_at_offset().find(|leaf| match leaf.kind() { FN_KW | MOD_KW | STRUCT_KW | ENUM_KW | TRAIT_KW => true, _ => false, @@ -41,11 +41,13 @@ fn add_vis(ctx: AssistCtx) -> Option { (vis_offset(field.syntax()), ident.range()) }; - ctx.build("make pub(crate)", |edit| { + ctx.add_action("make pub(crate)", |edit| { edit.target(target); edit.insert(offset, "pub(crate) "); edit.set_cursor(offset); - }) + }); + + ctx.build() } fn vis_offset(node: &SyntaxNode) -> TextUnit { @@ -59,20 +61,24 @@ fn vis_offset(node: &SyntaxNode) -> TextUnit { .unwrap_or(node.range().start()) } -fn change_vis(ctx: AssistCtx, vis: &ast::Visibility) -> Option { +fn change_vis(mut ctx: AssistCtx, vis: &ast::Visibility) -> Option { if vis.syntax().text() == "pub" { - return ctx.build("change to pub(crate)", |edit| { + ctx.add_action("change to pub(crate)", |edit| { edit.target(vis.syntax().range()); edit.replace(vis.syntax().range(), "pub(crate)"); - edit.set_cursor(vis.syntax().range().start()); + edit.set_cursor(vis.syntax().range().start()) }); + + return ctx.build(); } if vis.syntax().text() == "pub(crate)" { - return ctx.build("change to pub", |edit| { + ctx.add_action("change to pub", |edit| { edit.target(vis.syntax().range()); edit.replace(vis.syntax().range(), "pub"); edit.set_cursor(vis.syntax().range().start()); }); + + return ctx.build(); } None } diff --git a/crates/ra_assists/src/fill_match_arms.rs b/crates/ra_assists/src/fill_match_arms.rs index 69b535a27..d8e40b4b7 100644 --- a/crates/ra_assists/src/fill_match_arms.rs +++ b/crates/ra_assists/src/fill_match_arms.rs @@ -8,7 +8,7 @@ use ra_syntax::ast::{self, AstNode}; use crate::{AssistCtx, Assist}; -pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option { +pub(crate) fn fill_match_arms(mut ctx: AssistCtx) -> Option { let match_expr = ctx.node_at_offset::()?; // We already have some match arms, so we don't provide any assists. @@ -33,7 +33,7 @@ pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option) -> Option) -> Option { +pub(crate) fn flip_comma(mut ctx: AssistCtx) -> Option { let comma = ctx.leaf_at_offset().find(|leaf| leaf.kind() == COMMA)?; let prev = non_trivia_sibling(comma, Direction::Prev)?; let next = non_trivia_sibling(comma, Direction::Next)?; - ctx.build("flip comma", |edit| { + ctx.add_action("flip comma", |edit| { edit.target(comma.range()); edit.replace(prev.range(), next.text()); edit.replace(next.range(), prev.text()); - }) + }); + + ctx.build() } #[cfg(test)] diff --git a/crates/ra_assists/src/introduce_variable.rs b/crates/ra_assists/src/introduce_variable.rs index 954b97b05..f0e012105 100644 --- a/crates/ra_assists/src/introduce_variable.rs +++ b/crates/ra_assists/src/introduce_variable.rs @@ -8,7 +8,7 @@ use ra_syntax::{ use crate::{AssistCtx, Assist}; -pub(crate) fn introduce_variable(ctx: AssistCtx) -> Option { +pub(crate) fn introduce_variable(mut ctx: AssistCtx) -> Option { let node = ctx.covering_node(); if !valid_covering_node(node) { return None; @@ -19,7 +19,7 @@ pub(crate) fn introduce_variable(ctx: AssistCtx) -> Option) -> Option bool { diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index af578893e..c607a5142 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs @@ -7,6 +7,8 @@ mod assist_ctx; +use itertools::Itertools; + use ra_text_edit::TextEdit; use ra_syntax::{TextRange, TextUnit, SyntaxNode, Direction}; use ra_db::FileRange; @@ -14,12 +16,13 @@ use hir::db::HirDatabase; pub(crate) use crate::assist_ctx::{AssistCtx, Assist}; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct AssistLabel { /// Short description of the assist, as shown in the UI. pub label: String, } +#[derive(Debug, Clone)] pub struct AssistAction { pub edit: TextEdit, pub cursor_position: Option, @@ -39,10 +42,10 @@ where .iter() .filter_map(|f| f(ctx.clone())) .map(|a| match a { - Assist::Unresolved(label) => label, + Assist::Unresolved(labels) => labels, Assist::Resolved(..) => unreachable!(), }) - .collect() + .concat() }) } @@ -61,10 +64,10 @@ where .iter() .filter_map(|f| f(ctx.clone())) .map(|a| match a { - Assist::Resolved(label, action) => (label, action), + Assist::Resolved(labels_actions) => labels_actions, Assist::Unresolved(..) => unreachable!(), }) - .collect::>(); + .concat(); a.sort_by(|a, b| match (a.1.target, b.1.target) { (Some(a), Some(b)) => a.len().cmp(&b.len()), (Some(_), None) => Ordering::Less, @@ -118,6 +121,39 @@ mod helpers { assist: fn(AssistCtx) -> Option, before: &str, after: &str, + ) { + check_assist_nth_action(assist, before, after, 0) + } + + pub(crate) fn check_assist_range( + assist: fn(AssistCtx) -> Option, + before: &str, + after: &str, + ) { + check_assist_range_nth_action(assist, before, after, 0) + } + + pub(crate) fn check_assist_target( + assist: fn(AssistCtx) -> Option, + before: &str, + target: &str, + ) { + check_assist_target_nth_action(assist, before, target, 0) + } + + pub(crate) fn check_assist_range_target( + assist: fn(AssistCtx) -> Option, + before: &str, + target: &str, + ) { + check_assist_range_target_nth_action(assist, before, target, 0) + } + + pub(crate) fn check_assist_nth_action( + assist: fn(AssistCtx) -> Option, + before: &str, + after: &str, + index: usize, ) { let (before_cursor_pos, before) = extract_offset(before); let (db, _source_root, file_id) = MockDatabase::with_single_file(&before); @@ -125,11 +161,12 @@ mod helpers { FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) }; let assist = AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); - let action = match assist { + let labels_actions = match assist { Assist::Unresolved(_) => unreachable!(), - Assist::Resolved(_, it) => it, + Assist::Resolved(labels_actions) => labels_actions, }; + let (_, action) = labels_actions.get(index).expect("expect assist action at index"); let actual = action.edit.apply(&before); let actual_cursor_pos = match action.cursor_position { None => action @@ -142,21 +179,23 @@ mod helpers { assert_eq_text!(after, &actual); } - pub(crate) fn check_assist_range( + pub(crate) fn check_assist_range_nth_action( assist: fn(AssistCtx) -> Option, before: &str, after: &str, + index: usize, ) { let (range, before) = extract_range(before); let (db, _source_root, file_id) = MockDatabase::with_single_file(&before); let frange = FileRange { file_id, range }; let assist = AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); - let action = match assist { + let labels_actions = match assist { Assist::Unresolved(_) => unreachable!(), - Assist::Resolved(_, it) => it, + Assist::Resolved(labels_actions) => labels_actions, }; + let (_, action) = labels_actions.get(index).expect("expect assist action at index"); let mut actual = action.edit.apply(&before); if let Some(pos) = action.cursor_position { actual = add_cursor(&actual, pos); @@ -164,10 +203,11 @@ mod helpers { assert_eq_text!(after, &actual); } - pub(crate) fn check_assist_target( + pub(crate) fn check_assist_target_nth_action( assist: fn(AssistCtx) -> Option, before: &str, target: &str, + index: usize, ) { let (before_cursor_pos, before) = extract_offset(before); let (db, _source_root, file_id) = MockDatabase::with_single_file(&before); @@ -175,30 +215,33 @@ mod helpers { FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) }; let assist = AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); - let action = match assist { + let labels_actions = match assist { Assist::Unresolved(_) => unreachable!(), - Assist::Resolved(_, it) => it, + Assist::Resolved(labels_actions) => labels_actions, }; + let (_, action) = labels_actions.get(index).expect("expect assist action at index"); let range = action.target.expect("expected target on action"); assert_eq_text!(&before[range.start().to_usize()..range.end().to_usize()], target); } - pub(crate) fn check_assist_range_target( + pub(crate) fn check_assist_range_target_nth_action( assist: fn(AssistCtx) -> Option, before: &str, target: &str, + index: usize, ) { let (range, before) = extract_range(before); let (db, _source_root, file_id) = MockDatabase::with_single_file(&before); let frange = FileRange { file_id, range }; let assist = AssistCtx::with_ctx(&db, frange, true, assist).expect("code action is not applicable"); - let action = match assist { + let labels_actions = match assist { Assist::Unresolved(_) => unreachable!(), - Assist::Resolved(_, it) => it, + Assist::Resolved(labels_actions) => labels_actions, }; + let (_, action) = labels_actions.get(index).expect("expect assist action at index"); let range = action.target.expect("expected target on action"); assert_eq_text!(&before[range.start().to_usize()..range.end().to_usize()], target); } diff --git a/crates/ra_assists/src/remove_dbg.rs b/crates/ra_assists/src/remove_dbg.rs index e9d0a635b..db260c6ca 100644 --- a/crates/ra_assists/src/remove_dbg.rs +++ b/crates/ra_assists/src/remove_dbg.rs @@ -8,7 +8,7 @@ use ra_syntax::{ }; use crate::{AssistCtx, Assist}; -pub(crate) fn remove_dbg(ctx: AssistCtx) -> Option { +pub(crate) fn remove_dbg(mut ctx: AssistCtx) -> Option { let macro_call = ctx.node_at_offset::()?; if !is_valid_macrocall(macro_call, "dbg")? { @@ -46,11 +46,13 @@ pub(crate) fn remove_dbg(ctx: AssistCtx) -> Option { macro_args.text().slice(start..end).to_string() }; - ctx.build("remove dbg!()", |edit| { + ctx.add_action("remove dbg!()", |edit| { edit.target(macro_call.syntax().range()); edit.replace(macro_range, macro_content); edit.set_cursor(cursor_pos); - }) + }); + + ctx.build() } /// Verifies that the given macro_call actually matches the given name diff --git a/crates/ra_assists/src/replace_if_let_with_match.rs b/crates/ra_assists/src/replace_if_let_with_match.rs index f74ab3c67..87a2c1185 100644 --- a/crates/ra_assists/src/replace_if_let_with_match.rs +++ b/crates/ra_assists/src/replace_if_let_with_match.rs @@ -4,7 +4,7 @@ use hir::db::HirDatabase; use crate::{AssistCtx, Assist}; -pub(crate) fn replace_if_let_with_match(ctx: AssistCtx) -> Option { +pub(crate) fn replace_if_let_with_match(mut ctx: AssistCtx) -> Option { let if_expr: &ast::IfExpr = ctx.node_at_offset()?; let cond = if_expr.condition()?; let pat = cond.pat()?; @@ -15,12 +15,14 @@ pub(crate) fn replace_if_let_with_match(ctx: AssistCtx) -> Opt ast::ElseBranchFlavor::IfExpr(_) => return None, }; - ctx.build("replace with match", |edit| { + ctx.add_action("replace with match", |edit| { let match_expr = build_match_expr(expr, pat, then_block, else_block); edit.target(if_expr.syntax().range()); edit.replace_node_and_indent(if_expr.syntax(), match_expr); edit.set_cursor(if_expr.syntax().range().start()) - }) + }); + + ctx.build() } fn build_match_expr( diff --git a/crates/ra_assists/src/split_import.rs b/crates/ra_assists/src/split_import.rs index 051bc6fec..f043be636 100644 --- a/crates/ra_assists/src/split_import.rs +++ b/crates/ra_assists/src/split_import.rs @@ -7,7 +7,7 @@ use ra_syntax::{ use crate::{AssistCtx, Assist}; -pub(crate) fn split_import(ctx: AssistCtx) -> Option { +pub(crate) fn split_import(mut ctx: AssistCtx) -> Option { let colon_colon = ctx.leaf_at_offset().find(|leaf| leaf.kind() == COLONCOLON)?; let path = colon_colon.parent().and_then(ast::Path::cast)?; let top_path = generate(Some(path), |it| it.parent_path()).last()?; @@ -23,12 +23,14 @@ pub(crate) fn split_import(ctx: AssistCtx) -> Option { None => top_path.syntax().range().end(), }; - ctx.build("split import", |edit| { + ctx.add_action("split import", |edit| { edit.target(colon_colon.range()); edit.insert(l_curly, "{"); edit.insert(r_curly, "}"); edit.set_cursor(l_curly + TextUnit::of_str("{")); - }) + }); + + ctx.build() } #[cfg(test)] -- cgit v1.2.3