aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src/assist_ctx.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_assists/src/assist_ctx.rs')
-rw-r--r--crates/ra_assists/src/assist_ctx.rs140
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.
2use either::Either; 2use hir::{InFile, SourceAnalyzer, SourceBinder};
3use hir::{db::HirDatabase, InFile, SourceAnalyzer, SourceBinder}; 3use ra_db::{FileRange, SourceDatabase};
4use ra_db::FileRange;
5use ra_fmt::{leading_indent, reindent}; 4use ra_fmt::{leading_indent, reindent};
5use ra_ide_db::RootDatabase;
6use ra_syntax::{ 6use 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};
11use ra_text_edit::TextEditBuilder; 11use ra_text_edit::TextEditBuilder;
12 12
13use crate::{AssistAction, AssistId, AssistLabel, ResolvedAssist}; 13use crate::{AssistAction, AssistId, AssistLabel, GroupLabel, ResolvedAssist};
14 14
15#[derive(Clone, Debug)] 15#[derive(Clone, Debug)]
16pub(crate) enum Assist { 16pub(crate) struct Assist(pub(crate) Vec<AssistInfo>);
17 Unresolved { label: AssistLabel }, 17
18 Resolved { assist: ResolvedAssist }, 18#[derive(Clone, Debug)]
19pub(crate) struct AssistInfo {
20 pub(crate) label: AssistLabel,
21 pub(crate) group_label: Option<GroupLabel>,
22 pub(crate) action: Option<AssistAction>,
23}
24
25impl 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
45pub(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)]
52pub(crate) struct AssistCtx<'a, DB> { 78pub(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
59impl<'a, DB> Clone for AssistCtx<'a, DB> { 85impl 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
70impl<'a, DB: HirDatabase> AssistCtx<'a, DB> { 96impl<'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
158pub(crate) struct AssistGroup<'a> {
159 ctx: AssistCtx<'a>,
160 group_name: String,
161 assists: Vec<AssistInfo>,
162}
163
164impl<'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)]
163pub(crate) struct ActionBuilder { 193pub(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
170impl ActionBuilder { 199impl 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}