diff options
author | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-02-06 14:01:07 +0000 |
---|---|---|
committer | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-02-06 14:01:07 +0000 |
commit | f6cf9393a8327b4c0385bccbc5be84a79bd50057 (patch) | |
tree | 4af15c8906b85de01a15c717bc1fac388952cd3d /crates/ra_assists/src/assist_ctx.rs | |
parent | 736a55c97e69f95e6ff4a0c3dafb2018e8ea05f9 (diff) | |
parent | 0c5fd8f7cbf04eda763e55bc9a38dad5f7ec917d (diff) |
Merge #750
750: Move assists to a separate crate r=matklad a=matklad
I am slowly coming to conclusion that ide_api_light does not make a lot of sense after all :D
This PR moves assists to a separate crate, so that assists can use database (so, inspect types, do name-resolution, etc)
Co-authored-by: Aleksey Kladov <[email protected]>
Diffstat (limited to 'crates/ra_assists/src/assist_ctx.rs')
-rw-r--r-- | crates/ra_assists/src/assist_ctx.rs | 154 |
1 files changed, 154 insertions, 0 deletions
diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_ctx.rs new file mode 100644 index 000000000..6d09bde52 --- /dev/null +++ b/crates/ra_assists/src/assist_ctx.rs | |||
@@ -0,0 +1,154 @@ | |||
1 | use hir::db::HirDatabase; | ||
2 | use ra_text_edit::TextEditBuilder; | ||
3 | use ra_db::FileRange; | ||
4 | use ra_syntax::{ | ||
5 | SourceFile, TextRange, AstNode, TextUnit, SyntaxNode, | ||
6 | algo::{find_leaf_at_offset, find_node_at_offset, find_covering_node, LeafAtOffset}, | ||
7 | }; | ||
8 | use ra_ide_api_light::formatting::{leading_indent, reindent}; | ||
9 | |||
10 | use crate::{AssistLabel, AssistAction}; | ||
11 | |||
12 | pub(crate) enum Assist { | ||
13 | Unresolved(AssistLabel), | ||
14 | Resolved(AssistLabel, AssistAction), | ||
15 | } | ||
16 | |||
17 | /// `AssistCtx` allows to apply an assist or check if it could be applied. | ||
18 | /// | ||
19 | /// Assists use a somewhat overengineered approach, given the current needs. The | ||
20 | /// assists workflow consists of two phases. In the first phase, a user asks for | ||
21 | /// the list of available assists. In the second phase, the user picks a | ||
22 | /// particular assist and it gets applied. | ||
23 | /// | ||
24 | /// There are two peculiarities here: | ||
25 | /// | ||
26 | /// * first, we ideally avoid computing more things then necessary to answer | ||
27 | /// "is assist applicable" in the first phase. | ||
28 | /// * second, when we are applying assist, we don't have a guarantee that there | ||
29 | /// weren't any changes between the point when user asked for assists and when | ||
30 | /// they applied a particular assist. So, when applying assist, we need to do | ||
31 | /// all the checks from scratch. | ||
32 | /// | ||
33 | /// To avoid repeating the same code twice for both "check" and "apply" | ||
34 | /// functions, we use an approach reminiscent of that of Django's function based | ||
35 | /// views dealing with forms. Each assist receives a runtime parameter, | ||
36 | /// `should_compute_edit`. It first check if an edit is applicable (potentially | ||
37 | /// computing info required to compute the actual edit). If it is applicable, | ||
38 | /// and `should_compute_edit` is `true`, it then computes the actual edit. | ||
39 | /// | ||
40 | /// So, to implement the original assists workflow, we can first apply each edit | ||
41 | /// with `should_compute_edit = false`, and then applying the selected edit | ||
42 | /// again, with `should_compute_edit = true` this time. | ||
43 | /// | ||
44 | /// Note, however, that we don't actually use such two-phase logic at the | ||
45 | /// moment, because the LSP API is pretty awkward in this place, and it's much | ||
46 | /// easier to just compute the edit eagerly :-)#[derive(Debug, Clone)] | ||
47 | #[derive(Debug)] | ||
48 | pub(crate) struct AssistCtx<'a, DB> { | ||
49 | pub(crate) db: &'a DB, | ||
50 | pub(crate) frange: FileRange, | ||
51 | source_file: &'a SourceFile, | ||
52 | should_compute_edit: bool, | ||
53 | } | ||
54 | |||
55 | impl<'a, DB> Clone for AssistCtx<'a, DB> { | ||
56 | fn clone(&self) -> Self { | ||
57 | AssistCtx { | ||
58 | db: self.db, | ||
59 | frange: self.frange, | ||
60 | source_file: self.source_file, | ||
61 | should_compute_edit: self.should_compute_edit, | ||
62 | } | ||
63 | } | ||
64 | } | ||
65 | |||
66 | impl<'a, DB: HirDatabase> AssistCtx<'a, DB> { | ||
67 | pub(crate) fn with_ctx<F, T>(db: &DB, frange: FileRange, should_compute_edit: bool, f: F) -> T | ||
68 | where | ||
69 | F: FnOnce(AssistCtx<DB>) -> T, | ||
70 | { | ||
71 | let source_file = &db.parse(frange.file_id); | ||
72 | let ctx = AssistCtx { | ||
73 | db, | ||
74 | frange, | ||
75 | source_file, | ||
76 | should_compute_edit, | ||
77 | }; | ||
78 | f(ctx) | ||
79 | } | ||
80 | |||
81 | pub(crate) fn build( | ||
82 | self, | ||
83 | label: impl Into<String>, | ||
84 | f: impl FnOnce(&mut AssistBuilder), | ||
85 | ) -> Option<Assist> { | ||
86 | let label = AssistLabel { | ||
87 | label: label.into(), | ||
88 | }; | ||
89 | if !self.should_compute_edit { | ||
90 | return Some(Assist::Unresolved(label)); | ||
91 | } | ||
92 | let action = { | ||
93 | let mut edit = AssistBuilder::default(); | ||
94 | f(&mut edit); | ||
95 | edit.build() | ||
96 | }; | ||
97 | Some(Assist::Resolved(label, action)) | ||
98 | } | ||
99 | |||
100 | pub(crate) fn leaf_at_offset(&self) -> LeafAtOffset<&'a SyntaxNode> { | ||
101 | find_leaf_at_offset(self.source_file.syntax(), self.frange.range.start()) | ||
102 | } | ||
103 | |||
104 | pub(crate) fn node_at_offset<N: AstNode>(&self) -> Option<&'a N> { | ||
105 | find_node_at_offset(self.source_file.syntax(), self.frange.range.start()) | ||
106 | } | ||
107 | pub(crate) fn covering_node(&self) -> &'a SyntaxNode { | ||
108 | find_covering_node(self.source_file.syntax(), self.frange.range) | ||
109 | } | ||
110 | } | ||
111 | |||
112 | #[derive(Default)] | ||
113 | pub(crate) struct AssistBuilder { | ||
114 | edit: TextEditBuilder, | ||
115 | cursor_position: Option<TextUnit>, | ||
116 | } | ||
117 | |||
118 | impl AssistBuilder { | ||
119 | pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) { | ||
120 | self.edit.replace(range, replace_with.into()) | ||
121 | } | ||
122 | |||
123 | pub(crate) fn replace_node_and_indent( | ||
124 | &mut self, | ||
125 | node: &SyntaxNode, | ||
126 | replace_with: impl Into<String>, | ||
127 | ) { | ||
128 | let mut replace_with = replace_with.into(); | ||
129 | if let Some(indent) = leading_indent(node) { | ||
130 | replace_with = reindent(&replace_with, indent) | ||
131 | } | ||
132 | self.replace(node.range(), replace_with) | ||
133 | } | ||
134 | |||
135 | #[allow(unused)] | ||
136 | pub(crate) fn delete(&mut self, range: TextRange) { | ||
137 | self.edit.delete(range) | ||
138 | } | ||
139 | |||
140 | pub(crate) fn insert(&mut self, offset: TextUnit, text: impl Into<String>) { | ||
141 | self.edit.insert(offset, text.into()) | ||
142 | } | ||
143 | |||
144 | pub(crate) fn set_cursor(&mut self, offset: TextUnit) { | ||
145 | self.cursor_position = Some(offset) | ||
146 | } | ||
147 | |||
148 | fn build(self) -> AssistAction { | ||
149 | AssistAction { | ||
150 | edit: self.edit.finish(), | ||
151 | cursor_position: self.cursor_position, | ||
152 | } | ||
153 | } | ||
154 | } | ||