X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=crates%2Fide%2Fsrc%2Ftyping%2Fon_enter.rs;h=48c1713270b6c5c46c42deb7553ca98ac66384d3;hb=342c3c42bb282cae05e9d8740c2f724ea8014150;hp=63cd51b69c5682df85bdb0f8a8426a002e82fce2;hpb=42e00032c6ba07eaf2f7d5886c60133c65d84cf5;p=rust.git diff --git a/crates/ide/src/typing/on_enter.rs b/crates/ide/src/typing/on_enter.rs index 63cd51b69c5..48c1713270b 100644 --- a/crates/ide/src/typing/on_enter.rs +++ b/crates/ide/src/typing/on_enter.rs @@ -4,12 +4,13 @@ use ide_db::base_db::{FilePosition, SourceDatabase}; use ide_db::RootDatabase; use syntax::{ - ast::{self, AstToken}, + algo::find_node_at_offset, + ast::{self, edit::IndentLevel, AstToken}, AstNode, SmolStr, SourceFile, SyntaxKind::*, - SyntaxToken, TextRange, TextSize, TokenAtOffset, + SyntaxNode, SyntaxToken, TextRange, TextSize, TokenAtOffset, }; -use test_utils::mark; + use text_edit::TextEdit; // Feature: On Enter @@ -18,9 +19,15 @@ // // - kbd:[Enter] inside triple-slash comments automatically inserts `///` // - kbd:[Enter] in the middle or after a trailing space in `//` inserts `//` +// - kbd:[Enter] inside `//!` doc comments automatically inserts `//!` +// - kbd:[Enter] after `{` indents contents and closing `}` of single-line block // // This action needs to be assigned to shortcut explicitly. // +// Note that, depending on the other installed extensions, this feature can visibly slow down typing. +// Similarly, if rust-analyzer crashes or stops responding, `Enter` might not work. +// In that case, you can still press `Shift-Enter` to insert a newline. +// // VS Code:: // // Add the following to `keybindings.json`: @@ -32,49 +39,130 @@ // "when": "editorTextFocus && !suggestWidgetVisible && editorLangId == rust" // } // ---- +// +// When using the Vim plugin: +// [source,json] +// ---- +// { +// "key": "Enter", +// "command": "rust-analyzer.onEnter", +// "when": "editorTextFocus && !suggestWidgetVisible && editorLangId == rust && vim.mode == 'Insert'" +// } +// ---- +// +// image::https://user-images.githubusercontent.com/48062697/113065578-04c21800-91b1-11eb-82b8-22b8c481e645.gif[] 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)?; + let token = file.syntax().token_at_offset(position.offset).left_biased()?; + + if let Some(comment) = ast::Comment::cast(token.clone()) { + return on_enter_in_comment(&comment, &file, position.offset); + } + + if token.kind() == L_CURLY { + // Typing enter after the `{` of a block expression, where the `}` is on the same line + if let Some(edit) = find_node_at_offset(file.syntax(), position.offset - TextSize::of('{')) + .and_then(|block| on_enter_in_block(block, position)) + { + cov_mark::hit!(indent_block_contents); + return Some(edit); + } + + // Typing enter after the `{` of a use tree list. + if let Some(edit) = find_node_at_offset(file.syntax(), position.offset - TextSize::of('{')) + .and_then(|list| on_enter_in_use_tree_list(list, position)) + { + cov_mark::hit!(indent_block_contents); + return Some(edit); + } + } + None +} + +fn on_enter_in_comment( + comment: &ast::Comment, + file: &ast::SourceFile, + offset: TextSize, +) -> Option { 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() + TextSize::of(prefix) { + if offset < comment_range.start() + TextSize::of(prefix) { return None; } let mut remove_trailing_whitespace = false; // Continuing single-line non-doc comments (like this one :) ) is annoying - if prefix == "//" && comment_range.end() == position.offset { + if prefix == "//" && comment_range.end() == offset { if comment.text().ends_with(' ') { - mark::hit!(continues_end_of_line_comment_with_space); + cov_mark::hit!(continues_end_of_line_comment_with_space); remove_trailing_whitespace = true; - } else if !followed_by_comment(&comment) { + } else if !followed_by_comment(comment) { return None; } } - let indent = node_indent(&file, comment.syntax())?; + let indent = node_indent(file, comment.syntax())?; let inserted = format!("\n{}{} $0", indent, prefix); let delete = if remove_trailing_whitespace { let trimmed_len = comment.text().trim_end().len() as u32; let trailing_whitespace_len = comment.text().len() as u32 - trimmed_len; - TextRange::new(position.offset - TextSize::from(trailing_whitespace_len), position.offset) + TextRange::new(offset - TextSize::from(trailing_whitespace_len), offset) } else { - TextRange::empty(position.offset) + TextRange::empty(offset) }; let edit = TextEdit::replace(delete, inserted); Some(edit) } +fn on_enter_in_block(block: ast::BlockExpr, position: FilePosition) -> Option { + let contents = block_contents(&block)?; + + if block.syntax().text().contains_char('\n') { + return None; + } + + let indent = IndentLevel::from_node(block.syntax()); + let mut edit = TextEdit::insert(position.offset, format!("\n{}$0", indent + 1)); + edit.union(TextEdit::insert(contents.text_range().end(), format!("\n{}", indent))).ok()?; + Some(edit) +} + +fn on_enter_in_use_tree_list(list: ast::UseTreeList, position: FilePosition) -> Option { + if list.syntax().text().contains_char('\n') { + return None; + } + + let indent = IndentLevel::from_node(list.syntax()); + let mut edit = TextEdit::insert(position.offset, format!("\n{}$0", indent + 1)); + edit.union(TextEdit::insert( + list.r_curly_token()?.text_range().start(), + format!("\n{}", indent), + )) + .ok()?; + Some(edit) +} + +fn block_contents(block: &ast::BlockExpr) -> Option { + let mut node = block.tail_expr().map(|e| e.syntax().clone()); + + for stmt in block.statements() { + if node.is_some() { + // More than 1 node in the block + return None; + } + + node = Some(stmt.syntax().clone()); + } + + node +} + fn followed_by_comment(comment: &ast::Comment) -> bool { let ws = match comment.syntax().next_token().and_then(ast::Whitespace::cast) { Some(it) => it, @@ -109,12 +197,12 @@ fn node_indent(file: &SourceFile, token: &SyntaxToken) -> Option { #[cfg(test)] mod tests { use stdx::trim_indent; - use test_utils::{assert_eq_text, mark}; + use test_utils::assert_eq_text; use crate::fixture; fn apply_on_enter(before: &str) -> Option { - let (analysis, position) = fixture::position(&before); + let (analysis, position) = fixture::position(before); let result = analysis.on_enter(position).unwrap()?; let mut actual = analysis.file_text(position.file_id).unwrap().to_string(); @@ -184,6 +272,25 @@ fn does_not_continue_before_doc_comment() { do_check_noop(r"$0//! docz"); } + #[test] + fn continues_another_doc_comment() { + do_check( + r#" +fn main() { + //! Documentation for$0 on enter + let x = 1 + 1; +} +"#, + r#" +fn main() { + //! Documentation for + //! $0 on enter + let x = 1 + 1; +} +"#, + ); + } + #[test] fn continues_code_comment_in_the_middle_of_line() { do_check( @@ -238,7 +345,7 @@ fn main() { #[test] fn continues_end_of_line_comment_with_space() { - mark::check!(continues_end_of_line_comment_with_space); + cov_mark::check!(continues_end_of_line_comment_with_space); do_check( r#" fn main() { @@ -274,4 +381,236 @@ fn main() { ", ); } + + #[test] + fn indents_fn_body_block() { + cov_mark::check!(indent_block_contents); + do_check( + r#" +fn f() {$0()} + "#, + r#" +fn f() { + $0() +} + "#, + ); + } + + #[test] + fn indents_block_expr() { + do_check( + r#" +fn f() { + let x = {$0()}; +} + "#, + r#" +fn f() { + let x = { + $0() + }; +} + "#, + ); + } + + #[test] + fn indents_match_arm() { + do_check( + r#" +fn f() { + match 6 { + 1 => {$0f()}, + _ => (), + } +} + "#, + r#" +fn f() { + match 6 { + 1 => { + $0f() + }, + _ => (), + } +} + "#, + ); + } + + #[test] + fn indents_block_with_statement() { + do_check( + r#" +fn f() {$0a = b} + "#, + r#" +fn f() { + $0a = b +} + "#, + ); + do_check( + r#" +fn f() {$0fn f() {}} + "#, + r#" +fn f() { + $0fn f() {} +} + "#, + ); + } + + #[test] + fn indents_nested_blocks() { + do_check( + r#" +fn f() {$0{}} + "#, + r#" +fn f() { + $0{} +} + "#, + ); + } + + #[test] + fn does_not_indent_empty_block() { + do_check_noop( + r#" +fn f() {$0} + "#, + ); + do_check_noop( + r#" +fn f() {{$0}} + "#, + ); + } + + #[test] + fn does_not_indent_block_with_too_much_content() { + do_check_noop( + r#" +fn f() {$0 a = b; ()} + "#, + ); + do_check_noop( + r#" +fn f() {$0 a = b; a = b; } + "#, + ); + } + + #[test] + fn does_not_indent_multiline_block() { + do_check_noop( + r#" +fn f() {$0 +} + "#, + ); + do_check_noop( + r#" +fn f() {$0 + +} + "#, + ); + } + + #[test] + fn indents_use_tree_list() { + do_check( + r#" +use crate::{$0}; + "#, + r#" +use crate::{ + $0 +}; + "#, + ); + do_check( + r#" +use crate::{$0Object, path::to::OtherThing}; + "#, + r#" +use crate::{ + $0Object, path::to::OtherThing +}; + "#, + ); + do_check( + r#" +use {crate::{$0Object, path::to::OtherThing}}; + "#, + r#" +use {crate::{ + $0Object, path::to::OtherThing +}}; + "#, + ); + do_check( + r#" +use { + crate::{$0Object, path::to::OtherThing} +}; + "#, + r#" +use { + crate::{ + $0Object, path::to::OtherThing + } +}; + "#, + ); + } + + #[test] + fn does_not_indent_use_tree_list_when_not_at_curly_brace() { + do_check_noop( + r#" +use path::{Thing$0}; + "#, + ); + } + + #[test] + fn does_not_indent_use_tree_list_without_curly_braces() { + do_check_noop( + r#" +use path::Thing$0; + "#, + ); + do_check_noop( + r#" +use path::$0Thing; + "#, + ); + do_check_noop( + r#" +use path::Thing$0}; + "#, + ); + do_check_noop( + r#" +use path::{$0Thing; + "#, + ); + } + + #[test] + fn does_not_indent_multiline_use_tree_list() { + do_check_noop( + r#" +use path::{$0 + Thing +}; + "#, + ); + } }