about summary refs log tree commit diff
diff options
context:
space:
mode:
authorkjeremy <kjeremy@gmail.com>2020-07-13 17:41:47 -0400
committerkjeremy <kjeremy@gmail.com>2020-07-13 17:41:47 -0400
commitaa598ecb75331068f4cacd443512991bc48937dc (patch)
tree785b88b46016507ed2ced5befce6885780664ff2
parent853440775d72974585ca3fe39f9688a4d4302dd3 (diff)
downloadrust-aa598ecb75331068f4cacd443512991bc48937dc.tar.gz
rust-aa598ecb75331068f4cacd443512991bc48937dc.zip
Filter assists
-rw-r--r--crates/ra_assists/src/assist_context.rs38
-rw-r--r--crates/ra_assists/src/lib.rs31
-rw-r--r--crates/ra_assists/src/tests.rs52
-rw-r--r--crates/ra_ide/src/lib.rs6
-rw-r--r--crates/rust-analyzer/src/from_proto.rs16
-rw-r--r--crates/rust-analyzer/src/handlers.rs34
6 files changed, 157 insertions, 20 deletions
diff --git a/crates/ra_assists/src/assist_context.rs b/crates/ra_assists/src/assist_context.rs
index c3352536351..9ca2cfe68bc 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<Vec<AssistKind>>,
 }
 
 impl<'a> AssistContext<'a> {
@@ -64,9 +65,10 @@ impl<'a> AssistContext<'a> {
         sema: Semantics<'a, RootDatabase>,
         config: &'a AssistConfig,
         frange: FileRange,
+        allowed: Option<Vec<AssistKind>>,
     ) -> 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<SourceChange>)>,
+    allowed: Option<Vec<AssistKind>>,
 }
 
 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<Assist> {
@@ -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 3d61fbdedf7..13a28376015 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<Assist> {
+    pub fn unresolved(
+        db: &RootDatabase,
+        config: &AssistConfig,
+        range: FileRange,
+        allowed: Option<Vec<AssistKind>>,
+    ) -> Vec<Assist> {
         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<AssistKind>>,
     ) -> Vec<ResolvedAssist> {
         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 858f5ca80fa..861622d8673 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::<Vec<_>>()
@@ -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 6a4f5cb3db1..c21f2a0b1b8 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<Vec<AssistKind>>,
     ) -> Cancelable<Vec<ResolvedAssist>> {
-        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<Vec<AssistKind>>,
     ) -> Cancelable<Vec<Assist>> {
-        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 15b28110372..e2abfa3e077 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<AssistKind> {
+    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 f2e24178aef..357d5f8caf1 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, &params.text_document.uri)?;
     let line_index = snap.analysis.file_line_index(file_id)?;
     let range = from_proto::text_range(&line_index, params.range);
+
+    match &params.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<lsp_ext::CodeAction> = Vec::new();
 
     handle_fixes(&snap, &params, &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(&params.id, ':').unwrap();
     let index = index.parse::<usize>().unwrap();
     let assist = &assists[index];