diff options
Diffstat (limited to 'crates/ide/src')
-rw-r--r-- | crates/ide/src/diagnostics.rs | 143 | ||||
-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/goto_definition.rs | 18 | ||||
-rw-r--r-- | crates/ide/src/lib.rs | 36 | ||||
-rw-r--r-- | crates/ide/src/move_item.rs | 100 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting/highlight.rs | 59 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting/inject.rs | 10 | ||||
-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 | 98 | ||||
-rw-r--r-- | crates/ide/src/typing.rs | 200 | ||||
-rw-r--r-- | crates/ide/src/typing/on_enter.rs | 210 |
15 files changed, 646 insertions, 338 deletions
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index 0ace80a1e..1c911a8b2 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs | |||
@@ -25,7 +25,7 @@ use syntax::{ | |||
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. |
@@ -167,7 +153,7 @@ pub(crate) fn diagnostics( | |||
167 | // Override severity and mark as unused. | 153 | // Override severity and mark as unused. |
168 | res.borrow_mut().push( | 154 | res.borrow_mut().push( |
169 | Diagnostic::hint(range, d.message()) | 155 | Diagnostic::hint(range, d.message()) |
170 | .with_fix(d.fix(&sema)) | 156 | .with_fix(d.fix(&sema, resolve)) |
171 | .with_code(Some(d.code())), | 157 | .with_code(Some(d.code())), |
172 | ); | 158 | ); |
173 | }) | 159 | }) |
@@ -223,15 +209,23 @@ pub(crate) fn diagnostics( | |||
223 | res.into_inner() | 209 | res.into_inner() |
224 | } | 210 | } |
225 | 211 | ||
226 | 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 { | ||
227 | 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()) |
228 | .with_fix(d.fix(&sema)) | 218 | .with_fix(d.fix(&sema, resolve)) |
229 | .with_code(Some(d.code())) | 219 | .with_code(Some(d.code())) |
230 | } | 220 | } |
231 | 221 | ||
232 | 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 { | ||
233 | 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()) |
234 | .with_fix(d.fix(&sema)) | 228 | .with_fix(d.fix(&sema, resolve)) |
235 | .with_code(Some(d.code())) | 229 | .with_code(Some(d.code())) |
236 | } | 230 | } |
237 | 231 | ||
@@ -261,7 +255,8 @@ fn check_unnecessary_braces_in_use_statement( | |||
261 | 255 | ||
262 | acc.push( | 256 | acc.push( |
263 | Diagnostic::hint(use_range, "Unnecessary braces in use statement".to_string()) | 257 | Diagnostic::hint(use_range, "Unnecessary braces in use statement".to_string()) |
264 | .with_fix(Some(Fix::new( | 258 | .with_fix(Some(fix( |
259 | "remove_braces", | ||
265 | "Remove unnecessary braces", | 260 | "Remove unnecessary braces", |
266 | SourceChange::from_text_edit(file_id, edit), | 261 | SourceChange::from_text_edit(file_id, edit), |
267 | use_range, | 262 | use_range, |
@@ -284,6 +279,23 @@ fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement( | |||
284 | None | 279 | None |
285 | } | 280 | } |
286 | 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 | |||
287 | #[cfg(test)] | 299 | #[cfg(test)] |
288 | mod tests { | 300 | mod tests { |
289 | use expect_test::{expect, Expect}; | 301 | use expect_test::{expect, Expect}; |
@@ -302,16 +314,17 @@ mod tests { | |||
302 | 314 | ||
303 | let (analysis, file_position) = fixture::position(ra_fixture_before); | 315 | let (analysis, file_position) = fixture::position(ra_fixture_before); |
304 | let diagnostic = analysis | 316 | let diagnostic = analysis |
305 | .diagnostics(&DiagnosticsConfig::default(), file_position.file_id) | 317 | .diagnostics(&DiagnosticsConfig::default(), true, file_position.file_id) |
306 | .unwrap() | 318 | .unwrap() |
307 | .pop() | 319 | .pop() |
308 | .unwrap(); | 320 | .unwrap(); |
309 | let fix = diagnostic.fix.unwrap(); | 321 | let fix = diagnostic.fix.unwrap(); |
310 | let actual = { | 322 | let actual = { |
311 | 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(); | ||
312 | let mut actual = analysis.file_text(file_id).unwrap().to_string(); | 325 | let mut actual = analysis.file_text(file_id).unwrap().to_string(); |
313 | 326 | ||
314 | for edit in fix.source_change.source_file_edits.values() { | 327 | for edit in source_change.source_file_edits.values() { |
315 | edit.apply(&mut actual); | 328 | edit.apply(&mut actual); |
316 | } | 329 | } |
317 | actual | 330 | actual |
@@ -319,9 +332,9 @@ mod tests { | |||
319 | 332 | ||
320 | assert_eq_text!(&after, &actual); | 333 | assert_eq_text!(&after, &actual); |
321 | assert!( | 334 | assert!( |
322 | fix.fix_trigger_range.contains_inclusive(file_position.offset), | 335 | fix.target.contains_inclusive(file_position.offset), |
323 | "diagnostic fix range {:?} does not touch cursor position {:?}", | 336 | "diagnostic fix range {:?} does not touch cursor position {:?}", |
324 | fix.fix_trigger_range, | 337 | fix.target, |
325 | file_position.offset | 338 | file_position.offset |
326 | ); | 339 | ); |
327 | } | 340 | } |
@@ -330,7 +343,7 @@ mod tests { | |||
330 | fn check_no_fix(ra_fixture: &str) { | 343 | fn check_no_fix(ra_fixture: &str) { |
331 | let (analysis, file_position) = fixture::position(ra_fixture); | 344 | let (analysis, file_position) = fixture::position(ra_fixture); |
332 | let diagnostic = analysis | 345 | let diagnostic = analysis |
333 | .diagnostics(&DiagnosticsConfig::default(), file_position.file_id) | 346 | .diagnostics(&DiagnosticsConfig::default(), true, file_position.file_id) |
334 | .unwrap() | 347 | .unwrap() |
335 | .pop() | 348 | .pop() |
336 | .unwrap(); | 349 | .unwrap(); |
@@ -344,7 +357,7 @@ mod tests { | |||
344 | let diagnostics = files | 357 | let diagnostics = files |
345 | .into_iter() | 358 | .into_iter() |
346 | .flat_map(|file_id| { | 359 | .flat_map(|file_id| { |
347 | analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap() | 360 | analysis.diagnostics(&DiagnosticsConfig::default(), true, file_id).unwrap() |
348 | }) | 361 | }) |
349 | .collect::<Vec<_>>(); | 362 | .collect::<Vec<_>>(); |
350 | assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics); | 363 | assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics); |
@@ -352,7 +365,8 @@ mod tests { | |||
352 | 365 | ||
353 | fn check_expect(ra_fixture: &str, expect: Expect) { | 366 | fn check_expect(ra_fixture: &str, expect: Expect) { |
354 | let (analysis, file_id) = fixture::file(ra_fixture); | 367 | let (analysis, file_id) = fixture::file(ra_fixture); |
355 | let diagnostics = analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap(); | 368 | let diagnostics = |
369 | analysis.diagnostics(&DiagnosticsConfig::default(), true, file_id).unwrap(); | ||
356 | expect.assert_debug_eq(&diagnostics) | 370 | expect.assert_debug_eq(&diagnostics) |
357 | } | 371 | } |
358 | 372 | ||
@@ -665,24 +679,31 @@ fn test_fn() { | |||
665 | range: 0..8, | 679 | range: 0..8, |
666 | severity: Error, | 680 | severity: Error, |
667 | fix: Some( | 681 | fix: Some( |
668 | Fix { | 682 | Assist { |
683 | id: AssistId( | ||
684 | "create_module", | ||
685 | QuickFix, | ||
686 | ), | ||
669 | label: "Create module", | 687 | label: "Create module", |
670 | source_change: SourceChange { | 688 | group: None, |
671 | source_file_edits: {}, | 689 | target: 0..8, |
672 | file_system_edits: [ | 690 | source_change: Some( |
673 | CreateFile { | 691 | SourceChange { |
674 | dst: AnchoredPathBuf { | 692 | source_file_edits: {}, |
675 | anchor: FileId( | 693 | file_system_edits: [ |
676 | 0, | 694 | CreateFile { |
677 | ), | 695 | dst: AnchoredPathBuf { |
678 | path: "foo.rs", | 696 | anchor: FileId( |
697 | 0, | ||
698 | ), | ||
699 | path: "foo.rs", | ||
700 | }, | ||
701 | initial_contents: "", | ||
679 | }, | 702 | }, |
680 | initial_contents: "", | 703 | ], |
681 | }, | 704 | is_snippet: false, |
682 | ], | 705 | }, |
683 | is_snippet: false, | 706 | ), |
684 | }, | ||
685 | fix_trigger_range: 0..8, | ||
686 | }, | 707 | }, |
687 | ), | 708 | ), |
688 | unused: false, | 709 | unused: false, |
@@ -704,7 +725,7 @@ fn test_fn() { | |||
704 | expect![[r#" | 725 | expect![[r#" |
705 | [ | 726 | [ |
706 | Diagnostic { | 727 | Diagnostic { |
707 | message: "unresolved macro call", | 728 | message: "unresolved macro `foo::bar!`", |
708 | range: 5..8, | 729 | range: 5..8, |
709 | severity: Error, | 730 | severity: Error, |
710 | fix: None, | 731 | fix: None, |
@@ -890,10 +911,11 @@ struct Foo { | |||
890 | 911 | ||
891 | let (analysis, file_id) = fixture::file(r#"mod foo;"#); | 912 | let (analysis, file_id) = fixture::file(r#"mod foo;"#); |
892 | 913 | ||
893 | let diagnostics = analysis.diagnostics(&config, file_id).unwrap(); | 914 | let diagnostics = analysis.diagnostics(&config, true, file_id).unwrap(); |
894 | assert!(diagnostics.is_empty()); | 915 | assert!(diagnostics.is_empty()); |
895 | 916 | ||
896 | let diagnostics = analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap(); | 917 | let diagnostics = |
918 | analysis.diagnostics(&DiagnosticsConfig::default(), true, file_id).unwrap(); | ||
897 | assert!(!diagnostics.is_empty()); | 919 | assert!(!diagnostics.is_empty()); |
898 | } | 920 | } |
899 | 921 | ||
@@ -999,8 +1021,9 @@ impl TestStruct { | |||
999 | let expected = r#"fn foo() {}"#; | 1021 | let expected = r#"fn foo() {}"#; |
1000 | 1022 | ||
1001 | let (analysis, file_position) = fixture::position(input); | 1023 | let (analysis, file_position) = fixture::position(input); |
1002 | let diagnostics = | 1024 | let diagnostics = analysis |
1003 | analysis.diagnostics(&DiagnosticsConfig::default(), file_position.file_id).unwrap(); | 1025 | .diagnostics(&DiagnosticsConfig::default(), true, file_position.file_id) |
1026 | .unwrap(); | ||
1004 | assert_eq!(diagnostics.len(), 1); | 1027 | assert_eq!(diagnostics.len(), 1); |
1005 | 1028 | ||
1006 | 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/goto_definition.rs b/crates/ide/src/goto_definition.rs index d057d5402..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 | ||
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/move_item.rs b/crates/ide/src/move_item.rs index 8d37f4f92..246f10a0a 100644 --- a/crates/ide/src/move_item.rs +++ b/crates/ide/src/move_item.rs | |||
@@ -1,4 +1,4 @@ | |||
1 | use std::iter::once; | 1 | use std::{iter::once, mem}; |
2 | 2 | ||
3 | use hir::Semantics; | 3 | use hir::Semantics; |
4 | use ide_db::{base_db::FileRange, RootDatabase}; | 4 | use ide_db::{base_db::FileRange, RootDatabase}; |
@@ -102,7 +102,7 @@ fn move_in_direction( | |||
102 | ast::GenericArgList(it) => swap_sibling_in_list(node, it.generic_args(), range, direction), | 102 | ast::GenericArgList(it) => swap_sibling_in_list(node, it.generic_args(), range, direction), |
103 | ast::VariantList(it) => swap_sibling_in_list(node, it.variants(), range, direction), | 103 | ast::VariantList(it) => swap_sibling_in_list(node, it.variants(), range, direction), |
104 | ast::TypeBoundList(it) => swap_sibling_in_list(node, it.bounds(), range, direction), | 104 | ast::TypeBoundList(it) => swap_sibling_in_list(node, it.bounds(), range, direction), |
105 | _ => Some(replace_nodes(node, &match direction { | 105 | _ => Some(replace_nodes(range, node, &match direction { |
106 | Direction::Up => node.prev_sibling(), | 106 | Direction::Up => node.prev_sibling(), |
107 | Direction::Down => node.next_sibling(), | 107 | Direction::Down => node.next_sibling(), |
108 | }?)) | 108 | }?)) |
@@ -125,7 +125,7 @@ fn swap_sibling_in_list<A: AstNode + Clone, I: Iterator<Item = A>>( | |||
125 | .next(); | 125 | .next(); |
126 | 126 | ||
127 | if let Some((l, r)) = list_lookup { | 127 | if let Some((l, r)) = list_lookup { |
128 | Some(replace_nodes(l.syntax(), r.syntax())) | 128 | Some(replace_nodes(range, l.syntax(), r.syntax())) |
129 | } else { | 129 | } else { |
130 | // Cursor is beyond any movable list item (for example, on curly brace in enum). | 130 | // Cursor is beyond any movable list item (for example, on curly brace in enum). |
131 | // It's not necessary, that parent of list is movable (arg list's parent is not, for example), | 131 | // It's not necessary, that parent of list is movable (arg list's parent is not, for example), |
@@ -134,11 +134,38 @@ fn swap_sibling_in_list<A: AstNode + Clone, I: Iterator<Item = A>>( | |||
134 | } | 134 | } |
135 | } | 135 | } |
136 | 136 | ||
137 | fn replace_nodes(first: &SyntaxNode, second: &SyntaxNode) -> TextEdit { | 137 | fn replace_nodes<'a>( |
138 | range: TextRange, | ||
139 | mut first: &'a SyntaxNode, | ||
140 | mut second: &'a SyntaxNode, | ||
141 | ) -> TextEdit { | ||
142 | let cursor_offset = if range.is_empty() { | ||
143 | // FIXME: `applySnippetTextEdits` does not support non-empty selection ranges | ||
144 | if first.text_range().contains_range(range) { | ||
145 | Some(range.start() - first.text_range().start()) | ||
146 | } else if second.text_range().contains_range(range) { | ||
147 | mem::swap(&mut first, &mut second); | ||
148 | Some(range.start() - first.text_range().start()) | ||
149 | } else { | ||
150 | None | ||
151 | } | ||
152 | } else { | ||
153 | None | ||
154 | }; | ||
155 | |||
156 | let first_with_cursor = match cursor_offset { | ||
157 | Some(offset) => { | ||
158 | let mut item_text = first.text().to_string(); | ||
159 | item_text.insert_str(offset.into(), "$0"); | ||
160 | item_text | ||
161 | } | ||
162 | None => first.text().to_string(), | ||
163 | }; | ||
164 | |||
138 | let mut edit = TextEditBuilder::default(); | 165 | let mut edit = TextEditBuilder::default(); |
139 | 166 | ||
140 | algo::diff(first, second).into_text_edit(&mut edit); | 167 | algo::diff(first, second).into_text_edit(&mut edit); |
141 | algo::diff(second, first).into_text_edit(&mut edit); | 168 | edit.replace(second.text_range(), first_with_cursor); |
142 | 169 | ||
143 | edit.finish() | 170 | edit.finish() |
144 | } | 171 | } |
@@ -188,7 +215,7 @@ fn main() { | |||
188 | expect![[r#" | 215 | expect![[r#" |
189 | fn main() { | 216 | fn main() { |
190 | match true { | 217 | match true { |
191 | false => { | 218 | false =>$0 { |
192 | println!("Test"); | 219 | println!("Test"); |
193 | }, | 220 | }, |
194 | true => { | 221 | true => { |
@@ -222,7 +249,7 @@ fn main() { | |||
222 | false => { | 249 | false => { |
223 | println!("Test"); | 250 | println!("Test"); |
224 | }, | 251 | }, |
225 | true => { | 252 | true =>$0 { |
226 | println!("Hello, world"); | 253 | println!("Hello, world"); |
227 | } | 254 | } |
228 | }; | 255 | }; |
@@ -274,7 +301,7 @@ fn main() { | |||
274 | "#, | 301 | "#, |
275 | expect![[r#" | 302 | expect![[r#" |
276 | fn main() { | 303 | fn main() { |
277 | let test2 = 456; | 304 | let test2$0 = 456; |
278 | let test = 123; | 305 | let test = 123; |
279 | } | 306 | } |
280 | "#]], | 307 | "#]], |
@@ -293,7 +320,7 @@ fn main() { | |||
293 | "#, | 320 | "#, |
294 | expect![[r#" | 321 | expect![[r#" |
295 | fn main() { | 322 | fn main() { |
296 | println!("All I want to say is..."); | 323 | println!("All I want to say is...");$0 |
297 | println!("Hello, world"); | 324 | println!("Hello, world"); |
298 | } | 325 | } |
299 | "#]], | 326 | "#]], |
@@ -313,7 +340,7 @@ fn main() { | |||
313 | fn main() { | 340 | fn main() { |
314 | if true { | 341 | if true { |
315 | println!("Test"); | 342 | println!("Test"); |
316 | } | 343 | }$0 |
317 | 344 | ||
318 | println!("Hello, world"); | 345 | println!("Hello, world"); |
319 | } | 346 | } |
@@ -334,7 +361,7 @@ fn main() { | |||
334 | fn main() { | 361 | fn main() { |
335 | for i in 0..10 { | 362 | for i in 0..10 { |
336 | println!("Test"); | 363 | println!("Test"); |
337 | } | 364 | }$0 |
338 | 365 | ||
339 | println!("Hello, world"); | 366 | println!("Hello, world"); |
340 | } | 367 | } |
@@ -355,7 +382,7 @@ fn main() { | |||
355 | fn main() { | 382 | fn main() { |
356 | loop { | 383 | loop { |
357 | println!("Test"); | 384 | println!("Test"); |
358 | } | 385 | }$0 |
359 | 386 | ||
360 | println!("Hello, world"); | 387 | println!("Hello, world"); |
361 | } | 388 | } |
@@ -376,7 +403,7 @@ fn main() { | |||
376 | fn main() { | 403 | fn main() { |
377 | while true { | 404 | while true { |
378 | println!("Test"); | 405 | println!("Test"); |
379 | } | 406 | }$0 |
380 | 407 | ||
381 | println!("Hello, world"); | 408 | println!("Hello, world"); |
382 | } | 409 | } |
@@ -393,7 +420,7 @@ fn main() { | |||
393 | "#, | 420 | "#, |
394 | expect![[r#" | 421 | expect![[r#" |
395 | fn main() { | 422 | fn main() { |
396 | return 123; | 423 | return 123;$0 |
397 | 424 | ||
398 | println!("Hello, world"); | 425 | println!("Hello, world"); |
399 | } | 426 | } |
@@ -430,7 +457,7 @@ fn main() {} | |||
430 | fn foo() {}$0$0 | 457 | fn foo() {}$0$0 |
431 | "#, | 458 | "#, |
432 | expect![[r#" | 459 | expect![[r#" |
433 | fn foo() {} | 460 | fn foo() {}$0 |
434 | 461 | ||
435 | fn main() {} | 462 | fn main() {} |
436 | "#]], | 463 | "#]], |
@@ -451,7 +478,7 @@ impl Wow for Yay $0$0{} | |||
451 | expect![[r#" | 478 | expect![[r#" |
452 | struct Yay; | 479 | struct Yay; |
453 | 480 | ||
454 | impl Wow for Yay {} | 481 | impl Wow for Yay $0{} |
455 | 482 | ||
456 | trait Wow {} | 483 | trait Wow {} |
457 | "#]], | 484 | "#]], |
@@ -467,7 +494,7 @@ use std::vec::Vec; | |||
467 | use std::collections::HashMap$0$0; | 494 | use std::collections::HashMap$0$0; |
468 | "#, | 495 | "#, |
469 | expect![[r#" | 496 | expect![[r#" |
470 | use std::collections::HashMap; | 497 | use std::collections::HashMap$0; |
471 | use std::vec::Vec; | 498 | use std::vec::Vec; |
472 | "#]], | 499 | "#]], |
473 | Direction::Up, | 500 | Direction::Up, |
@@ -502,7 +529,7 @@ fn main() { | |||
502 | } | 529 | } |
503 | 530 | ||
504 | #[test] | 531 | #[test] |
505 | fn test_moves_param_up() { | 532 | fn test_moves_param() { |
506 | check( | 533 | check( |
507 | r#" | 534 | r#" |
508 | fn test(one: i32, two$0$0: u32) {} | 535 | fn test(one: i32, two$0$0: u32) {} |
@@ -512,7 +539,7 @@ fn main() { | |||
512 | } | 539 | } |
513 | "#, | 540 | "#, |
514 | expect![[r#" | 541 | expect![[r#" |
515 | fn test(two: u32, one: i32) {} | 542 | fn test(two$0: u32, one: i32) {} |
516 | 543 | ||
517 | fn main() { | 544 | fn main() { |
518 | test(123, 456); | 545 | test(123, 456); |
@@ -520,6 +547,15 @@ fn main() { | |||
520 | "#]], | 547 | "#]], |
521 | Direction::Up, | 548 | Direction::Up, |
522 | ); | 549 | ); |
550 | check( | ||
551 | r#" | ||
552 | fn f($0$0arg: u8, arg2: u16) {} | ||
553 | "#, | ||
554 | expect![[r#" | ||
555 | fn f(arg2: u16, $0arg: u8) {} | ||
556 | "#]], | ||
557 | Direction::Down, | ||
558 | ); | ||
523 | } | 559 | } |
524 | 560 | ||
525 | #[test] | 561 | #[test] |
@@ -536,7 +572,7 @@ fn main() { | |||
536 | fn test(one: i32, two: u32) {} | 572 | fn test(one: i32, two: u32) {} |
537 | 573 | ||
538 | fn main() { | 574 | fn main() { |
539 | test(456, 123); | 575 | test(456$0, 123); |
540 | } | 576 | } |
541 | "#]], | 577 | "#]], |
542 | Direction::Up, | 578 | Direction::Up, |
@@ -557,7 +593,7 @@ fn main() { | |||
557 | fn test(one: i32, two: u32) {} | 593 | fn test(one: i32, two: u32) {} |
558 | 594 | ||
559 | fn main() { | 595 | fn main() { |
560 | test(456, 123); | 596 | test(456, 123$0); |
561 | } | 597 | } |
562 | "#]], | 598 | "#]], |
563 | Direction::Down, | 599 | Direction::Down, |
@@ -594,7 +630,7 @@ struct Test<A, B$0$0>(A, B); | |||
594 | fn main() {} | 630 | fn main() {} |
595 | "#, | 631 | "#, |
596 | expect![[r#" | 632 | expect![[r#" |
597 | struct Test<B, A>(A, B); | 633 | struct Test<B$0, A>(A, B); |
598 | 634 | ||
599 | fn main() {} | 635 | fn main() {} |
600 | "#]], | 636 | "#]], |
@@ -616,7 +652,7 @@ fn main() { | |||
616 | struct Test<A, B>(A, B); | 652 | struct Test<A, B>(A, B); |
617 | 653 | ||
618 | fn main() { | 654 | fn main() { |
619 | let t = Test::<&str, i32>(123, "yay"); | 655 | let t = Test::<&str$0, i32>(123, "yay"); |
620 | } | 656 | } |
621 | "#]], | 657 | "#]], |
622 | Direction::Up, | 658 | Direction::Up, |
@@ -636,7 +672,7 @@ fn main() {} | |||
636 | "#, | 672 | "#, |
637 | expect![[r#" | 673 | expect![[r#" |
638 | enum Hello { | 674 | enum Hello { |
639 | Two, | 675 | Two$0, |
640 | One | 676 | One |
641 | } | 677 | } |
642 | 678 | ||
@@ -663,7 +699,7 @@ trait One {} | |||
663 | 699 | ||
664 | trait Two {} | 700 | trait Two {} |
665 | 701 | ||
666 | fn test<T: Two + One>(t: T) {} | 702 | fn test<T: Two$0 + One>(t: T) {} |
667 | 703 | ||
668 | fn main() {} | 704 | fn main() {} |
669 | "#]], | 705 | "#]], |
@@ -709,7 +745,7 @@ trait Yay { | |||
709 | impl Yay for Test { | 745 | impl Yay for Test { |
710 | type One = i32; | 746 | type One = i32; |
711 | 747 | ||
712 | fn inner() { | 748 | fn inner() {$0 |
713 | println!("Mmmm"); | 749 | println!("Mmmm"); |
714 | } | 750 | } |
715 | 751 | ||
@@ -736,7 +772,7 @@ fn test() { | |||
736 | "#, | 772 | "#, |
737 | expect![[r#" | 773 | expect![[r#" |
738 | fn test() { | 774 | fn test() { |
739 | mod hi { | 775 | mod hi {$0 |
740 | fn inner() {} | 776 | fn inner() {} |
741 | } | 777 | } |
742 | 778 | ||
@@ -764,7 +800,7 @@ fn main() {} | |||
764 | expect![[r#" | 800 | expect![[r#" |
765 | fn main() {} | 801 | fn main() {} |
766 | 802 | ||
767 | #[derive(Debug)] | 803 | $0#[derive(Debug)] |
768 | enum FooBar { | 804 | enum FooBar { |
769 | Foo, | 805 | Foo, |
770 | Bar, | 806 | Bar, |
@@ -784,7 +820,7 @@ fn main() {} | |||
784 | expect![[r#" | 820 | expect![[r#" |
785 | fn main() {} | 821 | fn main() {} |
786 | 822 | ||
787 | enum FooBar { | 823 | $0enum FooBar { |
788 | Foo, | 824 | Foo, |
789 | Bar, | 825 | Bar, |
790 | } | 826 | } |
@@ -804,7 +840,7 @@ fn main() {} | |||
804 | expect![[r#" | 840 | expect![[r#" |
805 | struct Test; | 841 | struct Test; |
806 | 842 | ||
807 | impl SomeTrait for Test {} | 843 | $0impl SomeTrait for Test {} |
808 | 844 | ||
809 | trait SomeTrait {} | 845 | trait SomeTrait {} |
810 | 846 | ||
@@ -831,7 +867,7 @@ fn main() {} | |||
831 | enum FooBar { | 867 | enum FooBar { |
832 | Foo, | 868 | Foo, |
833 | Bar, | 869 | Bar, |
834 | } | 870 | }$0 |
835 | "#]], | 871 | "#]], |
836 | Direction::Down, | 872 | Direction::Down, |
837 | ); | 873 | ); |
@@ -848,7 +884,7 @@ fn main() {} | |||
848 | expect![[r#" | 884 | expect![[r#" |
849 | struct Test; | 885 | struct Test; |
850 | 886 | ||
851 | impl SomeTrait for Test {} | 887 | impl SomeTrait for Test {}$0 |
852 | 888 | ||
853 | trait SomeTrait {} | 889 | trait SomeTrait {} |
854 | 890 | ||
diff --git a/crates/ide/src/syntax_highlighting/highlight.rs b/crates/ide/src/syntax_highlighting/highlight.rs index e921784bf..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, |
diff --git a/crates/ide/src/syntax_highlighting/inject.rs b/crates/ide/src/syntax_highlighting/inject.rs index 04fafd244..855c7fba8 100644 --- a/crates/ide/src/syntax_highlighting/inject.rs +++ b/crates/ide/src/syntax_highlighting/inject.rs | |||
@@ -90,6 +90,13 @@ const RUSTDOC_FENCE_TOKENS: &[&'static str] = &[ | |||
90 | "edition2021", | 90 | "edition2021", |
91 | ]; | 91 | ]; |
92 | 92 | ||
93 | fn is_rustdoc_fence_token(token: &str) -> bool { | ||
94 | if RUSTDOC_FENCE_TOKENS.contains(&token) { | ||
95 | return true; | ||
96 | } | ||
97 | token.starts_with('E') && token.len() == 5 && token[1..].parse::<u32>().is_ok() | ||
98 | } | ||
99 | |||
93 | /// Injection of syntax highlighting of doctests. | 100 | /// Injection of syntax highlighting of doctests. |
94 | pub(super) fn doc_comment( | 101 | pub(super) fn doc_comment( |
95 | hl: &mut Highlights, | 102 | hl: &mut Highlights, |
@@ -174,8 +181,7 @@ pub(super) fn doc_comment( | |||
174 | is_codeblock = !is_codeblock; | 181 | is_codeblock = !is_codeblock; |
175 | // Check whether code is rust by inspecting fence guards | 182 | // Check whether code is rust by inspecting fence guards |
176 | let guards = &line[idx + RUSTDOC_FENCE.len()..]; | 183 | let guards = &line[idx + RUSTDOC_FENCE.len()..]; |
177 | let is_rust = | 184 | let is_rust = guards.split(',').all(|sub| is_rustdoc_fence_token(sub.trim())); |
178 | guards.split(',').all(|sub| RUSTDOC_FENCE_TOKENS.contains(&sub.trim())); | ||
179 | is_doctest = is_codeblock && is_rust; | 185 | is_doctest = is_codeblock && is_rust; |
180 | continue; | 186 | continue; |
181 | } | 187 | } |
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 de2d22ac7..933cfa6f3 100644 --- a/crates/ide/src/syntax_highlighting/tests.rs +++ b/crates/ide/src/syntax_highlighting/tests.rs | |||
@@ -2,8 +2,7 @@ use std::time::Instant; | |||
2 | 2 | ||
3 | use expect_test::{expect_file, ExpectFile}; | 3 | use expect_test::{expect_file, ExpectFile}; |
4 | use ide_db::SymbolKind; | 4 | use ide_db::SymbolKind; |
5 | use stdx::format_to; | 5 | use test_utils::{bench, bench_fixture, skip_slow_tests, AssertLinear}; |
6 | use test_utils::{bench, bench_fixture, skip_slow_tests}; | ||
7 | 6 | ||
8 | use crate::{fixture, FileRange, HlTag, TextRange}; | 7 | use crate::{fixture, FileRange, HlTag, TextRange}; |
9 | 8 | ||
@@ -266,90 +265,27 @@ fn syntax_highlighting_not_quadratic() { | |||
266 | return; | 265 | return; |
267 | } | 266 | } |
268 | 267 | ||
269 | let mut measures = Vec::new(); | 268 | let mut al = AssertLinear::default(); |
270 | for i in 6..=10 { | 269 | while al.next_round() { |
271 | let n = 1 << i; | 270 | for i in 6..=10 { |
272 | let fixture = bench_fixture::big_struct_n(n); | 271 | let n = 1 << i; |
273 | let (analysis, file_id) = fixture::file(&fixture); | ||
274 | 272 | ||
275 | let time = Instant::now(); | 273 | let fixture = bench_fixture::big_struct_n(n); |
274 | let (analysis, file_id) = fixture::file(&fixture); | ||
276 | 275 | ||
277 | let hash = analysis | 276 | let time = Instant::now(); |
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 | 277 | ||
285 | let elapsed = time.elapsed(); | 278 | let hash = analysis |
286 | measures.push((n as f64, elapsed.as_millis() as f64)) | 279 | .highlight(file_id) |
287 | } | 280 | .unwrap() |
281 | .iter() | ||
282 | .filter(|it| it.highlight.tag == HlTag::Symbol(SymbolKind::Struct)) | ||
283 | .count(); | ||
284 | assert!(hash > n as usize); | ||
288 | 285 | ||
289 | assert_linear(&measures) | 286 | let elapsed = time.elapsed(); |
290 | } | 287 | al.sample(n as f64, elapsed.as_millis() as f64); |
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 | ||
306 | fn 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 | } | 288 | } |
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 | } | 289 | } |
354 | } | 290 | } |
355 | 291 | ||
diff --git a/crates/ide/src/typing.rs b/crates/ide/src/typing.rs index 1378048e5..82c732390 100644 --- a/crates/ide/src/typing.rs +++ b/crates/ide/src/typing.rs | |||
@@ -222,8 +222,8 @@ mod tests { | |||
222 | assert_eq_text!(ra_fixture_after, &actual); | 222 | assert_eq_text!(ra_fixture_after, &actual); |
223 | } | 223 | } |
224 | 224 | ||
225 | fn type_char_noop(char_typed: char, before: &str) { | 225 | fn type_char_noop(char_typed: char, ra_fixture_before: &str) { |
226 | let file_change = do_type_char(char_typed, before); | 226 | let file_change = do_type_char(char_typed, ra_fixture_before); |
227 | assert!(file_change.is_none()) | 227 | assert!(file_change.is_none()) |
228 | } | 228 | } |
229 | 229 | ||
@@ -240,16 +240,16 @@ mod tests { | |||
240 | // "); | 240 | // "); |
241 | type_char( | 241 | type_char( |
242 | '=', | 242 | '=', |
243 | r" | 243 | r#" |
244 | fn foo() { | 244 | fn foo() { |
245 | let foo $0 1 + 1 | 245 | let foo $0 1 + 1 |
246 | } | 246 | } |
247 | ", | 247 | "#, |
248 | r" | 248 | r#" |
249 | fn foo() { | 249 | fn foo() { |
250 | let foo = 1 + 1; | 250 | let foo = 1 + 1; |
251 | } | 251 | } |
252 | ", | 252 | "#, |
253 | ); | 253 | ); |
254 | // do_check(r" | 254 | // do_check(r" |
255 | // fn foo() { | 255 | // fn foo() { |
@@ -268,27 +268,27 @@ fn foo() { | |||
268 | fn indents_new_chain_call() { | 268 | fn indents_new_chain_call() { |
269 | type_char( | 269 | type_char( |
270 | '.', | 270 | '.', |
271 | r" | 271 | r#" |
272 | fn main() { | 272 | fn main() { |
273 | xs.foo() | 273 | xs.foo() |
274 | $0 | 274 | $0 |
275 | } | 275 | } |
276 | ", | 276 | "#, |
277 | r" | 277 | r#" |
278 | fn main() { | 278 | fn main() { |
279 | xs.foo() | 279 | xs.foo() |
280 | . | 280 | . |
281 | } | 281 | } |
282 | ", | 282 | "#, |
283 | ); | 283 | ); |
284 | type_char_noop( | 284 | type_char_noop( |
285 | '.', | 285 | '.', |
286 | r" | 286 | r#" |
287 | fn main() { | 287 | fn main() { |
288 | xs.foo() | 288 | xs.foo() |
289 | $0 | 289 | $0 |
290 | } | 290 | } |
291 | ", | 291 | "#, |
292 | ) | 292 | ) |
293 | } | 293 | } |
294 | 294 | ||
@@ -297,26 +297,26 @@ fn foo() { | |||
297 | type_char( | 297 | type_char( |
298 | '.', | 298 | '.', |
299 | r" | 299 | r" |
300 | fn main() { | 300 | fn main() { |
301 | xs.foo() | 301 | xs.foo() |
302 | $0; | 302 | $0; |
303 | } | 303 | } |
304 | ", | ||
305 | r" | ||
306 | fn main() { | ||
307 | xs.foo() | ||
308 | .; | ||
309 | } | ||
310 | ", | 304 | ", |
305 | r#" | ||
306 | fn main() { | ||
307 | xs.foo() | ||
308 | .; | ||
309 | } | ||
310 | "#, | ||
311 | ); | 311 | ); |
312 | type_char_noop( | 312 | type_char_noop( |
313 | '.', | 313 | '.', |
314 | r" | 314 | r#" |
315 | fn main() { | 315 | fn main() { |
316 | xs.foo() | 316 | xs.foo() |
317 | $0; | 317 | $0; |
318 | } | 318 | } |
319 | ", | 319 | "#, |
320 | ) | 320 | ) |
321 | } | 321 | } |
322 | 322 | ||
@@ -345,30 +345,30 @@ fn main() { | |||
345 | fn indents_continued_chain_call() { | 345 | fn indents_continued_chain_call() { |
346 | type_char( | 346 | type_char( |
347 | '.', | 347 | '.', |
348 | r" | 348 | r#" |
349 | fn main() { | 349 | fn main() { |
350 | xs.foo() | 350 | xs.foo() |
351 | .first() | 351 | .first() |
352 | $0 | 352 | $0 |
353 | } | 353 | } |
354 | ", | 354 | "#, |
355 | r" | 355 | r#" |
356 | fn main() { | 356 | fn main() { |
357 | xs.foo() | 357 | xs.foo() |
358 | .first() | 358 | .first() |
359 | . | 359 | . |
360 | } | 360 | } |
361 | ", | 361 | "#, |
362 | ); | 362 | ); |
363 | type_char_noop( | 363 | type_char_noop( |
364 | '.', | 364 | '.', |
365 | r" | 365 | r#" |
366 | fn main() { | 366 | fn main() { |
367 | xs.foo() | 367 | xs.foo() |
368 | .first() | 368 | .first() |
369 | $0 | 369 | $0 |
370 | } | 370 | } |
371 | ", | 371 | "#, |
372 | ); | 372 | ); |
373 | } | 373 | } |
374 | 374 | ||
@@ -376,33 +376,33 @@ fn main() { | |||
376 | fn indents_middle_of_chain_call() { | 376 | fn indents_middle_of_chain_call() { |
377 | type_char( | 377 | type_char( |
378 | '.', | 378 | '.', |
379 | r" | 379 | r#" |
380 | fn source_impl() { | 380 | fn source_impl() { |
381 | let var = enum_defvariant_list().unwrap() | 381 | let var = enum_defvariant_list().unwrap() |
382 | $0 | 382 | $0 |
383 | .nth(92) | 383 | .nth(92) |
384 | .unwrap(); | 384 | .unwrap(); |
385 | } | 385 | } |
386 | ", | 386 | "#, |
387 | r" | 387 | r#" |
388 | fn source_impl() { | 388 | fn source_impl() { |
389 | let var = enum_defvariant_list().unwrap() | 389 | let var = enum_defvariant_list().unwrap() |
390 | . | 390 | . |
391 | .nth(92) | 391 | .nth(92) |
392 | .unwrap(); | 392 | .unwrap(); |
393 | } | 393 | } |
394 | ", | 394 | "#, |
395 | ); | 395 | ); |
396 | type_char_noop( | 396 | type_char_noop( |
397 | '.', | 397 | '.', |
398 | r" | 398 | r#" |
399 | fn source_impl() { | 399 | fn source_impl() { |
400 | let var = enum_defvariant_list().unwrap() | 400 | let var = enum_defvariant_list().unwrap() |
401 | $0 | 401 | $0 |
402 | .nth(92) | 402 | .nth(92) |
403 | .unwrap(); | 403 | .unwrap(); |
404 | } | 404 | } |
405 | ", | 405 | "#, |
406 | ); | 406 | ); |
407 | } | 407 | } |
408 | 408 | ||
@@ -410,25 +410,33 @@ fn main() { | |||
410 | fn dont_indent_freestanding_dot() { | 410 | fn dont_indent_freestanding_dot() { |
411 | type_char_noop( | 411 | type_char_noop( |
412 | '.', | 412 | '.', |
413 | r" | 413 | r#" |
414 | fn main() { | 414 | fn main() { |
415 | $0 | 415 | $0 |
416 | } | 416 | } |
417 | ", | 417 | "#, |
418 | ); | 418 | ); |
419 | type_char_noop( | 419 | type_char_noop( |
420 | '.', | 420 | '.', |
421 | r" | 421 | r#" |
422 | fn main() { | 422 | fn main() { |
423 | $0 | 423 | $0 |
424 | } | 424 | } |
425 | ", | 425 | "#, |
426 | ); | 426 | ); |
427 | } | 427 | } |
428 | 428 | ||
429 | #[test] | 429 | #[test] |
430 | fn adds_space_after_return_type() { | 430 | fn adds_space_after_return_type() { |
431 | type_char('>', "fn foo() -$0{ 92 }", "fn foo() -> { 92 }") | 431 | type_char( |
432 | '>', | ||
433 | r#" | ||
434 | fn foo() -$0{ 92 } | ||
435 | "#, | ||
436 | r#" | ||
437 | fn foo() -> { 92 } | ||
438 | "#, | ||
439 | ); | ||
432 | } | 440 | } |
433 | 441 | ||
434 | #[test] | 442 | #[test] |
diff --git a/crates/ide/src/typing/on_enter.rs b/crates/ide/src/typing/on_enter.rs index 6f1ce3689..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; |
@@ -19,6 +20,7 @@ use text_edit::TextEdit; | |||
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 `//` |
21 | // - kbd:[Enter] inside `//!` doc comments automatically inserts `//!` | 22 | // - kbd:[Enter] inside `//!` doc comments automatically inserts `//!` |
23 | // - kbd:[Enter] after `{` indents contents and closing `}` of single-line block | ||
22 | // | 24 | // |
23 | // This action needs to be assigned to shortcut explicitly. | 25 | // This action needs to be assigned to shortcut explicitly. |
24 | // | 26 | // |
@@ -38,25 +40,43 @@ use text_edit::TextEdit; | |||
38 | pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<TextEdit> { | 40 | pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<TextEdit> { |
39 | let parse = db.parse(position.file_id); | 41 | let parse = db.parse(position.file_id); |
40 | let file = parse.tree(); | 42 | let file = parse.tree(); |
41 | let comment = file | 43 | let token = file.syntax().token_at_offset(position.offset).left_biased()?; |
42 | .syntax() | ||
43 | .token_at_offset(position.offset) | ||
44 | .left_biased() | ||
45 | .and_then(ast::Comment::cast)?; | ||
46 | 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> { | ||
47 | if comment.kind().shape.is_block() { | 67 | if comment.kind().shape.is_block() { |
48 | return None; | 68 | return None; |
49 | } | 69 | } |
50 | 70 | ||
51 | let prefix = comment.prefix(); | 71 | let prefix = comment.prefix(); |
52 | let comment_range = comment.syntax().text_range(); | 72 | let comment_range = comment.syntax().text_range(); |
53 | if position.offset < comment_range.start() + TextSize::of(prefix) { | 73 | if offset < comment_range.start() + TextSize::of(prefix) { |
54 | return None; | 74 | return None; |
55 | } | 75 | } |
56 | 76 | ||
57 | let mut remove_trailing_whitespace = false; | 77 | let mut remove_trailing_whitespace = false; |
58 | // Continuing single-line non-doc comments (like this one :) ) is annoying | 78 | // Continuing single-line non-doc comments (like this one :) ) is annoying |
59 | if prefix == "//" && comment_range.end() == position.offset { | 79 | if prefix == "//" && comment_range.end() == offset { |
60 | if comment.text().ends_with(' ') { | 80 | if comment.text().ends_with(' ') { |
61 | cov_mark::hit!(continues_end_of_line_comment_with_space); | 81 | cov_mark::hit!(continues_end_of_line_comment_with_space); |
62 | remove_trailing_whitespace = true; | 82 | remove_trailing_whitespace = true; |
@@ -70,14 +90,42 @@ pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<Text | |||
70 | let delete = if remove_trailing_whitespace { | 90 | let delete = if remove_trailing_whitespace { |
71 | let trimmed_len = comment.text().trim_end().len() as u32; | 91 | let trimmed_len = comment.text().trim_end().len() as u32; |
72 | let trailing_whitespace_len = comment.text().len() as u32 - trimmed_len; | 92 | let trailing_whitespace_len = comment.text().len() as u32 - trimmed_len; |
73 | TextRange::new(position.offset - TextSize::from(trailing_whitespace_len), position.offset) | 93 | TextRange::new(offset - TextSize::from(trailing_whitespace_len), offset) |
74 | } else { | 94 | } else { |
75 | TextRange::empty(position.offset) | 95 | TextRange::empty(offset) |
76 | }; | 96 | }; |
77 | let edit = TextEdit::replace(delete, inserted); | 97 | let edit = TextEdit::replace(delete, inserted); |
78 | Some(edit) | 98 | Some(edit) |
79 | } | 99 | } |
80 | 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 | |||
81 | fn followed_by_comment(comment: &ast::Comment) -> bool { | 129 | fn followed_by_comment(comment: &ast::Comment) -> bool { |
82 | 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) { |
83 | Some(it) => it, | 131 | Some(it) => it, |
@@ -296,4 +344,144 @@ fn main() { | |||
296 | ", | 344 | ", |
297 | ); | 345 | ); |
298 | } | 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 | } | ||
299 | } | 487 | } |