From aa598ecb75331068f4cacd443512991bc48937dc Mon Sep 17 00:00:00 2001 From: kjeremy Date: Mon, 13 Jul 2020 17:41:47 -0400 Subject: Filter assists --- crates/ra_assists/src/assist_context.rs | 38 +++++++++++++++++++++--- crates/ra_assists/src/lib.rs | 31 ++++++++++++++++++-- crates/ra_assists/src/tests.rs | 52 +++++++++++++++++++++++++++++---- crates/ra_ide/src/lib.rs | 6 ++-- crates/rust-analyzer/src/from_proto.rs | 16 +++++++++- crates/rust-analyzer/src/handlers.rs | 34 ++++++++++++++++++--- 6 files changed, 157 insertions(+), 20 deletions(-) (limited to 'crates') diff --git a/crates/ra_assists/src/assist_context.rs b/crates/ra_assists/src/assist_context.rs index c33525363..9ca2cfe68 100644 --- a/crates/ra_assists/src/assist_context.rs +++ b/crates/ra_assists/src/assist_context.rs @@ -19,7 +19,7 @@ use ra_text_edit::TextEditBuilder; use crate::{ assist_config::{AssistConfig, SnippetCap}, - Assist, AssistId, GroupLabel, ResolvedAssist, + Assist, AssistId, AssistKind, GroupLabel, ResolvedAssist, }; /// `AssistContext` allows to apply an assist or check if it could be applied. @@ -57,6 +57,7 @@ pub(crate) struct AssistContext<'a> { pub(crate) sema: Semantics<'a, RootDatabase>, pub(crate) frange: FileRange, source_file: SourceFile, + allowed: Option>, } impl<'a> AssistContext<'a> { @@ -64,9 +65,10 @@ impl<'a> AssistContext<'a> { sema: Semantics<'a, RootDatabase>, config: &'a AssistConfig, frange: FileRange, + allowed: Option>, ) -> AssistContext<'a> { let source_file = sema.parse(frange.file_id); - AssistContext { config, sema, frange, source_file } + AssistContext { config, sema, frange, source_file, allowed } } pub(crate) fn db(&self) -> &RootDatabase { @@ -103,14 +105,26 @@ pub(crate) struct Assists { resolve: bool, file: FileId, buf: Vec<(Assist, Option)>, + allowed: Option>, } impl Assists { pub(crate) fn new_resolved(ctx: &AssistContext) -> Assists { - Assists { resolve: true, file: ctx.frange.file_id, buf: Vec::new() } + Assists { + resolve: true, + file: ctx.frange.file_id, + buf: Vec::new(), + allowed: ctx.allowed.clone(), + } } + pub(crate) fn new_unresolved(ctx: &AssistContext) -> Assists { - Assists { resolve: false, file: ctx.frange.file_id, buf: Vec::new() } + Assists { + resolve: false, + file: ctx.frange.file_id, + buf: Vec::new(), + allowed: ctx.allowed.clone(), + } } pub(crate) fn finish_unresolved(self) -> Vec { @@ -139,9 +153,13 @@ impl Assists { target: TextRange, f: impl FnOnce(&mut AssistBuilder), ) -> Option<()> { + if !self.is_allowed(&id) { + return None; + } let label = Assist::new(id, label.into(), None, target); self.add_impl(label, f) } + pub(crate) fn add_group( &mut self, group: &GroupLabel, @@ -150,9 +168,14 @@ impl Assists { target: TextRange, f: impl FnOnce(&mut AssistBuilder), ) -> Option<()> { + if !self.is_allowed(&id) { + return None; + } + let label = Assist::new(id, label.into(), Some(group.clone()), target); self.add_impl(label, f) } + fn add_impl(&mut self, label: Assist, f: impl FnOnce(&mut AssistBuilder)) -> Option<()> { let source_change = if self.resolve { let mut builder = AssistBuilder::new(self.file); @@ -170,6 +193,13 @@ impl Assists { self.buf.sort_by_key(|(label, _edit)| label.target.len()); self.buf } + + fn is_allowed(&self, id: &AssistId) -> bool { + match &self.allowed { + Some(allowed) => allowed.iter().any(|kind| kind.contains(id.1)), + None => true, + } + } } pub(crate) struct AssistBuilder { diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index 3d61fbded..13a283760 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs @@ -37,6 +37,25 @@ pub enum AssistKind { RefactorRewrite, } +impl AssistKind { + pub fn contains(self, other: AssistKind) -> bool { + if self == other { + return true; + } + + match self { + AssistKind::None | AssistKind::Generate => return true, + AssistKind::Refactor => match other { + AssistKind::RefactorExtract + | AssistKind::RefactorInline + | AssistKind::RefactorRewrite => return true, + _ => return false, + }, + _ => return false, + } + } +} + /// Unique identifier of the assist, should not be shown to the user /// directly. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -67,9 +86,14 @@ impl Assist { /// /// Assists are returned in the "unresolved" state, that is only labels are /// returned, without actual edits. - pub fn unresolved(db: &RootDatabase, config: &AssistConfig, range: FileRange) -> Vec { + pub fn unresolved( + db: &RootDatabase, + config: &AssistConfig, + range: FileRange, + allowed: Option>, + ) -> Vec { let sema = Semantics::new(db); - let ctx = AssistContext::new(sema, config, range); + let ctx = AssistContext::new(sema, config, range, allowed); let mut acc = Assists::new_unresolved(&ctx); handlers::all().iter().for_each(|handler| { handler(&mut acc, &ctx); @@ -85,9 +109,10 @@ impl Assist { db: &RootDatabase, config: &AssistConfig, range: FileRange, + allowed: Option>, ) -> Vec { let sema = Semantics::new(db); - let ctx = AssistContext::new(sema, config, range); + let ctx = AssistContext::new(sema, config, range, allowed); let mut acc = Assists::new_resolved(&ctx); handlers::all().iter().for_each(|handler| { handler(&mut acc, &ctx); diff --git a/crates/ra_assists/src/tests.rs b/crates/ra_assists/src/tests.rs index 858f5ca80..861622d86 100644 --- a/crates/ra_assists/src/tests.rs +++ b/crates/ra_assists/src/tests.rs @@ -6,7 +6,7 @@ use ra_ide_db::RootDatabase; use ra_syntax::TextRange; use test_utils::{assert_eq_text, extract_offset, extract_range}; -use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, Assists}; +use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, AssistKind, Assists}; use stdx::trim_indent; pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) { @@ -35,14 +35,14 @@ fn check_doc_test(assist_id: &str, before: &str, after: &str) { let before = db.file_text(file_id).to_string(); let frange = FileRange { file_id, range: selection.into() }; - let mut assist = Assist::resolved(&db, &AssistConfig::default(), frange) + let mut assist = Assist::resolved(&db, &AssistConfig::default(), frange, None) .into_iter() .find(|assist| assist.assist.id.0 == assist_id) .unwrap_or_else(|| { panic!( "\n\nAssist is not applicable: {}\nAvailable assists: {}", assist_id, - Assist::resolved(&db, &AssistConfig::default(), frange) + Assist::resolved(&db, &AssistConfig::default(), frange, None) .into_iter() .map(|assist| assist.assist.id.0) .collect::>() @@ -73,7 +73,7 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult) { let sema = Semantics::new(&db); let config = AssistConfig::default(); - let ctx = AssistContext::new(sema, &config, frange); + let ctx = AssistContext::new(sema, &config, frange, None); let mut acc = Assists::new_resolved(&ctx); handler(&mut acc, &ctx); let mut res = acc.finish_resolved(); @@ -105,7 +105,7 @@ fn assist_order_field_struct() { let (before_cursor_pos, before) = extract_offset(before); let (db, file_id) = with_single_file(&before); let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) }; - let assists = Assist::resolved(&db, &AssistConfig::default(), frange); + let assists = Assist::resolved(&db, &AssistConfig::default(), frange, None); let mut assists = assists.iter(); assert_eq!( @@ -128,9 +128,49 @@ fn assist_order_if_expr() { let (range, before) = extract_range(before); let (db, file_id) = with_single_file(&before); let frange = FileRange { file_id, range }; - let assists = Assist::resolved(&db, &AssistConfig::default(), frange); + let assists = Assist::resolved(&db, &AssistConfig::default(), frange, None); let mut assists = assists.iter(); assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable"); assert_eq!(assists.next().expect("expected assist").assist.label, "Replace with match"); } + +#[test] +fn assist_filter_works() { + let before = " + pub fn test_some_range(a: int) -> bool { + if let 2..6 = <|>5<|> { + true + } else { + false + } + }"; + let (range, before) = extract_range(before); + let (db, file_id) = with_single_file(&before); + let frange = FileRange { file_id, range }; + + { + let allowed = Some(vec![AssistKind::Refactor]); + + let assists = Assist::resolved(&db, &AssistConfig::default(), frange, allowed); + let mut assists = assists.iter(); + + assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable"); + assert_eq!(assists.next().expect("expected assist").assist.label, "Replace with match"); + } + + { + let allowed = Some(vec![AssistKind::RefactorExtract]); + let assists = Assist::resolved(&db, &AssistConfig::default(), frange, allowed); + assert_eq!(assists.len(), 1); + + let mut assists = assists.iter(); + assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable"); + } + + { + let allowed = Some(vec![AssistKind::QuickFix]); + let assists = Assist::resolved(&db, &AssistConfig::default(), frange, allowed); + assert!(assists.is_empty(), "All asserts but quickfixes should be filtered out"); + } +} diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs index 6a4f5cb3d..c21f2a0b1 100644 --- a/crates/ra_ide/src/lib.rs +++ b/crates/ra_ide/src/lib.rs @@ -476,8 +476,9 @@ impl Analysis { &self, config: &AssistConfig, frange: FileRange, + allowed: Option>, ) -> Cancelable> { - self.with_db(|db| ra_assists::Assist::resolved(db, config, frange)) + self.with_db(|db| ra_assists::Assist::resolved(db, config, frange, allowed)) } /// Computes unresolved assists (aka code actions aka intentions) for the given @@ -486,8 +487,9 @@ impl Analysis { &self, config: &AssistConfig, frange: FileRange, + allowed: Option>, ) -> Cancelable> { - self.with_db(|db| Assist::unresolved(db, config, frange)) + self.with_db(|db| Assist::unresolved(db, config, frange, allowed)) } /// Computes the set of diagnostics for the given file. diff --git a/crates/rust-analyzer/src/from_proto.rs b/crates/rust-analyzer/src/from_proto.rs index 15b281103..e2abfa3e0 100644 --- a/crates/rust-analyzer/src/from_proto.rs +++ b/crates/rust-analyzer/src/from_proto.rs @@ -2,7 +2,7 @@ use std::convert::TryFrom; use ra_db::{FileId, FilePosition, FileRange}; -use ra_ide::{LineCol, LineIndex}; +use ra_ide::{AssistKind, LineCol, LineIndex}; use ra_syntax::{TextRange, TextSize}; use vfs::AbsPathBuf; @@ -52,3 +52,17 @@ pub(crate) fn file_range( let range = text_range(&line_index, range); Ok(FileRange { file_id, range }) } + +pub(crate) fn assist_kind(kind: lsp_types::CodeActionKind) -> Option { + let assist_kind = match &kind { + k if k == &lsp_types::CodeActionKind::EMPTY => AssistKind::None, + k if k == &lsp_types::CodeActionKind::QUICKFIX => AssistKind::QuickFix, + k if k == &lsp_types::CodeActionKind::REFACTOR => AssistKind::Refactor, + k if k == &lsp_types::CodeActionKind::REFACTOR => AssistKind::RefactorExtract, + k if k == &lsp_types::CodeActionKind::REFACTOR => AssistKind::RefactorInline, + k if k == &lsp_types::CodeActionKind::REFACTOR => AssistKind::RefactorRewrite, + _ => return None, + }; + + Some(assist_kind) +} diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index f2e24178a..357d5f8ca 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs @@ -746,6 +746,19 @@ fn handle_fixes( let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; let line_index = snap.analysis.file_line_index(file_id)?; let range = from_proto::text_range(&line_index, params.range); + + match ¶ms.context.only { + Some(v) => { + if v.iter().any(|it| { + it == &lsp_types::CodeActionKind::EMPTY + || it == &lsp_types::CodeActionKind::QUICKFIX + }) { + return Ok(()); + } + } + None => {} + }; + let diagnostics = snap.analysis.diagnostics(file_id)?; let fixes_from_diagnostics = diagnostics @@ -792,18 +805,26 @@ pub(crate) fn handle_code_action( let line_index = snap.analysis.file_line_index(file_id)?; let range = from_proto::text_range(&line_index, params.range); let frange = FileRange { file_id, range }; + let mut res: Vec = Vec::new(); handle_fixes(&snap, ¶ms, &mut res)?; + let only = + params.context.only.map(|it| it.into_iter().filter_map(from_proto::assist_kind).collect()); + if snap.config.client_caps.resolve_code_action { - for (index, assist) in - snap.analysis.unresolved_assists(&snap.config.assist, frange)?.into_iter().enumerate() + for (index, assist) in snap + .analysis + .unresolved_assists(&snap.config.assist, frange, only)? + .into_iter() + .enumerate() { res.push(to_proto::unresolved_code_action(&snap, assist, index)?); } } else { - for assist in snap.analysis.resolved_assists(&snap.config.assist, frange)?.into_iter() { + for assist in snap.analysis.resolved_assists(&snap.config.assist, frange, only)?.into_iter() + { res.push(to_proto::resolved_code_action(&snap, assist)?); } } @@ -820,8 +841,13 @@ pub(crate) fn handle_resolve_code_action( let line_index = snap.analysis.file_line_index(file_id)?; let range = from_proto::text_range(&line_index, params.code_action_params.range); let frange = FileRange { file_id, range }; + let only = params + .code_action_params + .context + .only + .map(|it| it.into_iter().filter_map(from_proto::assist_kind).collect()); - let assists = snap.analysis.resolved_assists(&snap.config.assist, frange)?; + let assists = snap.analysis.resolved_assists(&snap.config.assist, frange, only)?; let (id_string, index) = split_delim(¶ms.id, ':').unwrap(); let index = index.parse::().unwrap(); let assist = &assists[index]; -- cgit v1.2.3