diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 82dbcde4c0..b3b8deb61f 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -409,7 +409,7 @@ impl Analysis { self.with_db(|db| typing::on_enter(db, position)) } - pub const SUPPORTED_TRIGGER_CHARS: &'static str = typing::TRIGGER_CHARS; + pub const SUPPORTED_TRIGGER_CHARS: &[char] = typing::TRIGGER_CHARS; /// Returns an edit which should be applied after a character was typed. /// @@ -421,7 +421,7 @@ impl Analysis { char_typed: char, ) -> Cancellable> { // Fast path to not even parse the file. - if !typing::TRIGGER_CHARS.contains(char_typed) { + if !typing::TRIGGER_CHARS.contains(&char_typed) { return Ok(None); } diff --git a/crates/ide/src/typing.rs b/crates/ide/src/typing.rs index 4df7e25223..ed55ac5bf0 100644 --- a/crates/ide/src/typing.rs +++ b/crates/ide/src/typing.rs @@ -15,6 +15,7 @@ mod on_enter; +use either::Either; use hir::EditionedFileId; use ide_db::{FilePosition, RootDatabase, base_db::RootQueryDb}; use span::Edition; @@ -33,7 +34,7 @@ use crate::SourceChange; pub(crate) use on_enter::on_enter; // Don't forget to add new trigger characters to `server_capabilities` in `caps.rs`. -pub(crate) const TRIGGER_CHARS: &str = ".=<>{(|"; +pub(crate) const TRIGGER_CHARS: &[char] = &['.', '=', '<', '>', '{', '(', '|', '+']; struct ExtendedTextEdit { edit: TextEdit, @@ -66,7 +67,7 @@ pub(crate) fn on_char_typed( position: FilePosition, char_typed: char, ) -> Option { - if !stdx::always!(TRIGGER_CHARS.contains(char_typed)) { + if !TRIGGER_CHARS.contains(&char_typed) { return None; } // FIXME: We need to figure out the edition of the file here, but that means hitting the @@ -101,6 +102,7 @@ fn on_char_typed_( '>' => on_right_angle_typed(&file.tree(), offset), '{' | '(' | '<' => on_opening_delimiter_typed(file, offset, char_typed, edition), '|' => on_pipe_typed(&file.tree(), offset), + '+' => on_plus_typed(&file.tree(), offset), _ => None, } .map(conv) @@ -402,6 +404,28 @@ fn on_pipe_typed(file: &SourceFile, offset: TextSize) -> Option { Some(TextEdit::insert(after_lpipe, "|".to_owned())) } +fn on_plus_typed(file: &SourceFile, offset: TextSize) -> Option { + let plus_token = file.syntax().token_at_offset(offset).right_biased()?; + if plus_token.kind() != SyntaxKind::PLUS { + return None; + } + let mut ancestors = plus_token.parent_ancestors(); + ancestors.next().and_then(ast::TypeBoundList::cast)?; + let trait_type = + ancestors.next().and_then(>::cast)?; + let kind = ancestors.next()?.kind(); + + if ast::RefType::can_cast(kind) || ast::PtrType::can_cast(kind) || ast::RetType::can_cast(kind) + { + let mut builder = TextEdit::builder(); + builder.insert(trait_type.syntax().text_range().start(), "(".to_owned()); + builder.insert(trait_type.syntax().text_range().end(), ")".to_owned()); + Some(builder.finish()) + } else { + None + } +} + /// Adds a space after an arrow when `fn foo() { ... }` is turned into `fn foo() -> { ... }` fn on_right_angle_typed(file: &SourceFile, offset: TextSize) -> Option { let file_text = file.syntax().text(); @@ -1594,6 +1618,66 @@ fn foo() { fn foo() { let $0 } +"#, + ); + } + + #[test] + fn adds_parentheses_around_trait_object_in_ref_type() { + type_char( + '+', + r#" +fn foo(x: &dyn A$0) {} +"#, + r#" +fn foo(x: &(dyn A+)) {} +"#, + ); + type_char( + '+', + r#" +fn foo(x: &'static dyn A$0B) {} +"#, + r#" +fn foo(x: &'static (dyn A+B)) {} +"#, + ); + type_char_noop( + '+', + r#" +fn foo(x: &(dyn A$0)) {} +"#, + ); + type_char_noop( + '+', + r#" +fn foo(x: Box) {} +"#, + ); + } + + #[test] + fn adds_parentheses_around_trait_object_in_ptr_type() { + type_char( + '+', + r#" +fn foo(x: *const dyn A$0) {} +"#, + r#" +fn foo(x: *const (dyn A+)) {} +"#, + ); + } + + #[test] + fn adds_parentheses_around_trait_object_in_return_type() { + type_char( + '+', + r#" +fn foo(x: fn() -> dyn A$0) {} +"#, + r#" +fn foo(x: fn() -> (dyn A+)) {} "#, ); } diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 762b63f54b..99d1a4d64c 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -344,7 +344,7 @@ config_data! { /// - typing `{` in a use item adds a closing `}` in the right place /// - typing `>` to complete a return type `->` will insert a whitespace after it /// - typing `<` in a path or type position inserts a closing `>` after the path or type. - typing_triggerChars: Option = Some("=.".to_owned()), + typing_triggerChars: Option = Some("=.+".to_owned()), /// Enables automatic discovery of projects using [`DiscoverWorkspaceConfig::command`]. diff --git a/crates/rust-analyzer/src/lsp/capabilities.rs b/crates/rust-analyzer/src/lsp/capabilities.rs index 418fe95759..04e31f37fd 100644 --- a/crates/rust-analyzer/src/lsp/capabilities.rs +++ b/crates/rust-analyzer/src/lsp/capabilities.rs @@ -77,7 +77,7 @@ pub fn server_capabilities(config: &Config) -> ServerCapabilities { _ => Some(OneOf::Left(false)), }, document_on_type_formatting_provider: Some({ - let mut chars = ide::Analysis::SUPPORTED_TRIGGER_CHARS.chars(); + let mut chars = ide::Analysis::SUPPORTED_TRIGGER_CHARS.iter(); DocumentOnTypeFormattingOptions { first_trigger_character: chars.next().unwrap().to_string(), more_trigger_character: Some(chars.map(|c| c.to_string()).collect()), diff --git a/docs/book/src/configuration_generated.md b/docs/book/src/configuration_generated.md index 4eb9cfc4e5..521edb068a 100644 --- a/docs/book/src/configuration_generated.md +++ b/docs/book/src/configuration_generated.md @@ -1410,7 +1410,7 @@ Show documentation. ## rust-analyzer.typing.triggerChars {#typing.triggerChars} -Default: `"=."` +Default: `"=.+"` Specify the characters allowed to invoke special on typing triggers. - typing `=` after `let` tries to smartly add `;` if `=` is followed by an existing expression diff --git a/editors/code/package.json b/editors/code/package.json index dcdb4fe30e..0b86332d1a 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -2836,7 +2836,7 @@ "properties": { "rust-analyzer.typing.triggerChars": { "markdownDescription": "Specify the characters allowed to invoke special on typing triggers.\n- typing `=` after `let` tries to smartly add `;` if `=` is followed by an existing expression\n- typing `=` between two expressions adds `;` when in statement position\n- typing `=` to turn an assignment into an equality comparison removes `;` when in expression position\n- typing `.` in a chain method call auto-indents\n- typing `{` or `(` in front of an expression inserts a closing `}` or `)` after the expression\n- typing `{` in a use item adds a closing `}` in the right place\n- typing `>` to complete a return type `->` will insert a whitespace after it\n- typing `<` in a path or type position inserts a closing `>` after the path or type.", - "default": "=.", + "default": "=.+", "type": [ "null", "string"