aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_ide_api/src/typing.rs228
-rw-r--r--crates/ra_lsp_server/src/caps.rs2
-rw-r--r--crates/ra_lsp_server/src/main_loop/handlers.rs1
3 files changed, 119 insertions, 112 deletions
diff --git a/crates/ra_ide_api/src/typing.rs b/crates/ra_ide_api/src/typing.rs
index 17d0f08a5..26a3111fd 100644
--- a/crates/ra_ide_api/src/typing.rs
+++ b/crates/ra_ide_api/src/typing.rs
@@ -81,7 +81,7 @@ fn node_indent(file: &SourceFile, token: &SyntaxToken) -> Option<SmolStr> {
81 Some(text[pos..].into()) 81 Some(text[pos..].into())
82} 82}
83 83
84pub(crate) const TRIGGER_CHARS: &str = ".="; 84pub(crate) const TRIGGER_CHARS: &str = ".=>";
85 85
86pub(crate) fn on_char_typed( 86pub(crate) fn on_char_typed(
87 db: &RootDatabase, 87 db: &RootDatabase,
@@ -100,10 +100,12 @@ fn on_char_typed_inner(
100 offset: TextUnit, 100 offset: TextUnit,
101 char_typed: char, 101 char_typed: char,
102) -> Option<SingleFileChange> { 102) -> Option<SingleFileChange> {
103 assert!(TRIGGER_CHARS.contains(char_typed));
103 match char_typed { 104 match char_typed {
104 '.' => on_dot_typed(file, offset), 105 '.' => on_dot_typed(file, offset),
105 '=' => on_eq_typed(file, offset), 106 '=' => on_eq_typed(file, offset),
106 _ => None, 107 '>' => on_arrow_typed(file, offset),
108 _ => unreachable!(),
107 } 109 }
108} 110}
109 111
@@ -169,6 +171,25 @@ fn on_dot_typed(file: &SourceFile, offset: TextUnit) -> Option<SingleFileChange>
169 }) 171 })
170} 172}
171 173
174/// Adds a space after an arrow when `fn foo() { ... }` is turned into `fn foo() -> { ... }`
175fn on_arrow_typed(file: &SourceFile, offset: TextUnit) -> Option<SingleFileChange> {
176 let file_text = file.syntax().text();
177 assert_eq!(file_text.char_at(offset), Some('>'));
178 let after_arrow = offset + TextUnit::of_char('>');
179 if file_text.char_at(after_arrow) != Some('{') {
180 return None;
181 }
182 if find_node_at_offset::<ast::RetType>(file.syntax(), offset).is_none() {
183 return None;
184 }
185
186 Some(SingleFileChange {
187 label: "add space after return type".to_string(),
188 edit: TextEdit::insert(after_arrow, " ".to_string()),
189 cursor_position: Some(after_arrow),
190 })
191}
192
172#[cfg(test)] 193#[cfg(test)]
173mod tests { 194mod tests {
174 use test_utils::{add_cursor, assert_eq_text, extract_offset}; 195 use test_utils::{add_cursor, assert_eq_text, extract_offset};
@@ -177,25 +198,84 @@ mod tests {
177 198
178 use super::*; 199 use super::*;
179 200
180 fn type_char(char_typed: char, before: &str, after: &str) { 201 #[test]
202 fn test_on_enter() {
203 fn apply_on_enter(before: &str) -> Option<String> {
204 let (offset, before) = extract_offset(before);
205 let (analysis, file_id) = single_file(&before);
206 let result = analysis.on_enter(FilePosition { offset, file_id }).unwrap()?;
207
208 assert_eq!(result.source_file_edits.len(), 1);
209 let actual = result.source_file_edits[0].edit.apply(&before);
210 let actual = add_cursor(&actual, result.cursor_position.unwrap().offset);
211 Some(actual)
212 }
213
214 fn do_check(before: &str, after: &str) {
215 let actual = apply_on_enter(before).unwrap();
216 assert_eq_text!(after, &actual);
217 }
218
219 fn do_check_noop(text: &str) {
220 assert!(apply_on_enter(text).is_none())
221 }
222
223 do_check(
224 r"
225/// Some docs<|>
226fn foo() {
227}
228",
229 r"
230/// Some docs
231/// <|>
232fn foo() {
233}
234",
235 );
236 do_check(
237 r"
238impl S {
239 /// Some<|> docs.
240 fn foo() {}
241}
242",
243 r"
244impl S {
245 /// Some
246 /// <|> docs.
247 fn foo() {}
248}
249",
250 );
251 do_check_noop(r"<|>//! docz");
252 }
253
254 fn do_type_char(char_typed: char, before: &str) -> Option<(String, SingleFileChange)> {
181 let (offset, before) = extract_offset(before); 255 let (offset, before) = extract_offset(before);
182 let edit = TextEdit::insert(offset, char_typed.to_string()); 256 let edit = TextEdit::insert(offset, char_typed.to_string());
183 let before = edit.apply(&before); 257 let before = edit.apply(&before);
184 let parse = SourceFile::parse(&before); 258 let parse = SourceFile::parse(&before);
185 if let Some(result) = on_char_typed_inner(&parse.tree(), offset, char_typed) { 259 on_char_typed_inner(&parse.tree(), offset, char_typed)
186 let actual = result.edit.apply(&before); 260 .map(|it| (it.edit.apply(&before), it))
187 assert_eq_text!(after, &actual);
188 } else {
189 assert_eq_text!(&before, after)
190 };
191 } 261 }
192 262
193 fn type_eq(before: &str, after: &str) { 263 fn type_char(char_typed: char, before: &str, after: &str) {
194 type_char('=', before, after); 264 let (actual, file_change) = do_type_char(char_typed, before)
265 .expect(&format!("typing `{}` did nothing", char_typed));
266
267 if after.contains("<|>") {
268 let (offset, after) = extract_offset(after);
269 assert_eq_text!(&after, &actual);
270 assert_eq!(file_change.cursor_position, Some(offset))
271 } else {
272 assert_eq_text!(after, &actual);
273 }
195 } 274 }
196 275
197 fn type_dot(before: &str, after: &str) { 276 fn type_char_noop(char_typed: char, before: &str) {
198 type_char('.', before, after); 277 let file_change = do_type_char(char_typed, before);
278 assert!(file_change.is_none())
199 } 279 }
200 280
201 #[test] 281 #[test]
@@ -209,7 +289,8 @@ mod tests {
209 // let foo =; 289 // let foo =;
210 // } 290 // }
211 // "); 291 // ");
212 type_eq( 292 type_char(
293 '=',
213 r" 294 r"
214fn foo() { 295fn foo() {
215 let foo <|> 1 + 1 296 let foo <|> 1 + 1
@@ -236,7 +317,8 @@ fn foo() {
236 317
237 #[test] 318 #[test]
238 fn indents_new_chain_call() { 319 fn indents_new_chain_call() {
239 type_dot( 320 type_char(
321 '.',
240 r" 322 r"
241 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { 323 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
242 self.child_impl(db, name) 324 self.child_impl(db, name)
@@ -250,25 +332,21 @@ fn foo() {
250 } 332 }
251 ", 333 ",
252 ); 334 );
253 type_dot( 335 type_char_noop(
336 '.',
254 r" 337 r"
255 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { 338 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
256 self.child_impl(db, name) 339 self.child_impl(db, name)
257 <|> 340 <|>
258 } 341 }
259 ", 342 ",
260 r"
261 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
262 self.child_impl(db, name)
263 .
264 }
265 ",
266 ) 343 )
267 } 344 }
268 345
269 #[test] 346 #[test]
270 fn indents_new_chain_call_with_semi() { 347 fn indents_new_chain_call_with_semi() {
271 type_dot( 348 type_char(
349 '.',
272 r" 350 r"
273 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { 351 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
274 self.child_impl(db, name) 352 self.child_impl(db, name)
@@ -282,25 +360,21 @@ fn foo() {
282 } 360 }
283 ", 361 ",
284 ); 362 );
285 type_dot( 363 type_char_noop(
364 '.',
286 r" 365 r"
287 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { 366 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
288 self.child_impl(db, name) 367 self.child_impl(db, name)
289 <|>; 368 <|>;
290 } 369 }
291 ", 370 ",
292 r"
293 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
294 self.child_impl(db, name)
295 .;
296 }
297 ",
298 ) 371 )
299 } 372 }
300 373
301 #[test] 374 #[test]
302 fn indents_continued_chain_call() { 375 fn indents_continued_chain_call() {
303 type_dot( 376 type_char(
377 '.',
304 r" 378 r"
305 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { 379 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
306 self.child_impl(db, name) 380 self.child_impl(db, name)
@@ -316,7 +390,8 @@ fn foo() {
316 } 390 }
317 ", 391 ",
318 ); 392 );
319 type_dot( 393 type_char_noop(
394 '.',
320 r" 395 r"
321 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { 396 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
322 self.child_impl(db, name) 397 self.child_impl(db, name)
@@ -324,19 +399,13 @@ fn foo() {
324 <|> 399 <|>
325 } 400 }
326 ", 401 ",
327 r"
328 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
329 self.child_impl(db, name)
330 .first()
331 .
332 }
333 ",
334 ); 402 );
335 } 403 }
336 404
337 #[test] 405 #[test]
338 fn indents_middle_of_chain_call() { 406 fn indents_middle_of_chain_call() {
339 type_dot( 407 type_char(
408 '.',
340 r" 409 r"
341 fn source_impl() { 410 fn source_impl() {
342 let var = enum_defvariant_list().unwrap() 411 let var = enum_defvariant_list().unwrap()
@@ -354,7 +423,8 @@ fn foo() {
354 } 423 }
355 ", 424 ",
356 ); 425 );
357 type_dot( 426 type_char_noop(
427 '.',
358 r" 428 r"
359 fn source_impl() { 429 fn source_impl() {
360 let var = enum_defvariant_list().unwrap() 430 let var = enum_defvariant_list().unwrap()
@@ -363,95 +433,31 @@ fn foo() {
363 .unwrap(); 433 .unwrap();
364 } 434 }
365 ", 435 ",
366 r"
367 fn source_impl() {
368 let var = enum_defvariant_list().unwrap()
369 .
370 .nth(92)
371 .unwrap();
372 }
373 ",
374 ); 436 );
375 } 437 }
376 438
377 #[test] 439 #[test]
378 fn dont_indent_freestanding_dot() { 440 fn dont_indent_freestanding_dot() {
379 type_dot( 441 type_char_noop(
442 '.',
380 r" 443 r"
381 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { 444 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
382 <|> 445 <|>
383 } 446 }
384 ", 447 ",
385 r"
386 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
387 .
388 }
389 ",
390 ); 448 );
391 type_dot( 449 type_char_noop(
450 '.',
392 r" 451 r"
393 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { 452 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
394 <|> 453 <|>
395 } 454 }
396 ", 455 ",
397 r"
398 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
399 .
400 }
401 ",
402 ); 456 );
403 } 457 }
404 458
405 #[test] 459 #[test]
406 fn test_on_enter() { 460 fn adds_space_after_return_type() {
407 fn apply_on_enter(before: &str) -> Option<String> { 461 type_char('>', "fn foo() -<|>{ 92 }", "fn foo() -><|> { 92 }")
408 let (offset, before) = extract_offset(before);
409 let (analysis, file_id) = single_file(&before);
410 let result = analysis.on_enter(FilePosition { offset, file_id }).unwrap()?;
411
412 assert_eq!(result.source_file_edits.len(), 1);
413 let actual = result.source_file_edits[0].edit.apply(&before);
414 let actual = add_cursor(&actual, result.cursor_position.unwrap().offset);
415 Some(actual)
416 }
417
418 fn do_check(before: &str, after: &str) {
419 let actual = apply_on_enter(before).unwrap();
420 assert_eq_text!(after, &actual);
421 }
422
423 fn do_check_noop(text: &str) {
424 assert!(apply_on_enter(text).is_none())
425 }
426
427 do_check(
428 r"
429/// Some docs<|>
430fn foo() {
431}
432",
433 r"
434/// Some docs
435/// <|>
436fn foo() {
437}
438",
439 );
440 do_check(
441 r"
442impl S {
443 /// Some<|> docs.
444 fn foo() {}
445}
446",
447 r"
448impl S {
449 /// Some
450 /// <|> docs.
451 fn foo() {}
452}
453",
454 );
455 do_check_noop(r"<|>//! docz");
456 } 462 }
457} 463}
diff --git a/crates/ra_lsp_server/src/caps.rs b/crates/ra_lsp_server/src/caps.rs
index 30bcbd7a8..eea0965ed 100644
--- a/crates/ra_lsp_server/src/caps.rs
+++ b/crates/ra_lsp_server/src/caps.rs
@@ -38,7 +38,7 @@ pub fn server_capabilities() -> ServerCapabilities {
38 document_range_formatting_provider: None, 38 document_range_formatting_provider: None,
39 document_on_type_formatting_provider: Some(DocumentOnTypeFormattingOptions { 39 document_on_type_formatting_provider: Some(DocumentOnTypeFormattingOptions {
40 first_trigger_character: "=".to_string(), 40 first_trigger_character: "=".to_string(),
41 more_trigger_character: Some(vec![".".to_string()]), 41 more_trigger_character: Some(vec![".".to_string(), ">".to_string()]),
42 }), 42 }),
43 selection_range_provider: Some(GenericCapability::default()), 43 selection_range_provider: Some(GenericCapability::default()),
44 folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)), 44 folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)),
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs
index 530c4d8b6..6f1e59b4b 100644
--- a/crates/ra_lsp_server/src/main_loop/handlers.rs
+++ b/crates/ra_lsp_server/src/main_loop/handlers.rs
@@ -132,6 +132,7 @@ pub fn handle_on_enter(
132 } 132 }
133} 133}
134 134
135// Don't forget to add new trigger characters to `ServerCapabilities` in `caps.rs`.
135pub fn handle_on_type_formatting( 136pub fn handle_on_type_formatting(
136 world: WorldSnapshot, 137 world: WorldSnapshot,
137 params: req::DocumentOnTypeFormattingParams, 138 params: req::DocumentOnTypeFormattingParams,