diff options
Diffstat (limited to 'crates/ra_assists/src/assist_ctx.rs')
-rw-r--r-- | crates/ra_assists/src/assist_ctx.rs | 140 |
1 files changed, 81 insertions, 59 deletions
diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_ctx.rs index 43f0d664b..5aab5fb8b 100644 --- a/crates/ra_assists/src/assist_ctx.rs +++ b/crates/ra_assists/src/assist_ctx.rs | |||
@@ -1,8 +1,8 @@ | |||
1 | //! This module defines `AssistCtx` -- the API surface that is exposed to assists. | 1 | //! This module defines `AssistCtx` -- the API surface that is exposed to assists. |
2 | use either::Either; | 2 | use hir::{InFile, SourceAnalyzer, SourceBinder}; |
3 | use hir::{db::HirDatabase, InFile, SourceAnalyzer, SourceBinder}; | 3 | use ra_db::{FileRange, SourceDatabase}; |
4 | use ra_db::FileRange; | ||
5 | use ra_fmt::{leading_indent, reindent}; | 4 | use ra_fmt::{leading_indent, reindent}; |
5 | use ra_ide_db::RootDatabase; | ||
6 | use ra_syntax::{ | 6 | use ra_syntax::{ |
7 | algo::{self, find_covering_element, find_node_at_offset}, | 7 | algo::{self, find_covering_element, find_node_at_offset}, |
8 | AstNode, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextUnit, | 8 | AstNode, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextUnit, |
@@ -10,14 +10,40 @@ use ra_syntax::{ | |||
10 | }; | 10 | }; |
11 | use ra_text_edit::TextEditBuilder; | 11 | use ra_text_edit::TextEditBuilder; |
12 | 12 | ||
13 | use crate::{AssistAction, AssistId, AssistLabel, ResolvedAssist}; | 13 | use crate::{AssistAction, AssistId, AssistLabel, GroupLabel, ResolvedAssist}; |
14 | 14 | ||
15 | #[derive(Clone, Debug)] | 15 | #[derive(Clone, Debug)] |
16 | pub(crate) enum Assist { | 16 | pub(crate) struct Assist(pub(crate) Vec<AssistInfo>); |
17 | Unresolved { label: AssistLabel }, | 17 | |
18 | Resolved { assist: ResolvedAssist }, | 18 | #[derive(Clone, Debug)] |
19 | pub(crate) struct AssistInfo { | ||
20 | pub(crate) label: AssistLabel, | ||
21 | pub(crate) group_label: Option<GroupLabel>, | ||
22 | pub(crate) action: Option<AssistAction>, | ||
23 | } | ||
24 | |||
25 | impl AssistInfo { | ||
26 | fn new(label: AssistLabel) -> AssistInfo { | ||
27 | AssistInfo { label, group_label: None, action: None } | ||
28 | } | ||
29 | |||
30 | fn resolved(self, action: AssistAction) -> AssistInfo { | ||
31 | AssistInfo { action: Some(action), ..self } | ||
32 | } | ||
33 | |||
34 | fn with_group(self, group_label: GroupLabel) -> AssistInfo { | ||
35 | AssistInfo { group_label: Some(group_label), ..self } | ||
36 | } | ||
37 | |||
38 | pub(crate) fn into_resolved(self) -> Option<ResolvedAssist> { | ||
39 | let label = self.label; | ||
40 | let group_label = self.group_label; | ||
41 | self.action.map(|action| ResolvedAssist { label, group_label, action }) | ||
42 | } | ||
19 | } | 43 | } |
20 | 44 | ||
45 | pub(crate) type AssistHandler = fn(AssistCtx) -> Option<Assist>; | ||
46 | |||
21 | /// `AssistCtx` allows to apply an assist or check if it could be applied. | 47 | /// `AssistCtx` allows to apply an assist or check if it could be applied. |
22 | /// | 48 | /// |
23 | /// Assists use a somewhat over-engineered approach, given the current needs. The | 49 | /// Assists use a somewhat over-engineered approach, given the current needs. The |
@@ -49,14 +75,14 @@ pub(crate) enum Assist { | |||
49 | /// moment, because the LSP API is pretty awkward in this place, and it's much | 75 | /// moment, because the LSP API is pretty awkward in this place, and it's much |
50 | /// easier to just compute the edit eagerly :-) | 76 | /// easier to just compute the edit eagerly :-) |
51 | #[derive(Debug)] | 77 | #[derive(Debug)] |
52 | pub(crate) struct AssistCtx<'a, DB> { | 78 | pub(crate) struct AssistCtx<'a> { |
53 | pub(crate) db: &'a DB, | 79 | pub(crate) db: &'a RootDatabase, |
54 | pub(crate) frange: FileRange, | 80 | pub(crate) frange: FileRange, |
55 | source_file: SourceFile, | 81 | source_file: SourceFile, |
56 | should_compute_edit: bool, | 82 | should_compute_edit: bool, |
57 | } | 83 | } |
58 | 84 | ||
59 | impl<'a, DB> Clone for AssistCtx<'a, DB> { | 85 | impl Clone for AssistCtx<'_> { |
60 | fn clone(&self) -> Self { | 86 | fn clone(&self) -> Self { |
61 | AssistCtx { | 87 | AssistCtx { |
62 | db: self.db, | 88 | db: self.db, |
@@ -67,15 +93,10 @@ impl<'a, DB> Clone for AssistCtx<'a, DB> { | |||
67 | } | 93 | } |
68 | } | 94 | } |
69 | 95 | ||
70 | impl<'a, DB: HirDatabase> AssistCtx<'a, DB> { | 96 | impl<'a> AssistCtx<'a> { |
71 | pub(crate) fn with_ctx<F, T>(db: &DB, frange: FileRange, should_compute_edit: bool, f: F) -> T | 97 | pub fn new(db: &RootDatabase, frange: FileRange, should_compute_edit: bool) -> AssistCtx { |
72 | where | ||
73 | F: FnOnce(AssistCtx<DB>) -> T, | ||
74 | { | ||
75 | let parse = db.parse(frange.file_id); | 98 | let parse = db.parse(frange.file_id); |
76 | 99 | AssistCtx { db, frange, source_file: parse.tree(), should_compute_edit } | |
77 | let ctx = AssistCtx { db, frange, source_file: parse.tree(), should_compute_edit }; | ||
78 | f(ctx) | ||
79 | } | 100 | } |
80 | 101 | ||
81 | pub(crate) fn add_assist( | 102 | pub(crate) fn add_assist( |
@@ -84,48 +105,23 @@ impl<'a, DB: HirDatabase> AssistCtx<'a, DB> { | |||
84 | label: impl Into<String>, | 105 | label: impl Into<String>, |
85 | f: impl FnOnce(&mut ActionBuilder), | 106 | f: impl FnOnce(&mut ActionBuilder), |
86 | ) -> Option<Assist> { | 107 | ) -> Option<Assist> { |
87 | let label = AssistLabel { label: label.into(), id }; | 108 | let label = AssistLabel::new(label.into(), id); |
88 | assert!(label.label.chars().nth(0).unwrap().is_uppercase()); | ||
89 | 109 | ||
90 | let assist = if self.should_compute_edit { | 110 | let mut info = AssistInfo::new(label); |
111 | if self.should_compute_edit { | ||
91 | let action = { | 112 | let action = { |
92 | let mut edit = ActionBuilder::default(); | 113 | let mut edit = ActionBuilder::default(); |
93 | f(&mut edit); | 114 | f(&mut edit); |
94 | edit.build() | 115 | edit.build() |
95 | }; | 116 | }; |
96 | Assist::Resolved { assist: ResolvedAssist { label, action_data: Either::Left(action) } } | 117 | info = info.resolved(action) |
97 | } else { | ||
98 | Assist::Unresolved { label } | ||
99 | }; | 118 | }; |
100 | 119 | ||
101 | Some(assist) | 120 | Some(Assist(vec![info])) |
102 | } | 121 | } |
103 | 122 | ||
104 | #[allow(dead_code)] // will be used for auto import assist with multiple actions | 123 | pub(crate) fn add_assist_group(self, group_name: impl Into<String>) -> AssistGroup<'a> { |
105 | pub(crate) fn add_assist_group( | 124 | AssistGroup { ctx: self, group_name: group_name.into(), assists: Vec::new() } |
106 | self, | ||
107 | id: AssistId, | ||
108 | label: impl Into<String>, | ||
109 | f: impl FnOnce() -> Vec<ActionBuilder>, | ||
110 | ) -> Option<Assist> { | ||
111 | let label = AssistLabel { label: label.into(), id }; | ||
112 | let assist = if self.should_compute_edit { | ||
113 | let actions = f(); | ||
114 | assert!(!actions.is_empty(), "Assist cannot have no"); | ||
115 | |||
116 | Assist::Resolved { | ||
117 | assist: ResolvedAssist { | ||
118 | label, | ||
119 | action_data: Either::Right( | ||
120 | actions.into_iter().map(ActionBuilder::build).collect(), | ||
121 | ), | ||
122 | }, | ||
123 | } | ||
124 | } else { | ||
125 | Assist::Unresolved { label } | ||
126 | }; | ||
127 | |||
128 | Some(assist) | ||
129 | } | 125 | } |
130 | 126 | ||
131 | pub(crate) fn token_at_offset(&self) -> TokenAtOffset<SyntaxToken> { | 127 | pub(crate) fn token_at_offset(&self) -> TokenAtOffset<SyntaxToken> { |
@@ -142,7 +138,7 @@ impl<'a, DB: HirDatabase> AssistCtx<'a, DB> { | |||
142 | pub(crate) fn covering_element(&self) -> SyntaxElement { | 138 | pub(crate) fn covering_element(&self) -> SyntaxElement { |
143 | find_covering_element(self.source_file.syntax(), self.frange.range) | 139 | find_covering_element(self.source_file.syntax(), self.frange.range) |
144 | } | 140 | } |
145 | pub(crate) fn source_binder(&self) -> SourceBinder<'a, DB> { | 141 | pub(crate) fn source_binder(&self) -> SourceBinder<'a, RootDatabase> { |
146 | SourceBinder::new(self.db) | 142 | SourceBinder::new(self.db) |
147 | } | 143 | } |
148 | pub(crate) fn source_analyzer( | 144 | pub(crate) fn source_analyzer( |
@@ -159,21 +155,48 @@ impl<'a, DB: HirDatabase> AssistCtx<'a, DB> { | |||
159 | } | 155 | } |
160 | } | 156 | } |
161 | 157 | ||
158 | pub(crate) struct AssistGroup<'a> { | ||
159 | ctx: AssistCtx<'a>, | ||
160 | group_name: String, | ||
161 | assists: Vec<AssistInfo>, | ||
162 | } | ||
163 | |||
164 | impl<'a> AssistGroup<'a> { | ||
165 | pub(crate) fn add_assist( | ||
166 | &mut self, | ||
167 | id: AssistId, | ||
168 | label: impl Into<String>, | ||
169 | f: impl FnOnce(&mut ActionBuilder), | ||
170 | ) { | ||
171 | let label = AssistLabel::new(label.into(), id); | ||
172 | |||
173 | let mut info = AssistInfo::new(label).with_group(GroupLabel(self.group_name.clone())); | ||
174 | if self.ctx.should_compute_edit { | ||
175 | let action = { | ||
176 | let mut edit = ActionBuilder::default(); | ||
177 | f(&mut edit); | ||
178 | edit.build() | ||
179 | }; | ||
180 | info = info.resolved(action) | ||
181 | }; | ||
182 | |||
183 | self.assists.push(info) | ||
184 | } | ||
185 | |||
186 | pub(crate) fn finish(self) -> Option<Assist> { | ||
187 | assert!(!self.assists.is_empty()); | ||
188 | Some(Assist(self.assists)) | ||
189 | } | ||
190 | } | ||
191 | |||
162 | #[derive(Default)] | 192 | #[derive(Default)] |
163 | pub(crate) struct ActionBuilder { | 193 | pub(crate) struct ActionBuilder { |
164 | edit: TextEditBuilder, | 194 | edit: TextEditBuilder, |
165 | cursor_position: Option<TextUnit>, | 195 | cursor_position: Option<TextUnit>, |
166 | target: Option<TextRange>, | 196 | target: Option<TextRange>, |
167 | label: Option<String>, | ||
168 | } | 197 | } |
169 | 198 | ||
170 | impl ActionBuilder { | 199 | impl ActionBuilder { |
171 | #[allow(dead_code)] | ||
172 | /// Adds a custom label to the action, if it needs to be different from the assist label | ||
173 | pub(crate) fn label(&mut self, label: impl Into<String>) { | ||
174 | self.label = Some(label.into()) | ||
175 | } | ||
176 | |||
177 | /// Replaces specified `range` of text with a given string. | 200 | /// Replaces specified `range` of text with a given string. |
178 | pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) { | 201 | pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) { |
179 | self.edit.replace(range, replace_with.into()) | 202 | self.edit.replace(range, replace_with.into()) |
@@ -232,7 +255,6 @@ impl ActionBuilder { | |||
232 | edit: self.edit.finish(), | 255 | edit: self.edit.finish(), |
233 | cursor_position: self.cursor_position, | 256 | cursor_position: self.cursor_position, |
234 | target: self.target, | 257 | target: self.target, |
235 | label: self.label, | ||
236 | } | 258 | } |
237 | } | 259 | } |
238 | } | 260 | } |