]> git.lizzy.rs Git - rust.git/blob - crates/ra_assists/src/assist_ctx.rs
e240c35d6f3a68298381874c15b748f8117da842
[rust.git] / crates / ra_assists / src / assist_ctx.rs
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_fmt::{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 over-engineered 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 { db, frange, source_file, should_compute_edit };
73         f(ctx)
74     }
75
76     pub(crate) fn build(
77         self,
78         label: impl Into<String>,
79         f: impl FnOnce(&mut AssistBuilder),
80     ) -> Option<Assist> {
81         let label = AssistLabel { label: label.into() };
82         if !self.should_compute_edit {
83             return Some(Assist::Unresolved(label));
84         }
85         let action = {
86             let mut edit = AssistBuilder::default();
87             f(&mut edit);
88             edit.build()
89         };
90         Some(Assist::Resolved(label, action))
91     }
92
93     pub(crate) fn leaf_at_offset(&self) -> LeafAtOffset<&'a SyntaxNode> {
94         find_leaf_at_offset(self.source_file.syntax(), self.frange.range.start())
95     }
96
97     pub(crate) fn node_at_offset<N: AstNode>(&self) -> Option<&'a N> {
98         find_node_at_offset(self.source_file.syntax(), self.frange.range.start())
99     }
100     pub(crate) fn covering_node(&self) -> &'a SyntaxNode {
101         find_covering_node(self.source_file.syntax(), self.frange.range)
102     }
103 }
104
105 #[derive(Default)]
106 pub(crate) struct AssistBuilder {
107     edit: TextEditBuilder,
108     cursor_position: Option<TextUnit>,
109     target: Option<TextRange>,
110 }
111
112 impl AssistBuilder {
113     pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) {
114         self.edit.replace(range, replace_with.into())
115     }
116
117     pub(crate) fn replace_node_and_indent(
118         &mut self,
119         node: &SyntaxNode,
120         replace_with: impl Into<String>,
121     ) {
122         let mut replace_with = replace_with.into();
123         if let Some(indent) = leading_indent(node) {
124             replace_with = reindent(&replace_with, indent)
125         }
126         self.replace(node.range(), replace_with)
127     }
128
129     #[allow(unused)]
130     pub(crate) fn delete(&mut self, range: TextRange) {
131         self.edit.delete(range)
132     }
133
134     pub(crate) fn insert(&mut self, offset: TextUnit, text: impl Into<String>) {
135         self.edit.insert(offset, text.into())
136     }
137
138     pub(crate) fn set_cursor(&mut self, offset: TextUnit) {
139         self.cursor_position = Some(offset)
140     }
141
142     pub(crate) fn target(&mut self, target: TextRange) {
143         self.target = Some(target)
144     }
145
146     fn build(self) -> AssistAction {
147         AssistAction {
148             edit: self.edit.finish(),
149             cursor_position: self.cursor_position,
150             target: self.target,
151         }
152     }
153 }