aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src/completion/complete_postfix/format_like.rs
diff options
context:
space:
mode:
authorIgor Aleksanov <[email protected]>2020-09-12 15:14:17 +0100
committerIgor Aleksanov <[email protected]>2020-10-02 10:42:39 +0100
commitea320141c6f87383880878b91182355c9ad7dc7b (patch)
tree50e1ee50690c1ecea60caa01d6c6f7fbee983bfc /crates/ide/src/completion/complete_postfix/format_like.rs
parentc01cd6e3ed0763f8e773c34dc76db0e39396133d (diff)
Add postfix completion for format-like string literals
Diffstat (limited to 'crates/ide/src/completion/complete_postfix/format_like.rs')
-rw-r--r--crates/ide/src/completion/complete_postfix/format_like.rs310
1 files changed, 310 insertions, 0 deletions
diff --git a/crates/ide/src/completion/complete_postfix/format_like.rs b/crates/ide/src/completion/complete_postfix/format_like.rs
new file mode 100644
index 000000000..93211a35f
--- /dev/null
+++ b/crates/ide/src/completion/complete_postfix/format_like.rs
@@ -0,0 +1,310 @@
1//! Postfix completion for `format`-like strings.
2//!
3//! `"Result {result} is {2 + 2}"` is expanded to the `"Result {} is {}", result, 2 + 2`.
4//!
5//! The following postfix snippets are available:
6//!
7//! - `format` -> `format!(...)`
8//! - `println` -> `println!(...)`
9//! - `log`:
10//! + `logd` -> `log::debug!(...)`
11//! + `logt` -> `log::trace!(...)`
12//! + `logi` -> `log::info!(...)`
13//! + `logw` -> `log::warn!(...)`
14//! + `loge` -> `log::error!(...)`
15
16use super::postfix_snippet;
17use crate::completion::{
18 completion_config::SnippetCap, completion_context::CompletionContext,
19 completion_item::Completions,
20};
21use syntax::ast;
22
23pub(super) fn add_format_like_completions(
24 acc: &mut Completions,
25 ctx: &CompletionContext,
26 dot_receiver: &ast::Expr,
27 cap: SnippetCap,
28 receiver_text: &str,
29) {
30 assert!(receiver_text.len() >= 2);
31 let input = &receiver_text[1..receiver_text.len() - 1];
32
33 let mut parser = FormatStrParser::new(input);
34
35 if parser.parse().is_ok() {
36 for kind in PostfixKind::all_suggestions() {
37 let snippet = parser.into_suggestion(*kind);
38 let (label, detail) = kind.into_description();
39
40 postfix_snippet(ctx, cap, &dot_receiver, label, detail, &snippet).add_to(acc);
41 }
42 }
43}
44
45#[derive(Debug)]
46pub struct FormatStrParser {
47 input: String,
48 output: String,
49 extracted_expressions: Vec<String>,
50 state: State,
51 parsed: bool,
52}
53
54#[derive(Debug, Clone, Copy)]
55pub enum PostfixKind {
56 Format,
57 Panic,
58 Println,
59 LogDebug,
60 LogTrace,
61 LogInfo,
62 LogWarn,
63 LogError,
64}
65
66impl PostfixKind {
67 pub fn all_suggestions() -> &'static [PostfixKind] {
68 &[
69 Self::Format,
70 Self::Panic,
71 Self::Println,
72 Self::LogDebug,
73 Self::LogTrace,
74 Self::LogInfo,
75 Self::LogWarn,
76 Self::LogError,
77 ]
78 }
79
80 pub fn into_description(self) -> (&'static str, &'static str) {
81 match self {
82 Self::Format => ("fmt", "format!"),
83 Self::Panic => ("panic", "panic!"),
84 Self::Println => ("println", "println!"),
85 Self::LogDebug => ("logd", "log::debug!"),
86 Self::LogTrace => ("logt", "log::trace!"),
87 Self::LogInfo => ("logi", "log::info!"),
88 Self::LogWarn => ("logw", "log::warn!"),
89 Self::LogError => ("loge", "log::error!"),
90 }
91 }
92
93 pub fn into_macro_name(self) -> &'static str {
94 match self {
95 Self::Format => "format!",
96 Self::Panic => "panic!",
97 Self::Println => "println!",
98 Self::LogDebug => "log::debug!",
99 Self::LogTrace => "log::trace!",
100 Self::LogInfo => "log::info!",
101 Self::LogWarn => "log::warn!",
102 Self::LogError => "log::error!",
103 }
104 }
105}
106
107#[derive(Debug, Clone, Copy, PartialEq)]
108enum State {
109 NotExpr,
110 MaybeExpr,
111 Expr,
112 MaybeIncorrect,
113 FormatOpts,
114}
115
116impl FormatStrParser {
117 pub fn new(input: impl Into<String>) -> Self {
118 Self {
119 input: input.into(),
120 output: String::new(),
121 extracted_expressions: Vec::new(),
122 state: State::NotExpr,
123 parsed: false,
124 }
125 }
126
127 pub fn parse(&mut self) -> Result<(), ()> {
128 let mut current_expr = String::new();
129
130 let mut placeholders_count = 0;
131
132 // Count of open braces inside of an expression.
133 // We assume that user knows what they're doing, thus we treat it like a correct pattern, e.g.
134 // "{MyStruct { val_a: 0, val_b: 1 }}".
135 let mut inexpr_open_count = 0;
136
137 for chr in self.input.chars() {
138 match (self.state, chr) {
139 (State::NotExpr, '{') => {
140 self.output.push(chr);
141 self.state = State::MaybeExpr;
142 }
143 (State::NotExpr, '}') => {
144 self.output.push(chr);
145 self.state = State::MaybeIncorrect;
146 }
147 (State::NotExpr, _) => {
148 self.output.push(chr);
149 }
150 (State::MaybeIncorrect, '}') => {
151 // It's okay, we met "}}".
152 self.output.push(chr);
153 self.state = State::NotExpr;
154 }
155 (State::MaybeIncorrect, _) => {
156 // Error in the string.
157 return Err(());
158 }
159 (State::MaybeExpr, '{') => {
160 self.output.push(chr);
161 self.state = State::NotExpr;
162 }
163 (State::MaybeExpr, '}') => {
164 // This is an empty sequence '{}'. Replace it with placeholder.
165 self.output.push(chr);
166 self.extracted_expressions.push(format!("${}", placeholders_count));
167 placeholders_count += 1;
168 self.state = State::NotExpr;
169 }
170 (State::MaybeExpr, _) => {
171 current_expr.push(chr);
172 self.state = State::Expr;
173 }
174 (State::Expr, '}') => {
175 if inexpr_open_count == 0 {
176 self.output.push(chr);
177 self.extracted_expressions.push(current_expr.trim().into());
178 current_expr = String::new();
179 self.state = State::NotExpr;
180 } else {
181 // We're closing one brace met before inside of the expression.
182 current_expr.push(chr);
183 inexpr_open_count -= 1;
184 }
185 }
186 (State::Expr, ':') => {
187 if inexpr_open_count == 0 {
188 // We're outside of braces, thus assume that it's a specifier, like "{Some(value):?}"
189 self.output.push(chr);
190 self.extracted_expressions.push(current_expr.trim().into());
191 current_expr = String::new();
192 self.state = State::FormatOpts;
193 } else {
194 // We're inside of braced expression, assume that it's a struct field name/value delimeter.
195 current_expr.push(chr);
196 }
197 }
198 (State::Expr, '{') => {
199 current_expr.push(chr);
200 inexpr_open_count += 1;
201 }
202 (State::Expr, _) => {
203 current_expr.push(chr);
204 }
205 (State::FormatOpts, '}') => {
206 self.output.push(chr);
207 self.state = State::NotExpr;
208 }
209 (State::FormatOpts, _) => {
210 self.output.push(chr);
211 }
212 }
213 }
214
215 if self.state != State::NotExpr {
216 return Err(());
217 }
218
219 self.parsed = true;
220 Ok(())
221 }
222
223 pub fn into_suggestion(&self, kind: PostfixKind) -> String {
224 assert!(self.parsed, "Attempt to get a suggestion from not parsed expression");
225
226 let mut output = format!(r#"{}("{}""#, kind.into_macro_name(), self.output);
227 for expr in &self.extracted_expressions {
228 output += ", ";
229 output += expr;
230 }
231 output.push(')');
232
233 output
234 }
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240
241 #[test]
242 fn format_str_parser() {
243 let test_vector = &[
244 ("no expressions", Some(("no expressions", vec![]))),
245 ("{expr} is {2 + 2}", Some(("{} is {}", vec!["expr", "2 + 2"]))),
246 ("{expr:?}", Some(("{:?}", vec!["expr"]))),
247 ("{malformed", None),
248 ("malformed}", None),
249 ("{{correct", Some(("{{correct", vec![]))),
250 ("correct}}", Some(("correct}}", vec![]))),
251 ("{correct}}}", Some(("{}}}", vec!["correct"]))),
252 ("{correct}}}}}", Some(("{}}}}}", vec!["correct"]))),
253 ("{incorrect}}", None),
254 ("placeholders {} {}", Some(("placeholders {} {}", vec!["$0", "$1"]))),
255 ("mixed {} {2 + 2} {}", Some(("mixed {} {} {}", vec!["$0", "2 + 2", "$1"]))),
256 (
257 "{SomeStruct { val_a: 0, val_b: 1 }}",
258 Some(("{}", vec!["SomeStruct { val_a: 0, val_b: 1 }"])),
259 ),
260 ("{expr:?} is {2.32f64:.5}", Some(("{:?} is {:.5}", vec!["expr", "2.32f64"]))),
261 (
262 "{SomeStruct { val_a: 0, val_b: 1 }:?}",
263 Some(("{:?}", vec!["SomeStruct { val_a: 0, val_b: 1 }"])),
264 ),
265 ("{ 2 + 2 }", Some(("{}", vec!["2 + 2"]))),
266 ];
267
268 for (input, output) in test_vector {
269 let mut parser = FormatStrParser::new(*input);
270 let outcome = parser.parse();
271
272 if let Some((result_str, result_args)) = output {
273 assert!(
274 outcome.is_ok(),
275 "Outcome is error for input: {}, but the expected outcome is {:?}",
276 input,
277 output
278 );
279 assert_eq!(parser.output, *result_str);
280 assert_eq!(&parser.extracted_expressions, result_args);
281 } else {
282 assert!(
283 outcome.is_err(),
284 "Outcome is OK for input: {}, but the expected outcome is error",
285 input
286 );
287 }
288 }
289 }
290
291 #[test]
292 fn test_into_suggestion() {
293 let test_vector = &[
294 (PostfixKind::Println, "{}", r#"println!("{}", $0)"#),
295 (
296 PostfixKind::LogInfo,
297 "{} {expr} {} {2 + 2}",
298 r#"log::info!("{} {} {} {}", $0, expr, $1, 2 + 2)"#,
299 ),
300 (PostfixKind::Format, "{expr:?}", r#"format!("{:?}", expr)"#),
301 ];
302
303 for (kind, input, output) in test_vector {
304 let mut parser = FormatStrParser::new(*input);
305 parser.parse().expect("Parsing must succeed");
306
307 assert_eq!(&parser.into_suggestion(*kind), output);
308 }
309 }
310}