aboutsummaryrefslogtreecommitdiff
path: root/crates/ide
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide')
-rw-r--r--crates/ide/src/diagnostics.rs16
-rw-r--r--crates/ide/src/expand_macro.rs44
-rw-r--r--crates/ide/src/folding_ranges.rs17
-rw-r--r--crates/ide/src/syntax_highlighting/highlight.rs14
-rw-r--r--crates/ide/src/syntax_highlighting/tests.rs96
-rw-r--r--crates/ide/src/typing.rs168
-rw-r--r--crates/ide/src/typing/on_enter.rs20
7 files changed, 339 insertions, 36 deletions
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs
index dd42116a7..0ace80a1e 100644
--- a/crates/ide/src/diagnostics.rs
+++ b/crates/ide/src/diagnostics.rs
@@ -20,7 +20,7 @@ use itertools::Itertools;
20use rustc_hash::FxHashSet; 20use rustc_hash::FxHashSet;
21use syntax::{ 21use syntax::{
22 ast::{self, AstNode}, 22 ast::{self, AstNode},
23 SyntaxNode, SyntaxNodePtr, TextRange, 23 SyntaxNode, SyntaxNodePtr, TextRange, TextSize,
24}; 24};
25use text_edit::TextEdit; 25use text_edit::TextEdit;
26use unlinked_file::UnlinkedFile; 26use unlinked_file::UnlinkedFile;
@@ -159,14 +159,16 @@ pub(crate) fn diagnostics(
159 ); 159 );
160 }) 160 })
161 .on::<UnlinkedFile, _>(|d| { 161 .on::<UnlinkedFile, _>(|d| {
162 // Limit diagnostic to the first few characters in the file. This matches how VS Code
163 // renders it with the full span, but on other editors, and is less invasive.
164 let range = sema.diagnostics_display_range(d.display_source()).range;
165 let range = range.intersect(TextRange::up_to(TextSize::of("..."))).unwrap_or(range);
166
162 // Override severity and mark as unused. 167 // Override severity and mark as unused.
163 res.borrow_mut().push( 168 res.borrow_mut().push(
164 Diagnostic::hint( 169 Diagnostic::hint(range, d.message())
165 sema.diagnostics_display_range(d.display_source()).range, 170 .with_fix(d.fix(&sema))
166 d.message(), 171 .with_code(Some(d.code())),
167 )
168 .with_fix(d.fix(&sema))
169 .with_code(Some(d.code())),
170 ); 172 );
171 }) 173 })
172 .on::<hir::diagnostics::UnresolvedProcMacro, _>(|d| { 174 .on::<hir::diagnostics::UnresolvedProcMacro, _>(|d| {
diff --git a/crates/ide/src/expand_macro.rs b/crates/ide/src/expand_macro.rs
index 9eeabbeda..be0ee03bf 100644
--- a/crates/ide/src/expand_macro.rs
+++ b/crates/ide/src/expand_macro.rs
@@ -1,3 +1,5 @@
1use std::iter;
2
1use hir::Semantics; 3use hir::Semantics;
2use ide_db::RootDatabase; 4use ide_db::RootDatabase;
3use syntax::{ 5use syntax::{
@@ -91,24 +93,42 @@ fn insert_whitespaces(syn: SyntaxNode) -> String {
91 let is_last = 93 let is_last =
92 |f: fn(SyntaxKind) -> bool, default| -> bool { last.map(f).unwrap_or(default) }; 94 |f: fn(SyntaxKind) -> bool, default| -> bool { last.map(f).unwrap_or(default) };
93 95
94 res += &match token.kind() { 96 match token.kind() {
95 k if is_text(k) && is_next(|it| !it.is_punct(), true) => token.text().to_string() + " ", 97 k if is_text(k) && is_next(|it| !it.is_punct(), true) => {
98 res.push_str(token.text());
99 res.push(' ');
100 }
96 L_CURLY if is_next(|it| it != R_CURLY, true) => { 101 L_CURLY if is_next(|it| it != R_CURLY, true) => {
97 indent += 1; 102 indent += 1;
98 let leading_space = if is_last(is_text, false) { " " } else { "" }; 103 if is_last(is_text, false) {
99 format!("{}{{\n{}", leading_space, " ".repeat(indent)) 104 res.push(' ');
105 }
106 res.push_str("{\n");
107 res.extend(iter::repeat(" ").take(2 * indent));
100 } 108 }
101 R_CURLY if is_last(|it| it != L_CURLY, true) => { 109 R_CURLY if is_last(|it| it != L_CURLY, true) => {
102 indent = indent.saturating_sub(1); 110 indent = indent.saturating_sub(1);
103 format!("\n{}}}", " ".repeat(indent)) 111 res.push('\n');
112 res.extend(iter::repeat(" ").take(2 * indent));
113 res.push_str("}");
104 } 114 }
105 R_CURLY => format!("}}\n{}", " ".repeat(indent)), 115 R_CURLY => {
106 T![;] => format!(";\n{}", " ".repeat(indent)), 116 res.push_str("}\n");
107 T![->] => " -> ".to_string(), 117 res.extend(iter::repeat(" ").take(2 * indent));
108 T![=] => " = ".to_string(), 118 }
109 T![=>] => " => ".to_string(), 119 LIFETIME_IDENT if is_next(|it| it == IDENT, true) => {
110 _ => token.text().to_string(), 120 res.push_str(token.text());
111 }; 121 res.push(' ');
122 }
123 T![;] => {
124 res.push_str(";\n");
125 res.extend(iter::repeat(" ").take(2 * indent));
126 }
127 T![->] => res.push_str(" -> "),
128 T![=] => res.push_str(" = "),
129 T![=>] => res.push_str(" => "),
130 _ => res.push_str(token.text()),
131 }
112 132
113 last = Some(token.kind()); 133 last = Some(token.kind());
114 } 134 }
diff --git a/crates/ide/src/folding_ranges.rs b/crates/ide/src/folding_ranges.rs
index 153726ce8..2b9ed123c 100644
--- a/crates/ide/src/folding_ranges.rs
+++ b/crates/ide/src/folding_ranges.rs
@@ -19,6 +19,7 @@ pub enum FoldKind {
19 Region, 19 Region,
20 Consts, 20 Consts,
21 Statics, 21 Statics,
22 Array,
22} 23}
23 24
24#[derive(Debug)] 25#[derive(Debug)]
@@ -119,6 +120,7 @@ fn fold_kind(kind: SyntaxKind) -> Option<FoldKind> {
119 match kind { 120 match kind {
120 COMMENT => Some(FoldKind::Comment), 121 COMMENT => Some(FoldKind::Comment),
121 ARG_LIST | PARAM_LIST => Some(FoldKind::ArgList), 122 ARG_LIST | PARAM_LIST => Some(FoldKind::ArgList),
123 ARRAY_EXPR => Some(FoldKind::Array),
122 ASSOC_ITEM_LIST 124 ASSOC_ITEM_LIST
123 | RECORD_FIELD_LIST 125 | RECORD_FIELD_LIST
124 | RECORD_PAT_FIELD_LIST 126 | RECORD_PAT_FIELD_LIST
@@ -269,6 +271,7 @@ mod tests {
269 FoldKind::Region => "region", 271 FoldKind::Region => "region",
270 FoldKind::Consts => "consts", 272 FoldKind::Consts => "consts",
271 FoldKind::Statics => "statics", 273 FoldKind::Statics => "statics",
274 FoldKind::Array => "array",
272 }; 275 };
273 assert_eq!(kind, &attr.unwrap()); 276 assert_eq!(kind, &attr.unwrap());
274 } 277 }
@@ -465,6 +468,20 @@ fn foo<fold arglist>(
465 } 468 }
466 469
467 #[test] 470 #[test]
471 fn fold_multiline_array() {
472 check(
473 r#"
474const FOO: [usize; 4] = <fold array>[
475 1,
476 2,
477 3,
478 4,
479]</fold>;
480"#,
481 )
482 }
483
484 #[test]
468 fn fold_region() { 485 fn fold_region() {
469 check( 486 check(
470 r#" 487 r#"
diff --git a/crates/ide/src/syntax_highlighting/highlight.rs b/crates/ide/src/syntax_highlighting/highlight.rs
index 5ccb84714..e921784bf 100644
--- a/crates/ide/src/syntax_highlighting/highlight.rs
+++ b/crates/ide/src/syntax_highlighting/highlight.rs
@@ -323,8 +323,18 @@ fn highlight_def(db: &RootDatabase, def: Definition) -> Highlight {
323 hir::ModuleDef::Trait(_) => HlTag::Symbol(SymbolKind::Trait), 323 hir::ModuleDef::Trait(_) => HlTag::Symbol(SymbolKind::Trait),
324 hir::ModuleDef::TypeAlias(type_) => { 324 hir::ModuleDef::TypeAlias(type_) => {
325 let mut h = Highlight::new(HlTag::Symbol(SymbolKind::TypeAlias)); 325 let mut h = Highlight::new(HlTag::Symbol(SymbolKind::TypeAlias));
326 if type_.as_assoc_item(db).is_some() { 326 if let Some(item) = type_.as_assoc_item(db) {
327 h |= HlMod::Associated 327 h |= HlMod::Associated;
328 match item.container(db) {
329 AssocItemContainer::Impl(i) => {
330 if i.trait_(db).is_some() {
331 h |= HlMod::Trait;
332 }
333 }
334 AssocItemContainer::Trait(_t) => {
335 h |= HlMod::Trait;
336 }
337 }
328 } 338 }
329 return h; 339 return h;
330 } 340 }
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs
index 1b02857ec..de2d22ac7 100644
--- a/crates/ide/src/syntax_highlighting/tests.rs
+++ b/crates/ide/src/syntax_highlighting/tests.rs
@@ -1,5 +1,8 @@
1use std::time::Instant;
2
1use expect_test::{expect_file, ExpectFile}; 3use expect_test::{expect_file, ExpectFile};
2use ide_db::SymbolKind; 4use ide_db::SymbolKind;
5use stdx::format_to;
3use test_utils::{bench, bench_fixture, skip_slow_tests}; 6use test_utils::{bench, bench_fixture, skip_slow_tests};
4 7
5use crate::{fixture, FileRange, HlTag, TextRange}; 8use crate::{fixture, FileRange, HlTag, TextRange};
@@ -258,6 +261,99 @@ fn benchmark_syntax_highlighting_long_struct() {
258} 261}
259 262
260#[test] 263#[test]
264fn syntax_highlighting_not_quadratic() {
265 if skip_slow_tests() {
266 return;
267 }
268
269 let mut measures = Vec::new();
270 for i in 6..=10 {
271 let n = 1 << i;
272 let fixture = bench_fixture::big_struct_n(n);
273 let (analysis, file_id) = fixture::file(&fixture);
274
275 let time = Instant::now();
276
277 let hash = analysis
278 .highlight(file_id)
279 .unwrap()
280 .iter()
281 .filter(|it| it.highlight.tag == HlTag::Symbol(SymbolKind::Struct))
282 .count();
283 assert!(hash > n as usize);
284
285 let elapsed = time.elapsed();
286 measures.push((n as f64, elapsed.as_millis() as f64))
287 }
288
289 assert_linear(&measures)
290}
291
292/// Checks that a set of measurements looks like a liner function rather than
293/// like a quadratic function. Algorithm:
294///
295/// 1. Linearly scale input to be in [0; 1)
296/// 2. Using linear regression, compute the best linear function approximating
297/// the input.
298/// 3. Compute RMSE and maximal absolute error.
299/// 4. Check that errors are within tolerances and that the constant term is not
300/// too negative.
301///
302/// Ideally, we should use a proper "model selection" to directly compare
303/// quadratic and linear models, but that sounds rather complicated:
304///
305/// https://stats.stackexchange.com/questions/21844/selecting-best-model-based-on-linear-quadratic-and-cubic-fit-of-data
306fn assert_linear(xy: &[(f64, f64)]) {
307 let (mut xs, mut ys): (Vec<_>, Vec<_>) = xy.iter().copied().unzip();
308 normalize(&mut xs);
309 normalize(&mut ys);
310 let xy = xs.iter().copied().zip(ys.iter().copied());
311
312 // Linear regression: finding a and b to fit y = a + b*x.
313
314 let mean_x = mean(&xs);
315 let mean_y = mean(&ys);
316
317 let b = {
318 let mut num = 0.0;
319 let mut denom = 0.0;
320 for (x, y) in xy.clone() {
321 num += (x - mean_x) * (y - mean_y);
322 denom += (x - mean_x).powi(2);
323 }
324 num / denom
325 };
326
327 let a = mean_y - b * mean_x;
328
329 let mut plot = format!("y_pred = {:.3} + {:.3} * x\n\nx y y_pred\n", a, b);
330
331 let mut se = 0.0;
332 let mut max_error = 0.0f64;
333 for (x, y) in xy {
334 let y_pred = a + b * x;
335 se += (y - y_pred).powi(2);
336 max_error = max_error.max((y_pred - y).abs());
337
338 format_to!(plot, "{:.3} {:.3} {:.3}\n", x, y, y_pred);
339 }
340
341 let rmse = (se / xs.len() as f64).sqrt();
342 format_to!(plot, "\nrmse = {:.3} max error = {:.3}", rmse, max_error);
343
344 assert!(rmse < 0.05 && max_error < 0.1 && a > -0.1, "\nLooks quadratic\n{}", plot);
345
346 fn normalize(xs: &mut Vec<f64>) {
347 let max = xs.iter().copied().max_by(|a, b| a.partial_cmp(b).unwrap()).unwrap();
348 xs.iter_mut().for_each(|it| *it /= max);
349 }
350
351 fn mean(xs: &[f64]) -> f64 {
352 xs.iter().copied().sum::<f64>() / (xs.len() as f64)
353 }
354}
355
356#[test]
261fn benchmark_syntax_highlighting_parser() { 357fn benchmark_syntax_highlighting_parser() {
262 if skip_slow_tests() { 358 if skip_slow_tests() {
263 return; 359 return;
diff --git a/crates/ide/src/typing.rs b/crates/ide/src/typing.rs
index 11408d445..1378048e5 100644
--- a/crates/ide/src/typing.rs
+++ b/crates/ide/src/typing.rs
@@ -22,18 +22,19 @@ use ide_db::{
22use syntax::{ 22use syntax::{
23 algo::find_node_at_offset, 23 algo::find_node_at_offset,
24 ast::{self, edit::IndentLevel, AstToken}, 24 ast::{self, edit::IndentLevel, AstToken},
25 AstNode, SourceFile, 25 AstNode, Parse, SourceFile,
26 SyntaxKind::{FIELD_EXPR, METHOD_CALL_EXPR}, 26 SyntaxKind::{FIELD_EXPR, METHOD_CALL_EXPR},
27 TextRange, TextSize, 27 TextRange, TextSize,
28}; 28};
29 29
30use text_edit::TextEdit; 30use text_edit::{Indel, TextEdit};
31 31
32use crate::SourceChange; 32use crate::SourceChange;
33 33
34pub(crate) use on_enter::on_enter; 34pub(crate) use on_enter::on_enter;
35 35
36pub(crate) const TRIGGER_CHARS: &str = ".=>"; 36// Don't forget to add new trigger characters to `server_capabilities` in `caps.rs`.
37pub(crate) const TRIGGER_CHARS: &str = ".=>{";
37 38
38// Feature: On Typing Assists 39// Feature: On Typing Assists
39// 40//
@@ -41,6 +42,7 @@ pub(crate) const TRIGGER_CHARS: &str = ".=>";
41// 42//
42// - typing `let =` tries to smartly add `;` if `=` is followed by an existing expression 43// - typing `let =` tries to smartly add `;` if `=` is followed by an existing expression
43// - typing `.` in a chain method call auto-indents 44// - typing `.` in a chain method call auto-indents
45// - typing `{` in front of an expression inserts a closing `}` after the expression
44// 46//
45// VS Code:: 47// VS Code::
46// 48//
@@ -57,28 +59,79 @@ pub(crate) fn on_char_typed(
57 position: FilePosition, 59 position: FilePosition,
58 char_typed: char, 60 char_typed: char,
59) -> Option<SourceChange> { 61) -> Option<SourceChange> {
60 assert!(TRIGGER_CHARS.contains(char_typed)); 62 if !stdx::always!(TRIGGER_CHARS.contains(char_typed)) {
61 let file = &db.parse(position.file_id).tree(); 63 return None;
62 assert_eq!(file.syntax().text().char_at(position.offset), Some(char_typed)); 64 }
65 let file = &db.parse(position.file_id);
66 if !stdx::always!(file.tree().syntax().text().char_at(position.offset) == Some(char_typed)) {
67 return None;
68 }
63 let edit = on_char_typed_inner(file, position.offset, char_typed)?; 69 let edit = on_char_typed_inner(file, position.offset, char_typed)?;
64 Some(SourceChange::from_text_edit(position.file_id, edit)) 70 Some(SourceChange::from_text_edit(position.file_id, edit))
65} 71}
66 72
67fn on_char_typed_inner(file: &SourceFile, offset: TextSize, char_typed: char) -> Option<TextEdit> { 73fn on_char_typed_inner(
68 assert!(TRIGGER_CHARS.contains(char_typed)); 74 file: &Parse<SourceFile>,
75 offset: TextSize,
76 char_typed: char,
77) -> Option<TextEdit> {
78 if !stdx::always!(TRIGGER_CHARS.contains(char_typed)) {
79 return None;
80 }
69 match char_typed { 81 match char_typed {
70 '.' => on_dot_typed(file, offset), 82 '.' => on_dot_typed(&file.tree(), offset),
71 '=' => on_eq_typed(file, offset), 83 '=' => on_eq_typed(&file.tree(), offset),
72 '>' => on_arrow_typed(file, offset), 84 '>' => on_arrow_typed(&file.tree(), offset),
85 '{' => on_opening_brace_typed(file, offset),
73 _ => unreachable!(), 86 _ => unreachable!(),
74 } 87 }
75} 88}
76 89
90/// Inserts a closing `}` when the user types an opening `{`, wrapping an existing expression in a
91/// block.
92fn on_opening_brace_typed(file: &Parse<SourceFile>, offset: TextSize) -> Option<TextEdit> {
93 if !stdx::always!(file.tree().syntax().text().char_at(offset) == Some('{')) {
94 return None;
95 }
96
97 let brace_token = file.tree().syntax().token_at_offset(offset).right_biased()?;
98
99 // Remove the `{` to get a better parse tree, and reparse
100 let file = file.reparse(&Indel::delete(brace_token.text_range()));
101
102 let mut expr: ast::Expr = find_node_at_offset(file.tree().syntax(), offset)?;
103 if expr.syntax().text_range().start() != offset {
104 return None;
105 }
106
107 // Enclose the outermost expression starting at `offset`
108 while let Some(parent) = expr.syntax().parent() {
109 if parent.text_range().start() != expr.syntax().text_range().start() {
110 break;
111 }
112
113 match ast::Expr::cast(parent) {
114 Some(parent) => expr = parent,
115 None => break,
116 }
117 }
118
119 // If it's a statement in a block, we don't know how many statements should be included
120 if ast::ExprStmt::can_cast(expr.syntax().parent()?.kind()) {
121 return None;
122 }
123
124 // Insert `}` right after the expression.
125 Some(TextEdit::insert(expr.syntax().text_range().end() + TextSize::of("{"), "}".to_string()))
126}
127
77/// Returns an edit which should be applied after `=` was typed. Primarily, 128/// Returns an edit which should be applied after `=` was typed. Primarily,
78/// this works when adding `let =`. 129/// this works when adding `let =`.
79// FIXME: use a snippet completion instead of this hack here. 130// FIXME: use a snippet completion instead of this hack here.
80fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> { 131fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
81 assert_eq!(file.syntax().text().char_at(offset), Some('=')); 132 if !stdx::always!(file.syntax().text().char_at(offset) == Some('=')) {
133 return None;
134 }
82 let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?; 135 let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?;
83 if let_stmt.semicolon_token().is_some() { 136 if let_stmt.semicolon_token().is_some() {
84 return None; 137 return None;
@@ -100,7 +153,9 @@ fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
100 153
101/// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately. 154/// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately.
102fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> { 155fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
103 assert_eq!(file.syntax().text().char_at(offset), Some('.')); 156 if !stdx::always!(file.syntax().text().char_at(offset) == Some('.')) {
157 return None;
158 }
104 let whitespace = 159 let whitespace =
105 file.syntax().token_at_offset(offset).left_biased().and_then(ast::Whitespace::cast)?; 160 file.syntax().token_at_offset(offset).left_biased().and_then(ast::Whitespace::cast)?;
106 161
@@ -129,7 +184,9 @@ fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
129/// Adds a space after an arrow when `fn foo() { ... }` is turned into `fn foo() -> { ... }` 184/// Adds a space after an arrow when `fn foo() { ... }` is turned into `fn foo() -> { ... }`
130fn on_arrow_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> { 185fn on_arrow_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
131 let file_text = file.syntax().text(); 186 let file_text = file.syntax().text();
132 assert_eq!(file_text.char_at(offset), Some('>')); 187 if !stdx::always!(file_text.char_at(offset) == Some('>')) {
188 return None;
189 }
133 let after_arrow = offset + TextSize::of('>'); 190 let after_arrow = offset + TextSize::of('>');
134 if file_text.char_at(after_arrow) != Some('{') { 191 if file_text.char_at(after_arrow) != Some('{') {
135 return None; 192 return None;
@@ -152,7 +209,7 @@ mod tests {
152 let edit = TextEdit::insert(offset, char_typed.to_string()); 209 let edit = TextEdit::insert(offset, char_typed.to_string());
153 edit.apply(&mut before); 210 edit.apply(&mut before);
154 let parse = SourceFile::parse(&before); 211 let parse = SourceFile::parse(&before);
155 on_char_typed_inner(&parse.tree(), offset, char_typed).map(|it| { 212 on_char_typed_inner(&parse, offset, char_typed).map(|it| {
156 it.apply(&mut before); 213 it.apply(&mut before);
157 before.to_string() 214 before.to_string()
158 }) 215 })
@@ -373,4 +430,85 @@ fn main() {
373 fn adds_space_after_return_type() { 430 fn adds_space_after_return_type() {
374 type_char('>', "fn foo() -$0{ 92 }", "fn foo() -> { 92 }") 431 type_char('>', "fn foo() -$0{ 92 }", "fn foo() -> { 92 }")
375 } 432 }
433
434 #[test]
435 fn adds_closing_brace() {
436 type_char(
437 '{',
438 r#"
439fn f() { match () { _ => $0() } }
440 "#,
441 r#"
442fn f() { match () { _ => {()} } }
443 "#,
444 );
445 type_char(
446 '{',
447 r#"
448fn f() { $0() }
449 "#,
450 r#"
451fn f() { {()} }
452 "#,
453 );
454 type_char(
455 '{',
456 r#"
457fn f() { let x = $0(); }
458 "#,
459 r#"
460fn f() { let x = {()}; }
461 "#,
462 );
463 type_char(
464 '{',
465 r#"
466fn f() { let x = $0a.b(); }
467 "#,
468 r#"
469fn f() { let x = {a.b()}; }
470 "#,
471 );
472 type_char(
473 '{',
474 r#"
475const S: () = $0();
476fn f() {}
477 "#,
478 r#"
479const S: () = {()};
480fn f() {}
481 "#,
482 );
483 type_char(
484 '{',
485 r#"
486const S: () = $0a.b();
487fn f() {}
488 "#,
489 r#"
490const S: () = {a.b()};
491fn f() {}
492 "#,
493 );
494 type_char(
495 '{',
496 r#"
497fn f() {
498 match x {
499 0 => $0(),
500 1 => (),
501 }
502}
503 "#,
504 r#"
505fn f() {
506 match x {
507 0 => {()},
508 1 => (),
509 }
510}
511 "#,
512 );
513 }
376} 514}
diff --git a/crates/ide/src/typing/on_enter.rs b/crates/ide/src/typing/on_enter.rs
index 9144681bf..6f1ce3689 100644
--- a/crates/ide/src/typing/on_enter.rs
+++ b/crates/ide/src/typing/on_enter.rs
@@ -18,6 +18,7 @@ use text_edit::TextEdit;
18// 18//
19// - kbd:[Enter] inside triple-slash comments automatically inserts `///` 19// - kbd:[Enter] inside triple-slash comments automatically inserts `///`
20// - kbd:[Enter] in the middle or after a trailing space in `//` inserts `//` 20// - kbd:[Enter] in the middle or after a trailing space in `//` inserts `//`
21// - kbd:[Enter] inside `//!` doc comments automatically inserts `//!`
21// 22//
22// This action needs to be assigned to shortcut explicitly. 23// This action needs to be assigned to shortcut explicitly.
23// 24//
@@ -187,6 +188,25 @@ fn foo() {
187 } 188 }
188 189
189 #[test] 190 #[test]
191 fn continues_another_doc_comment() {
192 do_check(
193 r#"
194fn main() {
195 //! Documentation for$0 on enter
196 let x = 1 + 1;
197}
198"#,
199 r#"
200fn main() {
201 //! Documentation for
202 //! $0 on enter
203 let x = 1 + 1;
204}
205"#,
206 );
207 }
208
209 #[test]
190 fn continues_code_comment_in_the_middle_of_line() { 210 fn continues_code_comment_in_the_middle_of_line() {
191 do_check( 211 do_check(
192 r" 212 r"