From 53e3bee0cfcd7541b5ee882ab4b47c9dde9780b8 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Fri, 25 Oct 2019 12:16:56 +0300 Subject: insert space after `->` --- crates/ra_ide_api/src/typing.rs | 228 +++++++++++++++++++++------------------- 1 file changed, 117 insertions(+), 111 deletions(-) (limited to 'crates/ra_ide_api/src') 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 { Some(text[pos..].into()) } -pub(crate) const TRIGGER_CHARS: &str = ".="; +pub(crate) const TRIGGER_CHARS: &str = ".=>"; pub(crate) fn on_char_typed( db: &RootDatabase, @@ -100,10 +100,12 @@ fn on_char_typed_inner( offset: TextUnit, char_typed: char, ) -> Option { + assert!(TRIGGER_CHARS.contains(char_typed)); match char_typed { '.' => on_dot_typed(file, offset), '=' => on_eq_typed(file, offset), - _ => None, + '>' => on_arrow_typed(file, offset), + _ => unreachable!(), } } @@ -169,6 +171,25 @@ fn on_dot_typed(file: &SourceFile, offset: TextUnit) -> Option }) } +/// Adds a space after an arrow when `fn foo() { ... }` is turned into `fn foo() -> { ... }` +fn on_arrow_typed(file: &SourceFile, offset: TextUnit) -> Option { + let file_text = file.syntax().text(); + assert_eq!(file_text.char_at(offset), Some('>')); + let after_arrow = offset + TextUnit::of_char('>'); + if file_text.char_at(after_arrow) != Some('{') { + return None; + } + if find_node_at_offset::(file.syntax(), offset).is_none() { + return None; + } + + Some(SingleFileChange { + label: "add space after return type".to_string(), + edit: TextEdit::insert(after_arrow, " ".to_string()), + cursor_position: Some(after_arrow), + }) +} + #[cfg(test)] mod tests { use test_utils::{add_cursor, assert_eq_text, extract_offset}; @@ -177,25 +198,84 @@ mod tests { use super::*; - fn type_char(char_typed: char, before: &str, after: &str) { + #[test] + fn test_on_enter() { + fn apply_on_enter(before: &str) -> Option { + let (offset, before) = extract_offset(before); + let (analysis, file_id) = single_file(&before); + let result = analysis.on_enter(FilePosition { offset, file_id }).unwrap()?; + + assert_eq!(result.source_file_edits.len(), 1); + let actual = result.source_file_edits[0].edit.apply(&before); + let actual = add_cursor(&actual, result.cursor_position.unwrap().offset); + Some(actual) + } + + fn do_check(before: &str, after: &str) { + let actual = apply_on_enter(before).unwrap(); + assert_eq_text!(after, &actual); + } + + fn do_check_noop(text: &str) { + assert!(apply_on_enter(text).is_none()) + } + + do_check( + r" +/// Some docs<|> +fn foo() { +} +", + r" +/// Some docs +/// <|> +fn foo() { +} +", + ); + do_check( + r" +impl S { + /// Some<|> docs. + fn foo() {} +} +", + r" +impl S { + /// Some + /// <|> docs. + fn foo() {} +} +", + ); + do_check_noop(r"<|>//! docz"); + } + + fn do_type_char(char_typed: char, before: &str) -> Option<(String, SingleFileChange)> { let (offset, before) = extract_offset(before); let edit = TextEdit::insert(offset, char_typed.to_string()); let before = edit.apply(&before); let parse = SourceFile::parse(&before); - if let Some(result) = on_char_typed_inner(&parse.tree(), offset, char_typed) { - let actual = result.edit.apply(&before); - assert_eq_text!(after, &actual); - } else { - assert_eq_text!(&before, after) - }; + on_char_typed_inner(&parse.tree(), offset, char_typed) + .map(|it| (it.edit.apply(&before), it)) } - fn type_eq(before: &str, after: &str) { - type_char('=', before, after); + fn type_char(char_typed: char, before: &str, after: &str) { + let (actual, file_change) = do_type_char(char_typed, before) + .expect(&format!("typing `{}` did nothing", char_typed)); + + if after.contains("<|>") { + let (offset, after) = extract_offset(after); + assert_eq_text!(&after, &actual); + assert_eq!(file_change.cursor_position, Some(offset)) + } else { + assert_eq_text!(after, &actual); + } } - fn type_dot(before: &str, after: &str) { - type_char('.', before, after); + fn type_char_noop(char_typed: char, before: &str) { + let file_change = do_type_char(char_typed, before); + assert!(file_change.is_none()) } #[test] @@ -209,7 +289,8 @@ mod tests { // let foo =; // } // "); - type_eq( + type_char( + '=', r" fn foo() { let foo <|> 1 + 1 @@ -236,7 +317,8 @@ fn foo() { #[test] fn indents_new_chain_call() { - type_dot( + type_char( + '.', r" pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { self.child_impl(db, name) @@ -250,25 +332,21 @@ fn foo() { } ", ); - type_dot( + type_char_noop( + '.', r" pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { self.child_impl(db, name) <|> } ", - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - self.child_impl(db, name) - . - } - ", ) } #[test] fn indents_new_chain_call_with_semi() { - type_dot( + type_char( + '.', r" pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { self.child_impl(db, name) @@ -282,25 +360,21 @@ fn foo() { } ", ); - type_dot( + type_char_noop( + '.', r" pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { self.child_impl(db, name) <|>; } ", - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - self.child_impl(db, name) - .; - } - ", ) } #[test] fn indents_continued_chain_call() { - type_dot( + type_char( + '.', r" pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { self.child_impl(db, name) @@ -316,7 +390,8 @@ fn foo() { } ", ); - type_dot( + type_char_noop( + '.', r" pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { self.child_impl(db, name) @@ -324,19 +399,13 @@ fn foo() { <|> } ", - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - self.child_impl(db, name) - .first() - . - } - ", ); } #[test] fn indents_middle_of_chain_call() { - type_dot( + type_char( + '.', r" fn source_impl() { let var = enum_defvariant_list().unwrap() @@ -354,7 +423,8 @@ fn foo() { } ", ); - type_dot( + type_char_noop( + '.', r" fn source_impl() { let var = enum_defvariant_list().unwrap() @@ -363,95 +433,31 @@ fn foo() { .unwrap(); } ", - r" - fn source_impl() { - let var = enum_defvariant_list().unwrap() - . - .nth(92) - .unwrap(); - } - ", ); } #[test] fn dont_indent_freestanding_dot() { - type_dot( + type_char_noop( + '.', r" pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { <|> } ", - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - . - } - ", ); - type_dot( + type_char_noop( + '.', r" pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { <|> } ", - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - . - } - ", ); } #[test] - fn test_on_enter() { - fn apply_on_enter(before: &str) -> Option { - let (offset, before) = extract_offset(before); - let (analysis, file_id) = single_file(&before); - let result = analysis.on_enter(FilePosition { offset, file_id }).unwrap()?; - - assert_eq!(result.source_file_edits.len(), 1); - let actual = result.source_file_edits[0].edit.apply(&before); - let actual = add_cursor(&actual, result.cursor_position.unwrap().offset); - Some(actual) - } - - fn do_check(before: &str, after: &str) { - let actual = apply_on_enter(before).unwrap(); - assert_eq_text!(after, &actual); - } - - fn do_check_noop(text: &str) { - assert!(apply_on_enter(text).is_none()) - } - - do_check( - r" -/// Some docs<|> -fn foo() { -} -", - r" -/// Some docs -/// <|> -fn foo() { -} -", - ); - do_check( - r" -impl S { - /// Some<|> docs. - fn foo() {} -} -", - r" -impl S { - /// Some - /// <|> docs. - fn foo() {} -} -", - ); - do_check_noop(r"<|>//! docz"); + fn adds_space_after_return_type() { + type_char('>', "fn foo() -<|>{ 92 }", "fn foo() -><|> { 92 }") } } -- cgit v1.2.3