From 52da9e90a6002eb712175f7c8d76a472cf966d8e Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 11 Mar 2020 12:46:36 +0100 Subject: Move on enter to a separate module --- crates/ra_ide/src/typing.rs | 160 ++------------------------------ crates/ra_ide/src/typing/on_enter.rs | 171 +++++++++++++++++++++++++++++++++++ 2 files changed, 177 insertions(+), 154 deletions(-) create mode 100644 crates/ra_ide/src/typing/on_enter.rs diff --git a/crates/ra_ide/src/typing.rs b/crates/ra_ide/src/typing.rs index 7f1b9150f..53c65f8bc 100644 --- a/crates/ra_ide/src/typing.rs +++ b/crates/ra_ide/src/typing.rs @@ -13,77 +13,21 @@ //! Language server executes such typing assists synchronously. That is, they //! block user's typing and should be pretty fast for this reason! +mod on_enter; + use ra_db::{FilePosition, SourceDatabase}; use ra_fmt::leading_indent; use ra_ide_db::RootDatabase; use ra_syntax::{ algo::find_node_at_offset, ast::{self, AstToken}, - AstNode, SmolStr, SourceFile, - SyntaxKind::*, - SyntaxToken, TextRange, TextUnit, TokenAtOffset, + AstNode, SourceFile, TextRange, TextUnit, }; use ra_text_edit::TextEdit; -use crate::{source_change::SingleFileChange, SourceChange, SourceFileEdit}; - -pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option { - let parse = db.parse(position.file_id); - let file = parse.tree(); - let comment = file - .syntax() - .token_at_offset(position.offset) - .left_biased() - .and_then(ast::Comment::cast)?; - - if comment.kind().shape.is_block() { - return None; - } - - let prefix = comment.prefix(); - let comment_range = comment.syntax().text_range(); - if position.offset < comment_range.start() + TextUnit::of_str(prefix) { - return None; - } - - // Continuing non-doc line comments (like this one :) ) is annoying - if prefix == "//" && comment_range.end() == position.offset { - return None; - } - - let indent = node_indent(&file, comment.syntax())?; - let inserted = format!("\n{}{} ", indent, prefix); - let cursor_position = position.offset + TextUnit::of_str(&inserted); - let edit = TextEdit::insert(position.offset, inserted); +use crate::{source_change::SingleFileChange, SourceChange}; - Some( - SourceChange::source_file_edit( - "on enter", - SourceFileEdit { edit, file_id: position.file_id }, - ) - .with_cursor(FilePosition { offset: cursor_position, file_id: position.file_id }), - ) -} - -fn node_indent(file: &SourceFile, token: &SyntaxToken) -> Option { - let ws = match file.syntax().token_at_offset(token.text_range().start()) { - TokenAtOffset::Between(l, r) => { - assert!(r == *token); - l - } - TokenAtOffset::Single(n) => { - assert!(n == *token); - return Some("".into()); - } - TokenAtOffset::None => unreachable!(), - }; - if ws.kind() != WHITESPACE { - return None; - } - let text = ws.text(); - let pos = text.rfind('\n').map(|it| it + 1).unwrap_or(0); - Some(text[pos..].into()) -} +pub(crate) use on_enter::on_enter; pub(crate) const TRIGGER_CHARS: &str = ".=>"; @@ -196,102 +140,10 @@ fn on_arrow_typed(file: &SourceFile, offset: TextUnit) -> Option 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( - r" -fn main() { - // Fix<|> me - let x = 1 + 1; -} -", - r" -fn main() { - // Fix - // <|> me - let x = 1 + 1; -} -", - ); - do_check( - r" -///<|> Some docs -fn foo() { -} -", - r" -/// -/// <|> Some docs -fn foo() { -} -", - ); - do_check_noop( - r" -fn main() { - // Fix me<|> - let x = 1 + 1; -} -", - ); - - 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()); diff --git a/crates/ra_ide/src/typing/on_enter.rs b/crates/ra_ide/src/typing/on_enter.rs new file mode 100644 index 000000000..359794f67 --- /dev/null +++ b/crates/ra_ide/src/typing/on_enter.rs @@ -0,0 +1,171 @@ +//! Handles the `Enter` key press. At the momently, this only continues +//! comments, but should handle indent some time in the future as well. + +use ra_db::{FilePosition, SourceDatabase}; +use ra_ide_db::RootDatabase; +use ra_syntax::{ + ast::{self, AstToken}, + AstNode, SmolStr, SourceFile, + SyntaxKind::*, + SyntaxToken, TextUnit, TokenAtOffset, +}; +use ra_text_edit::TextEdit; + +use crate::{SourceChange, SourceFileEdit}; + +pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option { + let parse = db.parse(position.file_id); + let file = parse.tree(); + let comment = file + .syntax() + .token_at_offset(position.offset) + .left_biased() + .and_then(ast::Comment::cast)?; + + if comment.kind().shape.is_block() { + return None; + } + + let prefix = comment.prefix(); + let comment_range = comment.syntax().text_range(); + if position.offset < comment_range.start() + TextUnit::of_str(prefix) { + return None; + } + + // Continuing non-doc line comments (like this one :) ) is annoying + if prefix == "//" && comment_range.end() == position.offset { + return None; + } + + let indent = node_indent(&file, comment.syntax())?; + let inserted = format!("\n{}{} ", indent, prefix); + let cursor_position = position.offset + TextUnit::of_str(&inserted); + let edit = TextEdit::insert(position.offset, inserted); + + Some( + SourceChange::source_file_edit( + "on enter", + SourceFileEdit { edit, file_id: position.file_id }, + ) + .with_cursor(FilePosition { offset: cursor_position, file_id: position.file_id }), + ) +} + +fn node_indent(file: &SourceFile, token: &SyntaxToken) -> Option { + let ws = match file.syntax().token_at_offset(token.text_range().start()) { + TokenAtOffset::Between(l, r) => { + assert!(r == *token); + l + } + TokenAtOffset::Single(n) => { + assert!(n == *token); + return Some("".into()); + } + TokenAtOffset::None => unreachable!(), + }; + if ws.kind() != WHITESPACE { + return None; + } + let text = ws.text(); + let pos = text.rfind('\n').map(|it| it + 1).unwrap_or(0); + Some(text[pos..].into()) +} + +#[cfg(test)] +mod tests { + use test_utils::{add_cursor, assert_eq_text, extract_offset}; + + use crate::mock_analysis::single_file; + + use super::*; + + 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(ra_fixture_before: &str, ra_fixture_after: &str) { + let actual = apply_on_enter(ra_fixture_before).unwrap(); + assert_eq_text!(ra_fixture_after, &actual); + } + + fn do_check_noop(ra_fixture_text: &str) { + assert!(apply_on_enter(ra_fixture_text).is_none()) + } + + #[test] + fn test_on_enter() { + 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( + r" +fn main() { + // Fix<|> me + let x = 1 + 1; +} +", + r" +fn main() { + // Fix + // <|> me + let x = 1 + 1; +} +", + ); + do_check( + r" +///<|> Some docs +fn foo() { +} +", + r" +/// +/// <|> Some docs +fn foo() { +} +", + ); + do_check_noop( + r" +fn main() { + // Fix me<|> + let x = 1 + 1; +} +", + ); + + do_check_noop(r"<|>//! docz"); + } +} -- cgit v1.2.3 From 90fe534f036b69c220c9db537e736e77508c4d31 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 11 Mar 2020 12:49:17 +0100 Subject: Split on enter tests --- crates/ra_ide/src/typing/on_enter.rs | 43 +++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/crates/ra_ide/src/typing/on_enter.rs b/crates/ra_ide/src/typing/on_enter.rs index 359794f67..7c979694d 100644 --- a/crates/ra_ide/src/typing/on_enter.rs +++ b/crates/ra_ide/src/typing/on_enter.rs @@ -100,7 +100,7 @@ mod tests { } #[test] - fn test_on_enter() { + fn continues_doc_comment() { do_check( r" /// Some docs<|> @@ -114,6 +114,7 @@ fn foo() { } ", ); + do_check( r" impl S { @@ -129,34 +130,48 @@ impl S { } ", ); + do_check( r" -fn main() { - // Fix<|> me - let x = 1 + 1; +///<|> Some docs +fn foo() { } ", r" -fn main() { - // Fix - // <|> me - let x = 1 + 1; +/// +/// <|> Some docs +fn foo() { } ", ); + } + + #[test] + fn does_not_continue_before_doc_comment() { + do_check_noop(r"<|>//! docz"); + } + + #[test] + fn continues_code_comment_in_the_middle() { do_check( r" -///<|> Some docs -fn foo() { +fn main() { + // Fix<|> me + let x = 1 + 1; } ", r" -/// -/// <|> Some docs -fn foo() { +fn main() { + // Fix + // <|> me + let x = 1 + 1; } ", ); + } + + #[test] + fn does_not_continue_end_of_code_comment() { do_check_noop( r" fn main() { @@ -165,7 +180,5 @@ fn main() { } ", ); - - do_check_noop(r"<|>//! docz"); } } -- cgit v1.2.3 From 85c30b1915314487515f69f32dfe5e1a7301ab95 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 11 Mar 2020 12:56:12 +0100 Subject: Continue multiline non-doc comment blocks --- crates/ra_ide/src/typing/on_enter.rs | 38 +++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/crates/ra_ide/src/typing/on_enter.rs b/crates/ra_ide/src/typing/on_enter.rs index 7c979694d..6bcf2d72b 100644 --- a/crates/ra_ide/src/typing/on_enter.rs +++ b/crates/ra_ide/src/typing/on_enter.rs @@ -32,8 +32,8 @@ pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option Option bool { + let ws = match comment.syntax().next_token().and_then(ast::Whitespace::cast) { + Some(it) => it, + None => return false, + }; + if ws.spans_multiple_lines() { + return false; + } + ws.syntax().next_token().and_then(ast::Comment::cast).is_some() +} + fn node_indent(file: &SourceFile, token: &SyntaxToken) -> Option { let ws = match file.syntax().token_at_offset(token.text_range().start()) { TokenAtOffset::Between(l, r) => { @@ -152,7 +163,7 @@ fn foo() { } #[test] - fn continues_code_comment_in_the_middle() { + fn continues_code_comment_in_the_middle_of_line() { do_check( r" fn main() { @@ -170,6 +181,27 @@ fn main() { ); } + #[test] + fn continues_code_comment_in_the_middle_several_lines() { + do_check( + r" +fn main() { + // Fix<|> + // me + let x = 1 + 1; +} +", + r" +fn main() { + // Fix + // <|> + // me + let x = 1 + 1; +} +", + ); + } + #[test] fn does_not_continue_end_of_code_comment() { do_check_noop( -- cgit v1.2.3