aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide/src/typing.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide/src/typing.rs')
-rw-r--r--crates/ra_ide/src/typing.rs365
1 files changed, 0 insertions, 365 deletions
diff --git a/crates/ra_ide/src/typing.rs b/crates/ra_ide/src/typing.rs
deleted file mode 100644
index d3ce744b4..000000000
--- a/crates/ra_ide/src/typing.rs
+++ /dev/null
@@ -1,365 +0,0 @@
1//! This module handles auto-magic editing actions applied together with users
2//! edits. For example, if the user typed
3//!
4//! ```text
5//! foo
6//! .bar()
7//! .baz()
8//! | // <- cursor is here
9//! ```
10//!
11//! and types `.` next, we want to indent the dot.
12//!
13//! Language server executes such typing assists synchronously. That is, they
14//! block user's typing and should be pretty fast for this reason!
15
16mod on_enter;
17
18use ra_db::{FilePosition, SourceDatabase};
19use ra_fmt::leading_indent;
20use ra_ide_db::{source_change::SourceFileEdit, RootDatabase};
21use ra_syntax::{
22 algo::find_node_at_offset,
23 ast::{self, AstToken},
24 AstNode, SourceFile,
25 SyntaxKind::{FIELD_EXPR, METHOD_CALL_EXPR},
26 TextRange, TextSize,
27};
28
29use ra_text_edit::TextEdit;
30
31use crate::SourceChange;
32
33pub(crate) use on_enter::on_enter;
34
35pub(crate) const TRIGGER_CHARS: &str = ".=>";
36
37// Feature: On Typing Assists
38//
39// Some features trigger on typing certain characters:
40//
41// - typing `let =` tries to smartly add `;` if `=` is followed by an existing expression
42// - typing `.` in a chain method call auto-indents
43pub(crate) fn on_char_typed(
44 db: &RootDatabase,
45 position: FilePosition,
46 char_typed: char,
47) -> Option<SourceChange> {
48 assert!(TRIGGER_CHARS.contains(char_typed));
49 let file = &db.parse(position.file_id).tree();
50 assert_eq!(file.syntax().text().char_at(position.offset), Some(char_typed));
51 let edit = on_char_typed_inner(file, position.offset, char_typed)?;
52 Some(SourceFileEdit { file_id: position.file_id, edit }.into())
53}
54
55fn on_char_typed_inner(file: &SourceFile, offset: TextSize, char_typed: char) -> Option<TextEdit> {
56 assert!(TRIGGER_CHARS.contains(char_typed));
57 match char_typed {
58 '.' => on_dot_typed(file, offset),
59 '=' => on_eq_typed(file, offset),
60 '>' => on_arrow_typed(file, offset),
61 _ => unreachable!(),
62 }
63}
64
65/// Returns an edit which should be applied after `=` was typed. Primarily,
66/// this works when adding `let =`.
67// FIXME: use a snippet completion instead of this hack here.
68fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
69 assert_eq!(file.syntax().text().char_at(offset), Some('='));
70 let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?;
71 if let_stmt.semicolon_token().is_some() {
72 return None;
73 }
74 if let Some(expr) = let_stmt.initializer() {
75 let expr_range = expr.syntax().text_range();
76 if expr_range.contains(offset) && offset != expr_range.start() {
77 return None;
78 }
79 if file.syntax().text().slice(offset..expr_range.start()).contains_char('\n') {
80 return None;
81 }
82 } else {
83 return None;
84 }
85 let offset = let_stmt.syntax().text_range().end();
86 Some(TextEdit::insert(offset, ";".to_string()))
87}
88
89/// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately.
90fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
91 assert_eq!(file.syntax().text().char_at(offset), Some('.'));
92 let whitespace =
93 file.syntax().token_at_offset(offset).left_biased().and_then(ast::Whitespace::cast)?;
94
95 let current_indent = {
96 let text = whitespace.text();
97 let newline = text.rfind('\n')?;
98 &text[newline + 1..]
99 };
100 let current_indent_len = TextSize::of(current_indent);
101
102 let parent = whitespace.syntax().parent();
103 // Make sure dot is a part of call chain
104 if !matches!(parent.kind(), FIELD_EXPR | METHOD_CALL_EXPR) {
105 return None;
106 }
107 let prev_indent = leading_indent(&parent)?;
108 let target_indent = format!(" {}", prev_indent);
109 let target_indent_len = TextSize::of(&target_indent);
110 if current_indent_len == target_indent_len {
111 return None;
112 }
113
114 Some(TextEdit::replace(TextRange::new(offset - current_indent_len, offset), target_indent))
115}
116
117/// Adds a space after an arrow when `fn foo() { ... }` is turned into `fn foo() -> { ... }`
118fn on_arrow_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
119 let file_text = file.syntax().text();
120 assert_eq!(file_text.char_at(offset), Some('>'));
121 let after_arrow = offset + TextSize::of('>');
122 if file_text.char_at(after_arrow) != Some('{') {
123 return None;
124 }
125 if find_node_at_offset::<ast::RetType>(file.syntax(), offset).is_none() {
126 return None;
127 }
128
129 Some(TextEdit::insert(after_arrow, " ".to_string()))
130}
131
132#[cfg(test)]
133mod tests {
134 use test_utils::{assert_eq_text, extract_offset};
135
136 use super::*;
137
138 fn do_type_char(char_typed: char, before: &str) -> Option<String> {
139 let (offset, before) = extract_offset(before);
140 let edit = TextEdit::insert(offset, char_typed.to_string());
141 let mut before = before.to_string();
142 edit.apply(&mut before);
143 let parse = SourceFile::parse(&before);
144 on_char_typed_inner(&parse.tree(), offset, char_typed).map(|it| {
145 it.apply(&mut before);
146 before.to_string()
147 })
148 }
149
150 fn type_char(char_typed: char, ra_fixture_before: &str, ra_fixture_after: &str) {
151 let actual = do_type_char(char_typed, ra_fixture_before)
152 .unwrap_or_else(|| panic!("typing `{}` did nothing", char_typed));
153
154 assert_eq_text!(ra_fixture_after, &actual);
155 }
156
157 fn type_char_noop(char_typed: char, before: &str) {
158 let file_change = do_type_char(char_typed, before);
159 assert!(file_change.is_none())
160 }
161
162 #[test]
163 fn test_on_eq_typed() {
164 // do_check(r"
165 // fn foo() {
166 // let foo =<|>
167 // }
168 // ", r"
169 // fn foo() {
170 // let foo =;
171 // }
172 // ");
173 type_char(
174 '=',
175 r"
176fn foo() {
177 let foo <|> 1 + 1
178}
179",
180 r"
181fn foo() {
182 let foo = 1 + 1;
183}
184",
185 );
186 // do_check(r"
187 // fn foo() {
188 // let foo =<|>
189 // let bar = 1;
190 // }
191 // ", r"
192 // fn foo() {
193 // let foo =;
194 // let bar = 1;
195 // }
196 // ");
197 }
198
199 #[test]
200 fn indents_new_chain_call() {
201 type_char(
202 '.',
203 r"
204 fn main() {
205 xs.foo()
206 <|>
207 }
208 ",
209 r"
210 fn main() {
211 xs.foo()
212 .
213 }
214 ",
215 );
216 type_char_noop(
217 '.',
218 r"
219 fn main() {
220 xs.foo()
221 <|>
222 }
223 ",
224 )
225 }
226
227 #[test]
228 fn indents_new_chain_call_with_semi() {
229 type_char(
230 '.',
231 r"
232 fn main() {
233 xs.foo()
234 <|>;
235 }
236 ",
237 r"
238 fn main() {
239 xs.foo()
240 .;
241 }
242 ",
243 );
244 type_char_noop(
245 '.',
246 r"
247 fn main() {
248 xs.foo()
249 <|>;
250 }
251 ",
252 )
253 }
254
255 #[test]
256 fn indents_new_chain_call_with_let() {
257 type_char(
258 '.',
259 r#"
260fn main() {
261 let _ = foo
262 <|>
263 bar()
264}
265"#,
266 r#"
267fn main() {
268 let _ = foo
269 .
270 bar()
271}
272"#,
273 );
274 }
275
276 #[test]
277 fn indents_continued_chain_call() {
278 type_char(
279 '.',
280 r"
281 fn main() {
282 xs.foo()
283 .first()
284 <|>
285 }
286 ",
287 r"
288 fn main() {
289 xs.foo()
290 .first()
291 .
292 }
293 ",
294 );
295 type_char_noop(
296 '.',
297 r"
298 fn main() {
299 xs.foo()
300 .first()
301 <|>
302 }
303 ",
304 );
305 }
306
307 #[test]
308 fn indents_middle_of_chain_call() {
309 type_char(
310 '.',
311 r"
312 fn source_impl() {
313 let var = enum_defvariant_list().unwrap()
314 <|>
315 .nth(92)
316 .unwrap();
317 }
318 ",
319 r"
320 fn source_impl() {
321 let var = enum_defvariant_list().unwrap()
322 .
323 .nth(92)
324 .unwrap();
325 }
326 ",
327 );
328 type_char_noop(
329 '.',
330 r"
331 fn source_impl() {
332 let var = enum_defvariant_list().unwrap()
333 <|>
334 .nth(92)
335 .unwrap();
336 }
337 ",
338 );
339 }
340
341 #[test]
342 fn dont_indent_freestanding_dot() {
343 type_char_noop(
344 '.',
345 r"
346 fn main() {
347 <|>
348 }
349 ",
350 );
351 type_char_noop(
352 '.',
353 r"
354 fn main() {
355 <|>
356 }
357 ",
358 );
359 }
360
361 #[test]
362 fn adds_space_after_return_type() {
363 type_char('>', "fn foo() -<|>{ 92 }", "fn foo() -> { 92 }")
364 }
365}