diff options
Diffstat (limited to 'crates/ide/src')
-rw-r--r-- | crates/ide/src/diagnostics.rs | 155 | ||||
-rw-r--r-- | crates/ide/src/diagnostics/field_shorthand.rs | 8 | ||||
-rw-r--r-- | crates/ide/src/diagnostics/fixes.rs | 60 | ||||
-rw-r--r-- | crates/ide/src/diagnostics/unlinked_file.rs | 14 | ||||
-rw-r--r-- | crates/ide/src/expand_macro.rs | 43 | ||||
-rw-r--r-- | crates/ide/src/goto_definition.rs | 44 | ||||
-rw-r--r-- | crates/ide/src/lib.rs | 36 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting/highlight.rs | 73 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting/tags.rs | 24 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html | 2 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting/test_data/highlighting.html | 2 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting/tests.rs | 34 | ||||
-rw-r--r-- | crates/ide/src/typing/on_enter.rs | 230 |
13 files changed, 577 insertions, 148 deletions
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index dd42116a7..9a883acb9 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs | |||
@@ -20,12 +20,12 @@ use itertools::Itertools; | |||
20 | use rustc_hash::FxHashSet; | 20 | use rustc_hash::FxHashSet; |
21 | use syntax::{ | 21 | use syntax::{ |
22 | ast::{self, AstNode}, | 22 | ast::{self, AstNode}, |
23 | SyntaxNode, SyntaxNodePtr, TextRange, | 23 | SyntaxNode, SyntaxNodePtr, TextRange, TextSize, |
24 | }; | 24 | }; |
25 | use text_edit::TextEdit; | 25 | use text_edit::TextEdit; |
26 | use unlinked_file::UnlinkedFile; | 26 | use unlinked_file::UnlinkedFile; |
27 | 27 | ||
28 | use crate::{FileId, Label, SourceChange}; | 28 | use crate::{Assist, AssistId, AssistKind, FileId, Label, SourceChange}; |
29 | 29 | ||
30 | use self::fixes::DiagnosticWithFix; | 30 | use self::fixes::DiagnosticWithFix; |
31 | 31 | ||
@@ -35,7 +35,7 @@ pub struct Diagnostic { | |||
35 | pub message: String, | 35 | pub message: String, |
36 | pub range: TextRange, | 36 | pub range: TextRange, |
37 | pub severity: Severity, | 37 | pub severity: Severity, |
38 | pub fix: Option<Fix>, | 38 | pub fix: Option<Assist>, |
39 | pub unused: bool, | 39 | pub unused: bool, |
40 | pub code: Option<DiagnosticCode>, | 40 | pub code: Option<DiagnosticCode>, |
41 | } | 41 | } |
@@ -56,7 +56,7 @@ impl Diagnostic { | |||
56 | } | 56 | } |
57 | } | 57 | } |
58 | 58 | ||
59 | fn with_fix(self, fix: Option<Fix>) -> Self { | 59 | fn with_fix(self, fix: Option<Assist>) -> Self { |
60 | Self { fix, ..self } | 60 | Self { fix, ..self } |
61 | } | 61 | } |
62 | 62 | ||
@@ -69,21 +69,6 @@ impl Diagnostic { | |||
69 | } | 69 | } |
70 | } | 70 | } |
71 | 71 | ||
72 | #[derive(Debug)] | ||
73 | pub struct Fix { | ||
74 | pub label: Label, | ||
75 | pub source_change: SourceChange, | ||
76 | /// Allows to trigger the fix only when the caret is in the range given | ||
77 | pub fix_trigger_range: TextRange, | ||
78 | } | ||
79 | |||
80 | impl Fix { | ||
81 | fn new(label: &str, source_change: SourceChange, fix_trigger_range: TextRange) -> Self { | ||
82 | let label = Label::new(label); | ||
83 | Self { label, source_change, fix_trigger_range } | ||
84 | } | ||
85 | } | ||
86 | |||
87 | #[derive(Debug, Copy, Clone)] | 72 | #[derive(Debug, Copy, Clone)] |
88 | pub enum Severity { | 73 | pub enum Severity { |
89 | Error, | 74 | Error, |
@@ -99,6 +84,7 @@ pub struct DiagnosticsConfig { | |||
99 | pub(crate) fn diagnostics( | 84 | pub(crate) fn diagnostics( |
100 | db: &RootDatabase, | 85 | db: &RootDatabase, |
101 | config: &DiagnosticsConfig, | 86 | config: &DiagnosticsConfig, |
87 | resolve: bool, | ||
102 | file_id: FileId, | 88 | file_id: FileId, |
103 | ) -> Vec<Diagnostic> { | 89 | ) -> Vec<Diagnostic> { |
104 | let _p = profile::span("diagnostics"); | 90 | let _p = profile::span("diagnostics"); |
@@ -122,25 +108,25 @@ pub(crate) fn diagnostics( | |||
122 | let res = RefCell::new(res); | 108 | let res = RefCell::new(res); |
123 | let sink_builder = DiagnosticSinkBuilder::new() | 109 | let sink_builder = DiagnosticSinkBuilder::new() |
124 | .on::<hir::diagnostics::UnresolvedModule, _>(|d| { | 110 | .on::<hir::diagnostics::UnresolvedModule, _>(|d| { |
125 | res.borrow_mut().push(diagnostic_with_fix(d, &sema)); | 111 | res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve)); |
126 | }) | 112 | }) |
127 | .on::<hir::diagnostics::MissingFields, _>(|d| { | 113 | .on::<hir::diagnostics::MissingFields, _>(|d| { |
128 | res.borrow_mut().push(diagnostic_with_fix(d, &sema)); | 114 | res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve)); |
129 | }) | 115 | }) |
130 | .on::<hir::diagnostics::MissingOkOrSomeInTailExpr, _>(|d| { | 116 | .on::<hir::diagnostics::MissingOkOrSomeInTailExpr, _>(|d| { |
131 | res.borrow_mut().push(diagnostic_with_fix(d, &sema)); | 117 | res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve)); |
132 | }) | 118 | }) |
133 | .on::<hir::diagnostics::NoSuchField, _>(|d| { | 119 | .on::<hir::diagnostics::NoSuchField, _>(|d| { |
134 | res.borrow_mut().push(diagnostic_with_fix(d, &sema)); | 120 | res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve)); |
135 | }) | 121 | }) |
136 | .on::<hir::diagnostics::RemoveThisSemicolon, _>(|d| { | 122 | .on::<hir::diagnostics::RemoveThisSemicolon, _>(|d| { |
137 | res.borrow_mut().push(diagnostic_with_fix(d, &sema)); | 123 | res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve)); |
138 | }) | 124 | }) |
139 | .on::<hir::diagnostics::IncorrectCase, _>(|d| { | 125 | .on::<hir::diagnostics::IncorrectCase, _>(|d| { |
140 | res.borrow_mut().push(warning_with_fix(d, &sema)); | 126 | res.borrow_mut().push(warning_with_fix(d, &sema, resolve)); |
141 | }) | 127 | }) |
142 | .on::<hir::diagnostics::ReplaceFilterMapNextWithFindMap, _>(|d| { | 128 | .on::<hir::diagnostics::ReplaceFilterMapNextWithFindMap, _>(|d| { |
143 | res.borrow_mut().push(warning_with_fix(d, &sema)); | 129 | res.borrow_mut().push(warning_with_fix(d, &sema, resolve)); |
144 | }) | 130 | }) |
145 | .on::<hir::diagnostics::InactiveCode, _>(|d| { | 131 | .on::<hir::diagnostics::InactiveCode, _>(|d| { |
146 | // If there's inactive code somewhere in a macro, don't propagate to the call-site. | 132 | // If there's inactive code somewhere in a macro, don't propagate to the call-site. |
@@ -159,14 +145,16 @@ pub(crate) fn diagnostics( | |||
159 | ); | 145 | ); |
160 | }) | 146 | }) |
161 | .on::<UnlinkedFile, _>(|d| { | 147 | .on::<UnlinkedFile, _>(|d| { |
148 | // Limit diagnostic to the first few characters in the file. This matches how VS Code | ||
149 | // renders it with the full span, but on other editors, and is less invasive. | ||
150 | let range = sema.diagnostics_display_range(d.display_source()).range; | ||
151 | let range = range.intersect(TextRange::up_to(TextSize::of("..."))).unwrap_or(range); | ||
152 | |||
162 | // Override severity and mark as unused. | 153 | // Override severity and mark as unused. |
163 | res.borrow_mut().push( | 154 | res.borrow_mut().push( |
164 | Diagnostic::hint( | 155 | Diagnostic::hint(range, d.message()) |
165 | sema.diagnostics_display_range(d.display_source()).range, | 156 | .with_fix(d.fix(&sema, resolve)) |
166 | d.message(), | 157 | .with_code(Some(d.code())), |
167 | ) | ||
168 | .with_fix(d.fix(&sema)) | ||
169 | .with_code(Some(d.code())), | ||
170 | ); | 158 | ); |
171 | }) | 159 | }) |
172 | .on::<hir::diagnostics::UnresolvedProcMacro, _>(|d| { | 160 | .on::<hir::diagnostics::UnresolvedProcMacro, _>(|d| { |
@@ -221,15 +209,23 @@ pub(crate) fn diagnostics( | |||
221 | res.into_inner() | 209 | res.into_inner() |
222 | } | 210 | } |
223 | 211 | ||
224 | fn diagnostic_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic { | 212 | fn diagnostic_with_fix<D: DiagnosticWithFix>( |
213 | d: &D, | ||
214 | sema: &Semantics<RootDatabase>, | ||
215 | resolve: bool, | ||
216 | ) -> Diagnostic { | ||
225 | Diagnostic::error(sema.diagnostics_display_range(d.display_source()).range, d.message()) | 217 | Diagnostic::error(sema.diagnostics_display_range(d.display_source()).range, d.message()) |
226 | .with_fix(d.fix(&sema)) | 218 | .with_fix(d.fix(&sema, resolve)) |
227 | .with_code(Some(d.code())) | 219 | .with_code(Some(d.code())) |
228 | } | 220 | } |
229 | 221 | ||
230 | fn warning_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic { | 222 | fn warning_with_fix<D: DiagnosticWithFix>( |
223 | d: &D, | ||
224 | sema: &Semantics<RootDatabase>, | ||
225 | resolve: bool, | ||
226 | ) -> Diagnostic { | ||
231 | Diagnostic::hint(sema.diagnostics_display_range(d.display_source()).range, d.message()) | 227 | Diagnostic::hint(sema.diagnostics_display_range(d.display_source()).range, d.message()) |
232 | .with_fix(d.fix(&sema)) | 228 | .with_fix(d.fix(&sema, resolve)) |
233 | .with_code(Some(d.code())) | 229 | .with_code(Some(d.code())) |
234 | } | 230 | } |
235 | 231 | ||
@@ -259,7 +255,8 @@ fn check_unnecessary_braces_in_use_statement( | |||
259 | 255 | ||
260 | acc.push( | 256 | acc.push( |
261 | Diagnostic::hint(use_range, "Unnecessary braces in use statement".to_string()) | 257 | Diagnostic::hint(use_range, "Unnecessary braces in use statement".to_string()) |
262 | .with_fix(Some(Fix::new( | 258 | .with_fix(Some(fix( |
259 | "remove_braces", | ||
263 | "Remove unnecessary braces", | 260 | "Remove unnecessary braces", |
264 | SourceChange::from_text_edit(file_id, edit), | 261 | SourceChange::from_text_edit(file_id, edit), |
265 | use_range, | 262 | use_range, |
@@ -282,6 +279,23 @@ fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement( | |||
282 | None | 279 | None |
283 | } | 280 | } |
284 | 281 | ||
282 | fn fix(id: &'static str, label: &str, source_change: SourceChange, target: TextRange) -> Assist { | ||
283 | let mut res = unresolved_fix(id, label, target); | ||
284 | res.source_change = Some(source_change); | ||
285 | res | ||
286 | } | ||
287 | |||
288 | fn unresolved_fix(id: &'static str, label: &str, target: TextRange) -> Assist { | ||
289 | assert!(!id.contains(' ')); | ||
290 | Assist { | ||
291 | id: AssistId(id, AssistKind::QuickFix), | ||
292 | label: Label::new(label), | ||
293 | group: None, | ||
294 | target, | ||
295 | source_change: None, | ||
296 | } | ||
297 | } | ||
298 | |||
285 | #[cfg(test)] | 299 | #[cfg(test)] |
286 | mod tests { | 300 | mod tests { |
287 | use expect_test::{expect, Expect}; | 301 | use expect_test::{expect, Expect}; |
@@ -300,16 +314,17 @@ mod tests { | |||
300 | 314 | ||
301 | let (analysis, file_position) = fixture::position(ra_fixture_before); | 315 | let (analysis, file_position) = fixture::position(ra_fixture_before); |
302 | let diagnostic = analysis | 316 | let diagnostic = analysis |
303 | .diagnostics(&DiagnosticsConfig::default(), file_position.file_id) | 317 | .diagnostics(&DiagnosticsConfig::default(), true, file_position.file_id) |
304 | .unwrap() | 318 | .unwrap() |
305 | .pop() | 319 | .pop() |
306 | .unwrap(); | 320 | .unwrap(); |
307 | let fix = diagnostic.fix.unwrap(); | 321 | let fix = diagnostic.fix.unwrap(); |
308 | let actual = { | 322 | let actual = { |
309 | let file_id = *fix.source_change.source_file_edits.keys().next().unwrap(); | 323 | let source_change = fix.source_change.unwrap(); |
324 | let file_id = *source_change.source_file_edits.keys().next().unwrap(); | ||
310 | let mut actual = analysis.file_text(file_id).unwrap().to_string(); | 325 | let mut actual = analysis.file_text(file_id).unwrap().to_string(); |
311 | 326 | ||
312 | for edit in fix.source_change.source_file_edits.values() { | 327 | for edit in source_change.source_file_edits.values() { |
313 | edit.apply(&mut actual); | 328 | edit.apply(&mut actual); |
314 | } | 329 | } |
315 | actual | 330 | actual |
@@ -317,9 +332,9 @@ mod tests { | |||
317 | 332 | ||
318 | assert_eq_text!(&after, &actual); | 333 | assert_eq_text!(&after, &actual); |
319 | assert!( | 334 | assert!( |
320 | fix.fix_trigger_range.contains_inclusive(file_position.offset), | 335 | fix.target.contains_inclusive(file_position.offset), |
321 | "diagnostic fix range {:?} does not touch cursor position {:?}", | 336 | "diagnostic fix range {:?} does not touch cursor position {:?}", |
322 | fix.fix_trigger_range, | 337 | fix.target, |
323 | file_position.offset | 338 | file_position.offset |
324 | ); | 339 | ); |
325 | } | 340 | } |
@@ -328,7 +343,7 @@ mod tests { | |||
328 | fn check_no_fix(ra_fixture: &str) { | 343 | fn check_no_fix(ra_fixture: &str) { |
329 | let (analysis, file_position) = fixture::position(ra_fixture); | 344 | let (analysis, file_position) = fixture::position(ra_fixture); |
330 | let diagnostic = analysis | 345 | let diagnostic = analysis |
331 | .diagnostics(&DiagnosticsConfig::default(), file_position.file_id) | 346 | .diagnostics(&DiagnosticsConfig::default(), true, file_position.file_id) |
332 | .unwrap() | 347 | .unwrap() |
333 | .pop() | 348 | .pop() |
334 | .unwrap(); | 349 | .unwrap(); |
@@ -342,7 +357,7 @@ mod tests { | |||
342 | let diagnostics = files | 357 | let diagnostics = files |
343 | .into_iter() | 358 | .into_iter() |
344 | .flat_map(|file_id| { | 359 | .flat_map(|file_id| { |
345 | analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap() | 360 | analysis.diagnostics(&DiagnosticsConfig::default(), true, file_id).unwrap() |
346 | }) | 361 | }) |
347 | .collect::<Vec<_>>(); | 362 | .collect::<Vec<_>>(); |
348 | assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics); | 363 | assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics); |
@@ -350,7 +365,8 @@ mod tests { | |||
350 | 365 | ||
351 | fn check_expect(ra_fixture: &str, expect: Expect) { | 366 | fn check_expect(ra_fixture: &str, expect: Expect) { |
352 | let (analysis, file_id) = fixture::file(ra_fixture); | 367 | let (analysis, file_id) = fixture::file(ra_fixture); |
353 | let diagnostics = analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap(); | 368 | let diagnostics = |
369 | analysis.diagnostics(&DiagnosticsConfig::default(), true, file_id).unwrap(); | ||
354 | expect.assert_debug_eq(&diagnostics) | 370 | expect.assert_debug_eq(&diagnostics) |
355 | } | 371 | } |
356 | 372 | ||
@@ -663,24 +679,31 @@ fn test_fn() { | |||
663 | range: 0..8, | 679 | range: 0..8, |
664 | severity: Error, | 680 | severity: Error, |
665 | fix: Some( | 681 | fix: Some( |
666 | Fix { | 682 | Assist { |
683 | id: AssistId( | ||
684 | "create_module", | ||
685 | QuickFix, | ||
686 | ), | ||
667 | label: "Create module", | 687 | label: "Create module", |
668 | source_change: SourceChange { | 688 | group: None, |
669 | source_file_edits: {}, | 689 | target: 0..8, |
670 | file_system_edits: [ | 690 | source_change: Some( |
671 | CreateFile { | 691 | SourceChange { |
672 | dst: AnchoredPathBuf { | 692 | source_file_edits: {}, |
673 | anchor: FileId( | 693 | file_system_edits: [ |
674 | 0, | 694 | CreateFile { |
675 | ), | 695 | dst: AnchoredPathBuf { |
676 | path: "foo.rs", | 696 | anchor: FileId( |
697 | 0, | ||
698 | ), | ||
699 | path: "foo.rs", | ||
700 | }, | ||
701 | initial_contents: "", | ||
677 | }, | 702 | }, |
678 | initial_contents: "", | 703 | ], |
679 | }, | 704 | is_snippet: false, |
680 | ], | 705 | }, |
681 | is_snippet: false, | 706 | ), |
682 | }, | ||
683 | fix_trigger_range: 0..8, | ||
684 | }, | 707 | }, |
685 | ), | 708 | ), |
686 | unused: false, | 709 | unused: false, |
@@ -888,10 +911,11 @@ struct Foo { | |||
888 | 911 | ||
889 | let (analysis, file_id) = fixture::file(r#"mod foo;"#); | 912 | let (analysis, file_id) = fixture::file(r#"mod foo;"#); |
890 | 913 | ||
891 | let diagnostics = analysis.diagnostics(&config, file_id).unwrap(); | 914 | let diagnostics = analysis.diagnostics(&config, true, file_id).unwrap(); |
892 | assert!(diagnostics.is_empty()); | 915 | assert!(diagnostics.is_empty()); |
893 | 916 | ||
894 | let diagnostics = analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap(); | 917 | let diagnostics = |
918 | analysis.diagnostics(&DiagnosticsConfig::default(), true, file_id).unwrap(); | ||
895 | assert!(!diagnostics.is_empty()); | 919 | assert!(!diagnostics.is_empty()); |
896 | } | 920 | } |
897 | 921 | ||
@@ -997,8 +1021,9 @@ impl TestStruct { | |||
997 | let expected = r#"fn foo() {}"#; | 1021 | let expected = r#"fn foo() {}"#; |
998 | 1022 | ||
999 | let (analysis, file_position) = fixture::position(input); | 1023 | let (analysis, file_position) = fixture::position(input); |
1000 | let diagnostics = | 1024 | let diagnostics = analysis |
1001 | analysis.diagnostics(&DiagnosticsConfig::default(), file_position.file_id).unwrap(); | 1025 | .diagnostics(&DiagnosticsConfig::default(), true, file_position.file_id) |
1026 | .unwrap(); | ||
1002 | assert_eq!(diagnostics.len(), 1); | 1027 | assert_eq!(diagnostics.len(), 1); |
1003 | 1028 | ||
1004 | check_fix(input, expected); | 1029 | check_fix(input, expected); |
diff --git a/crates/ide/src/diagnostics/field_shorthand.rs b/crates/ide/src/diagnostics/field_shorthand.rs index 5c89e2170..2b1787f9b 100644 --- a/crates/ide/src/diagnostics/field_shorthand.rs +++ b/crates/ide/src/diagnostics/field_shorthand.rs | |||
@@ -5,7 +5,7 @@ use ide_db::{base_db::FileId, source_change::SourceChange}; | |||
5 | use syntax::{ast, match_ast, AstNode, SyntaxNode}; | 5 | use syntax::{ast, match_ast, AstNode, SyntaxNode}; |
6 | use text_edit::TextEdit; | 6 | use text_edit::TextEdit; |
7 | 7 | ||
8 | use crate::{Diagnostic, Fix}; | 8 | use crate::{diagnostics::fix, Diagnostic}; |
9 | 9 | ||
10 | pub(super) fn check(acc: &mut Vec<Diagnostic>, file_id: FileId, node: &SyntaxNode) { | 10 | pub(super) fn check(acc: &mut Vec<Diagnostic>, file_id: FileId, node: &SyntaxNode) { |
11 | match_ast! { | 11 | match_ast! { |
@@ -47,7 +47,8 @@ fn check_expr_field_shorthand( | |||
47 | let field_range = record_field.syntax().text_range(); | 47 | let field_range = record_field.syntax().text_range(); |
48 | acc.push( | 48 | acc.push( |
49 | Diagnostic::hint(field_range, "Shorthand struct initialization".to_string()).with_fix( | 49 | Diagnostic::hint(field_range, "Shorthand struct initialization".to_string()).with_fix( |
50 | Some(Fix::new( | 50 | Some(fix( |
51 | "use_expr_field_shorthand", | ||
51 | "Use struct shorthand initialization", | 52 | "Use struct shorthand initialization", |
52 | SourceChange::from_text_edit(file_id, edit), | 53 | SourceChange::from_text_edit(file_id, edit), |
53 | field_range, | 54 | field_range, |
@@ -86,7 +87,8 @@ fn check_pat_field_shorthand( | |||
86 | 87 | ||
87 | let field_range = record_pat_field.syntax().text_range(); | 88 | let field_range = record_pat_field.syntax().text_range(); |
88 | acc.push(Diagnostic::hint(field_range, "Shorthand struct pattern".to_string()).with_fix( | 89 | acc.push(Diagnostic::hint(field_range, "Shorthand struct pattern".to_string()).with_fix( |
89 | Some(Fix::new( | 90 | Some(fix( |
91 | "use_pat_field_shorthand", | ||
90 | "Use struct field shorthand", | 92 | "Use struct field shorthand", |
91 | SourceChange::from_text_edit(file_id, edit), | 93 | SourceChange::from_text_edit(file_id, edit), |
92 | field_range, | 94 | field_range, |
diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs index 5fb3e2d91..7be8b3459 100644 --- a/crates/ide/src/diagnostics/fixes.rs +++ b/crates/ide/src/diagnostics/fixes.rs | |||
@@ -20,20 +20,30 @@ use syntax::{ | |||
20 | }; | 20 | }; |
21 | use text_edit::TextEdit; | 21 | use text_edit::TextEdit; |
22 | 22 | ||
23 | use crate::{diagnostics::Fix, references::rename::rename_with_semantics, FilePosition}; | 23 | use crate::{ |
24 | diagnostics::{fix, unresolved_fix}, | ||
25 | references::rename::rename_with_semantics, | ||
26 | Assist, FilePosition, | ||
27 | }; | ||
24 | 28 | ||
25 | /// A [Diagnostic] that potentially has a fix available. | 29 | /// A [Diagnostic] that potentially has a fix available. |
26 | /// | 30 | /// |
27 | /// [Diagnostic]: hir::diagnostics::Diagnostic | 31 | /// [Diagnostic]: hir::diagnostics::Diagnostic |
28 | pub(crate) trait DiagnosticWithFix: Diagnostic { | 32 | pub(crate) trait DiagnosticWithFix: Diagnostic { |
29 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix>; | 33 | /// `resolve` determines if the diagnostic should fill in the `edit` field |
34 | /// of the assist. | ||
35 | /// | ||
36 | /// If `resolve` is false, the edit will be computed later, on demand, and | ||
37 | /// can be omitted. | ||
38 | fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist>; | ||
30 | } | 39 | } |
31 | 40 | ||
32 | impl DiagnosticWithFix for UnresolvedModule { | 41 | impl DiagnosticWithFix for UnresolvedModule { |
33 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> { | 42 | fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> { |
34 | let root = sema.db.parse_or_expand(self.file)?; | 43 | let root = sema.db.parse_or_expand(self.file)?; |
35 | let unresolved_module = self.decl.to_node(&root); | 44 | let unresolved_module = self.decl.to_node(&root); |
36 | Some(Fix::new( | 45 | Some(fix( |
46 | "create_module", | ||
37 | "Create module", | 47 | "Create module", |
38 | FileSystemEdit::CreateFile { | 48 | FileSystemEdit::CreateFile { |
39 | dst: AnchoredPathBuf { | 49 | dst: AnchoredPathBuf { |
@@ -49,7 +59,7 @@ impl DiagnosticWithFix for UnresolvedModule { | |||
49 | } | 59 | } |
50 | 60 | ||
51 | impl DiagnosticWithFix for NoSuchField { | 61 | impl DiagnosticWithFix for NoSuchField { |
52 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> { | 62 | fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> { |
53 | let root = sema.db.parse_or_expand(self.file)?; | 63 | let root = sema.db.parse_or_expand(self.file)?; |
54 | missing_record_expr_field_fix( | 64 | missing_record_expr_field_fix( |
55 | &sema, | 65 | &sema, |
@@ -60,7 +70,7 @@ impl DiagnosticWithFix for NoSuchField { | |||
60 | } | 70 | } |
61 | 71 | ||
62 | impl DiagnosticWithFix for MissingFields { | 72 | impl DiagnosticWithFix for MissingFields { |
63 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> { | 73 | fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> { |
64 | // Note that although we could add a diagnostics to | 74 | // Note that although we could add a diagnostics to |
65 | // fill the missing tuple field, e.g : | 75 | // fill the missing tuple field, e.g : |
66 | // `struct A(usize);` | 76 | // `struct A(usize);` |
@@ -86,7 +96,8 @@ impl DiagnosticWithFix for MissingFields { | |||
86 | .into_text_edit(&mut builder); | 96 | .into_text_edit(&mut builder); |
87 | builder.finish() | 97 | builder.finish() |
88 | }; | 98 | }; |
89 | Some(Fix::new( | 99 | Some(fix( |
100 | "fill_missing_fields", | ||
90 | "Fill struct fields", | 101 | "Fill struct fields", |
91 | SourceChange::from_text_edit(self.file.original_file(sema.db), edit), | 102 | SourceChange::from_text_edit(self.file.original_file(sema.db), edit), |
92 | sema.original_range(&field_list_parent.syntax()).range, | 103 | sema.original_range(&field_list_parent.syntax()).range, |
@@ -95,7 +106,7 @@ impl DiagnosticWithFix for MissingFields { | |||
95 | } | 106 | } |
96 | 107 | ||
97 | impl DiagnosticWithFix for MissingOkOrSomeInTailExpr { | 108 | impl DiagnosticWithFix for MissingOkOrSomeInTailExpr { |
98 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> { | 109 | fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> { |
99 | let root = sema.db.parse_or_expand(self.file)?; | 110 | let root = sema.db.parse_or_expand(self.file)?; |
100 | let tail_expr = self.expr.to_node(&root); | 111 | let tail_expr = self.expr.to_node(&root); |
101 | let tail_expr_range = tail_expr.syntax().text_range(); | 112 | let tail_expr_range = tail_expr.syntax().text_range(); |
@@ -103,12 +114,12 @@ impl DiagnosticWithFix for MissingOkOrSomeInTailExpr { | |||
103 | let edit = TextEdit::replace(tail_expr_range, replacement); | 114 | let edit = TextEdit::replace(tail_expr_range, replacement); |
104 | let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit); | 115 | let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit); |
105 | let name = if self.required == "Ok" { "Wrap with Ok" } else { "Wrap with Some" }; | 116 | let name = if self.required == "Ok" { "Wrap with Ok" } else { "Wrap with Some" }; |
106 | Some(Fix::new(name, source_change, tail_expr_range)) | 117 | Some(fix("wrap_tail_expr", name, source_change, tail_expr_range)) |
107 | } | 118 | } |
108 | } | 119 | } |
109 | 120 | ||
110 | impl DiagnosticWithFix for RemoveThisSemicolon { | 121 | impl DiagnosticWithFix for RemoveThisSemicolon { |
111 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> { | 122 | fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> { |
112 | let root = sema.db.parse_or_expand(self.file)?; | 123 | let root = sema.db.parse_or_expand(self.file)?; |
113 | 124 | ||
114 | let semicolon = self | 125 | let semicolon = self |
@@ -123,12 +134,12 @@ impl DiagnosticWithFix for RemoveThisSemicolon { | |||
123 | let edit = TextEdit::delete(semicolon); | 134 | let edit = TextEdit::delete(semicolon); |
124 | let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit); | 135 | let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit); |
125 | 136 | ||
126 | Some(Fix::new("Remove this semicolon", source_change, semicolon)) | 137 | Some(fix("remove_semicolon", "Remove this semicolon", source_change, semicolon)) |
127 | } | 138 | } |
128 | } | 139 | } |
129 | 140 | ||
130 | impl DiagnosticWithFix for IncorrectCase { | 141 | impl DiagnosticWithFix for IncorrectCase { |
131 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> { | 142 | fn fix(&self, sema: &Semantics<RootDatabase>, resolve: bool) -> Option<Assist> { |
132 | let root = sema.db.parse_or_expand(self.file)?; | 143 | let root = sema.db.parse_or_expand(self.file)?; |
133 | let name_node = self.ident.to_node(&root); | 144 | let name_node = self.ident.to_node(&root); |
134 | 145 | ||
@@ -136,16 +147,19 @@ impl DiagnosticWithFix for IncorrectCase { | |||
136 | let frange = name_node.original_file_range(sema.db); | 147 | let frange = name_node.original_file_range(sema.db); |
137 | let file_position = FilePosition { file_id: frange.file_id, offset: frange.range.start() }; | 148 | let file_position = FilePosition { file_id: frange.file_id, offset: frange.range.start() }; |
138 | 149 | ||
139 | let rename_changes = | ||
140 | rename_with_semantics(sema, file_position, &self.suggested_text).ok()?; | ||
141 | |||
142 | let label = format!("Rename to {}", self.suggested_text); | 150 | let label = format!("Rename to {}", self.suggested_text); |
143 | Some(Fix::new(&label, rename_changes, frange.range)) | 151 | let mut res = unresolved_fix("change_case", &label, frange.range); |
152 | if resolve { | ||
153 | let source_change = rename_with_semantics(sema, file_position, &self.suggested_text); | ||
154 | res.source_change = Some(source_change.ok().unwrap_or_default()); | ||
155 | } | ||
156 | |||
157 | Some(res) | ||
144 | } | 158 | } |
145 | } | 159 | } |
146 | 160 | ||
147 | impl DiagnosticWithFix for ReplaceFilterMapNextWithFindMap { | 161 | impl DiagnosticWithFix for ReplaceFilterMapNextWithFindMap { |
148 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> { | 162 | fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> { |
149 | let root = sema.db.parse_or_expand(self.file)?; | 163 | let root = sema.db.parse_or_expand(self.file)?; |
150 | let next_expr = self.next_expr.to_node(&root); | 164 | let next_expr = self.next_expr.to_node(&root); |
151 | let next_call = ast::MethodCallExpr::cast(next_expr.syntax().clone())?; | 165 | let next_call = ast::MethodCallExpr::cast(next_expr.syntax().clone())?; |
@@ -163,7 +177,8 @@ impl DiagnosticWithFix for ReplaceFilterMapNextWithFindMap { | |||
163 | 177 | ||
164 | let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit); | 178 | let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit); |
165 | 179 | ||
166 | Some(Fix::new( | 180 | Some(fix( |
181 | "replace_with_find_map", | ||
167 | "Replace filter_map(..).next() with find_map()", | 182 | "Replace filter_map(..).next() with find_map()", |
168 | source_change, | 183 | source_change, |
169 | trigger_range, | 184 | trigger_range, |
@@ -175,7 +190,7 @@ fn missing_record_expr_field_fix( | |||
175 | sema: &Semantics<RootDatabase>, | 190 | sema: &Semantics<RootDatabase>, |
176 | usage_file_id: FileId, | 191 | usage_file_id: FileId, |
177 | record_expr_field: &ast::RecordExprField, | 192 | record_expr_field: &ast::RecordExprField, |
178 | ) -> Option<Fix> { | 193 | ) -> Option<Assist> { |
179 | let record_lit = ast::RecordExpr::cast(record_expr_field.syntax().parent()?.parent()?)?; | 194 | let record_lit = ast::RecordExpr::cast(record_expr_field.syntax().parent()?.parent()?)?; |
180 | let def_id = sema.resolve_variant(record_lit)?; | 195 | let def_id = sema.resolve_variant(record_lit)?; |
181 | let module; | 196 | let module; |
@@ -233,7 +248,12 @@ fn missing_record_expr_field_fix( | |||
233 | def_file_id, | 248 | def_file_id, |
234 | TextEdit::insert(last_field_syntax.text_range().end(), new_field), | 249 | TextEdit::insert(last_field_syntax.text_range().end(), new_field), |
235 | ); | 250 | ); |
236 | return Some(Fix::new("Create field", source_change, record_expr_field.syntax().text_range())); | 251 | return Some(fix( |
252 | "create_field", | ||
253 | "Create field", | ||
254 | source_change, | ||
255 | record_expr_field.syntax().text_range(), | ||
256 | )); | ||
237 | 257 | ||
238 | fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> { | 258 | fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> { |
239 | match field_def_list { | 259 | match field_def_list { |
diff --git a/crates/ide/src/diagnostics/unlinked_file.rs b/crates/ide/src/diagnostics/unlinked_file.rs index e174fb767..7d39f4fbe 100644 --- a/crates/ide/src/diagnostics/unlinked_file.rs +++ b/crates/ide/src/diagnostics/unlinked_file.rs | |||
@@ -16,9 +16,10 @@ use syntax::{ | |||
16 | }; | 16 | }; |
17 | use text_edit::TextEdit; | 17 | use text_edit::TextEdit; |
18 | 18 | ||
19 | use crate::Fix; | 19 | use crate::{ |
20 | 20 | diagnostics::{fix, fixes::DiagnosticWithFix}, | |
21 | use super::fixes::DiagnosticWithFix; | 21 | Assist, |
22 | }; | ||
22 | 23 | ||
23 | // Diagnostic: unlinked-file | 24 | // Diagnostic: unlinked-file |
24 | // | 25 | // |
@@ -49,7 +50,7 @@ impl Diagnostic for UnlinkedFile { | |||
49 | } | 50 | } |
50 | 51 | ||
51 | impl DiagnosticWithFix for UnlinkedFile { | 52 | impl DiagnosticWithFix for UnlinkedFile { |
52 | fn fix(&self, sema: &hir::Semantics<RootDatabase>) -> Option<Fix> { | 53 | fn fix(&self, sema: &hir::Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> { |
53 | // If there's an existing module that could add a `mod` item to include the unlinked file, | 54 | // If there's an existing module that could add a `mod` item to include the unlinked file, |
54 | // suggest that as a fix. | 55 | // suggest that as a fix. |
55 | 56 | ||
@@ -100,7 +101,7 @@ fn make_fix( | |||
100 | parent_file_id: FileId, | 101 | parent_file_id: FileId, |
101 | new_mod_name: &str, | 102 | new_mod_name: &str, |
102 | added_file_id: FileId, | 103 | added_file_id: FileId, |
103 | ) -> Option<Fix> { | 104 | ) -> Option<Assist> { |
104 | fn is_outline_mod(item: &ast::Item) -> bool { | 105 | fn is_outline_mod(item: &ast::Item) -> bool { |
105 | matches!(item, ast::Item::Module(m) if m.item_list().is_none()) | 106 | matches!(item, ast::Item::Module(m) if m.item_list().is_none()) |
106 | } | 107 | } |
@@ -152,7 +153,8 @@ fn make_fix( | |||
152 | 153 | ||
153 | let edit = builder.finish(); | 154 | let edit = builder.finish(); |
154 | let trigger_range = db.parse(added_file_id).tree().syntax().text_range(); | 155 | let trigger_range = db.parse(added_file_id).tree().syntax().text_range(); |
155 | Some(Fix::new( | 156 | Some(fix( |
157 | "add_mod_declaration", | ||
156 | &format!("Insert `{}`", mod_decl), | 158 | &format!("Insert `{}`", mod_decl), |
157 | SourceChange::from_text_edit(parent_file_id, edit), | 159 | SourceChange::from_text_edit(parent_file_id, edit), |
158 | trigger_range, | 160 | trigger_range, |
diff --git a/crates/ide/src/expand_macro.rs b/crates/ide/src/expand_macro.rs index d5628e3df..be0ee03bf 100644 --- a/crates/ide/src/expand_macro.rs +++ b/crates/ide/src/expand_macro.rs | |||
@@ -1,3 +1,5 @@ | |||
1 | use std::iter; | ||
2 | |||
1 | use hir::Semantics; | 3 | use hir::Semantics; |
2 | use ide_db::RootDatabase; | 4 | use ide_db::RootDatabase; |
3 | use syntax::{ | 5 | use syntax::{ |
@@ -91,27 +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("}"); | ||
114 | } | ||
115 | R_CURLY => { | ||
116 | res.push_str("}\n"); | ||
117 | res.extend(iter::repeat(" ").take(2 * indent)); | ||
104 | } | 118 | } |
105 | R_CURLY => format!("}}\n{}", " ".repeat(indent)), | ||
106 | LIFETIME_IDENT if is_next(|it| it == IDENT, true) => { | 119 | LIFETIME_IDENT if is_next(|it| it == IDENT, true) => { |
107 | format!("{} ", token.text().to_string()) | 120 | res.push_str(token.text()); |
121 | res.push(' '); | ||
108 | } | 122 | } |
109 | T![;] => format!(";\n{}", " ".repeat(indent)), | 123 | T![;] => { |
110 | T![->] => " -> ".to_string(), | 124 | res.push_str(";\n"); |
111 | T![=] => " = ".to_string(), | 125 | res.extend(iter::repeat(" ").take(2 * indent)); |
112 | T![=>] => " => ".to_string(), | 126 | } |
113 | _ => token.text().to_string(), | 127 | T![->] => res.push_str(" -> "), |
114 | }; | 128 | T![=] => res.push_str(" = "), |
129 | T![=>] => res.push_str(" => "), | ||
130 | _ => res.push_str(token.text()), | ||
131 | } | ||
115 | 132 | ||
116 | last = Some(token.kind()); | 133 | last = Some(token.kind()); |
117 | } | 134 | } |
diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index ca8ccb2da..a04333e63 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs | |||
@@ -110,6 +110,13 @@ mod tests { | |||
110 | assert_eq!(expected, FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() }); | 110 | assert_eq!(expected, FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() }); |
111 | } | 111 | } |
112 | 112 | ||
113 | fn check_unresolved(ra_fixture: &str) { | ||
114 | let (analysis, position) = fixture::position(ra_fixture); | ||
115 | let navs = analysis.goto_definition(position).unwrap().expect("no definition found").info; | ||
116 | |||
117 | assert!(navs.is_empty(), "didn't expect this to resolve anywhere: {:?}", navs) | ||
118 | } | ||
119 | |||
113 | #[test] | 120 | #[test] |
114 | fn goto_def_for_extern_crate() { | 121 | fn goto_def_for_extern_crate() { |
115 | check( | 122 | check( |
@@ -927,17 +934,12 @@ fn f() -> impl Iterator<Item$0 = u8> {} | |||
927 | } | 934 | } |
928 | 935 | ||
929 | #[test] | 936 | #[test] |
930 | #[should_panic = "unresolved reference"] | ||
931 | fn unknown_assoc_ty() { | 937 | fn unknown_assoc_ty() { |
932 | check( | 938 | check_unresolved( |
933 | r#" | 939 | r#" |
934 | trait Iterator { | 940 | trait Iterator { type Item; } |
935 | type Item; | ||
936 | //^^^^ | ||
937 | } | ||
938 | |||
939 | fn f() -> impl Iterator<Invalid$0 = u8> {} | 941 | fn f() -> impl Iterator<Invalid$0 = u8> {} |
940 | "#, | 942 | "#, |
941 | ) | 943 | ) |
942 | } | 944 | } |
943 | 945 | ||
@@ -1188,4 +1190,30 @@ pub fn gimme() -> theitem::TheItem { | |||
1188 | "#, | 1190 | "#, |
1189 | ); | 1191 | ); |
1190 | } | 1192 | } |
1193 | |||
1194 | #[test] | ||
1195 | fn goto_ident_from_pat_macro() { | ||
1196 | check( | ||
1197 | r#" | ||
1198 | macro_rules! pat { | ||
1199 | ($name:ident) => { Enum::Variant1($name) } | ||
1200 | } | ||
1201 | |||
1202 | enum Enum { | ||
1203 | Variant1(u8), | ||
1204 | Variant2, | ||
1205 | } | ||
1206 | |||
1207 | fn f(e: Enum) { | ||
1208 | match e { | ||
1209 | pat!(bind) => { | ||
1210 | //^^^^ | ||
1211 | bind$0 | ||
1212 | } | ||
1213 | Enum::Variant2 => {} | ||
1214 | } | ||
1215 | } | ||
1216 | "#, | ||
1217 | ); | ||
1218 | } | ||
1191 | } | 1219 | } |
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 3f73c0632..b24c664ba 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs | |||
@@ -69,7 +69,7 @@ use crate::display::ToNav; | |||
69 | pub use crate::{ | 69 | pub use crate::{ |
70 | annotations::{Annotation, AnnotationConfig, AnnotationKind}, | 70 | annotations::{Annotation, AnnotationConfig, AnnotationKind}, |
71 | call_hierarchy::CallItem, | 71 | call_hierarchy::CallItem, |
72 | diagnostics::{Diagnostic, DiagnosticsConfig, Fix, Severity}, | 72 | diagnostics::{Diagnostic, DiagnosticsConfig, Severity}, |
73 | display::navigation_target::NavigationTarget, | 73 | display::navigation_target::NavigationTarget, |
74 | expand_macro::ExpandedMacro, | 74 | expand_macro::ExpandedMacro, |
75 | file_structure::{StructureNode, StructureNodeKind}, | 75 | file_structure::{StructureNode, StructureNodeKind}, |
@@ -82,7 +82,7 @@ pub use crate::{ | |||
82 | references::{rename::RenameError, ReferenceSearchResult}, | 82 | references::{rename::RenameError, ReferenceSearchResult}, |
83 | runnables::{Runnable, RunnableKind, TestId}, | 83 | runnables::{Runnable, RunnableKind, TestId}, |
84 | syntax_highlighting::{ | 84 | syntax_highlighting::{ |
85 | tags::{Highlight, HlMod, HlMods, HlPunct, HlTag}, | 85 | tags::{Highlight, HlMod, HlMods, HlOperator, HlPunct, HlTag}, |
86 | HlRange, | 86 | HlRange, |
87 | }, | 87 | }, |
88 | }; | 88 | }; |
@@ -526,9 +526,39 @@ impl Analysis { | |||
526 | pub fn diagnostics( | 526 | pub fn diagnostics( |
527 | &self, | 527 | &self, |
528 | config: &DiagnosticsConfig, | 528 | config: &DiagnosticsConfig, |
529 | resolve: bool, | ||
529 | file_id: FileId, | 530 | file_id: FileId, |
530 | ) -> Cancelable<Vec<Diagnostic>> { | 531 | ) -> Cancelable<Vec<Diagnostic>> { |
531 | self.with_db(|db| diagnostics::diagnostics(db, config, file_id)) | 532 | self.with_db(|db| diagnostics::diagnostics(db, config, resolve, file_id)) |
533 | } | ||
534 | |||
535 | /// Convenience function to return assists + quick fixes for diagnostics | ||
536 | pub fn assists_with_fixes( | ||
537 | &self, | ||
538 | assist_config: &AssistConfig, | ||
539 | diagnostics_config: &DiagnosticsConfig, | ||
540 | resolve: bool, | ||
541 | frange: FileRange, | ||
542 | ) -> Cancelable<Vec<Assist>> { | ||
543 | let include_fixes = match &assist_config.allowed { | ||
544 | Some(it) => it.iter().any(|&it| it == AssistKind::None || it == AssistKind::QuickFix), | ||
545 | None => true, | ||
546 | }; | ||
547 | |||
548 | self.with_db(|db| { | ||
549 | let mut res = Assist::get(db, assist_config, resolve, frange); | ||
550 | ssr::add_ssr_assist(db, &mut res, resolve, frange); | ||
551 | |||
552 | if include_fixes { | ||
553 | res.extend( | ||
554 | diagnostics::diagnostics(db, diagnostics_config, resolve, frange.file_id) | ||
555 | .into_iter() | ||
556 | .filter_map(|it| it.fix) | ||
557 | .filter(|it| it.target.intersect(frange.range).is_some()), | ||
558 | ); | ||
559 | } | ||
560 | res | ||
561 | }) | ||
532 | } | 562 | } |
533 | 563 | ||
534 | /// Returns the edit required to rename reference at the position to the new | 564 | /// Returns the edit required to rename reference at the position to the new |
diff --git a/crates/ide/src/syntax_highlighting/highlight.rs b/crates/ide/src/syntax_highlighting/highlight.rs index 5ccb84714..8cc877c1c 100644 --- a/crates/ide/src/syntax_highlighting/highlight.rs +++ b/crates/ide/src/syntax_highlighting/highlight.rs | |||
@@ -12,7 +12,10 @@ use syntax::{ | |||
12 | SyntaxNode, SyntaxToken, T, | 12 | SyntaxNode, SyntaxToken, T, |
13 | }; | 13 | }; |
14 | 14 | ||
15 | use crate::{syntax_highlighting::tags::HlPunct, Highlight, HlMod, HlTag}; | 15 | use crate::{ |
16 | syntax_highlighting::tags::{HlOperator, HlPunct}, | ||
17 | Highlight, HlMod, HlTag, | ||
18 | }; | ||
16 | 19 | ||
17 | pub(super) fn element( | 20 | pub(super) fn element( |
18 | sema: &Semantics<RootDatabase>, | 21 | sema: &Semantics<RootDatabase>, |
@@ -132,7 +135,7 @@ pub(super) fn element( | |||
132 | INT_NUMBER | FLOAT_NUMBER => HlTag::NumericLiteral.into(), | 135 | INT_NUMBER | FLOAT_NUMBER => HlTag::NumericLiteral.into(), |
133 | BYTE => HlTag::ByteLiteral.into(), | 136 | BYTE => HlTag::ByteLiteral.into(), |
134 | CHAR => HlTag::CharLiteral.into(), | 137 | CHAR => HlTag::CharLiteral.into(), |
135 | QUESTION => Highlight::new(HlTag::Operator) | HlMod::ControlFlow, | 138 | QUESTION => Highlight::new(HlTag::Operator(HlOperator::Other)) | HlMod::ControlFlow, |
136 | LIFETIME => { | 139 | LIFETIME => { |
137 | let lifetime = element.into_node().and_then(ast::Lifetime::cast).unwrap(); | 140 | let lifetime = element.into_node().and_then(ast::Lifetime::cast).unwrap(); |
138 | 141 | ||
@@ -146,8 +149,11 @@ pub(super) fn element( | |||
146 | } | 149 | } |
147 | } | 150 | } |
148 | p if p.is_punct() => match p { | 151 | p if p.is_punct() => match p { |
152 | T![&] if element.parent().and_then(ast::BinExpr::cast).is_some() => { | ||
153 | HlTag::Operator(HlOperator::Bitwise).into() | ||
154 | } | ||
149 | T![&] => { | 155 | T![&] => { |
150 | let h = HlTag::Operator.into(); | 156 | let h = HlTag::Operator(HlOperator::Other).into(); |
151 | let is_unsafe = element | 157 | let is_unsafe = element |
152 | .parent() | 158 | .parent() |
153 | .and_then(ast::RefExpr::cast) | 159 | .and_then(ast::RefExpr::cast) |
@@ -159,13 +165,18 @@ pub(super) fn element( | |||
159 | h | 165 | h |
160 | } | 166 | } |
161 | } | 167 | } |
162 | T![::] | T![->] | T![=>] | T![..] | T![=] | T![@] | T![.] => HlTag::Operator.into(), | 168 | T![::] | T![->] | T![=>] | T![..] | T![=] | T![@] | T![.] => { |
169 | HlTag::Operator(HlOperator::Other).into() | ||
170 | } | ||
163 | T![!] if element.parent().and_then(ast::MacroCall::cast).is_some() => { | 171 | T![!] if element.parent().and_then(ast::MacroCall::cast).is_some() => { |
164 | HlTag::Symbol(SymbolKind::Macro).into() | 172 | HlTag::Symbol(SymbolKind::Macro).into() |
165 | } | 173 | } |
166 | T![!] if element.parent().and_then(ast::NeverType::cast).is_some() => { | 174 | T![!] if element.parent().and_then(ast::NeverType::cast).is_some() => { |
167 | HlTag::BuiltinType.into() | 175 | HlTag::BuiltinType.into() |
168 | } | 176 | } |
177 | T![!] if element.parent().and_then(ast::PrefixExpr::cast).is_some() => { | ||
178 | HlTag::Operator(HlOperator::Logical).into() | ||
179 | } | ||
169 | T![*] if element.parent().and_then(ast::PtrType::cast).is_some() => { | 180 | T![*] if element.parent().and_then(ast::PtrType::cast).is_some() => { |
170 | HlTag::Keyword.into() | 181 | HlTag::Keyword.into() |
171 | } | 182 | } |
@@ -175,9 +186,9 @@ pub(super) fn element( | |||
175 | let expr = prefix_expr.expr()?; | 186 | let expr = prefix_expr.expr()?; |
176 | let ty = sema.type_of_expr(&expr)?; | 187 | let ty = sema.type_of_expr(&expr)?; |
177 | if ty.is_raw_ptr() { | 188 | if ty.is_raw_ptr() { |
178 | HlTag::Operator | HlMod::Unsafe | 189 | HlTag::Operator(HlOperator::Other) | HlMod::Unsafe |
179 | } else if let Some(ast::PrefixOp::Deref) = prefix_expr.op_kind() { | 190 | } else if let Some(ast::PrefixOp::Deref) = prefix_expr.op_kind() { |
180 | HlTag::Operator.into() | 191 | HlTag::Operator(HlOperator::Other).into() |
181 | } else { | 192 | } else { |
182 | HlTag::Punctuation(HlPunct::Other).into() | 193 | HlTag::Punctuation(HlPunct::Other).into() |
183 | } | 194 | } |
@@ -188,19 +199,43 @@ pub(super) fn element( | |||
188 | let expr = prefix_expr.expr()?; | 199 | let expr = prefix_expr.expr()?; |
189 | match expr { | 200 | match expr { |
190 | ast::Expr::Literal(_) => HlTag::NumericLiteral, | 201 | ast::Expr::Literal(_) => HlTag::NumericLiteral, |
191 | _ => HlTag::Operator, | 202 | _ => HlTag::Operator(HlOperator::Other), |
192 | } | 203 | } |
193 | .into() | 204 | .into() |
194 | } | 205 | } |
195 | _ if element.parent().and_then(ast::PrefixExpr::cast).is_some() => { | 206 | _ if element.parent().and_then(ast::PrefixExpr::cast).is_some() => { |
196 | HlTag::Operator.into() | 207 | HlTag::Operator(HlOperator::Other).into() |
208 | } | ||
209 | T![+] | T![-] | T![*] | T![/] | T![+=] | T![-=] | T![*=] | T![/=] | ||
210 | if element.parent().and_then(ast::BinExpr::cast).is_some() => | ||
211 | { | ||
212 | HlTag::Operator(HlOperator::Arithmetic).into() | ||
213 | } | ||
214 | T![|] | T![&] | T![!] | T![^] | T![|=] | T![&=] | T![^=] | ||
215 | if element.parent().and_then(ast::BinExpr::cast).is_some() => | ||
216 | { | ||
217 | HlTag::Operator(HlOperator::Bitwise).into() | ||
218 | } | ||
219 | T![&&] | T![||] if element.parent().and_then(ast::BinExpr::cast).is_some() => { | ||
220 | HlTag::Operator(HlOperator::Logical).into() | ||
221 | } | ||
222 | T![>] | T![<] | T![==] | T![>=] | T![<=] | T![!=] | ||
223 | if element.parent().and_then(ast::BinExpr::cast).is_some() => | ||
224 | { | ||
225 | HlTag::Operator(HlOperator::Comparision).into() | ||
226 | } | ||
227 | _ if element.parent().and_then(ast::BinExpr::cast).is_some() => { | ||
228 | HlTag::Operator(HlOperator::Other).into() | ||
197 | } | 229 | } |
198 | _ if element.parent().and_then(ast::BinExpr::cast).is_some() => HlTag::Operator.into(), | ||
199 | _ if element.parent().and_then(ast::RangeExpr::cast).is_some() => { | 230 | _ if element.parent().and_then(ast::RangeExpr::cast).is_some() => { |
200 | HlTag::Operator.into() | 231 | HlTag::Operator(HlOperator::Other).into() |
232 | } | ||
233 | _ if element.parent().and_then(ast::RangePat::cast).is_some() => { | ||
234 | HlTag::Operator(HlOperator::Other).into() | ||
235 | } | ||
236 | _ if element.parent().and_then(ast::RestPat::cast).is_some() => { | ||
237 | HlTag::Operator(HlOperator::Other).into() | ||
201 | } | 238 | } |
202 | _ if element.parent().and_then(ast::RangePat::cast).is_some() => HlTag::Operator.into(), | ||
203 | _ if element.parent().and_then(ast::RestPat::cast).is_some() => HlTag::Operator.into(), | ||
204 | _ if element.parent().and_then(ast::Attr::cast).is_some() => HlTag::Attribute.into(), | 239 | _ if element.parent().and_then(ast::Attr::cast).is_some() => HlTag::Attribute.into(), |
205 | kind => HlTag::Punctuation(match kind { | 240 | kind => HlTag::Punctuation(match kind { |
206 | T!['['] | T![']'] => HlPunct::Bracket, | 241 | T!['['] | T![']'] => HlPunct::Bracket, |
@@ -323,8 +358,18 @@ fn highlight_def(db: &RootDatabase, def: Definition) -> Highlight { | |||
323 | hir::ModuleDef::Trait(_) => HlTag::Symbol(SymbolKind::Trait), | 358 | hir::ModuleDef::Trait(_) => HlTag::Symbol(SymbolKind::Trait), |
324 | hir::ModuleDef::TypeAlias(type_) => { | 359 | hir::ModuleDef::TypeAlias(type_) => { |
325 | let mut h = Highlight::new(HlTag::Symbol(SymbolKind::TypeAlias)); | 360 | let mut h = Highlight::new(HlTag::Symbol(SymbolKind::TypeAlias)); |
326 | if type_.as_assoc_item(db).is_some() { | 361 | if let Some(item) = type_.as_assoc_item(db) { |
327 | h |= HlMod::Associated | 362 | h |= HlMod::Associated; |
363 | match item.container(db) { | ||
364 | AssocItemContainer::Impl(i) => { | ||
365 | if i.trait_(db).is_some() { | ||
366 | h |= HlMod::Trait; | ||
367 | } | ||
368 | } | ||
369 | AssocItemContainer::Trait(_t) => { | ||
370 | h |= HlMod::Trait; | ||
371 | } | ||
372 | } | ||
328 | } | 373 | } |
329 | return h; | 374 | return h; |
330 | } | 375 | } |
diff --git a/crates/ide/src/syntax_highlighting/tags.rs b/crates/ide/src/syntax_highlighting/tags.rs index 1cec991aa..8128d231d 100644 --- a/crates/ide/src/syntax_highlighting/tags.rs +++ b/crates/ide/src/syntax_highlighting/tags.rs | |||
@@ -28,7 +28,7 @@ pub enum HlTag { | |||
28 | FormatSpecifier, | 28 | FormatSpecifier, |
29 | Keyword, | 29 | Keyword, |
30 | NumericLiteral, | 30 | NumericLiteral, |
31 | Operator, | 31 | Operator(HlOperator), |
32 | Punctuation(HlPunct), | 32 | Punctuation(HlPunct), |
33 | StringLiteral, | 33 | StringLiteral, |
34 | UnresolvedReference, | 34 | UnresolvedReference, |
@@ -87,6 +87,20 @@ pub enum HlPunct { | |||
87 | Other, | 87 | Other, |
88 | } | 88 | } |
89 | 89 | ||
90 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] | ||
91 | pub enum HlOperator { | ||
92 | /// |, &, !, ^, |=, &=, ^= | ||
93 | Bitwise, | ||
94 | /// +, -, *, /, +=, -=, *=, /= | ||
95 | Arithmetic, | ||
96 | /// &&, ||, ! | ||
97 | Logical, | ||
98 | /// >, <, ==, >=, <=, != | ||
99 | Comparision, | ||
100 | /// | ||
101 | Other, | ||
102 | } | ||
103 | |||
90 | impl HlTag { | 104 | impl HlTag { |
91 | fn as_str(self) -> &'static str { | 105 | fn as_str(self) -> &'static str { |
92 | match self { | 106 | match self { |
@@ -133,7 +147,13 @@ impl HlTag { | |||
133 | HlPunct::Other => "punctuation", | 147 | HlPunct::Other => "punctuation", |
134 | }, | 148 | }, |
135 | HlTag::NumericLiteral => "numeric_literal", | 149 | HlTag::NumericLiteral => "numeric_literal", |
136 | HlTag::Operator => "operator", | 150 | HlTag::Operator(op) => match op { |
151 | HlOperator::Bitwise => "bitwise", | ||
152 | HlOperator::Arithmetic => "arithmetic", | ||
153 | HlOperator::Logical => "logical", | ||
154 | HlOperator::Comparision => "comparision", | ||
155 | HlOperator::Other => "operator", | ||
156 | }, | ||
137 | HlTag::StringLiteral => "string_literal", | 157 | HlTag::StringLiteral => "string_literal", |
138 | HlTag::UnresolvedReference => "unresolved_reference", | 158 | HlTag::UnresolvedReference => "unresolved_reference", |
139 | HlTag::None => "none", | 159 | HlTag::None => "none", |
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html index b6d1cac4e..6ee6d85fb 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html | |||
@@ -76,7 +76,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
76 | <span class="comment documentation">/// </span><span class="comment injected">// calls bar on foo</span> | 76 | <span class="comment documentation">/// </span><span class="comment injected">// calls bar on foo</span> |
77 | <span class="comment documentation">/// </span><span class="macro injected">assert!</span><span class="parenthesis injected">(</span><span class="none injected">foo</span><span class="operator injected">.</span><span class="none injected">bar</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span> | 77 | <span class="comment documentation">/// </span><span class="macro injected">assert!</span><span class="parenthesis injected">(</span><span class="none injected">foo</span><span class="operator injected">.</span><span class="none injected">bar</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span> |
78 | <span class="comment documentation">///</span> | 78 | <span class="comment documentation">///</span> |
79 | <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="variable declaration injected">bar</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="variable injected">foo</span><span class="operator injected">.</span><span class="field injected">bar</span><span class="none injected"> </span><span class="operator injected">||</span><span class="none injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="constant injected">bar</span><span class="semicolon injected">;</span> | 79 | <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="variable declaration injected">bar</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="variable injected">foo</span><span class="operator injected">.</span><span class="field injected">bar</span><span class="none injected"> </span><span class="logical injected">||</span><span class="none injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="constant injected">bar</span><span class="semicolon injected">;</span> |
80 | <span class="comment documentation">///</span> | 80 | <span class="comment documentation">///</span> |
81 | <span class="comment documentation">/// </span><span class="comment injected">/* multi-line</span> | 81 | <span class="comment documentation">/// </span><span class="comment injected">/* multi-line</span> |
82 | <span class="comment documentation">/// </span><span class="comment injected"> comment */</span> | 82 | <span class="comment documentation">/// </span><span class="comment injected"> comment */</span> |
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlighting.html b/crates/ide/src/syntax_highlighting/test_data/highlighting.html index 973173254..c43bcb691 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlighting.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlighting.html | |||
@@ -213,7 +213,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
213 | <span class="keyword">let</span> <span class="variable declaration">baz</span> <span class="operator">=</span> <span class="numeric_literal">-</span><span class="numeric_literal">42</span><span class="semicolon">;</span> | 213 | <span class="keyword">let</span> <span class="variable declaration">baz</span> <span class="operator">=</span> <span class="numeric_literal">-</span><span class="numeric_literal">42</span><span class="semicolon">;</span> |
214 | <span class="keyword">let</span> <span class="variable declaration">baz</span> <span class="operator">=</span> <span class="operator">-</span><span class="variable">baz</span><span class="semicolon">;</span> | 214 | <span class="keyword">let</span> <span class="variable declaration">baz</span> <span class="operator">=</span> <span class="operator">-</span><span class="variable">baz</span><span class="semicolon">;</span> |
215 | 215 | ||
216 | <span class="keyword">let</span> <span class="punctuation">_</span> <span class="operator">=</span> <span class="operator">!</span><span class="bool_literal">true</span><span class="semicolon">;</span> | 216 | <span class="keyword">let</span> <span class="punctuation">_</span> <span class="operator">=</span> <span class="logical">!</span><span class="bool_literal">true</span><span class="semicolon">;</span> |
217 | 217 | ||
218 | <span class="label declaration">'foo</span><span class="colon">:</span> <span class="keyword control">loop</span> <span class="brace">{</span> | 218 | <span class="label declaration">'foo</span><span class="colon">:</span> <span class="keyword control">loop</span> <span class="brace">{</span> |
219 | <span class="keyword control">break</span> <span class="label">'foo</span><span class="semicolon">;</span> | 219 | <span class="keyword control">break</span> <span class="label">'foo</span><span class="semicolon">;</span> |
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs index 1b02857ec..933cfa6f3 100644 --- a/crates/ide/src/syntax_highlighting/tests.rs +++ b/crates/ide/src/syntax_highlighting/tests.rs | |||
@@ -1,6 +1,8 @@ | |||
1 | use std::time::Instant; | ||
2 | |||
1 | use expect_test::{expect_file, ExpectFile}; | 3 | use expect_test::{expect_file, ExpectFile}; |
2 | use ide_db::SymbolKind; | 4 | use ide_db::SymbolKind; |
3 | use test_utils::{bench, bench_fixture, skip_slow_tests}; | 5 | use test_utils::{bench, bench_fixture, skip_slow_tests, AssertLinear}; |
4 | 6 | ||
5 | use crate::{fixture, FileRange, HlTag, TextRange}; | 7 | use crate::{fixture, FileRange, HlTag, TextRange}; |
6 | 8 | ||
@@ -258,6 +260,36 @@ fn benchmark_syntax_highlighting_long_struct() { | |||
258 | } | 260 | } |
259 | 261 | ||
260 | #[test] | 262 | #[test] |
263 | fn syntax_highlighting_not_quadratic() { | ||
264 | if skip_slow_tests() { | ||
265 | return; | ||
266 | } | ||
267 | |||
268 | let mut al = AssertLinear::default(); | ||
269 | while al.next_round() { | ||
270 | for i in 6..=10 { | ||
271 | let n = 1 << i; | ||
272 | |||
273 | let fixture = bench_fixture::big_struct_n(n); | ||
274 | let (analysis, file_id) = fixture::file(&fixture); | ||
275 | |||
276 | let time = Instant::now(); | ||
277 | |||
278 | let hash = analysis | ||
279 | .highlight(file_id) | ||
280 | .unwrap() | ||
281 | .iter() | ||
282 | .filter(|it| it.highlight.tag == HlTag::Symbol(SymbolKind::Struct)) | ||
283 | .count(); | ||
284 | assert!(hash > n as usize); | ||
285 | |||
286 | let elapsed = time.elapsed(); | ||
287 | al.sample(n as f64, elapsed.as_millis() as f64); | ||
288 | } | ||
289 | } | ||
290 | } | ||
291 | |||
292 | #[test] | ||
261 | fn benchmark_syntax_highlighting_parser() { | 293 | fn benchmark_syntax_highlighting_parser() { |
262 | if skip_slow_tests() { | 294 | if skip_slow_tests() { |
263 | return; | 295 | return; |
diff --git a/crates/ide/src/typing/on_enter.rs b/crates/ide/src/typing/on_enter.rs index 9144681bf..7d2db201a 100644 --- a/crates/ide/src/typing/on_enter.rs +++ b/crates/ide/src/typing/on_enter.rs | |||
@@ -4,10 +4,11 @@ | |||
4 | use ide_db::base_db::{FilePosition, SourceDatabase}; | 4 | use ide_db::base_db::{FilePosition, SourceDatabase}; |
5 | use ide_db::RootDatabase; | 5 | use ide_db::RootDatabase; |
6 | use syntax::{ | 6 | use syntax::{ |
7 | ast::{self, AstToken}, | 7 | algo::find_node_at_offset, |
8 | ast::{self, edit::IndentLevel, AstToken}, | ||
8 | AstNode, SmolStr, SourceFile, | 9 | AstNode, SmolStr, SourceFile, |
9 | SyntaxKind::*, | 10 | SyntaxKind::*, |
10 | SyntaxToken, TextRange, TextSize, TokenAtOffset, | 11 | SyntaxNode, SyntaxToken, TextRange, TextSize, TokenAtOffset, |
11 | }; | 12 | }; |
12 | 13 | ||
13 | use text_edit::TextEdit; | 14 | use text_edit::TextEdit; |
@@ -18,6 +19,8 @@ use text_edit::TextEdit; | |||
18 | // | 19 | // |
19 | // - kbd:[Enter] inside triple-slash comments automatically inserts `///` | 20 | // - kbd:[Enter] inside triple-slash comments automatically inserts `///` |
20 | // - kbd:[Enter] in the middle or after a trailing space in `//` inserts `//` | 21 | // - kbd:[Enter] in the middle or after a trailing space in `//` inserts `//` |
22 | // - kbd:[Enter] inside `//!` doc comments automatically inserts `//!` | ||
23 | // - kbd:[Enter] after `{` indents contents and closing `}` of single-line block | ||
21 | // | 24 | // |
22 | // This action needs to be assigned to shortcut explicitly. | 25 | // This action needs to be assigned to shortcut explicitly. |
23 | // | 26 | // |
@@ -37,25 +40,43 @@ use text_edit::TextEdit; | |||
37 | pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<TextEdit> { | 40 | pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<TextEdit> { |
38 | let parse = db.parse(position.file_id); | 41 | let parse = db.parse(position.file_id); |
39 | let file = parse.tree(); | 42 | let file = parse.tree(); |
40 | let comment = file | 43 | let token = file.syntax().token_at_offset(position.offset).left_biased()?; |
41 | .syntax() | ||
42 | .token_at_offset(position.offset) | ||
43 | .left_biased() | ||
44 | .and_then(ast::Comment::cast)?; | ||
45 | 44 | ||
45 | if let Some(comment) = ast::Comment::cast(token.clone()) { | ||
46 | return on_enter_in_comment(&comment, &file, position.offset); | ||
47 | } | ||
48 | |||
49 | if token.kind() == L_CURLY { | ||
50 | // Typing enter after the `{` of a block expression, where the `}` is on the same line | ||
51 | if let Some(edit) = find_node_at_offset(file.syntax(), position.offset - TextSize::of('{')) | ||
52 | .and_then(|block| on_enter_in_block(block, position)) | ||
53 | { | ||
54 | cov_mark::hit!(indent_block_contents); | ||
55 | return Some(edit); | ||
56 | } | ||
57 | } | ||
58 | |||
59 | None | ||
60 | } | ||
61 | |||
62 | fn on_enter_in_comment( | ||
63 | comment: &ast::Comment, | ||
64 | file: &ast::SourceFile, | ||
65 | offset: TextSize, | ||
66 | ) -> Option<TextEdit> { | ||
46 | if comment.kind().shape.is_block() { | 67 | if comment.kind().shape.is_block() { |
47 | return None; | 68 | return None; |
48 | } | 69 | } |
49 | 70 | ||
50 | let prefix = comment.prefix(); | 71 | let prefix = comment.prefix(); |
51 | let comment_range = comment.syntax().text_range(); | 72 | let comment_range = comment.syntax().text_range(); |
52 | if position.offset < comment_range.start() + TextSize::of(prefix) { | 73 | if offset < comment_range.start() + TextSize::of(prefix) { |
53 | return None; | 74 | return None; |
54 | } | 75 | } |
55 | 76 | ||
56 | let mut remove_trailing_whitespace = false; | 77 | let mut remove_trailing_whitespace = false; |
57 | // Continuing single-line non-doc comments (like this one :) ) is annoying | 78 | // Continuing single-line non-doc comments (like this one :) ) is annoying |
58 | if prefix == "//" && comment_range.end() == position.offset { | 79 | if prefix == "//" && comment_range.end() == offset { |
59 | if comment.text().ends_with(' ') { | 80 | if comment.text().ends_with(' ') { |
60 | cov_mark::hit!(continues_end_of_line_comment_with_space); | 81 | cov_mark::hit!(continues_end_of_line_comment_with_space); |
61 | remove_trailing_whitespace = true; | 82 | remove_trailing_whitespace = true; |
@@ -69,14 +90,42 @@ pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<Text | |||
69 | let delete = if remove_trailing_whitespace { | 90 | let delete = if remove_trailing_whitespace { |
70 | let trimmed_len = comment.text().trim_end().len() as u32; | 91 | let trimmed_len = comment.text().trim_end().len() as u32; |
71 | let trailing_whitespace_len = comment.text().len() as u32 - trimmed_len; | 92 | let trailing_whitespace_len = comment.text().len() as u32 - trimmed_len; |
72 | TextRange::new(position.offset - TextSize::from(trailing_whitespace_len), position.offset) | 93 | TextRange::new(offset - TextSize::from(trailing_whitespace_len), offset) |
73 | } else { | 94 | } else { |
74 | TextRange::empty(position.offset) | 95 | TextRange::empty(offset) |
75 | }; | 96 | }; |
76 | let edit = TextEdit::replace(delete, inserted); | 97 | let edit = TextEdit::replace(delete, inserted); |
77 | Some(edit) | 98 | Some(edit) |
78 | } | 99 | } |
79 | 100 | ||
101 | fn on_enter_in_block(block: ast::BlockExpr, position: FilePosition) -> Option<TextEdit> { | ||
102 | let contents = block_contents(&block)?; | ||
103 | |||
104 | if block.syntax().text().contains_char('\n') { | ||
105 | return None; | ||
106 | } | ||
107 | |||
108 | let indent = IndentLevel::from_node(block.syntax()); | ||
109 | let mut edit = TextEdit::insert(position.offset, format!("\n{}$0", indent + 1)); | ||
110 | edit.union(TextEdit::insert(contents.text_range().end(), format!("\n{}", indent))).ok()?; | ||
111 | Some(edit) | ||
112 | } | ||
113 | |||
114 | fn block_contents(block: &ast::BlockExpr) -> Option<SyntaxNode> { | ||
115 | let mut node = block.tail_expr().map(|e| e.syntax().clone()); | ||
116 | |||
117 | for stmt in block.statements() { | ||
118 | if node.is_some() { | ||
119 | // More than 1 node in the block | ||
120 | return None; | ||
121 | } | ||
122 | |||
123 | node = Some(stmt.syntax().clone()); | ||
124 | } | ||
125 | |||
126 | node | ||
127 | } | ||
128 | |||
80 | fn followed_by_comment(comment: &ast::Comment) -> bool { | 129 | fn followed_by_comment(comment: &ast::Comment) -> bool { |
81 | let ws = match comment.syntax().next_token().and_then(ast::Whitespace::cast) { | 130 | let ws = match comment.syntax().next_token().and_then(ast::Whitespace::cast) { |
82 | Some(it) => it, | 131 | Some(it) => it, |
@@ -187,6 +236,25 @@ fn foo() { | |||
187 | } | 236 | } |
188 | 237 | ||
189 | #[test] | 238 | #[test] |
239 | fn continues_another_doc_comment() { | ||
240 | do_check( | ||
241 | r#" | ||
242 | fn main() { | ||
243 | //! Documentation for$0 on enter | ||
244 | let x = 1 + 1; | ||
245 | } | ||
246 | "#, | ||
247 | r#" | ||
248 | fn main() { | ||
249 | //! Documentation for | ||
250 | //! $0 on enter | ||
251 | let x = 1 + 1; | ||
252 | } | ||
253 | "#, | ||
254 | ); | ||
255 | } | ||
256 | |||
257 | #[test] | ||
190 | fn continues_code_comment_in_the_middle_of_line() { | 258 | fn continues_code_comment_in_the_middle_of_line() { |
191 | do_check( | 259 | do_check( |
192 | r" | 260 | r" |
@@ -276,4 +344,144 @@ fn main() { | |||
276 | ", | 344 | ", |
277 | ); | 345 | ); |
278 | } | 346 | } |
347 | |||
348 | #[test] | ||
349 | fn indents_fn_body_block() { | ||
350 | cov_mark::check!(indent_block_contents); | ||
351 | do_check( | ||
352 | r#" | ||
353 | fn f() {$0()} | ||
354 | "#, | ||
355 | r#" | ||
356 | fn f() { | ||
357 | $0() | ||
358 | } | ||
359 | "#, | ||
360 | ); | ||
361 | } | ||
362 | |||
363 | #[test] | ||
364 | fn indents_block_expr() { | ||
365 | do_check( | ||
366 | r#" | ||
367 | fn f() { | ||
368 | let x = {$0()}; | ||
369 | } | ||
370 | "#, | ||
371 | r#" | ||
372 | fn f() { | ||
373 | let x = { | ||
374 | $0() | ||
375 | }; | ||
376 | } | ||
377 | "#, | ||
378 | ); | ||
379 | } | ||
380 | |||
381 | #[test] | ||
382 | fn indents_match_arm() { | ||
383 | do_check( | ||
384 | r#" | ||
385 | fn f() { | ||
386 | match 6 { | ||
387 | 1 => {$0f()}, | ||
388 | _ => (), | ||
389 | } | ||
390 | } | ||
391 | "#, | ||
392 | r#" | ||
393 | fn f() { | ||
394 | match 6 { | ||
395 | 1 => { | ||
396 | $0f() | ||
397 | }, | ||
398 | _ => (), | ||
399 | } | ||
400 | } | ||
401 | "#, | ||
402 | ); | ||
403 | } | ||
404 | |||
405 | #[test] | ||
406 | fn indents_block_with_statement() { | ||
407 | do_check( | ||
408 | r#" | ||
409 | fn f() {$0a = b} | ||
410 | "#, | ||
411 | r#" | ||
412 | fn f() { | ||
413 | $0a = b | ||
414 | } | ||
415 | "#, | ||
416 | ); | ||
417 | do_check( | ||
418 | r#" | ||
419 | fn f() {$0fn f() {}} | ||
420 | "#, | ||
421 | r#" | ||
422 | fn f() { | ||
423 | $0fn f() {} | ||
424 | } | ||
425 | "#, | ||
426 | ); | ||
427 | } | ||
428 | |||
429 | #[test] | ||
430 | fn indents_nested_blocks() { | ||
431 | do_check( | ||
432 | r#" | ||
433 | fn f() {$0{}} | ||
434 | "#, | ||
435 | r#" | ||
436 | fn f() { | ||
437 | $0{} | ||
438 | } | ||
439 | "#, | ||
440 | ); | ||
441 | } | ||
442 | |||
443 | #[test] | ||
444 | fn does_not_indent_empty_block() { | ||
445 | do_check_noop( | ||
446 | r#" | ||
447 | fn f() {$0} | ||
448 | "#, | ||
449 | ); | ||
450 | do_check_noop( | ||
451 | r#" | ||
452 | fn f() {{$0}} | ||
453 | "#, | ||
454 | ); | ||
455 | } | ||
456 | |||
457 | #[test] | ||
458 | fn does_not_indent_block_with_too_much_content() { | ||
459 | do_check_noop( | ||
460 | r#" | ||
461 | fn f() {$0 a = b; ()} | ||
462 | "#, | ||
463 | ); | ||
464 | do_check_noop( | ||
465 | r#" | ||
466 | fn f() {$0 a = b; a = b; } | ||
467 | "#, | ||
468 | ); | ||
469 | } | ||
470 | |||
471 | #[test] | ||
472 | fn does_not_indent_multiline_block() { | ||
473 | do_check_noop( | ||
474 | r#" | ||
475 | fn f() {$0 | ||
476 | } | ||
477 | "#, | ||
478 | ); | ||
479 | do_check_noop( | ||
480 | r#" | ||
481 | fn f() {$0 | ||
482 | |||
483 | } | ||
484 | "#, | ||
485 | ); | ||
486 | } | ||
279 | } | 487 | } |