aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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 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;
19 19
20use crate::{ 20use crate::{
21 assist_config::{AssistConfig, SnippetCap}, 21 assist_config::{AssistConfig, SnippetCap},
22 Assist, AssistId, GroupLabel, ResolvedAssist, 22 Assist, AssistId, AssistKind, GroupLabel, ResolvedAssist,
23}; 23};
24 24
25/// `AssistContext` allows to apply an assist or check if it could be applied. 25/// `AssistContext` allows to apply an assist or check if it could be applied.
@@ -57,6 +57,7 @@ pub(crate) struct AssistContext<'a> {
57 pub(crate) sema: Semantics<'a, RootDatabase>, 57 pub(crate) sema: Semantics<'a, RootDatabase>,
58 pub(crate) frange: FileRange, 58 pub(crate) frange: FileRange,
59 source_file: SourceFile, 59 source_file: SourceFile,
60 allowed: Option<Vec<AssistKind>>,
60} 61}
61 62
62impl<'a> AssistContext<'a> { 63impl<'a> AssistContext<'a> {
@@ -64,9 +65,10 @@ impl<'a> AssistContext<'a> {
64 sema: Semantics<'a, RootDatabase>, 65 sema: Semantics<'a, RootDatabase>,
65 config: &'a AssistConfig, 66 config: &'a AssistConfig,
66 frange: FileRange, 67 frange: FileRange,
68 allowed: Option<Vec<AssistKind>>,
67 ) -> AssistContext<'a> { 69 ) -> AssistContext<'a> {
68 let source_file = sema.parse(frange.file_id); 70 let source_file = sema.parse(frange.file_id);
69 AssistContext { config, sema, frange, source_file } 71 AssistContext { config, sema, frange, source_file, allowed }
70 } 72 }
71 73
72 pub(crate) fn db(&self) -> &RootDatabase { 74 pub(crate) fn db(&self) -> &RootDatabase {
@@ -103,14 +105,26 @@ pub(crate) struct Assists {
103 resolve: bool, 105 resolve: bool,
104 file: FileId, 106 file: FileId,
105 buf: Vec<(Assist, Option<SourceChange>)>, 107 buf: Vec<(Assist, Option<SourceChange>)>,
108 allowed: Option<Vec<AssistKind>>,
106} 109}
107 110
108impl Assists { 111impl Assists {
109 pub(crate) fn new_resolved(ctx: &AssistContext) -> Assists { 112 pub(crate) fn new_resolved(ctx: &AssistContext) -> Assists {
110 Assists { resolve: true, file: ctx.frange.file_id, buf: Vec::new() } 113 Assists {
114 resolve: true,
115 file: ctx.frange.file_id,
116 buf: Vec::new(),
117 allowed: ctx.allowed.clone(),
118 }
111 } 119 }
120
112 pub(crate) fn new_unresolved(ctx: &AssistContext) -> Assists { 121 pub(crate) fn new_unresolved(ctx: &AssistContext) -> Assists {
113 Assists { resolve: false, file: ctx.frange.file_id, buf: Vec::new() } 122 Assists {
123 resolve: false,
124 file: ctx.frange.file_id,
125 buf: Vec::new(),
126 allowed: ctx.allowed.clone(),
127 }
114 } 128 }
115 129
116 pub(crate) fn finish_unresolved(self) -> Vec<Assist> { 130 pub(crate) fn finish_unresolved(self) -> Vec<Assist> {
@@ -139,9 +153,13 @@ impl Assists {
139 target: TextRange, 153 target: TextRange,
140 f: impl FnOnce(&mut AssistBuilder), 154 f: impl FnOnce(&mut AssistBuilder),
141 ) -> Option<()> { 155 ) -> Option<()> {
156 if !self.is_allowed(&id) {
157 return None;
158 }
142 let label = Assist::new(id, label.into(), None, target); 159 let label = Assist::new(id, label.into(), None, target);
143 self.add_impl(label, f) 160 self.add_impl(label, f)
144 } 161 }
162
145 pub(crate) fn add_group( 163 pub(crate) fn add_group(
146 &mut self, 164 &mut self,
147 group: &GroupLabel, 165 group: &GroupLabel,
@@ -150,9 +168,14 @@ impl Assists {
150 target: TextRange, 168 target: TextRange,
151 f: impl FnOnce(&mut AssistBuilder), 169 f: impl FnOnce(&mut AssistBuilder),
152 ) -> Option<()> { 170 ) -> Option<()> {
171 if !self.is_allowed(&id) {
172 return None;
173 }
174
153 let label = Assist::new(id, label.into(), Some(group.clone()), target); 175 let label = Assist::new(id, label.into(), Some(group.clone()), target);
154 self.add_impl(label, f) 176 self.add_impl(label, f)
155 } 177 }
178
156 fn add_impl(&mut self, label: Assist, f: impl FnOnce(&mut AssistBuilder)) -> Option<()> { 179 fn add_impl(&mut self, label: Assist, f: impl FnOnce(&mut AssistBuilder)) -> Option<()> {
157 let source_change = if self.resolve { 180 let source_change = if self.resolve {
158 let mut builder = AssistBuilder::new(self.file); 181 let mut builder = AssistBuilder::new(self.file);
@@ -170,6 +193,13 @@ impl Assists {
170 self.buf.sort_by_key(|(label, _edit)| label.target.len()); 193 self.buf.sort_by_key(|(label, _edit)| label.target.len());
171 self.buf 194 self.buf
172 } 195 }
196
197 fn is_allowed(&self, id: &AssistId) -> bool {
198 match &self.allowed {
199 Some(allowed) => allowed.iter().any(|kind| kind.contains(id.1)),
200 None => true,
201 }
202 }
173} 203}
174 204
175pub(crate) struct AssistBuilder { 205pub(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 {
37 RefactorRewrite, 37 RefactorRewrite,
38} 38}
39 39
40impl AssistKind {
41 pub fn contains(self, other: AssistKind) -> bool {
42 if self == other {
43 return true;
44 }
45
46 match self {
47 AssistKind::None | AssistKind::Generate => return true,
48 AssistKind::Refactor => match other {
49 AssistKind::RefactorExtract
50 | AssistKind::RefactorInline
51 | AssistKind::RefactorRewrite => return true,
52 _ => return false,
53 },
54 _ => return false,
55 }
56 }
57}
58
40/// Unique identifier of the assist, should not be shown to the user 59/// Unique identifier of the assist, should not be shown to the user
41/// directly. 60/// directly.
42#[derive(Debug, Clone, Copy, PartialEq, Eq)] 61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -67,9 +86,14 @@ impl Assist {
67 /// 86 ///
68 /// Assists are returned in the "unresolved" state, that is only labels are 87 /// Assists are returned in the "unresolved" state, that is only labels are
69 /// returned, without actual edits. 88 /// returned, without actual edits.
70 pub fn unresolved(db: &RootDatabase, config: &AssistConfig, range: FileRange) -> Vec<Assist> { 89 pub fn unresolved(
90 db: &RootDatabase,
91 config: &AssistConfig,
92 range: FileRange,
93 allowed: Option<Vec<AssistKind>>,
94 ) -> Vec<Assist> {
71 let sema = Semantics::new(db); 95 let sema = Semantics::new(db);
72 let ctx = AssistContext::new(sema, config, range); 96 let ctx = AssistContext::new(sema, config, range, allowed);
73 let mut acc = Assists::new_unresolved(&ctx); 97 let mut acc = Assists::new_unresolved(&ctx);
74 handlers::all().iter().for_each(|handler| { 98 handlers::all().iter().for_each(|handler| {
75 handler(&mut acc, &ctx); 99 handler(&mut acc, &ctx);
@@ -85,9 +109,10 @@ impl Assist {
85 db: &RootDatabase, 109 db: &RootDatabase,
86 config: &AssistConfig, 110 config: &AssistConfig,
87 range: FileRange, 111 range: FileRange,
112 allowed: Option<Vec<AssistKind>>,
88 ) -> Vec<ResolvedAssist> { 113 ) -> Vec<ResolvedAssist> {
89 let sema = Semantics::new(db); 114 let sema = Semantics::new(db);
90 let ctx = AssistContext::new(sema, config, range); 115 let ctx = AssistContext::new(sema, config, range, allowed);
91 let mut acc = Assists::new_resolved(&ctx); 116 let mut acc = Assists::new_resolved(&ctx);
92 handlers::all().iter().for_each(|handler| { 117 handlers::all().iter().for_each(|handler| {
93 handler(&mut acc, &ctx); 118 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;
6use ra_syntax::TextRange; 6use ra_syntax::TextRange;
7use test_utils::{assert_eq_text, extract_offset, extract_range}; 7use test_utils::{assert_eq_text, extract_offset, extract_range};
8 8
9use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, Assists}; 9use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, AssistKind, Assists};
10use stdx::trim_indent; 10use stdx::trim_indent;
11 11
12pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) { 12pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) {
@@ -35,14 +35,14 @@ fn check_doc_test(assist_id: &str, before: &str, after: &str) {
35 let before = db.file_text(file_id).to_string(); 35 let before = db.file_text(file_id).to_string();
36 let frange = FileRange { file_id, range: selection.into() }; 36 let frange = FileRange { file_id, range: selection.into() };
37 37
38 let mut assist = Assist::resolved(&db, &AssistConfig::default(), frange) 38 let mut assist = Assist::resolved(&db, &AssistConfig::default(), frange, None)
39 .into_iter() 39 .into_iter()
40 .find(|assist| assist.assist.id.0 == assist_id) 40 .find(|assist| assist.assist.id.0 == assist_id)
41 .unwrap_or_else(|| { 41 .unwrap_or_else(|| {
42 panic!( 42 panic!(
43 "\n\nAssist is not applicable: {}\nAvailable assists: {}", 43 "\n\nAssist is not applicable: {}\nAvailable assists: {}",
44 assist_id, 44 assist_id,
45 Assist::resolved(&db, &AssistConfig::default(), frange) 45 Assist::resolved(&db, &AssistConfig::default(), frange, None)
46 .into_iter() 46 .into_iter()
47 .map(|assist| assist.assist.id.0) 47 .map(|assist| assist.assist.id.0)
48 .collect::<Vec<_>>() 48 .collect::<Vec<_>>()
@@ -73,7 +73,7 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult) {
73 73
74 let sema = Semantics::new(&db); 74 let sema = Semantics::new(&db);
75 let config = AssistConfig::default(); 75 let config = AssistConfig::default();
76 let ctx = AssistContext::new(sema, &config, frange); 76 let ctx = AssistContext::new(sema, &config, frange, None);
77 let mut acc = Assists::new_resolved(&ctx); 77 let mut acc = Assists::new_resolved(&ctx);
78 handler(&mut acc, &ctx); 78 handler(&mut acc, &ctx);
79 let mut res = acc.finish_resolved(); 79 let mut res = acc.finish_resolved();
@@ -105,7 +105,7 @@ fn assist_order_field_struct() {
105 let (before_cursor_pos, before) = extract_offset(before); 105 let (before_cursor_pos, before) = extract_offset(before);
106 let (db, file_id) = with_single_file(&before); 106 let (db, file_id) = with_single_file(&before);
107 let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) }; 107 let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) };
108 let assists = Assist::resolved(&db, &AssistConfig::default(), frange); 108 let assists = Assist::resolved(&db, &AssistConfig::default(), frange, None);
109 let mut assists = assists.iter(); 109 let mut assists = assists.iter();
110 110
111 assert_eq!( 111 assert_eq!(
@@ -128,9 +128,49 @@ fn assist_order_if_expr() {
128 let (range, before) = extract_range(before); 128 let (range, before) = extract_range(before);
129 let (db, file_id) = with_single_file(&before); 129 let (db, file_id) = with_single_file(&before);
130 let frange = FileRange { file_id, range }; 130 let frange = FileRange { file_id, range };
131 let assists = Assist::resolved(&db, &AssistConfig::default(), frange); 131 let assists = Assist::resolved(&db, &AssistConfig::default(), frange, None);
132 let mut assists = assists.iter(); 132 let mut assists = assists.iter();
133 133
134 assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable"); 134 assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable");
135 assert_eq!(assists.next().expect("expected assist").assist.label, "Replace with match"); 135 assert_eq!(assists.next().expect("expected assist").assist.label, "Replace with match");
136} 136}
137
138#[test]
139fn assist_filter_works() {
140 let before = "
141 pub fn test_some_range(a: int) -> bool {
142 if let 2..6 = <|>5<|> {
143 true
144 } else {
145 false
146 }
147 }";
148 let (range, before) = extract_range(before);
149 let (db, file_id) = with_single_file(&before);
150 let frange = FileRange { file_id, range };
151
152 {
153 let allowed = Some(vec![AssistKind::Refactor]);
154
155 let assists = Assist::resolved(&db, &AssistConfig::default(), frange, allowed);
156 let mut assists = assists.iter();
157
158 assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable");
159 assert_eq!(assists.next().expect("expected assist").assist.label, "Replace with match");
160 }
161
162 {
163 let allowed = Some(vec![AssistKind::RefactorExtract]);
164 let assists = Assist::resolved(&db, &AssistConfig::default(), frange, allowed);
165 assert_eq!(assists.len(), 1);
166
167 let mut assists = assists.iter();
168 assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable");
169 }
170
171 {
172 let allowed = Some(vec![AssistKind::QuickFix]);
173 let assists = Assist::resolved(&db, &AssistConfig::default(), frange, allowed);
174 assert!(assists.is_empty(), "All asserts but quickfixes should be filtered out");
175 }
176}
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 {
476 &self, 476 &self,
477 config: &AssistConfig, 477 config: &AssistConfig,
478 frange: FileRange, 478 frange: FileRange,
479 allowed: Option<Vec<AssistKind>>,
479 ) -> Cancelable<Vec<ResolvedAssist>> { 480 ) -> Cancelable<Vec<ResolvedAssist>> {
480 self.with_db(|db| ra_assists::Assist::resolved(db, config, frange)) 481 self.with_db(|db| ra_assists::Assist::resolved(db, config, frange, allowed))
481 } 482 }
482 483
483 /// Computes unresolved assists (aka code actions aka intentions) for the given 484 /// Computes unresolved assists (aka code actions aka intentions) for the given
@@ -486,8 +487,9 @@ impl Analysis {
486 &self, 487 &self,
487 config: &AssistConfig, 488 config: &AssistConfig,
488 frange: FileRange, 489 frange: FileRange,
490 allowed: Option<Vec<AssistKind>>,
489 ) -> Cancelable<Vec<Assist>> { 491 ) -> Cancelable<Vec<Assist>> {
490 self.with_db(|db| Assist::unresolved(db, config, frange)) 492 self.with_db(|db| Assist::unresolved(db, config, frange, allowed))
491 } 493 }
492 494
493 /// Computes the set of diagnostics for the given file. 495 /// 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 @@
2use std::convert::TryFrom; 2use std::convert::TryFrom;
3 3
4use ra_db::{FileId, FilePosition, FileRange}; 4use ra_db::{FileId, FilePosition, FileRange};
5use ra_ide::{LineCol, LineIndex}; 5use ra_ide::{AssistKind, LineCol, LineIndex};
6use ra_syntax::{TextRange, TextSize}; 6use ra_syntax::{TextRange, TextSize};
7use vfs::AbsPathBuf; 7use vfs::AbsPathBuf;
8 8
@@ -52,3 +52,17 @@ pub(crate) fn file_range(
52 let range = text_range(&line_index, range); 52 let range = text_range(&line_index, range);
53 Ok(FileRange { file_id, range }) 53 Ok(FileRange { file_id, range })
54} 54}
55
56pub(crate) fn assist_kind(kind: lsp_types::CodeActionKind) -> Option<AssistKind> {
57 let assist_kind = match &kind {
58 k if k == &lsp_types::CodeActionKind::EMPTY => AssistKind::None,
59 k if k == &lsp_types::CodeActionKind::QUICKFIX => AssistKind::QuickFix,
60 k if k == &lsp_types::CodeActionKind::REFACTOR => AssistKind::Refactor,
61 k if k == &lsp_types::CodeActionKind::REFACTOR => AssistKind::RefactorExtract,
62 k if k == &lsp_types::CodeActionKind::REFACTOR => AssistKind::RefactorInline,
63 k if k == &lsp_types::CodeActionKind::REFACTOR => AssistKind::RefactorRewrite,
64 _ => return None,
65 };
66
67 Some(assist_kind)
68}
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(
746 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?; 746 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
747 let line_index = snap.analysis.file_line_index(file_id)?; 747 let line_index = snap.analysis.file_line_index(file_id)?;
748 let range = from_proto::text_range(&line_index, params.range); 748 let range = from_proto::text_range(&line_index, params.range);
749
750 match &params.context.only {
751 Some(v) => {
752 if v.iter().any(|it| {
753 it == &lsp_types::CodeActionKind::EMPTY
754 || it == &lsp_types::CodeActionKind::QUICKFIX
755 }) {
756 return Ok(());
757 }
758 }
759 None => {}
760 };
761
749 let diagnostics = snap.analysis.diagnostics(file_id)?; 762 let diagnostics = snap.analysis.diagnostics(file_id)?;
750 763
751 let fixes_from_diagnostics = diagnostics 764 let fixes_from_diagnostics = diagnostics
@@ -792,18 +805,26 @@ pub(crate) fn handle_code_action(
792 let line_index = snap.analysis.file_line_index(file_id)?; 805 let line_index = snap.analysis.file_line_index(file_id)?;
793 let range = from_proto::text_range(&line_index, params.range); 806 let range = from_proto::text_range(&line_index, params.range);
794 let frange = FileRange { file_id, range }; 807 let frange = FileRange { file_id, range };
808
795 let mut res: Vec<lsp_ext::CodeAction> = Vec::new(); 809 let mut res: Vec<lsp_ext::CodeAction> = Vec::new();
796 810
797 handle_fixes(&snap, &params, &mut res)?; 811 handle_fixes(&snap, &params, &mut res)?;
798 812
813 let only =
814 params.context.only.map(|it| it.into_iter().filter_map(from_proto::assist_kind).collect());
815
799 if snap.config.client_caps.resolve_code_action { 816 if snap.config.client_caps.resolve_code_action {
800 for (index, assist) in 817 for (index, assist) in snap
801 snap.analysis.unresolved_assists(&snap.config.assist, frange)?.into_iter().enumerate() 818 .analysis
819 .unresolved_assists(&snap.config.assist, frange, only)?
820 .into_iter()
821 .enumerate()
802 { 822 {
803 res.push(to_proto::unresolved_code_action(&snap, assist, index)?); 823 res.push(to_proto::unresolved_code_action(&snap, assist, index)?);
804 } 824 }
805 } else { 825 } else {
806 for assist in snap.analysis.resolved_assists(&snap.config.assist, frange)?.into_iter() { 826 for assist in snap.analysis.resolved_assists(&snap.config.assist, frange, only)?.into_iter()
827 {
807 res.push(to_proto::resolved_code_action(&snap, assist)?); 828 res.push(to_proto::resolved_code_action(&snap, assist)?);
808 } 829 }
809 } 830 }
@@ -820,8 +841,13 @@ pub(crate) fn handle_resolve_code_action(
820 let line_index = snap.analysis.file_line_index(file_id)?; 841 let line_index = snap.analysis.file_line_index(file_id)?;
821 let range = from_proto::text_range(&line_index, params.code_action_params.range); 842 let range = from_proto::text_range(&line_index, params.code_action_params.range);
822 let frange = FileRange { file_id, range }; 843 let frange = FileRange { file_id, range };
844 let only = params
845 .code_action_params
846 .context
847 .only
848 .map(|it| it.into_iter().filter_map(from_proto::assist_kind).collect());
823 849
824 let assists = snap.analysis.resolved_assists(&snap.config.assist, frange)?; 850 let assists = snap.analysis.resolved_assists(&snap.config.assist, frange, only)?;
825 let (id_string, index) = split_delim(&params.id, ':').unwrap(); 851 let (id_string, index) = split_delim(&params.id, ':').unwrap();
826 let index = index.parse::<usize>().unwrap(); 852 let index = index.parse::<usize>().unwrap();
827 let assist = &assists[index]; 853 let assist = &assists[index];