diff options
Diffstat (limited to 'crates/ide/src')
38 files changed, 1629 insertions, 522 deletions
diff --git a/crates/ide/src/annotations.rs b/crates/ide/src/annotations.rs index 64bc926f1..5ebe7fd0e 100644 --- a/crates/ide/src/annotations.rs +++ b/crates/ide/src/annotations.rs | |||
@@ -19,6 +19,8 @@ use crate::{ | |||
19 | // | 19 | // |
20 | // Provides user with annotations above items for looking up references or impl blocks | 20 | // Provides user with annotations above items for looking up references or impl blocks |
21 | // and running/debugging binaries. | 21 | // and running/debugging binaries. |
22 | // | ||
23 | // image::https://user-images.githubusercontent.com/48062697/113020672-b7c34f00-917a-11eb-8f6e-858735660a0e.png[] | ||
22 | #[derive(Debug)] | 24 | #[derive(Debug)] |
23 | pub struct Annotation { | 25 | pub struct Annotation { |
24 | pub range: TextRange, | 26 | pub range: TextRange, |
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index 22697a537..1c911a8b2 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,15 +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_unused(true) | ||
169 | .with_fix(d.fix(&sema)) | ||
170 | .with_code(Some(d.code())), | ||
171 | ); | 158 | ); |
172 | }) | 159 | }) |
173 | .on::<hir::diagnostics::UnresolvedProcMacro, _>(|d| { | 160 | .on::<hir::diagnostics::UnresolvedProcMacro, _>(|d| { |
@@ -222,15 +209,23 @@ pub(crate) fn diagnostics( | |||
222 | res.into_inner() | 209 | res.into_inner() |
223 | } | 210 | } |
224 | 211 | ||
225 | 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 { | ||
226 | 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()) |
227 | .with_fix(d.fix(&sema)) | 218 | .with_fix(d.fix(&sema, resolve)) |
228 | .with_code(Some(d.code())) | 219 | .with_code(Some(d.code())) |
229 | } | 220 | } |
230 | 221 | ||
231 | 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 { | ||
232 | 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()) |
233 | .with_fix(d.fix(&sema)) | 228 | .with_fix(d.fix(&sema, resolve)) |
234 | .with_code(Some(d.code())) | 229 | .with_code(Some(d.code())) |
235 | } | 230 | } |
236 | 231 | ||
@@ -260,7 +255,8 @@ fn check_unnecessary_braces_in_use_statement( | |||
260 | 255 | ||
261 | acc.push( | 256 | acc.push( |
262 | Diagnostic::hint(use_range, "Unnecessary braces in use statement".to_string()) | 257 | Diagnostic::hint(use_range, "Unnecessary braces in use statement".to_string()) |
263 | .with_fix(Some(Fix::new( | 258 | .with_fix(Some(fix( |
259 | "remove_braces", | ||
264 | "Remove unnecessary braces", | 260 | "Remove unnecessary braces", |
265 | SourceChange::from_text_edit(file_id, edit), | 261 | SourceChange::from_text_edit(file_id, edit), |
266 | use_range, | 262 | use_range, |
@@ -283,6 +279,23 @@ fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement( | |||
283 | None | 279 | None |
284 | } | 280 | } |
285 | 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 | |||
286 | #[cfg(test)] | 299 | #[cfg(test)] |
287 | mod tests { | 300 | mod tests { |
288 | use expect_test::{expect, Expect}; | 301 | use expect_test::{expect, Expect}; |
@@ -301,16 +314,17 @@ mod tests { | |||
301 | 314 | ||
302 | let (analysis, file_position) = fixture::position(ra_fixture_before); | 315 | let (analysis, file_position) = fixture::position(ra_fixture_before); |
303 | let diagnostic = analysis | 316 | let diagnostic = analysis |
304 | .diagnostics(&DiagnosticsConfig::default(), file_position.file_id) | 317 | .diagnostics(&DiagnosticsConfig::default(), true, file_position.file_id) |
305 | .unwrap() | 318 | .unwrap() |
306 | .pop() | 319 | .pop() |
307 | .unwrap(); | 320 | .unwrap(); |
308 | let fix = diagnostic.fix.unwrap(); | 321 | let fix = diagnostic.fix.unwrap(); |
309 | let actual = { | 322 | let actual = { |
310 | 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(); | ||
311 | let mut actual = analysis.file_text(file_id).unwrap().to_string(); | 325 | let mut actual = analysis.file_text(file_id).unwrap().to_string(); |
312 | 326 | ||
313 | for edit in fix.source_change.source_file_edits.values() { | 327 | for edit in source_change.source_file_edits.values() { |
314 | edit.apply(&mut actual); | 328 | edit.apply(&mut actual); |
315 | } | 329 | } |
316 | actual | 330 | actual |
@@ -318,9 +332,9 @@ mod tests { | |||
318 | 332 | ||
319 | assert_eq_text!(&after, &actual); | 333 | assert_eq_text!(&after, &actual); |
320 | assert!( | 334 | assert!( |
321 | fix.fix_trigger_range.contains_inclusive(file_position.offset), | 335 | fix.target.contains_inclusive(file_position.offset), |
322 | "diagnostic fix range {:?} does not touch cursor position {:?}", | 336 | "diagnostic fix range {:?} does not touch cursor position {:?}", |
323 | fix.fix_trigger_range, | 337 | fix.target, |
324 | file_position.offset | 338 | file_position.offset |
325 | ); | 339 | ); |
326 | } | 340 | } |
@@ -329,7 +343,7 @@ mod tests { | |||
329 | fn check_no_fix(ra_fixture: &str) { | 343 | fn check_no_fix(ra_fixture: &str) { |
330 | let (analysis, file_position) = fixture::position(ra_fixture); | 344 | let (analysis, file_position) = fixture::position(ra_fixture); |
331 | let diagnostic = analysis | 345 | let diagnostic = analysis |
332 | .diagnostics(&DiagnosticsConfig::default(), file_position.file_id) | 346 | .diagnostics(&DiagnosticsConfig::default(), true, file_position.file_id) |
333 | .unwrap() | 347 | .unwrap() |
334 | .pop() | 348 | .pop() |
335 | .unwrap(); | 349 | .unwrap(); |
@@ -343,7 +357,7 @@ mod tests { | |||
343 | let diagnostics = files | 357 | let diagnostics = files |
344 | .into_iter() | 358 | .into_iter() |
345 | .flat_map(|file_id| { | 359 | .flat_map(|file_id| { |
346 | analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap() | 360 | analysis.diagnostics(&DiagnosticsConfig::default(), true, file_id).unwrap() |
347 | }) | 361 | }) |
348 | .collect::<Vec<_>>(); | 362 | .collect::<Vec<_>>(); |
349 | assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics); | 363 | assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics); |
@@ -351,7 +365,8 @@ mod tests { | |||
351 | 365 | ||
352 | fn check_expect(ra_fixture: &str, expect: Expect) { | 366 | fn check_expect(ra_fixture: &str, expect: Expect) { |
353 | let (analysis, file_id) = fixture::file(ra_fixture); | 367 | let (analysis, file_id) = fixture::file(ra_fixture); |
354 | let diagnostics = analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap(); | 368 | let diagnostics = |
369 | analysis.diagnostics(&DiagnosticsConfig::default(), true, file_id).unwrap(); | ||
355 | expect.assert_debug_eq(&diagnostics) | 370 | expect.assert_debug_eq(&diagnostics) |
356 | } | 371 | } |
357 | 372 | ||
@@ -664,24 +679,31 @@ fn test_fn() { | |||
664 | range: 0..8, | 679 | range: 0..8, |
665 | severity: Error, | 680 | severity: Error, |
666 | fix: Some( | 681 | fix: Some( |
667 | Fix { | 682 | Assist { |
683 | id: AssistId( | ||
684 | "create_module", | ||
685 | QuickFix, | ||
686 | ), | ||
668 | label: "Create module", | 687 | label: "Create module", |
669 | source_change: SourceChange { | 688 | group: None, |
670 | source_file_edits: {}, | 689 | target: 0..8, |
671 | file_system_edits: [ | 690 | source_change: Some( |
672 | CreateFile { | 691 | SourceChange { |
673 | dst: AnchoredPathBuf { | 692 | source_file_edits: {}, |
674 | anchor: FileId( | 693 | file_system_edits: [ |
675 | 0, | 694 | CreateFile { |
676 | ), | 695 | dst: AnchoredPathBuf { |
677 | path: "foo.rs", | 696 | anchor: FileId( |
697 | 0, | ||
698 | ), | ||
699 | path: "foo.rs", | ||
700 | }, | ||
701 | initial_contents: "", | ||
678 | }, | 702 | }, |
679 | initial_contents: "", | 703 | ], |
680 | }, | 704 | is_snippet: false, |
681 | ], | 705 | }, |
682 | is_snippet: false, | 706 | ), |
683 | }, | ||
684 | fix_trigger_range: 0..8, | ||
685 | }, | 707 | }, |
686 | ), | 708 | ), |
687 | unused: false, | 709 | unused: false, |
@@ -703,7 +725,7 @@ fn test_fn() { | |||
703 | expect![[r#" | 725 | expect![[r#" |
704 | [ | 726 | [ |
705 | Diagnostic { | 727 | Diagnostic { |
706 | message: "unresolved macro call", | 728 | message: "unresolved macro `foo::bar!`", |
707 | range: 5..8, | 729 | range: 5..8, |
708 | severity: Error, | 730 | severity: Error, |
709 | fix: None, | 731 | fix: None, |
@@ -889,10 +911,11 @@ struct Foo { | |||
889 | 911 | ||
890 | let (analysis, file_id) = fixture::file(r#"mod foo;"#); | 912 | let (analysis, file_id) = fixture::file(r#"mod foo;"#); |
891 | 913 | ||
892 | let diagnostics = analysis.diagnostics(&config, file_id).unwrap(); | 914 | let diagnostics = analysis.diagnostics(&config, true, file_id).unwrap(); |
893 | assert!(diagnostics.is_empty()); | 915 | assert!(diagnostics.is_empty()); |
894 | 916 | ||
895 | let diagnostics = analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap(); | 917 | let diagnostics = |
918 | analysis.diagnostics(&DiagnosticsConfig::default(), true, file_id).unwrap(); | ||
896 | assert!(!diagnostics.is_empty()); | 919 | assert!(!diagnostics.is_empty()); |
897 | } | 920 | } |
898 | 921 | ||
@@ -998,8 +1021,9 @@ impl TestStruct { | |||
998 | let expected = r#"fn foo() {}"#; | 1021 | let expected = r#"fn foo() {}"#; |
999 | 1022 | ||
1000 | let (analysis, file_position) = fixture::position(input); | 1023 | let (analysis, file_position) = fixture::position(input); |
1001 | let diagnostics = | 1024 | let diagnostics = analysis |
1002 | analysis.diagnostics(&DiagnosticsConfig::default(), file_position.file_id).unwrap(); | 1025 | .diagnostics(&DiagnosticsConfig::default(), true, file_position.file_id) |
1026 | .unwrap(); | ||
1003 | assert_eq!(diagnostics.len(), 1); | 1027 | assert_eq!(diagnostics.len(), 1); |
1004 | 1028 | ||
1005 | 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/doc_links.rs b/crates/ide/src/doc_links.rs index 9301cdeff..c5dc14a23 100644 --- a/crates/ide/src/doc_links.rs +++ b/crates/ide/src/doc_links.rs | |||
@@ -1,6 +1,9 @@ | |||
1 | //! Extracts, resolves and rewrites links and intra-doc links in markdown documentation. | 1 | //! Extracts, resolves and rewrites links and intra-doc links in markdown documentation. |
2 | 2 | ||
3 | use std::{convert::TryFrom, iter::once, ops::Range}; | 3 | use std::{ |
4 | convert::{TryFrom, TryInto}, | ||
5 | iter::once, | ||
6 | }; | ||
4 | 7 | ||
5 | use itertools::Itertools; | 8 | use itertools::Itertools; |
6 | use pulldown_cmark::{BrokenLink, CowStr, Event, InlineStr, LinkType, Options, Parser, Tag}; | 9 | use pulldown_cmark::{BrokenLink, CowStr, Event, InlineStr, LinkType, Options, Parser, Tag}; |
@@ -16,8 +19,7 @@ use ide_db::{ | |||
16 | RootDatabase, | 19 | RootDatabase, |
17 | }; | 20 | }; |
18 | use syntax::{ | 21 | use syntax::{ |
19 | ast, match_ast, AstNode, AstToken, SyntaxKind::*, SyntaxNode, SyntaxToken, TextRange, TextSize, | 22 | ast, match_ast, AstNode, SyntaxKind::*, SyntaxNode, SyntaxToken, TextRange, TokenAtOffset, T, |
20 | TokenAtOffset, T, | ||
21 | }; | 23 | }; |
22 | 24 | ||
23 | use crate::{FilePosition, Semantics}; | 25 | use crate::{FilePosition, Semantics}; |
@@ -26,12 +28,7 @@ pub(crate) type DocumentationLink = String; | |||
26 | 28 | ||
27 | /// Rewrite documentation links in markdown to point to an online host (e.g. docs.rs) | 29 | /// Rewrite documentation links in markdown to point to an online host (e.g. docs.rs) |
28 | pub(crate) fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> String { | 30 | pub(crate) fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> String { |
29 | let mut cb = |link: BrokenLink| { | 31 | let mut cb = broken_link_clone_cb; |
30 | Some(( | ||
31 | /*url*/ link.reference.to_owned().into(), | ||
32 | /*title*/ link.reference.to_owned().into(), | ||
33 | )) | ||
34 | }; | ||
35 | let doc = Parser::new_with_broken_link_callback(markdown, Options::empty(), Some(&mut cb)); | 32 | let doc = Parser::new_with_broken_link_callback(markdown, Options::empty(), Some(&mut cb)); |
36 | 33 | ||
37 | let doc = map_links(doc, |target, title: &str| { | 34 | let doc = map_links(doc, |target, title: &str| { |
@@ -98,76 +95,52 @@ pub(crate) fn remove_links(markdown: &str) -> String { | |||
98 | out | 95 | out |
99 | } | 96 | } |
100 | 97 | ||
98 | /// Retrieve a link to documentation for the given symbol. | ||
99 | pub(crate) fn external_docs( | ||
100 | db: &RootDatabase, | ||
101 | position: &FilePosition, | ||
102 | ) -> Option<DocumentationLink> { | ||
103 | let sema = Semantics::new(db); | ||
104 | let file = sema.parse(position.file_id).syntax().clone(); | ||
105 | let token = pick_best(file.token_at_offset(position.offset))?; | ||
106 | let token = sema.descend_into_macros(token); | ||
107 | |||
108 | let node = token.parent()?; | ||
109 | let definition = match_ast! { | ||
110 | match node { | ||
111 | ast::NameRef(name_ref) => NameRefClass::classify(&sema, &name_ref).map(|d| d.referenced(sema.db)), | ||
112 | ast::Name(name) => NameClass::classify(&sema, &name).map(|d| d.referenced_or_defined(sema.db)), | ||
113 | _ => None, | ||
114 | } | ||
115 | }; | ||
116 | |||
117 | get_doc_link(db, definition?) | ||
118 | } | ||
119 | |||
120 | /// Extracts all links from a given markdown text. | ||
101 | pub(crate) fn extract_definitions_from_markdown( | 121 | pub(crate) fn extract_definitions_from_markdown( |
102 | markdown: &str, | 122 | markdown: &str, |
103 | ) -> Vec<(Range<usize>, String, Option<hir::Namespace>)> { | 123 | ) -> Vec<(TextRange, String, Option<hir::Namespace>)> { |
104 | let mut res = vec![]; | 124 | Parser::new_with_broken_link_callback( |
105 | let mut cb = |link: BrokenLink| { | 125 | markdown, |
106 | // These allocations are actually unnecessary but the lifetimes on BrokenLinkCallback are wrong | 126 | Options::empty(), |
107 | // this is fixed in the repo but not on the crates.io release yet | 127 | Some(&mut broken_link_clone_cb), |
108 | Some(( | 128 | ) |
109 | /*url*/ link.reference.to_owned().into(), | 129 | .into_offset_iter() |
110 | /*title*/ link.reference.to_owned().into(), | 130 | .filter_map(|(event, range)| { |
111 | )) | ||
112 | }; | ||
113 | let doc = Parser::new_with_broken_link_callback(markdown, Options::empty(), Some(&mut cb)); | ||
114 | for (event, range) in doc.into_offset_iter() { | ||
115 | if let Event::Start(Tag::Link(_, target, title)) = event { | 131 | if let Event::Start(Tag::Link(_, target, title)) = event { |
116 | let link = if target.is_empty() { title } else { target }; | 132 | let link = if target.is_empty() { title } else { target }; |
117 | let (link, ns) = parse_intra_doc_link(&link); | 133 | let (link, ns) = parse_intra_doc_link(&link); |
118 | res.push((range, link.to_string(), ns)); | 134 | Some(( |
119 | } | 135 | TextRange::new(range.start.try_into().ok()?, range.end.try_into().ok()?), |
120 | } | 136 | link.to_string(), |
121 | res | 137 | ns, |
122 | } | 138 | )) |
123 | 139 | } else { | |
124 | /// Extracts a link from a comment at the given position returning the spanning range, link and | 140 | None |
125 | /// optionally it's namespace. | ||
126 | pub(crate) fn extract_positioned_link_from_comment( | ||
127 | position: TextSize, | ||
128 | comment: &ast::Comment, | ||
129 | ) -> Option<(TextRange, String, Option<hir::Namespace>)> { | ||
130 | let doc_comment = comment.doc_comment()?; | ||
131 | let comment_start = | ||
132 | comment.syntax().text_range().start() + TextSize::from(comment.prefix().len() as u32); | ||
133 | let def_links = extract_definitions_from_markdown(doc_comment); | ||
134 | let (range, def_link, ns) = | ||
135 | def_links.into_iter().find_map(|(Range { start, end }, def_link, ns)| { | ||
136 | let range = TextRange::at( | ||
137 | comment_start + TextSize::from(start as u32), | ||
138 | TextSize::from((end - start) as u32), | ||
139 | ); | ||
140 | range.contains(position).then(|| (range, def_link, ns)) | ||
141 | })?; | ||
142 | Some((range, def_link, ns)) | ||
143 | } | ||
144 | |||
145 | /// Turns a syntax node into it's [`Definition`] if it can hold docs. | ||
146 | pub(crate) fn doc_owner_to_def( | ||
147 | sema: &Semantics<RootDatabase>, | ||
148 | item: &SyntaxNode, | ||
149 | ) -> Option<Definition> { | ||
150 | let res: hir::ModuleDef = match_ast! { | ||
151 | match item { | ||
152 | ast::SourceFile(_it) => sema.scope(item).module()?.into(), | ||
153 | ast::Fn(it) => sema.to_def(&it)?.into(), | ||
154 | ast::Struct(it) => sema.to_def(&it)?.into(), | ||
155 | ast::Enum(it) => sema.to_def(&it)?.into(), | ||
156 | ast::Union(it) => sema.to_def(&it)?.into(), | ||
157 | ast::Trait(it) => sema.to_def(&it)?.into(), | ||
158 | ast::Const(it) => sema.to_def(&it)?.into(), | ||
159 | ast::Static(it) => sema.to_def(&it)?.into(), | ||
160 | ast::TypeAlias(it) => sema.to_def(&it)?.into(), | ||
161 | ast::Variant(it) => sema.to_def(&it)?.into(), | ||
162 | ast::Trait(it) => sema.to_def(&it)?.into(), | ||
163 | ast::Impl(it) => return sema.to_def(&it).map(Definition::SelfType), | ||
164 | ast::Macro(it) => return sema.to_def(&it).map(Definition::Macro), | ||
165 | ast::TupleField(it) => return sema.to_def(&it).map(Definition::Field), | ||
166 | ast::RecordField(it) => return sema.to_def(&it).map(Definition::Field), | ||
167 | _ => return None, | ||
168 | } | 141 | } |
169 | }; | 142 | }) |
170 | Some(Definition::ModuleDef(res)) | 143 | .collect() |
171 | } | 144 | } |
172 | 145 | ||
173 | pub(crate) fn resolve_doc_path_for_def( | 146 | pub(crate) fn resolve_doc_path_for_def( |
@@ -178,15 +151,15 @@ pub(crate) fn resolve_doc_path_for_def( | |||
178 | ) -> Option<hir::ModuleDef> { | 151 | ) -> Option<hir::ModuleDef> { |
179 | match def { | 152 | match def { |
180 | Definition::ModuleDef(def) => match def { | 153 | Definition::ModuleDef(def) => match def { |
181 | ModuleDef::Module(it) => it.resolve_doc_path(db, &link, ns), | 154 | hir::ModuleDef::Module(it) => it.resolve_doc_path(db, &link, ns), |
182 | ModuleDef::Function(it) => it.resolve_doc_path(db, &link, ns), | 155 | hir::ModuleDef::Function(it) => it.resolve_doc_path(db, &link, ns), |
183 | ModuleDef::Adt(it) => it.resolve_doc_path(db, &link, ns), | 156 | hir::ModuleDef::Adt(it) => it.resolve_doc_path(db, &link, ns), |
184 | ModuleDef::Variant(it) => it.resolve_doc_path(db, &link, ns), | 157 | hir::ModuleDef::Variant(it) => it.resolve_doc_path(db, &link, ns), |
185 | ModuleDef::Const(it) => it.resolve_doc_path(db, &link, ns), | 158 | hir::ModuleDef::Const(it) => it.resolve_doc_path(db, &link, ns), |
186 | ModuleDef::Static(it) => it.resolve_doc_path(db, &link, ns), | 159 | hir::ModuleDef::Static(it) => it.resolve_doc_path(db, &link, ns), |
187 | ModuleDef::Trait(it) => it.resolve_doc_path(db, &link, ns), | 160 | hir::ModuleDef::Trait(it) => it.resolve_doc_path(db, &link, ns), |
188 | ModuleDef::TypeAlias(it) => it.resolve_doc_path(db, &link, ns), | 161 | hir::ModuleDef::TypeAlias(it) => it.resolve_doc_path(db, &link, ns), |
189 | ModuleDef::BuiltinType(_) => None, | 162 | hir::ModuleDef::BuiltinType(_) => None, |
190 | }, | 163 | }, |
191 | Definition::Macro(it) => it.resolve_doc_path(db, &link, ns), | 164 | Definition::Macro(it) => it.resolve_doc_path(db, &link, ns), |
192 | Definition::Field(it) => it.resolve_doc_path(db, &link, ns), | 165 | Definition::Field(it) => it.resolve_doc_path(db, &link, ns), |
@@ -197,6 +170,42 @@ pub(crate) fn resolve_doc_path_for_def( | |||
197 | } | 170 | } |
198 | } | 171 | } |
199 | 172 | ||
173 | pub(crate) fn doc_attributes( | ||
174 | sema: &Semantics<RootDatabase>, | ||
175 | node: &SyntaxNode, | ||
176 | ) -> Option<(hir::AttrsWithOwner, Definition)> { | ||
177 | match_ast! { | ||
178 | match node { | ||
179 | ast::SourceFile(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Module(def)))), | ||
180 | ast::Module(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Module(def)))), | ||
181 | ast::Fn(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Function(def)))), | ||
182 | ast::Struct(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Struct(def))))), | ||
183 | ast::Union(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Union(def))))), | ||
184 | ast::Enum(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Enum(def))))), | ||
185 | ast::Variant(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Variant(def)))), | ||
186 | ast::Trait(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Trait(def)))), | ||
187 | ast::Static(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Static(def)))), | ||
188 | ast::Const(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Const(def)))), | ||
189 | ast::TypeAlias(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::TypeAlias(def)))), | ||
190 | ast::Impl(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::SelfType(def))), | ||
191 | ast::RecordField(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Field(def))), | ||
192 | ast::TupleField(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Field(def))), | ||
193 | ast::Macro(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Macro(def))), | ||
194 | // ast::Use(it) => sema.to_def(&it).map(|def| (Box::new(it) as _, def.attrs(sema.db))), | ||
195 | _ => return None | ||
196 | } | ||
197 | } | ||
198 | } | ||
199 | |||
200 | fn broken_link_clone_cb<'a, 'b>(link: BrokenLink<'a>) -> Option<(CowStr<'b>, CowStr<'b>)> { | ||
201 | // These allocations are actually unnecessary but the lifetimes on BrokenLinkCallback are wrong | ||
202 | // this is fixed in the repo but not on the crates.io release yet | ||
203 | Some(( | ||
204 | /*url*/ link.reference.to_owned().into(), | ||
205 | /*title*/ link.reference.to_owned().into(), | ||
206 | )) | ||
207 | } | ||
208 | |||
200 | // FIXME: | 209 | // FIXME: |
201 | // BUG: For Option::Some | 210 | // BUG: For Option::Some |
202 | // Returns https://doc.rust-lang.org/nightly/core/prelude/v1/enum.Option.html#variant.Some | 211 | // Returns https://doc.rust-lang.org/nightly/core/prelude/v1/enum.Option.html#variant.Some |
@@ -214,7 +223,7 @@ fn get_doc_link(db: &RootDatabase, definition: Definition) -> Option<String> { | |||
214 | .and_then(|assoc| match assoc.container(db) { | 223 | .and_then(|assoc| match assoc.container(db) { |
215 | AssocItemContainer::Trait(t) => Some(t.into()), | 224 | AssocItemContainer::Trait(t) => Some(t.into()), |
216 | AssocItemContainer::Impl(impld) => { | 225 | AssocItemContainer::Impl(impld) => { |
217 | impld.target_ty(db).as_adt().map(|adt| adt.into()) | 226 | impld.self_ty(db).as_adt().map(|adt| adt.into()) |
218 | } | 227 | } |
219 | }) | 228 | }) |
220 | .unwrap_or_else(|| f.clone().into()), | 229 | .unwrap_or_else(|| f.clone().into()), |
@@ -328,28 +337,6 @@ fn rewrite_url_link(db: &RootDatabase, def: ModuleDef, target: &str) -> Option<S | |||
328 | .map(|url| url.into_string()) | 337 | .map(|url| url.into_string()) |
329 | } | 338 | } |
330 | 339 | ||
331 | /// Retrieve a link to documentation for the given symbol. | ||
332 | pub(crate) fn external_docs( | ||
333 | db: &RootDatabase, | ||
334 | position: &FilePosition, | ||
335 | ) -> Option<DocumentationLink> { | ||
336 | let sema = Semantics::new(db); | ||
337 | let file = sema.parse(position.file_id).syntax().clone(); | ||
338 | let token = pick_best(file.token_at_offset(position.offset))?; | ||
339 | let token = sema.descend_into_macros(token); | ||
340 | |||
341 | let node = token.parent()?; | ||
342 | let definition = match_ast! { | ||
343 | match node { | ||
344 | ast::NameRef(name_ref) => NameRefClass::classify(&sema, &name_ref).map(|d| d.referenced(sema.db)), | ||
345 | ast::Name(name) => NameClass::classify(&sema, &name).map(|d| d.referenced_or_defined(sema.db)), | ||
346 | _ => None, | ||
347 | } | ||
348 | }; | ||
349 | |||
350 | get_doc_link(db, definition?) | ||
351 | } | ||
352 | |||
353 | /// Rewrites a markdown document, applying 'callback' to each link. | 340 | /// Rewrites a markdown document, applying 'callback' to each link. |
354 | fn map_links<'e>( | 341 | fn map_links<'e>( |
355 | events: impl Iterator<Item = Event<'e>>, | 342 | events: impl Iterator<Item = Event<'e>>, |
diff --git a/crates/ide/src/expand_macro.rs b/crates/ide/src/expand_macro.rs index ffb3a6f7d..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::{ |
@@ -23,6 +25,8 @@ pub struct ExpandedMacro { | |||
23 | // | 25 | // |
24 | // | VS Code | **Rust Analyzer: Expand macro recursively** | 26 | // | VS Code | **Rust Analyzer: Expand macro recursively** |
25 | // |=== | 27 | // |=== |
28 | // | ||
29 | // image::https://user-images.githubusercontent.com/48062697/113020648-b3973180-917a-11eb-84a9-ecb921293dc5.gif[] | ||
26 | pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<ExpandedMacro> { | 30 | pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<ExpandedMacro> { |
27 | let sema = Semantics::new(db); | 31 | let sema = Semantics::new(db); |
28 | let file = sema.parse(position.file_id); | 32 | let file = sema.parse(position.file_id); |
@@ -89,24 +93,42 @@ fn insert_whitespaces(syn: SyntaxNode) -> String { | |||
89 | let is_last = | 93 | let is_last = |
90 | |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) }; |
91 | 95 | ||
92 | res += &match token.kind() { | 96 | match token.kind() { |
93 | 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 | } | ||
94 | L_CURLY if is_next(|it| it != R_CURLY, true) => { | 101 | L_CURLY if is_next(|it| it != R_CURLY, true) => { |
95 | indent += 1; | 102 | indent += 1; |
96 | let leading_space = if is_last(is_text, false) { " " } else { "" }; | 103 | if is_last(is_text, false) { |
97 | format!("{}{{\n{}", leading_space, " ".repeat(indent)) | 104 | res.push(' '); |
105 | } | ||
106 | res.push_str("{\n"); | ||
107 | res.extend(iter::repeat(" ").take(2 * indent)); | ||
98 | } | 108 | } |
99 | R_CURLY if is_last(|it| it != L_CURLY, true) => { | 109 | R_CURLY if is_last(|it| it != L_CURLY, true) => { |
100 | indent = indent.saturating_sub(1); | 110 | indent = indent.saturating_sub(1); |
101 | format!("\n{}}}", " ".repeat(indent)) | 111 | res.push('\n'); |
112 | res.extend(iter::repeat(" ").take(2 * indent)); | ||
113 | res.push_str("}"); | ||
102 | } | 114 | } |
103 | R_CURLY => format!("}}\n{}", " ".repeat(indent)), | 115 | R_CURLY => { |
104 | T![;] => format!(";\n{}", " ".repeat(indent)), | 116 | res.push_str("}\n"); |
105 | T![->] => " -> ".to_string(), | 117 | res.extend(iter::repeat(" ").take(2 * indent)); |
106 | T![=] => " = ".to_string(), | 118 | } |
107 | T![=>] => " => ".to_string(), | 119 | LIFETIME_IDENT if is_next(|it| it == IDENT, true) => { |
108 | _ => token.text().to_string(), | 120 | res.push_str(token.text()); |
109 | }; | 121 | res.push(' '); |
122 | } | ||
123 | T![;] => { | ||
124 | res.push_str(";\n"); | ||
125 | res.extend(iter::repeat(" ").take(2 * indent)); | ||
126 | } | ||
127 | T![->] => res.push_str(" -> "), | ||
128 | T![=] => res.push_str(" = "), | ||
129 | T![=>] => res.push_str(" => "), | ||
130 | _ => res.push_str(token.text()), | ||
131 | } | ||
110 | 132 | ||
111 | last = Some(token.kind()); | 133 | last = Some(token.kind()); |
112 | } | 134 | } |
diff --git a/crates/ide/src/extend_selection.rs b/crates/ide/src/extend_selection.rs index 5201ce587..7032889ac 100644 --- a/crates/ide/src/extend_selection.rs +++ b/crates/ide/src/extend_selection.rs | |||
@@ -24,6 +24,8 @@ use crate::FileRange; | |||
24 | // | 24 | // |
25 | // | VS Code | kbd:[Alt+Shift+→], kbd:[Alt+Shift+←] | 25 | // | VS Code | kbd:[Alt+Shift+→], kbd:[Alt+Shift+←] |
26 | // |=== | 26 | // |=== |
27 | // | ||
28 | // image::https://user-images.githubusercontent.com/48062697/113020651-b42fc800-917a-11eb-8a4f-cf1a07859fac.gif[] | ||
27 | pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange { | 29 | pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange { |
28 | let sema = Semantics::new(db); | 30 | let sema = Semantics::new(db); |
29 | let src = sema.parse(frange.file_id); | 31 | let src = sema.parse(frange.file_id); |
diff --git a/crates/ide/src/file_structure.rs b/crates/ide/src/file_structure.rs index 2c898eae8..19071d6be 100644 --- a/crates/ide/src/file_structure.rs +++ b/crates/ide/src/file_structure.rs | |||
@@ -35,6 +35,9 @@ pub enum StructureNodeKind { | |||
35 | // | 35 | // |
36 | // | VS Code | kbd:[Ctrl+Shift+O] | 36 | // | VS Code | kbd:[Ctrl+Shift+O] |
37 | // |=== | 37 | // |=== |
38 | // | ||
39 | // image::https://user-images.githubusercontent.com/48062697/113020654-b42fc800-917a-11eb-8388-e7dc4d92b02e.gif[] | ||
40 | |||
38 | pub(crate) fn file_structure(file: &SourceFile) -> Vec<StructureNode> { | 41 | pub(crate) fn file_structure(file: &SourceFile) -> Vec<StructureNode> { |
39 | let mut res = Vec::new(); | 42 | let mut res = Vec::new(); |
40 | let mut stack = Vec::new(); | 43 | let mut stack = Vec::new(); |
diff --git a/crates/ide/src/folding_ranges.rs b/crates/ide/src/folding_ranges.rs index 4b1b24562..2b9ed123c 100644 --- a/crates/ide/src/folding_ranges.rs +++ b/crates/ide/src/folding_ranges.rs | |||
@@ -17,6 +17,9 @@ pub enum FoldKind { | |||
17 | Block, | 17 | Block, |
18 | ArgList, | 18 | ArgList, |
19 | Region, | 19 | Region, |
20 | Consts, | ||
21 | Statics, | ||
22 | Array, | ||
20 | } | 23 | } |
21 | 24 | ||
22 | #[derive(Debug)] | 25 | #[derive(Debug)] |
@@ -30,6 +33,8 @@ pub(crate) fn folding_ranges(file: &SourceFile) -> Vec<Fold> { | |||
30 | let mut visited_comments = FxHashSet::default(); | 33 | let mut visited_comments = FxHashSet::default(); |
31 | let mut visited_imports = FxHashSet::default(); | 34 | let mut visited_imports = FxHashSet::default(); |
32 | let mut visited_mods = FxHashSet::default(); | 35 | let mut visited_mods = FxHashSet::default(); |
36 | let mut visited_consts = FxHashSet::default(); | ||
37 | let mut visited_statics = FxHashSet::default(); | ||
33 | // regions can be nested, here is a LIFO buffer | 38 | // regions can be nested, here is a LIFO buffer |
34 | let mut regions_starts: Vec<TextSize> = vec![]; | 39 | let mut regions_starts: Vec<TextSize> = vec![]; |
35 | 40 | ||
@@ -91,6 +96,19 @@ pub(crate) fn folding_ranges(file: &SourceFile) -> Vec<Fold> { | |||
91 | res.push(Fold { range, kind: FoldKind::Mods }) | 96 | res.push(Fold { range, kind: FoldKind::Mods }) |
92 | } | 97 | } |
93 | } | 98 | } |
99 | |||
100 | // Fold groups of consts | ||
101 | if node.kind() == CONST && !visited_consts.contains(&node) { | ||
102 | if let Some(range) = contiguous_range_for_group(&node, &mut visited_consts) { | ||
103 | res.push(Fold { range, kind: FoldKind::Consts }) | ||
104 | } | ||
105 | } | ||
106 | // Fold groups of consts | ||
107 | if node.kind() == STATIC && !visited_statics.contains(&node) { | ||
108 | if let Some(range) = contiguous_range_for_group(&node, &mut visited_statics) { | ||
109 | res.push(Fold { range, kind: FoldKind::Statics }) | ||
110 | } | ||
111 | } | ||
94 | } | 112 | } |
95 | } | 113 | } |
96 | } | 114 | } |
@@ -102,6 +120,7 @@ fn fold_kind(kind: SyntaxKind) -> Option<FoldKind> { | |||
102 | match kind { | 120 | match kind { |
103 | COMMENT => Some(FoldKind::Comment), | 121 | COMMENT => Some(FoldKind::Comment), |
104 | ARG_LIST | PARAM_LIST => Some(FoldKind::ArgList), | 122 | ARG_LIST | PARAM_LIST => Some(FoldKind::ArgList), |
123 | ARRAY_EXPR => Some(FoldKind::Array), | ||
105 | ASSOC_ITEM_LIST | 124 | ASSOC_ITEM_LIST |
106 | | RECORD_FIELD_LIST | 125 | | RECORD_FIELD_LIST |
107 | | RECORD_PAT_FIELD_LIST | 126 | | RECORD_PAT_FIELD_LIST |
@@ -250,6 +269,9 @@ mod tests { | |||
250 | FoldKind::Block => "block", | 269 | FoldKind::Block => "block", |
251 | FoldKind::ArgList => "arglist", | 270 | FoldKind::ArgList => "arglist", |
252 | FoldKind::Region => "region", | 271 | FoldKind::Region => "region", |
272 | FoldKind::Consts => "consts", | ||
273 | FoldKind::Statics => "statics", | ||
274 | FoldKind::Array => "array", | ||
253 | }; | 275 | }; |
254 | assert_eq!(kind, &attr.unwrap()); | 276 | assert_eq!(kind, &attr.unwrap()); |
255 | } | 277 | } |
@@ -446,6 +468,20 @@ fn foo<fold arglist>( | |||
446 | } | 468 | } |
447 | 469 | ||
448 | #[test] | 470 | #[test] |
471 | fn fold_multiline_array() { | ||
472 | check( | ||
473 | r#" | ||
474 | const FOO: [usize; 4] = <fold array>[ | ||
475 | 1, | ||
476 | 2, | ||
477 | 3, | ||
478 | 4, | ||
479 | ]</fold>; | ||
480 | "#, | ||
481 | ) | ||
482 | } | ||
483 | |||
484 | #[test] | ||
449 | fn fold_region() { | 485 | fn fold_region() { |
450 | check( | 486 | check( |
451 | r#" | 487 | r#" |
@@ -457,4 +493,24 @@ calling_function(x,y); | |||
457 | "#, | 493 | "#, |
458 | ) | 494 | ) |
459 | } | 495 | } |
496 | |||
497 | #[test] | ||
498 | fn fold_consecutive_const() { | ||
499 | check( | ||
500 | r#" | ||
501 | <fold consts>const FIRST_CONST: &str = "first"; | ||
502 | const SECOND_CONST: &str = "second";</fold> | ||
503 | "#, | ||
504 | ) | ||
505 | } | ||
506 | |||
507 | #[test] | ||
508 | fn fold_consecutive_static() { | ||
509 | check( | ||
510 | r#" | ||
511 | <fold statics>static FIRST_STATIC: &str = "first"; | ||
512 | static SECOND_STATIC: &str = "second";</fold> | ||
513 | "#, | ||
514 | ) | ||
515 | } | ||
460 | } | 516 | } |
diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index a2c97061f..a04333e63 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs | |||
@@ -1,5 +1,5 @@ | |||
1 | use either::Either; | 1 | use either::Either; |
2 | use hir::Semantics; | 2 | use hir::{InFile, Semantics}; |
3 | use ide_db::{ | 3 | use ide_db::{ |
4 | defs::{NameClass, NameRefClass}, | 4 | defs::{NameClass, NameRefClass}, |
5 | RootDatabase, | 5 | RootDatabase, |
@@ -8,7 +8,7 @@ use syntax::{ast, match_ast, AstNode, AstToken, SyntaxKind::*, SyntaxToken, Toke | |||
8 | 8 | ||
9 | use crate::{ | 9 | use crate::{ |
10 | display::TryToNav, | 10 | display::TryToNav, |
11 | doc_links::{doc_owner_to_def, extract_positioned_link_from_comment, resolve_doc_path_for_def}, | 11 | doc_links::{doc_attributes, extract_definitions_from_markdown, resolve_doc_path_for_def}, |
12 | FilePosition, NavigationTarget, RangeInfo, | 12 | FilePosition, NavigationTarget, RangeInfo, |
13 | }; | 13 | }; |
14 | 14 | ||
@@ -21,6 +21,8 @@ use crate::{ | |||
21 | // | 21 | // |
22 | // | VS Code | kbd:[F12] | 22 | // | VS Code | kbd:[F12] |
23 | // |=== | 23 | // |=== |
24 | // | ||
25 | // image::https://user-images.githubusercontent.com/48062697/113065563-025fbe00-91b1-11eb-83e4-a5a703610b23.gif[] | ||
24 | pub(crate) fn goto_definition( | 26 | pub(crate) fn goto_definition( |
25 | db: &RootDatabase, | 27 | db: &RootDatabase, |
26 | position: FilePosition, | 28 | position: FilePosition, |
@@ -30,9 +32,16 @@ pub(crate) fn goto_definition( | |||
30 | let original_token = pick_best(file.token_at_offset(position.offset))?; | 32 | let original_token = pick_best(file.token_at_offset(position.offset))?; |
31 | let token = sema.descend_into_macros(original_token.clone()); | 33 | let token = sema.descend_into_macros(original_token.clone()); |
32 | let parent = token.parent()?; | 34 | let parent = token.parent()?; |
33 | if let Some(comment) = ast::Comment::cast(token) { | 35 | if let Some(_) = ast::Comment::cast(token) { |
34 | let (_, link, ns) = extract_positioned_link_from_comment(position.offset, &comment)?; | 36 | let (attributes, def) = doc_attributes(&sema, &parent)?; |
35 | let def = doc_owner_to_def(&sema, &parent)?; | 37 | |
38 | let (docs, doc_mapping) = attributes.docs_with_rangemap(db)?; | ||
39 | let (_, link, ns) = | ||
40 | extract_definitions_from_markdown(docs.as_str()).into_iter().find(|(range, ..)| { | ||
41 | doc_mapping.map(range.clone()).map_or(false, |InFile { file_id, value: range }| { | ||
42 | file_id == position.file_id.into() && range.contains(position.offset) | ||
43 | }) | ||
44 | })?; | ||
36 | let nav = resolve_doc_path_for_def(db, def, &link, ns)?.try_to_nav(db)?; | 45 | let nav = resolve_doc_path_for_def(db, def, &link, ns)?.try_to_nav(db)?; |
37 | return Some(RangeInfo::new(original_token.text_range(), vec![nav])); | 46 | return Some(RangeInfo::new(original_token.text_range(), vec![nav])); |
38 | } | 47 | } |
@@ -101,6 +110,13 @@ mod tests { | |||
101 | 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() }); |
102 | } | 111 | } |
103 | 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 | |||
104 | #[test] | 120 | #[test] |
105 | fn goto_def_for_extern_crate() { | 121 | fn goto_def_for_extern_crate() { |
106 | check( | 122 | check( |
@@ -918,6 +934,16 @@ fn f() -> impl Iterator<Item$0 = u8> {} | |||
918 | } | 934 | } |
919 | 935 | ||
920 | #[test] | 936 | #[test] |
937 | fn unknown_assoc_ty() { | ||
938 | check_unresolved( | ||
939 | r#" | ||
940 | trait Iterator { type Item; } | ||
941 | fn f() -> impl Iterator<Invalid$0 = u8> {} | ||
942 | "#, | ||
943 | ) | ||
944 | } | ||
945 | |||
946 | #[test] | ||
921 | fn goto_def_for_assoc_ty_in_path_multiple() { | 947 | fn goto_def_for_assoc_ty_in_path_multiple() { |
922 | check( | 948 | check( |
923 | r#" | 949 | r#" |
@@ -1143,4 +1169,51 @@ fn fn_macro() {} | |||
1143 | "#, | 1169 | "#, |
1144 | ) | 1170 | ) |
1145 | } | 1171 | } |
1172 | |||
1173 | #[test] | ||
1174 | fn goto_intra_doc_links() { | ||
1175 | check( | ||
1176 | r#" | ||
1177 | |||
1178 | pub mod theitem { | ||
1179 | /// This is the item. Cool! | ||
1180 | pub struct TheItem; | ||
1181 | //^^^^^^^ | ||
1182 | } | ||
1183 | |||
1184 | /// Gives you a [`TheItem$0`]. | ||
1185 | /// | ||
1186 | /// [`TheItem`]: theitem::TheItem | ||
1187 | pub fn gimme() -> theitem::TheItem { | ||
1188 | theitem::TheItem | ||
1189 | } | ||
1190 | "#, | ||
1191 | ); | ||
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 | } | ||
1146 | } | 1219 | } |
diff --git a/crates/ide/src/goto_implementation.rs b/crates/ide/src/goto_implementation.rs index f4d7c14a6..05130a237 100644 --- a/crates/ide/src/goto_implementation.rs +++ b/crates/ide/src/goto_implementation.rs | |||
@@ -16,6 +16,8 @@ use crate::{display::TryToNav, FilePosition, NavigationTarget, RangeInfo}; | |||
16 | // | 16 | // |
17 | // | VS Code | kbd:[Ctrl+F12] | 17 | // | VS Code | kbd:[Ctrl+F12] |
18 | // |=== | 18 | // |=== |
19 | // | ||
20 | // image::https://user-images.githubusercontent.com/48062697/113065566-02f85480-91b1-11eb-9288-aaad8abd8841.gif[] | ||
19 | pub(crate) fn goto_implementation( | 21 | pub(crate) fn goto_implementation( |
20 | db: &RootDatabase, | 22 | db: &RootDatabase, |
21 | position: FilePosition, | 23 | position: FilePosition, |
diff --git a/crates/ide/src/goto_type_definition.rs b/crates/ide/src/goto_type_definition.rs index 2d38cb112..9d34b109b 100644 --- a/crates/ide/src/goto_type_definition.rs +++ b/crates/ide/src/goto_type_definition.rs | |||
@@ -12,6 +12,8 @@ use crate::{display::TryToNav, FilePosition, NavigationTarget, RangeInfo}; | |||
12 | // | 12 | // |
13 | // | VS Code | **Go to Type Definition* | 13 | // | VS Code | **Go to Type Definition* |
14 | // |=== | 14 | // |=== |
15 | // | ||
16 | // image::https://user-images.githubusercontent.com/48062697/113020657-b560f500-917a-11eb-9007-0f809733a338.gif[] | ||
15 | pub(crate) fn goto_type_definition( | 17 | pub(crate) fn goto_type_definition( |
16 | db: &RootDatabase, | 18 | db: &RootDatabase, |
17 | position: FilePosition, | 19 | position: FilePosition, |
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index 3c951c507..9de653739 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs | |||
@@ -1,6 +1,6 @@ | |||
1 | use either::Either; | 1 | use either::Either; |
2 | use hir::{ | 2 | use hir::{ |
3 | AsAssocItem, AssocItemContainer, GenericParam, HasAttrs, HasSource, HirDisplay, Module, | 3 | AsAssocItem, AssocItemContainer, GenericParam, HasAttrs, HasSource, HirDisplay, InFile, Module, |
4 | ModuleDef, Semantics, | 4 | ModuleDef, Semantics, |
5 | }; | 5 | }; |
6 | use ide_db::{ | 6 | use ide_db::{ |
@@ -16,8 +16,8 @@ use syntax::{ast, match_ast, AstNode, AstToken, SyntaxKind::*, SyntaxToken, Toke | |||
16 | use crate::{ | 16 | use crate::{ |
17 | display::{macro_label, TryToNav}, | 17 | display::{macro_label, TryToNav}, |
18 | doc_links::{ | 18 | doc_links::{ |
19 | doc_owner_to_def, extract_positioned_link_from_comment, remove_links, | 19 | doc_attributes, extract_definitions_from_markdown, remove_links, resolve_doc_path_for_def, |
20 | resolve_doc_path_for_def, rewrite_links, | 20 | rewrite_links, |
21 | }, | 21 | }, |
22 | markdown_remove::remove_markdown, | 22 | markdown_remove::remove_markdown, |
23 | markup::Markup, | 23 | markup::Markup, |
@@ -82,6 +82,8 @@ pub struct HoverResult { | |||
82 | // | 82 | // |
83 | // Shows additional information, like type of an expression or documentation for definition when "focusing" code. | 83 | // Shows additional information, like type of an expression or documentation for definition when "focusing" code. |
84 | // Focusing is usually hovering with a mouse, but can also be triggered with a shortcut. | 84 | // Focusing is usually hovering with a mouse, but can also be triggered with a shortcut. |
85 | // | ||
86 | // image::https://user-images.githubusercontent.com/48062697/113020658-b5f98b80-917a-11eb-9f88-3dbc27320c95.gif[] | ||
85 | pub(crate) fn hover( | 87 | pub(crate) fn hover( |
86 | db: &RootDatabase, | 88 | db: &RootDatabase, |
87 | position: FilePosition, | 89 | position: FilePosition, |
@@ -114,11 +116,19 @@ pub(crate) fn hover( | |||
114 | ), | 116 | ), |
115 | 117 | ||
116 | _ => ast::Comment::cast(token.clone()) | 118 | _ => ast::Comment::cast(token.clone()) |
117 | .and_then(|comment| { | 119 | .and_then(|_| { |
120 | let (attributes, def) = doc_attributes(&sema, &node)?; | ||
121 | let (docs, doc_mapping) = attributes.docs_with_rangemap(db)?; | ||
118 | let (idl_range, link, ns) = | 122 | let (idl_range, link, ns) = |
119 | extract_positioned_link_from_comment(position.offset, &comment)?; | 123 | extract_definitions_from_markdown(docs.as_str()).into_iter().find_map(|(range, link, ns)| { |
124 | let InFile { file_id, value: range } = doc_mapping.map(range.clone())?; | ||
125 | if file_id == position.file_id.into() && range.contains(position.offset) { | ||
126 | Some((range, link, ns)) | ||
127 | } else { | ||
128 | None | ||
129 | } | ||
130 | })?; | ||
120 | range = Some(idl_range); | 131 | range = Some(idl_range); |
121 | let def = doc_owner_to_def(&sema, &node)?; | ||
122 | resolve_doc_path_for_def(db, def, &link, ns) | 132 | resolve_doc_path_for_def(db, def, &link, ns) |
123 | }) | 133 | }) |
124 | .map(Definition::ModuleDef), | 134 | .map(Definition::ModuleDef), |
@@ -195,7 +205,7 @@ fn show_implementations_action(db: &RootDatabase, def: Definition) -> Option<Hov | |||
195 | let adt = match def { | 205 | let adt = match def { |
196 | Definition::ModuleDef(ModuleDef::Trait(it)) => return it.try_to_nav(db).map(to_action), | 206 | Definition::ModuleDef(ModuleDef::Trait(it)) => return it.try_to_nav(db).map(to_action), |
197 | Definition::ModuleDef(ModuleDef::Adt(it)) => Some(it), | 207 | Definition::ModuleDef(ModuleDef::Adt(it)) => Some(it), |
198 | Definition::SelfType(it) => it.target_ty(db).as_adt(), | 208 | Definition::SelfType(it) => it.self_ty(db).as_adt(), |
199 | _ => None, | 209 | _ => None, |
200 | }?; | 210 | }?; |
201 | adt.try_to_nav(db).map(to_action) | 211 | adt.try_to_nav(db).map(to_action) |
@@ -318,7 +328,7 @@ fn definition_owner_name(db: &RootDatabase, def: &Definition) -> Option<String> | |||
318 | Definition::ModuleDef(md) => match md { | 328 | Definition::ModuleDef(md) => match md { |
319 | ModuleDef::Function(f) => match f.as_assoc_item(db)?.container(db) { | 329 | ModuleDef::Function(f) => match f.as_assoc_item(db)?.container(db) { |
320 | AssocItemContainer::Trait(t) => Some(t.name(db)), | 330 | AssocItemContainer::Trait(t) => Some(t.name(db)), |
321 | AssocItemContainer::Impl(i) => i.target_ty(db).as_adt().map(|adt| adt.name(db)), | 331 | AssocItemContainer::Impl(i) => i.self_ty(db).as_adt().map(|adt| adt.name(db)), |
322 | }, | 332 | }, |
323 | ModuleDef::Variant(e) => Some(e.parent_enum(db).name(db)), | 333 | ModuleDef::Variant(e) => Some(e.parent_enum(db).name(db)), |
324 | _ => None, | 334 | _ => None, |
@@ -376,7 +386,7 @@ fn hover_for_definition( | |||
376 | }, | 386 | }, |
377 | Definition::Local(it) => hover_for_local(it, db), | 387 | Definition::Local(it) => hover_for_local(it, db), |
378 | Definition::SelfType(impl_def) => { | 388 | Definition::SelfType(impl_def) => { |
379 | impl_def.target_ty(db).as_adt().and_then(|adt| from_hir_fmt(db, adt, mod_path)) | 389 | impl_def.self_ty(db).as_adt().and_then(|adt| from_hir_fmt(db, adt, mod_path)) |
380 | } | 390 | } |
381 | Definition::GenericParam(it) => from_hir_fmt(db, it, None), | 391 | Definition::GenericParam(it) => from_hir_fmt(db, it, None), |
382 | Definition::Label(it) => Some(Markup::fenced_block(&it.name(db))), | 392 | Definition::Label(it) => Some(Markup::fenced_block(&it.name(db))), |
@@ -470,6 +480,7 @@ fn find_std_module(famous_defs: &FamousDefs, name: &str) -> Option<hir::Module> | |||
470 | 480 | ||
471 | fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { | 481 | fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { |
472 | return tokens.max_by_key(priority); | 482 | return tokens.max_by_key(priority); |
483 | |||
473 | fn priority(n: &SyntaxToken) -> usize { | 484 | fn priority(n: &SyntaxToken) -> usize { |
474 | match n.kind() { | 485 | match n.kind() { |
475 | IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | T![super] | T![crate] => 3, | 486 | IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | T![super] | T![crate] => 3, |
@@ -3811,24 +3822,139 @@ fn main() { | |||
3811 | fn hover_intra_doc_links() { | 3822 | fn hover_intra_doc_links() { |
3812 | check( | 3823 | check( |
3813 | r#" | 3824 | r#" |
3814 | /// This is the [`foo`](foo$0) function. | 3825 | |
3815 | fn foo() {} | 3826 | pub mod theitem { |
3827 | /// This is the item. Cool! | ||
3828 | pub struct TheItem; | ||
3829 | } | ||
3830 | |||
3831 | /// Gives you a [`TheItem$0`]. | ||
3832 | /// | ||
3833 | /// [`TheItem`]: theitem::TheItem | ||
3834 | pub fn gimme() -> theitem::TheItem { | ||
3835 | theitem::TheItem | ||
3836 | } | ||
3816 | "#, | 3837 | "#, |
3817 | expect![[r#" | 3838 | expect![[r#" |
3818 | *[`foo`](foo)* | 3839 | *[`TheItem`]* |
3819 | 3840 | ||
3820 | ```rust | 3841 | ```rust |
3821 | test | 3842 | test::theitem |
3822 | ``` | 3843 | ``` |
3823 | 3844 | ||
3824 | ```rust | 3845 | ```rust |
3825 | fn foo() | 3846 | pub struct TheItem |
3826 | ``` | 3847 | ``` |
3827 | 3848 | ||
3828 | --- | 3849 | --- |
3829 | 3850 | ||
3830 | This is the [`foo`](https://docs.rs/test/*/test/fn.foo.html) function. | 3851 | This is the item. Cool! |
3852 | "#]], | ||
3853 | ); | ||
3854 | } | ||
3855 | |||
3856 | #[test] | ||
3857 | fn hover_generic_assoc() { | ||
3858 | check( | ||
3859 | r#" | ||
3860 | fn foo<T: A>() where T::Assoc$0: {} | ||
3861 | |||
3862 | trait A { | ||
3863 | type Assoc; | ||
3864 | }"#, | ||
3865 | expect![[r#" | ||
3866 | *Assoc* | ||
3867 | |||
3868 | ```rust | ||
3869 | test | ||
3870 | ``` | ||
3871 | |||
3872 | ```rust | ||
3873 | type Assoc | ||
3874 | ``` | ||
3875 | "#]], | ||
3876 | ); | ||
3877 | check( | ||
3878 | r#" | ||
3879 | fn foo<T: A>() { | ||
3880 | let _: <T>::Assoc$0; | ||
3881 | } | ||
3882 | |||
3883 | trait A { | ||
3884 | type Assoc; | ||
3885 | }"#, | ||
3886 | expect![[r#" | ||
3887 | *Assoc* | ||
3888 | |||
3889 | ```rust | ||
3890 | test | ||
3891 | ``` | ||
3892 | |||
3893 | ```rust | ||
3894 | type Assoc | ||
3895 | ``` | ||
3896 | "#]], | ||
3897 | ); | ||
3898 | check( | ||
3899 | r#" | ||
3900 | trait A where | ||
3901 | Self::Assoc$0: , | ||
3902 | { | ||
3903 | type Assoc; | ||
3904 | }"#, | ||
3905 | expect![[r#" | ||
3906 | *Assoc* | ||
3907 | |||
3908 | ```rust | ||
3909 | test | ||
3910 | ``` | ||
3911 | |||
3912 | ```rust | ||
3913 | type Assoc | ||
3914 | ``` | ||
3831 | "#]], | 3915 | "#]], |
3832 | ); | 3916 | ); |
3833 | } | 3917 | } |
3918 | |||
3919 | #[test] | ||
3920 | fn string_shadowed_with_inner_items() { | ||
3921 | check( | ||
3922 | r#" | ||
3923 | //- /main.rs crate:main deps:alloc | ||
3924 | |||
3925 | /// Custom `String` type. | ||
3926 | struct String; | ||
3927 | |||
3928 | fn f() { | ||
3929 | let _: String$0; | ||
3930 | |||
3931 | fn inner() {} | ||
3932 | } | ||
3933 | |||
3934 | //- /alloc.rs crate:alloc | ||
3935 | #[prelude_import] | ||
3936 | pub use string::*; | ||
3937 | |||
3938 | mod string { | ||
3939 | /// This is `alloc::String`. | ||
3940 | pub struct String; | ||
3941 | } | ||
3942 | "#, | ||
3943 | expect![[r#" | ||
3944 | *String* | ||
3945 | |||
3946 | ```rust | ||
3947 | main | ||
3948 | ``` | ||
3949 | |||
3950 | ```rust | ||
3951 | struct String | ||
3952 | ``` | ||
3953 | |||
3954 | --- | ||
3955 | |||
3956 | Custom `String` type. | ||
3957 | "#]], | ||
3958 | ) | ||
3959 | } | ||
3834 | } | 3960 | } |
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs index 25f96222c..d5ef054d8 100644 --- a/crates/ide/src/inlay_hints.rs +++ b/crates/ide/src/inlay_hints.rs | |||
@@ -52,6 +52,8 @@ pub struct InlayHint { | |||
52 | // | 52 | // |
53 | // | VS Code | **Rust Analyzer: Toggle inlay hints* | 53 | // | VS Code | **Rust Analyzer: Toggle inlay hints* |
54 | // |=== | 54 | // |=== |
55 | // | ||
56 | // image::https://user-images.githubusercontent.com/48062697/113020660-b5f98b80-917a-11eb-8d70-3be3fd558cdd.png[] | ||
55 | pub(crate) fn inlay_hints( | 57 | pub(crate) fn inlay_hints( |
56 | db: &RootDatabase, | 58 | db: &RootDatabase, |
57 | file_id: FileId, | 59 | file_id: FileId, |
@@ -232,7 +234,7 @@ fn hint_iterator( | |||
232 | hir::AssocItem::TypeAlias(alias) if alias.name(db) == known::Item => Some(alias), | 234 | hir::AssocItem::TypeAlias(alias) if alias.name(db) == known::Item => Some(alias), |
233 | _ => None, | 235 | _ => None, |
234 | })?; | 236 | })?; |
235 | if let Some(ty) = ty.normalize_trait_assoc_type(db, iter_trait, &[], assoc_type_item) { | 237 | if let Some(ty) = ty.normalize_trait_assoc_type(db, &[], assoc_type_item) { |
236 | const LABEL_START: &str = "impl Iterator<Item = "; | 238 | const LABEL_START: &str = "impl Iterator<Item = "; |
237 | const LABEL_END: &str = ">"; | 239 | const LABEL_END: &str = ">"; |
238 | 240 | ||
diff --git a/crates/ide/src/join_lines.rs b/crates/ide/src/join_lines.rs index 4b25135cd..fe2a349e6 100644 --- a/crates/ide/src/join_lines.rs +++ b/crates/ide/src/join_lines.rs | |||
@@ -19,6 +19,8 @@ use text_edit::{TextEdit, TextEditBuilder}; | |||
19 | // | 19 | // |
20 | // | VS Code | **Rust Analyzer: Join lines** | 20 | // | VS Code | **Rust Analyzer: Join lines** |
21 | // |=== | 21 | // |=== |
22 | // | ||
23 | // image::https://user-images.githubusercontent.com/48062697/113020661-b6922200-917a-11eb-87c4-b75acc028f11.gif[] | ||
22 | pub(crate) fn join_lines(file: &SourceFile, range: TextRange) -> TextEdit { | 24 | pub(crate) fn join_lines(file: &SourceFile, range: TextRange) -> TextEdit { |
23 | let range = if range.is_empty() { | 25 | let range = if range.is_empty() { |
24 | let syntax = file.syntax(); | 26 | let syntax = file.syntax(); |
@@ -86,8 +88,11 @@ fn remove_newline(edit: &mut TextEditBuilder, token: &SyntaxToken, offset: TextS | |||
86 | } | 88 | } |
87 | 89 | ||
88 | // The node is between two other nodes | 90 | // The node is between two other nodes |
89 | let prev = token.prev_sibling_or_token().unwrap(); | 91 | let (prev, next) = match (token.prev_sibling_or_token(), token.next_sibling_or_token()) { |
90 | let next = token.next_sibling_or_token().unwrap(); | 92 | (Some(prev), Some(next)) => (prev, next), |
93 | _ => return, | ||
94 | }; | ||
95 | |||
91 | if is_trailing_comma(prev.kind(), next.kind()) { | 96 | if is_trailing_comma(prev.kind(), next.kind()) { |
92 | // Removes: trailing comma, newline (incl. surrounding whitespace) | 97 | // Removes: trailing comma, newline (incl. surrounding whitespace) |
93 | edit.delete(TextRange::new(prev.text_range().start(), token.text_range().end())); | 98 | edit.delete(TextRange::new(prev.text_range().start(), token.text_range().end())); |
@@ -827,4 +832,15 @@ $0hello world | |||
827 | "#, | 832 | "#, |
828 | ); | 833 | ); |
829 | } | 834 | } |
835 | #[test] | ||
836 | fn join_last_line_empty() { | ||
837 | check_join_lines( | ||
838 | r#" | ||
839 | fn main() {$0} | ||
840 | "#, | ||
841 | r#" | ||
842 | fn main() {$0} | ||
843 | "#, | ||
844 | ); | ||
845 | } | ||
830 | } | 846 | } |
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/matching_brace.rs b/crates/ide/src/matching_brace.rs index 4241a6dac..261dcc255 100644 --- a/crates/ide/src/matching_brace.rs +++ b/crates/ide/src/matching_brace.rs | |||
@@ -14,6 +14,8 @@ use syntax::{ | |||
14 | // | 14 | // |
15 | // | VS Code | **Rust Analyzer: Find matching brace** | 15 | // | VS Code | **Rust Analyzer: Find matching brace** |
16 | // |=== | 16 | // |=== |
17 | // | ||
18 | // image::https://user-images.githubusercontent.com/48062697/113065573-04298180-91b1-11eb-8dec-d4e2a202f304.gif[] | ||
17 | pub(crate) fn matching_brace(file: &SourceFile, offset: TextSize) -> Option<TextSize> { | 19 | pub(crate) fn matching_brace(file: &SourceFile, offset: TextSize) -> Option<TextSize> { |
18 | const BRACES: &[SyntaxKind] = | 20 | const BRACES: &[SyntaxKind] = |
19 | &[T!['{'], T!['}'], T!['['], T![']'], T!['('], T![')'], T![<], T![>], T![|], T![|]]; | 21 | &[T!['{'], T!['}'], T!['['], T![']'], T!['('], T![')'], T![<], T![>], T![|], T![|]]; |
diff --git a/crates/ide/src/move_item.rs b/crates/ide/src/move_item.rs index 05fa8fc13..246f10a0a 100644 --- a/crates/ide/src/move_item.rs +++ b/crates/ide/src/move_item.rs | |||
@@ -1,13 +1,15 @@ | |||
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}; |
5 | use itertools::Itertools; | 5 | use itertools::Itertools; |
6 | use syntax::{ | 6 | use syntax::{ |
7 | algo, ast, match_ast, AstNode, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode, TextRange, | 7 | algo, ast, match_ast, AstNode, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, |
8 | TokenAtOffset, | ||
8 | }; | 9 | }; |
9 | use text_edit::{TextEdit, TextEditBuilder}; | 10 | use text_edit::{TextEdit, TextEditBuilder}; |
10 | 11 | ||
12 | #[derive(Copy, Clone, Debug)] | ||
11 | pub enum Direction { | 13 | pub enum Direction { |
12 | Up, | 14 | Up, |
13 | Down, | 15 | Down, |
@@ -23,6 +25,8 @@ pub enum Direction { | |||
23 | // | VS Code | **Rust Analyzer: Move item up** | 25 | // | VS Code | **Rust Analyzer: Move item up** |
24 | // | VS Code | **Rust Analyzer: Move item down** | 26 | // | VS Code | **Rust Analyzer: Move item down** |
25 | // |=== | 27 | // |=== |
28 | // | ||
29 | // image::https://user-images.githubusercontent.com/48062697/113065576-04298180-91b1-11eb-91ce-4505e99ed598.gif[] | ||
26 | pub(crate) fn move_item( | 30 | pub(crate) fn move_item( |
27 | db: &RootDatabase, | 31 | db: &RootDatabase, |
28 | range: FileRange, | 32 | range: FileRange, |
@@ -31,14 +35,19 @@ pub(crate) fn move_item( | |||
31 | let sema = Semantics::new(db); | 35 | let sema = Semantics::new(db); |
32 | let file = sema.parse(range.file_id); | 36 | let file = sema.parse(range.file_id); |
33 | 37 | ||
34 | let item = file.syntax().covering_element(range.range); | 38 | let item = if range.range.is_empty() { |
39 | SyntaxElement::Token(pick_best(file.syntax().token_at_offset(range.range.start()))?) | ||
40 | } else { | ||
41 | file.syntax().covering_element(range.range) | ||
42 | }; | ||
43 | |||
35 | find_ancestors(item, direction, range.range) | 44 | find_ancestors(item, direction, range.range) |
36 | } | 45 | } |
37 | 46 | ||
38 | fn find_ancestors(item: SyntaxElement, direction: Direction, range: TextRange) -> Option<TextEdit> { | 47 | fn find_ancestors(item: SyntaxElement, direction: Direction, range: TextRange) -> Option<TextEdit> { |
39 | let root = match item { | 48 | let root = match item { |
40 | NodeOrToken::Node(node) => node, | 49 | SyntaxElement::Node(node) => node, |
41 | NodeOrToken::Token(token) => token.parent()?, | 50 | SyntaxElement::Token(token) => token.parent()?, |
42 | }; | 51 | }; |
43 | 52 | ||
44 | let movable = [ | 53 | let movable = [ |
@@ -51,6 +60,11 @@ fn find_ancestors(item: SyntaxElement, direction: Direction, range: TextRange) - | |||
51 | SyntaxKind::PARAM, | 60 | SyntaxKind::PARAM, |
52 | SyntaxKind::LET_STMT, | 61 | SyntaxKind::LET_STMT, |
53 | SyntaxKind::EXPR_STMT, | 62 | SyntaxKind::EXPR_STMT, |
63 | SyntaxKind::IF_EXPR, | ||
64 | SyntaxKind::FOR_EXPR, | ||
65 | SyntaxKind::LOOP_EXPR, | ||
66 | SyntaxKind::WHILE_EXPR, | ||
67 | SyntaxKind::RETURN_EXPR, | ||
54 | SyntaxKind::MATCH_EXPR, | 68 | SyntaxKind::MATCH_EXPR, |
55 | SyntaxKind::MACRO_CALL, | 69 | SyntaxKind::MACRO_CALL, |
56 | SyntaxKind::TYPE_ALIAS, | 70 | SyntaxKind::TYPE_ALIAS, |
@@ -83,12 +97,12 @@ fn move_in_direction( | |||
83 | ) -> Option<TextEdit> { | 97 | ) -> Option<TextEdit> { |
84 | match_ast! { | 98 | match_ast! { |
85 | match node { | 99 | match node { |
86 | ast::ArgList(it) => swap_sibling_in_list(it.args(), range, direction), | 100 | ast::ArgList(it) => swap_sibling_in_list(node, it.args(), range, direction), |
87 | ast::GenericParamList(it) => swap_sibling_in_list(it.generic_params(), range, direction), | 101 | ast::GenericParamList(it) => swap_sibling_in_list(node, it.generic_params(), range, direction), |
88 | ast::GenericArgList(it) => swap_sibling_in_list(it.generic_args(), range, direction), | 102 | ast::GenericArgList(it) => swap_sibling_in_list(node, it.generic_args(), range, direction), |
89 | ast::VariantList(it) => swap_sibling_in_list(it.variants(), range, direction), | 103 | ast::VariantList(it) => swap_sibling_in_list(node, it.variants(), range, direction), |
90 | ast::TypeBoundList(it) => swap_sibling_in_list(it.bounds(), range, direction), | 104 | ast::TypeBoundList(it) => swap_sibling_in_list(node, it.bounds(), range, direction), |
91 | _ => Some(replace_nodes(node, &match direction { | 105 | _ => Some(replace_nodes(range, node, &match direction { |
92 | Direction::Up => node.prev_sibling(), | 106 | Direction::Up => node.prev_sibling(), |
93 | Direction::Down => node.next_sibling(), | 107 | Direction::Down => node.next_sibling(), |
94 | }?)) | 108 | }?)) |
@@ -97,30 +111,77 @@ fn move_in_direction( | |||
97 | } | 111 | } |
98 | 112 | ||
99 | fn swap_sibling_in_list<A: AstNode + Clone, I: Iterator<Item = A>>( | 113 | fn swap_sibling_in_list<A: AstNode + Clone, I: Iterator<Item = A>>( |
114 | node: &SyntaxNode, | ||
100 | list: I, | 115 | list: I, |
101 | range: TextRange, | 116 | range: TextRange, |
102 | direction: Direction, | 117 | direction: Direction, |
103 | ) -> Option<TextEdit> { | 118 | ) -> Option<TextEdit> { |
104 | let (l, r) = list | 119 | let list_lookup = list |
105 | .tuple_windows() | 120 | .tuple_windows() |
106 | .filter(|(l, r)| match direction { | 121 | .filter(|(l, r)| match direction { |
107 | Direction::Up => r.syntax().text_range().contains_range(range), | 122 | Direction::Up => r.syntax().text_range().contains_range(range), |
108 | Direction::Down => l.syntax().text_range().contains_range(range), | 123 | Direction::Down => l.syntax().text_range().contains_range(range), |
109 | }) | 124 | }) |
110 | .next()?; | 125 | .next(); |
111 | 126 | ||
112 | Some(replace_nodes(l.syntax(), r.syntax())) | 127 | if let Some((l, r)) = list_lookup { |
128 | Some(replace_nodes(range, l.syntax(), r.syntax())) | ||
129 | } else { | ||
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), | ||
132 | // and we have to continue tree traversal to find suitable node. | ||
133 | find_ancestors(SyntaxElement::Node(node.parent()?), direction, range) | ||
134 | } | ||
113 | } | 135 | } |
114 | 136 | ||
115 | 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 | |||
116 | let mut edit = TextEditBuilder::default(); | 165 | let mut edit = TextEditBuilder::default(); |
117 | 166 | ||
118 | algo::diff(first, second).into_text_edit(&mut edit); | 167 | algo::diff(first, second).into_text_edit(&mut edit); |
119 | algo::diff(second, first).into_text_edit(&mut edit); | 168 | edit.replace(second.text_range(), first_with_cursor); |
120 | 169 | ||
121 | edit.finish() | 170 | edit.finish() |
122 | } | 171 | } |
123 | 172 | ||
173 | fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { | ||
174 | return tokens.max_by_key(priority); | ||
175 | |||
176 | fn priority(n: &SyntaxToken) -> usize { | ||
177 | match n.kind() { | ||
178 | SyntaxKind::IDENT | SyntaxKind::LIFETIME_IDENT => 2, | ||
179 | kind if kind.is_trivia() => 0, | ||
180 | _ => 1, | ||
181 | } | ||
182 | } | ||
183 | } | ||
184 | |||
124 | #[cfg(test)] | 185 | #[cfg(test)] |
125 | mod tests { | 186 | mod tests { |
126 | use crate::fixture; | 187 | use crate::fixture; |
@@ -154,7 +215,7 @@ fn main() { | |||
154 | expect![[r#" | 215 | expect![[r#" |
155 | fn main() { | 216 | fn main() { |
156 | match true { | 217 | match true { |
157 | false => { | 218 | false =>$0 { |
158 | println!("Test"); | 219 | println!("Test"); |
159 | }, | 220 | }, |
160 | true => { | 221 | true => { |
@@ -188,7 +249,7 @@ fn main() { | |||
188 | false => { | 249 | false => { |
189 | println!("Test"); | 250 | println!("Test"); |
190 | }, | 251 | }, |
191 | true => { | 252 | true =>$0 { |
192 | println!("Hello, world"); | 253 | println!("Hello, world"); |
193 | } | 254 | } |
194 | }; | 255 | }; |
@@ -240,7 +301,7 @@ fn main() { | |||
240 | "#, | 301 | "#, |
241 | expect![[r#" | 302 | expect![[r#" |
242 | fn main() { | 303 | fn main() { |
243 | let test2 = 456; | 304 | let test2$0 = 456; |
244 | let test = 123; | 305 | let test = 123; |
245 | } | 306 | } |
246 | "#]], | 307 | "#]], |
@@ -259,7 +320,108 @@ fn main() { | |||
259 | "#, | 320 | "#, |
260 | expect![[r#" | 321 | expect![[r#" |
261 | fn main() { | 322 | fn main() { |
262 | println!("All I want to say is..."); | 323 | println!("All I want to say is...");$0 |
324 | println!("Hello, world"); | ||
325 | } | ||
326 | "#]], | ||
327 | Direction::Up, | ||
328 | ); | ||
329 | check( | ||
330 | r#" | ||
331 | fn main() { | ||
332 | println!("Hello, world"); | ||
333 | |||
334 | if true { | ||
335 | println!("Test"); | ||
336 | }$0$0 | ||
337 | } | ||
338 | "#, | ||
339 | expect![[r#" | ||
340 | fn main() { | ||
341 | if true { | ||
342 | println!("Test"); | ||
343 | }$0 | ||
344 | |||
345 | println!("Hello, world"); | ||
346 | } | ||
347 | "#]], | ||
348 | Direction::Up, | ||
349 | ); | ||
350 | check( | ||
351 | r#" | ||
352 | fn main() { | ||
353 | println!("Hello, world"); | ||
354 | |||
355 | for i in 0..10 { | ||
356 | println!("Test"); | ||
357 | }$0$0 | ||
358 | } | ||
359 | "#, | ||
360 | expect![[r#" | ||
361 | fn main() { | ||
362 | for i in 0..10 { | ||
363 | println!("Test"); | ||
364 | }$0 | ||
365 | |||
366 | println!("Hello, world"); | ||
367 | } | ||
368 | "#]], | ||
369 | Direction::Up, | ||
370 | ); | ||
371 | check( | ||
372 | r#" | ||
373 | fn main() { | ||
374 | println!("Hello, world"); | ||
375 | |||
376 | loop { | ||
377 | println!("Test"); | ||
378 | }$0$0 | ||
379 | } | ||
380 | "#, | ||
381 | expect![[r#" | ||
382 | fn main() { | ||
383 | loop { | ||
384 | println!("Test"); | ||
385 | }$0 | ||
386 | |||
387 | println!("Hello, world"); | ||
388 | } | ||
389 | "#]], | ||
390 | Direction::Up, | ||
391 | ); | ||
392 | check( | ||
393 | r#" | ||
394 | fn main() { | ||
395 | println!("Hello, world"); | ||
396 | |||
397 | while true { | ||
398 | println!("Test"); | ||
399 | }$0$0 | ||
400 | } | ||
401 | "#, | ||
402 | expect![[r#" | ||
403 | fn main() { | ||
404 | while true { | ||
405 | println!("Test"); | ||
406 | }$0 | ||
407 | |||
408 | println!("Hello, world"); | ||
409 | } | ||
410 | "#]], | ||
411 | Direction::Up, | ||
412 | ); | ||
413 | check( | ||
414 | r#" | ||
415 | fn main() { | ||
416 | println!("Hello, world"); | ||
417 | |||
418 | return 123;$0$0 | ||
419 | } | ||
420 | "#, | ||
421 | expect![[r#" | ||
422 | fn main() { | ||
423 | return 123;$0 | ||
424 | |||
263 | println!("Hello, world"); | 425 | println!("Hello, world"); |
264 | } | 426 | } |
265 | "#]], | 427 | "#]], |
@@ -295,7 +457,7 @@ fn main() {} | |||
295 | fn foo() {}$0$0 | 457 | fn foo() {}$0$0 |
296 | "#, | 458 | "#, |
297 | expect![[r#" | 459 | expect![[r#" |
298 | fn foo() {} | 460 | fn foo() {}$0 |
299 | 461 | ||
300 | fn main() {} | 462 | fn main() {} |
301 | "#]], | 463 | "#]], |
@@ -316,7 +478,7 @@ impl Wow for Yay $0$0{} | |||
316 | expect![[r#" | 478 | expect![[r#" |
317 | struct Yay; | 479 | struct Yay; |
318 | 480 | ||
319 | impl Wow for Yay {} | 481 | impl Wow for Yay $0{} |
320 | 482 | ||
321 | trait Wow {} | 483 | trait Wow {} |
322 | "#]], | 484 | "#]], |
@@ -332,7 +494,7 @@ use std::vec::Vec; | |||
332 | use std::collections::HashMap$0$0; | 494 | use std::collections::HashMap$0$0; |
333 | "#, | 495 | "#, |
334 | expect![[r#" | 496 | expect![[r#" |
335 | use std::collections::HashMap; | 497 | use std::collections::HashMap$0; |
336 | use std::vec::Vec; | 498 | use std::vec::Vec; |
337 | "#]], | 499 | "#]], |
338 | Direction::Up, | 500 | Direction::Up, |
@@ -367,7 +529,7 @@ fn main() { | |||
367 | } | 529 | } |
368 | 530 | ||
369 | #[test] | 531 | #[test] |
370 | fn test_moves_param_up() { | 532 | fn test_moves_param() { |
371 | check( | 533 | check( |
372 | r#" | 534 | r#" |
373 | fn test(one: i32, two$0$0: u32) {} | 535 | fn test(one: i32, two$0$0: u32) {} |
@@ -377,7 +539,7 @@ fn main() { | |||
377 | } | 539 | } |
378 | "#, | 540 | "#, |
379 | expect![[r#" | 541 | expect![[r#" |
380 | fn test(two: u32, one: i32) {} | 542 | fn test(two$0: u32, one: i32) {} |
381 | 543 | ||
382 | fn main() { | 544 | fn main() { |
383 | test(123, 456); | 545 | test(123, 456); |
@@ -385,6 +547,15 @@ fn main() { | |||
385 | "#]], | 547 | "#]], |
386 | Direction::Up, | 548 | Direction::Up, |
387 | ); | 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 | ); | ||
388 | } | 559 | } |
389 | 560 | ||
390 | #[test] | 561 | #[test] |
@@ -401,7 +572,7 @@ fn main() { | |||
401 | fn test(one: i32, two: u32) {} | 572 | fn test(one: i32, two: u32) {} |
402 | 573 | ||
403 | fn main() { | 574 | fn main() { |
404 | test(456, 123); | 575 | test(456$0, 123); |
405 | } | 576 | } |
406 | "#]], | 577 | "#]], |
407 | Direction::Up, | 578 | Direction::Up, |
@@ -422,7 +593,7 @@ fn main() { | |||
422 | fn test(one: i32, two: u32) {} | 593 | fn test(one: i32, two: u32) {} |
423 | 594 | ||
424 | fn main() { | 595 | fn main() { |
425 | test(456, 123); | 596 | test(456, 123$0); |
426 | } | 597 | } |
427 | "#]], | 598 | "#]], |
428 | Direction::Down, | 599 | Direction::Down, |
@@ -459,7 +630,7 @@ struct Test<A, B$0$0>(A, B); | |||
459 | fn main() {} | 630 | fn main() {} |
460 | "#, | 631 | "#, |
461 | expect![[r#" | 632 | expect![[r#" |
462 | struct Test<B, A>(A, B); | 633 | struct Test<B$0, A>(A, B); |
463 | 634 | ||
464 | fn main() {} | 635 | fn main() {} |
465 | "#]], | 636 | "#]], |
@@ -481,7 +652,7 @@ fn main() { | |||
481 | struct Test<A, B>(A, B); | 652 | struct Test<A, B>(A, B); |
482 | 653 | ||
483 | fn main() { | 654 | fn main() { |
484 | let t = Test::<&str, i32>(123, "yay"); | 655 | let t = Test::<&str$0, i32>(123, "yay"); |
485 | } | 656 | } |
486 | "#]], | 657 | "#]], |
487 | Direction::Up, | 658 | Direction::Up, |
@@ -501,7 +672,7 @@ fn main() {} | |||
501 | "#, | 672 | "#, |
502 | expect![[r#" | 673 | expect![[r#" |
503 | enum Hello { | 674 | enum Hello { |
504 | Two, | 675 | Two$0, |
505 | One | 676 | One |
506 | } | 677 | } |
507 | 678 | ||
@@ -528,7 +699,7 @@ trait One {} | |||
528 | 699 | ||
529 | trait Two {} | 700 | trait Two {} |
530 | 701 | ||
531 | fn test<T: Two + One>(t: T) {} | 702 | fn test<T: Two$0 + One>(t: T) {} |
532 | 703 | ||
533 | fn main() {} | 704 | fn main() {} |
534 | "#]], | 705 | "#]], |
@@ -574,7 +745,7 @@ trait Yay { | |||
574 | impl Yay for Test { | 745 | impl Yay for Test { |
575 | type One = i32; | 746 | type One = i32; |
576 | 747 | ||
577 | fn inner() { | 748 | fn inner() {$0 |
578 | println!("Mmmm"); | 749 | println!("Mmmm"); |
579 | } | 750 | } |
580 | 751 | ||
@@ -601,7 +772,7 @@ fn test() { | |||
601 | "#, | 772 | "#, |
602 | expect![[r#" | 773 | expect![[r#" |
603 | fn test() { | 774 | fn test() { |
604 | mod hi { | 775 | mod hi {$0 |
605 | fn inner() {} | 776 | fn inner() {} |
606 | } | 777 | } |
607 | 778 | ||
@@ -615,6 +786,115 @@ fn test() { | |||
615 | } | 786 | } |
616 | 787 | ||
617 | #[test] | 788 | #[test] |
789 | fn test_cursor_at_item_start() { | ||
790 | check( | ||
791 | r#" | ||
792 | $0$0#[derive(Debug)] | ||
793 | enum FooBar { | ||
794 | Foo, | ||
795 | Bar, | ||
796 | } | ||
797 | |||
798 | fn main() {} | ||
799 | "#, | ||
800 | expect![[r#" | ||
801 | fn main() {} | ||
802 | |||
803 | $0#[derive(Debug)] | ||
804 | enum FooBar { | ||
805 | Foo, | ||
806 | Bar, | ||
807 | } | ||
808 | "#]], | ||
809 | Direction::Down, | ||
810 | ); | ||
811 | check( | ||
812 | r#" | ||
813 | $0$0enum FooBar { | ||
814 | Foo, | ||
815 | Bar, | ||
816 | } | ||
817 | |||
818 | fn main() {} | ||
819 | "#, | ||
820 | expect![[r#" | ||
821 | fn main() {} | ||
822 | |||
823 | $0enum FooBar { | ||
824 | Foo, | ||
825 | Bar, | ||
826 | } | ||
827 | "#]], | ||
828 | Direction::Down, | ||
829 | ); | ||
830 | check( | ||
831 | r#" | ||
832 | struct Test; | ||
833 | |||
834 | trait SomeTrait {} | ||
835 | |||
836 | $0$0impl SomeTrait for Test {} | ||
837 | |||
838 | fn main() {} | ||
839 | "#, | ||
840 | expect![[r#" | ||
841 | struct Test; | ||
842 | |||
843 | $0impl SomeTrait for Test {} | ||
844 | |||
845 | trait SomeTrait {} | ||
846 | |||
847 | fn main() {} | ||
848 | "#]], | ||
849 | Direction::Up, | ||
850 | ); | ||
851 | } | ||
852 | |||
853 | #[test] | ||
854 | fn test_cursor_at_item_end() { | ||
855 | check( | ||
856 | r#" | ||
857 | enum FooBar { | ||
858 | Foo, | ||
859 | Bar, | ||
860 | }$0$0 | ||
861 | |||
862 | fn main() {} | ||
863 | "#, | ||
864 | expect![[r#" | ||
865 | fn main() {} | ||
866 | |||
867 | enum FooBar { | ||
868 | Foo, | ||
869 | Bar, | ||
870 | }$0 | ||
871 | "#]], | ||
872 | Direction::Down, | ||
873 | ); | ||
874 | check( | ||
875 | r#" | ||
876 | struct Test; | ||
877 | |||
878 | trait SomeTrait {} | ||
879 | |||
880 | impl SomeTrait for Test {}$0$0 | ||
881 | |||
882 | fn main() {} | ||
883 | "#, | ||
884 | expect![[r#" | ||
885 | struct Test; | ||
886 | |||
887 | impl SomeTrait for Test {}$0 | ||
888 | |||
889 | trait SomeTrait {} | ||
890 | |||
891 | fn main() {} | ||
892 | "#]], | ||
893 | Direction::Up, | ||
894 | ); | ||
895 | } | ||
896 | |||
897 | #[test] | ||
618 | fn handles_empty_file() { | 898 | fn handles_empty_file() { |
619 | check(r#"$0$0"#, expect![[r#""#]], Direction::Up); | 899 | check(r#"$0$0"#, expect![[r#""#]], Direction::Up); |
620 | } | 900 | } |
diff --git a/crates/ide/src/parent_module.rs b/crates/ide/src/parent_module.rs index 22b0d6ecb..99365c8a7 100644 --- a/crates/ide/src/parent_module.rs +++ b/crates/ide/src/parent_module.rs | |||
@@ -18,6 +18,8 @@ use crate::NavigationTarget; | |||
18 | // | 18 | // |
19 | // | VS Code | **Rust Analyzer: Locate parent module** | 19 | // | VS Code | **Rust Analyzer: Locate parent module** |
20 | // |=== | 20 | // |=== |
21 | // | ||
22 | // image::https://user-images.githubusercontent.com/48062697/113065580-04c21800-91b1-11eb-9a32-00086161c0bd.gif[] | ||
21 | 23 | ||
22 | /// This returns `Vec` because a module may be included from several places. | 24 | /// This returns `Vec` because a module may be included from several places. |
23 | pub(crate) fn parent_module(db: &RootDatabase, position: FilePosition) -> Vec<NavigationTarget> { | 25 | pub(crate) fn parent_module(db: &RootDatabase, position: FilePosition) -> Vec<NavigationTarget> { |
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs index 95ed8a045..11ca7ec6b 100644 --- a/crates/ide/src/references.rs +++ b/crates/ide/src/references.rs | |||
@@ -48,6 +48,8 @@ pub struct Declaration { | |||
48 | // | 48 | // |
49 | // | VS Code | kbd:[Shift+Alt+F12] | 49 | // | VS Code | kbd:[Shift+Alt+F12] |
50 | // |=== | 50 | // |=== |
51 | // | ||
52 | // image::https://user-images.githubusercontent.com/48062697/113020670-b7c34f00-917a-11eb-8003-370ac5f2b3cb.gif[] | ||
51 | pub(crate) fn find_all_refs( | 53 | pub(crate) fn find_all_refs( |
52 | sema: &Semantics<RootDatabase>, | 54 | sema: &Semantics<RootDatabase>, |
53 | position: FilePosition, | 55 | position: FilePosition, |
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs index 26d6dc9c9..2408a0181 100644 --- a/crates/ide/src/references/rename.rs +++ b/crates/ide/src/references/rename.rs | |||
@@ -70,6 +70,8 @@ pub(crate) fn prepare_rename( | |||
70 | // | 70 | // |
71 | // | VS Code | kbd:[F2] | 71 | // | VS Code | kbd:[F2] |
72 | // |=== | 72 | // |=== |
73 | // | ||
74 | // image::https://user-images.githubusercontent.com/48062697/113065582-055aae80-91b1-11eb-8ade-2b58e6d81883.gif[] | ||
73 | pub(crate) fn rename( | 75 | pub(crate) fn rename( |
74 | db: &RootDatabase, | 76 | db: &RootDatabase, |
75 | position: FilePosition, | 77 | position: FilePosition, |
@@ -307,7 +309,7 @@ fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameRe | |||
307 | hir::AssocItemContainer::Impl(impl_) => impl_, | 309 | hir::AssocItemContainer::Impl(impl_) => impl_, |
308 | }; | 310 | }; |
309 | let first_param_ty = first_param.ty(); | 311 | let first_param_ty = first_param.ty(); |
310 | let impl_ty = impl_.target_ty(sema.db); | 312 | let impl_ty = impl_.self_ty(sema.db); |
311 | let (ty, self_param) = if impl_ty.remove_ref().is_some() { | 313 | let (ty, self_param) = if impl_ty.remove_ref().is_some() { |
312 | // if the impl is a ref to the type we can just match the `&T` with self directly | 314 | // if the impl is a ref to the type we can just match the `&T` with self directly |
313 | (first_param_ty.clone(), "self") | 315 | (first_param_ty.clone(), "self") |
diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs index 7e4c5a078..3eb9e27ee 100644 --- a/crates/ide/src/runnables.rs +++ b/crates/ide/src/runnables.rs | |||
@@ -98,6 +98,7 @@ impl Runnable { | |||
98 | // | 98 | // |
99 | // | VS Code | **Rust Analyzer: Run** | 99 | // | VS Code | **Rust Analyzer: Run** |
100 | // |=== | 100 | // |=== |
101 | // image::https://user-images.githubusercontent.com/48062697/113065583-055aae80-91b1-11eb-958f-d67efcaf6a2f.gif[] | ||
101 | pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> { | 102 | pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> { |
102 | let sema = Semantics::new(db); | 103 | let sema = Semantics::new(db); |
103 | 104 | ||
@@ -298,7 +299,7 @@ fn module_def_doctest(sema: &Semantics<RootDatabase>, def: hir::ModuleDef) -> Op | |||
298 | // FIXME: this also looks very wrong | 299 | // FIXME: this also looks very wrong |
299 | if let Some(assoc_def) = assoc_def { | 300 | if let Some(assoc_def) = assoc_def { |
300 | if let hir::AssocItemContainer::Impl(imp) = assoc_def.container(sema.db) { | 301 | if let hir::AssocItemContainer::Impl(imp) = assoc_def.container(sema.db) { |
301 | let ty = imp.target_ty(sema.db); | 302 | let ty = imp.self_ty(sema.db); |
302 | if let Some(adt) = ty.as_adt() { | 303 | if let Some(adt) = ty.as_adt() { |
303 | let name = adt.name(sema.db); | 304 | let name = adt.name(sema.db); |
304 | let idx = path.rfind(':').map_or(0, |idx| idx + 1); | 305 | let idx = path.rfind(':').map_or(0, |idx| idx + 1); |
diff --git a/crates/ide/src/status.rs b/crates/ide/src/status.rs index 137c38c0d..49fde1945 100644 --- a/crates/ide/src/status.rs +++ b/crates/ide/src/status.rs | |||
@@ -31,6 +31,7 @@ fn macro_syntax_tree_stats(db: &RootDatabase) -> SyntaxTreeStats { | |||
31 | // | 31 | // |
32 | // | VS Code | **Rust Analyzer: Status** | 32 | // | VS Code | **Rust Analyzer: Status** |
33 | // |=== | 33 | // |=== |
34 | // image::https://user-images.githubusercontent.com/48062697/113065584-05f34500-91b1-11eb-98cc-5c196f76be7f.gif[] | ||
34 | pub(crate) fn status(db: &RootDatabase, file_id: Option<FileId>) -> String { | 35 | pub(crate) fn status(db: &RootDatabase, file_id: Option<FileId>) -> String { |
35 | let mut buf = String::new(); | 36 | let mut buf = String::new(); |
36 | format_to!(buf, "{}\n", FileTextQuery.in_db(db).entries::<FilesStats>()); | 37 | format_to!(buf, "{}\n", FileTextQuery.in_db(db).entries::<FilesStats>()); |
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs index 67a10766b..9df8d21af 100644 --- a/crates/ide/src/syntax_highlighting.rs +++ b/crates/ide/src/syntax_highlighting.rs | |||
@@ -48,6 +48,9 @@ pub struct HlRange { | |||
48 | // | 48 | // |
49 | // The general rule is that a reference to an entity gets colored the same way as the entity itself. | 49 | // The general rule is that a reference to an entity gets colored the same way as the entity itself. |
50 | // We also give special modifier for `mut` and `&mut` local variables. | 50 | // We also give special modifier for `mut` and `&mut` local variables. |
51 | // | ||
52 | // image::https://user-images.githubusercontent.com/48062697/113164457-06cfb980-9239-11eb-819b-0f93e646acf8.png[] | ||
53 | // image::https://user-images.githubusercontent.com/48062697/113187625-f7f50100-9250-11eb-825e-91c58f236071.png[] | ||
51 | pub(crate) fn highlight( | 54 | pub(crate) fn highlight( |
52 | db: &RootDatabase, | 55 | db: &RootDatabase, |
53 | file_id: FileId, | 56 | file_id: FileId, |
diff --git a/crates/ide/src/syntax_highlighting/highlight.rs b/crates/ide/src/syntax_highlighting/highlight.rs index b0cfdd8b7..8cc877c1c 100644 --- a/crates/ide/src/syntax_highlighting/highlight.rs +++ b/crates/ide/src/syntax_highlighting/highlight.rs | |||
@@ -1,6 +1,6 @@ | |||
1 | //! Computes color for a single element. | 1 | //! Computes color for a single element. |
2 | 2 | ||
3 | use hir::{AsAssocItem, Semantics, VariantDef}; | 3 | use hir::{AsAssocItem, AssocItemContainer, Semantics, VariantDef}; |
4 | use ide_db::{ | 4 | use ide_db::{ |
5 | defs::{Definition, NameClass, NameRefClass}, | 5 | defs::{Definition, NameClass, NameRefClass}, |
6 | RootDatabase, SymbolKind, | 6 | RootDatabase, SymbolKind, |
@@ -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, |
@@ -275,12 +310,24 @@ fn highlight_def(db: &RootDatabase, def: Definition) -> Highlight { | |||
275 | hir::ModuleDef::Module(_) => HlTag::Symbol(SymbolKind::Module), | 310 | hir::ModuleDef::Module(_) => HlTag::Symbol(SymbolKind::Module), |
276 | hir::ModuleDef::Function(func) => { | 311 | hir::ModuleDef::Function(func) => { |
277 | let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Function)); | 312 | let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Function)); |
278 | if func.as_assoc_item(db).is_some() { | 313 | if let Some(item) = func.as_assoc_item(db) { |
279 | h |= HlMod::Associated; | 314 | h |= HlMod::Associated; |
280 | if func.self_param(db).is_none() { | 315 | if func.self_param(db).is_none() { |
281 | h |= HlMod::Static | 316 | h |= HlMod::Static |
282 | } | 317 | } |
318 | |||
319 | match item.container(db) { | ||
320 | AssocItemContainer::Impl(i) => { | ||
321 | if i.trait_(db).is_some() { | ||
322 | h |= HlMod::Trait; | ||
323 | } | ||
324 | } | ||
325 | AssocItemContainer::Trait(_t) => { | ||
326 | h |= HlMod::Trait; | ||
327 | } | ||
328 | } | ||
283 | } | 329 | } |
330 | |||
284 | if func.is_unsafe(db) { | 331 | if func.is_unsafe(db) { |
285 | h |= HlMod::Unsafe; | 332 | h |= HlMod::Unsafe; |
286 | } | 333 | } |
@@ -292,16 +339,37 @@ fn highlight_def(db: &RootDatabase, def: Definition) -> Highlight { | |||
292 | hir::ModuleDef::Variant(_) => HlTag::Symbol(SymbolKind::Variant), | 339 | hir::ModuleDef::Variant(_) => HlTag::Symbol(SymbolKind::Variant), |
293 | hir::ModuleDef::Const(konst) => { | 340 | hir::ModuleDef::Const(konst) => { |
294 | let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Const)); | 341 | let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Const)); |
295 | if konst.as_assoc_item(db).is_some() { | 342 | if let Some(item) = konst.as_assoc_item(db) { |
296 | h |= HlMod::Associated | 343 | h |= HlMod::Associated; |
344 | match item.container(db) { | ||
345 | AssocItemContainer::Impl(i) => { | ||
346 | if i.trait_(db).is_some() { | ||
347 | h |= HlMod::Trait; | ||
348 | } | ||
349 | } | ||
350 | AssocItemContainer::Trait(_t) => { | ||
351 | h |= HlMod::Trait; | ||
352 | } | ||
353 | } | ||
297 | } | 354 | } |
355 | |||
298 | return h; | 356 | return h; |
299 | } | 357 | } |
300 | hir::ModuleDef::Trait(_) => HlTag::Symbol(SymbolKind::Trait), | 358 | hir::ModuleDef::Trait(_) => HlTag::Symbol(SymbolKind::Trait), |
301 | hir::ModuleDef::TypeAlias(type_) => { | 359 | hir::ModuleDef::TypeAlias(type_) => { |
302 | let mut h = Highlight::new(HlTag::Symbol(SymbolKind::TypeAlias)); | 360 | let mut h = Highlight::new(HlTag::Symbol(SymbolKind::TypeAlias)); |
303 | if type_.as_assoc_item(db).is_some() { | 361 | if let Some(item) = type_.as_assoc_item(db) { |
304 | 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 | } | ||
305 | } | 373 | } |
306 | return h; | 374 | return h; |
307 | } | 375 | } |
@@ -362,6 +430,10 @@ fn highlight_method_call( | |||
362 | if func.is_unsafe(sema.db) || sema.is_unsafe_method_call(&method_call) { | 430 | if func.is_unsafe(sema.db) || sema.is_unsafe_method_call(&method_call) { |
363 | h |= HlMod::Unsafe; | 431 | h |= HlMod::Unsafe; |
364 | } | 432 | } |
433 | if func.as_assoc_item(sema.db).and_then(|it| it.containing_trait(sema.db)).is_some() { | ||
434 | h |= HlMod::Trait | ||
435 | } | ||
436 | |||
365 | if let Some(self_param) = func.self_param(sema.db) { | 437 | if let Some(self_param) = func.self_param(sema.db) { |
366 | match self_param.access(sema.db) { | 438 | match self_param.access(sema.db) { |
367 | hir::Access::Shared => (), | 439 | hir::Access::Shared => (), |
diff --git a/crates/ide/src/syntax_highlighting/inject.rs b/crates/ide/src/syntax_highlighting/inject.rs index 963c3fb59..04fafd244 100644 --- a/crates/ide/src/syntax_highlighting/inject.rs +++ b/crates/ide/src/syntax_highlighting/inject.rs | |||
@@ -1,17 +1,18 @@ | |||
1 | //! "Recursive" Syntax highlighting for code in doctests and fixtures. | 1 | //! "Recursive" Syntax highlighting for code in doctests and fixtures. |
2 | 2 | ||
3 | use std::{mem, ops::Range}; | 3 | use std::mem; |
4 | 4 | ||
5 | use either::Either; | 5 | use either::Either; |
6 | use hir::{HasAttrs, InFile, Semantics}; | 6 | use hir::{InFile, Semantics}; |
7 | use ide_db::{call_info::ActiveParameter, defs::Definition, SymbolKind}; | 7 | use ide_db::{call_info::ActiveParameter, SymbolKind}; |
8 | use syntax::{ | 8 | use syntax::{ |
9 | ast::{self, AstNode}, | 9 | ast::{self, AstNode}, |
10 | match_ast, AstToken, NodeOrToken, SyntaxNode, SyntaxToken, TextRange, TextSize, | 10 | AstToken, NodeOrToken, SyntaxNode, SyntaxToken, TextRange, TextSize, |
11 | }; | 11 | }; |
12 | 12 | ||
13 | use crate::{ | 13 | use crate::{ |
14 | doc_links::extract_definitions_from_markdown, Analysis, HlMod, HlRange, HlTag, RootDatabase, | 14 | doc_links::{doc_attributes, extract_definitions_from_markdown, resolve_doc_path_for_def}, |
15 | Analysis, HlMod, HlRange, HlTag, RootDatabase, | ||
15 | }; | 16 | }; |
16 | 17 | ||
17 | use super::{highlights::Highlights, injector::Injector}; | 18 | use super::{highlights::Highlights, injector::Injector}; |
@@ -89,33 +90,6 @@ const RUSTDOC_FENCE_TOKENS: &[&'static str] = &[ | |||
89 | "edition2021", | 90 | "edition2021", |
90 | ]; | 91 | ]; |
91 | 92 | ||
92 | fn doc_attributes<'node>( | ||
93 | sema: &Semantics<RootDatabase>, | ||
94 | node: &'node SyntaxNode, | ||
95 | ) -> Option<(hir::AttrsWithOwner, Definition)> { | ||
96 | match_ast! { | ||
97 | match node { | ||
98 | ast::SourceFile(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Module(def)))), | ||
99 | ast::Module(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Module(def)))), | ||
100 | ast::Fn(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Function(def)))), | ||
101 | ast::Struct(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Struct(def))))), | ||
102 | ast::Union(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Union(def))))), | ||
103 | ast::Enum(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Enum(def))))), | ||
104 | ast::Variant(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Variant(def)))), | ||
105 | ast::Trait(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Trait(def)))), | ||
106 | ast::Static(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Static(def)))), | ||
107 | ast::Const(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::Const(def)))), | ||
108 | ast::TypeAlias(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::ModuleDef(hir::ModuleDef::TypeAlias(def)))), | ||
109 | ast::Impl(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::SelfType(def))), | ||
110 | ast::RecordField(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Field(def))), | ||
111 | ast::TupleField(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Field(def))), | ||
112 | ast::Macro(it) => sema.to_def(&it).map(|def| (def.attrs(sema.db), Definition::Macro(def))), | ||
113 | // ast::Use(it) => sema.to_def(&it).map(|def| (Box::new(it) as _, def.attrs(sema.db))), | ||
114 | _ => return None | ||
115 | } | ||
116 | } | ||
117 | } | ||
118 | |||
119 | /// Injection of syntax highlighting of doctests. | 93 | /// Injection of syntax highlighting of doctests. |
120 | pub(super) fn doc_comment( | 94 | pub(super) fn doc_comment( |
121 | hl: &mut Highlights, | 95 | hl: &mut Highlights, |
@@ -138,8 +112,28 @@ pub(super) fn doc_comment( | |||
138 | // Replace the original, line-spanning comment ranges by new, only comment-prefix | 112 | // Replace the original, line-spanning comment ranges by new, only comment-prefix |
139 | // spanning comment ranges. | 113 | // spanning comment ranges. |
140 | let mut new_comments = Vec::new(); | 114 | let mut new_comments = Vec::new(); |
141 | let mut intra_doc_links = Vec::new(); | ||
142 | let mut string; | 115 | let mut string; |
116 | |||
117 | if let Some((docs, doc_mapping)) = attributes.docs_with_rangemap(sema.db) { | ||
118 | extract_definitions_from_markdown(docs.as_str()) | ||
119 | .into_iter() | ||
120 | .filter_map(|(range, link, ns)| { | ||
121 | let def = resolve_doc_path_for_def(sema.db, def, &link, ns)?; | ||
122 | let InFile { file_id, value: range } = doc_mapping.map(range)?; | ||
123 | (file_id == node.file_id).then(|| (range, def)) | ||
124 | }) | ||
125 | .for_each(|(range, def)| { | ||
126 | hl.add(HlRange { | ||
127 | range, | ||
128 | highlight: module_def_to_hl_tag(def) | ||
129 | | HlMod::Documentation | ||
130 | | HlMod::Injected | ||
131 | | HlMod::IntraDocLink, | ||
132 | binding_hash: None, | ||
133 | }) | ||
134 | }); | ||
135 | } | ||
136 | |||
143 | for attr in attributes.by_key("doc").attrs() { | 137 | for attr in attributes.by_key("doc").attrs() { |
144 | let InFile { file_id, value: src } = attrs_source_map.source_of(&attr); | 138 | let InFile { file_id, value: src } = attrs_source_map.source_of(&attr); |
145 | if file_id != node.file_id { | 139 | if file_id != node.file_id { |
@@ -185,25 +179,7 @@ pub(super) fn doc_comment( | |||
185 | is_doctest = is_codeblock && is_rust; | 179 | is_doctest = is_codeblock && is_rust; |
186 | continue; | 180 | continue; |
187 | } | 181 | } |
188 | None if !is_doctest => { | 182 | None if !is_doctest => continue, |
189 | intra_doc_links.extend( | ||
190 | extract_definitions_from_markdown(line) | ||
191 | .into_iter() | ||
192 | .filter_map(|(range, link, ns)| { | ||
193 | Some(range).zip(validate_intra_doc_link(sema.db, &def, &link, ns)) | ||
194 | }) | ||
195 | .map(|(Range { start, end }, def)| { | ||
196 | ( | ||
197 | def, | ||
198 | TextRange::at( | ||
199 | prev_range_start + TextSize::from(start as u32), | ||
200 | TextSize::from((end - start) as u32), | ||
201 | ), | ||
202 | ) | ||
203 | }), | ||
204 | ); | ||
205 | continue; | ||
206 | } | ||
207 | None => (), | 183 | None => (), |
208 | } | 184 | } |
209 | 185 | ||
@@ -222,17 +198,6 @@ pub(super) fn doc_comment( | |||
222 | } | 198 | } |
223 | } | 199 | } |
224 | 200 | ||
225 | for (def, range) in intra_doc_links { | ||
226 | hl.add(HlRange { | ||
227 | range, | ||
228 | highlight: module_def_to_hl_tag(def) | ||
229 | | HlMod::Documentation | ||
230 | | HlMod::Injected | ||
231 | | HlMod::IntraDocLink, | ||
232 | binding_hash: None, | ||
233 | }); | ||
234 | } | ||
235 | |||
236 | if new_comments.is_empty() { | 201 | if new_comments.is_empty() { |
237 | return; // no need to run an analysis on an empty file | 202 | return; // no need to run an analysis on an empty file |
238 | } | 203 | } |
@@ -283,33 +248,6 @@ fn find_doc_string_in_attr(attr: &hir::Attr, it: &ast::Attr) -> Option<ast::Stri | |||
283 | } | 248 | } |
284 | } | 249 | } |
285 | 250 | ||
286 | fn validate_intra_doc_link( | ||
287 | db: &RootDatabase, | ||
288 | def: &Definition, | ||
289 | link: &str, | ||
290 | ns: Option<hir::Namespace>, | ||
291 | ) -> Option<hir::ModuleDef> { | ||
292 | match def { | ||
293 | Definition::ModuleDef(def) => match def { | ||
294 | hir::ModuleDef::Module(it) => it.resolve_doc_path(db, &link, ns), | ||
295 | hir::ModuleDef::Function(it) => it.resolve_doc_path(db, &link, ns), | ||
296 | hir::ModuleDef::Adt(it) => it.resolve_doc_path(db, &link, ns), | ||
297 | hir::ModuleDef::Variant(it) => it.resolve_doc_path(db, &link, ns), | ||
298 | hir::ModuleDef::Const(it) => it.resolve_doc_path(db, &link, ns), | ||
299 | hir::ModuleDef::Static(it) => it.resolve_doc_path(db, &link, ns), | ||
300 | hir::ModuleDef::Trait(it) => it.resolve_doc_path(db, &link, ns), | ||
301 | hir::ModuleDef::TypeAlias(it) => it.resolve_doc_path(db, &link, ns), | ||
302 | hir::ModuleDef::BuiltinType(_) => None, | ||
303 | }, | ||
304 | Definition::Macro(it) => it.resolve_doc_path(db, &link, ns), | ||
305 | Definition::Field(it) => it.resolve_doc_path(db, &link, ns), | ||
306 | Definition::SelfType(_) | ||
307 | | Definition::Local(_) | ||
308 | | Definition::GenericParam(_) | ||
309 | | Definition::Label(_) => None, | ||
310 | } | ||
311 | } | ||
312 | |||
313 | fn module_def_to_hl_tag(def: hir::ModuleDef) -> HlTag { | 251 | fn module_def_to_hl_tag(def: hir::ModuleDef) -> HlTag { |
314 | let symbol = match def { | 252 | let symbol = match def { |
315 | hir::ModuleDef::Module(_) => SymbolKind::Module, | 253 | hir::ModuleDef::Module(_) => SymbolKind::Module, |
diff --git a/crates/ide/src/syntax_highlighting/tags.rs b/crates/ide/src/syntax_highlighting/tags.rs index 93db79b89..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, |
@@ -58,6 +58,8 @@ pub enum HlMod { | |||
58 | Associated, | 58 | Associated, |
59 | /// Used for intra doc links in doc injection. | 59 | /// Used for intra doc links in doc injection. |
60 | IntraDocLink, | 60 | IntraDocLink, |
61 | /// Used for items in traits and trait impls. | ||
62 | Trait, | ||
61 | 63 | ||
62 | /// Keep this last! | 64 | /// Keep this last! |
63 | Unsafe, | 65 | Unsafe, |
@@ -85,6 +87,20 @@ pub enum HlPunct { | |||
85 | Other, | 87 | Other, |
86 | } | 88 | } |
87 | 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 | |||
88 | impl HlTag { | 104 | impl HlTag { |
89 | fn as_str(self) -> &'static str { | 105 | fn as_str(self) -> &'static str { |
90 | match self { | 106 | match self { |
@@ -131,7 +147,13 @@ impl HlTag { | |||
131 | HlPunct::Other => "punctuation", | 147 | HlPunct::Other => "punctuation", |
132 | }, | 148 | }, |
133 | HlTag::NumericLiteral => "numeric_literal", | 149 | HlTag::NumericLiteral => "numeric_literal", |
134 | 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 | }, | ||
135 | HlTag::StringLiteral => "string_literal", | 157 | HlTag::StringLiteral => "string_literal", |
136 | HlTag::UnresolvedReference => "unresolved_reference", | 158 | HlTag::UnresolvedReference => "unresolved_reference", |
137 | HlTag::None => "none", | 159 | HlTag::None => "none", |
@@ -158,6 +180,7 @@ impl HlMod { | |||
158 | HlMod::Callable, | 180 | HlMod::Callable, |
159 | HlMod::Static, | 181 | HlMod::Static, |
160 | HlMod::Associated, | 182 | HlMod::Associated, |
183 | HlMod::Trait, | ||
161 | HlMod::Unsafe, | 184 | HlMod::Unsafe, |
162 | ]; | 185 | ]; |
163 | 186 | ||
@@ -174,6 +197,7 @@ impl HlMod { | |||
174 | HlMod::IntraDocLink => "intra_doc_link", | 197 | HlMod::IntraDocLink => "intra_doc_link", |
175 | HlMod::Mutable => "mutable", | 198 | HlMod::Mutable => "mutable", |
176 | HlMod::Static => "static", | 199 | HlMod::Static => "static", |
200 | HlMod::Trait => "trait", | ||
177 | HlMod::Unsafe => "unsafe", | 201 | HlMod::Unsafe => "unsafe", |
178 | } | 202 | } |
179 | } | 203 | } |
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html b/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html index 4635ea927..8cde3906c 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html | |||
@@ -47,12 +47,12 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
47 | <span class="brace">}</span> | 47 | <span class="brace">}</span> |
48 | 48 | ||
49 | <span class="keyword">trait</span> <span class="trait declaration">t</span> <span class="brace">{</span> | 49 | <span class="keyword">trait</span> <span class="trait declaration">t</span> <span class="brace">{</span> |
50 | <span class="keyword">fn</span> <span class="function declaration static associated">t_is_static</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span> | 50 | <span class="keyword">fn</span> <span class="function declaration static associated trait">t_is_static</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span> |
51 | <span class="keyword">fn</span> <span class="function declaration associated">t_is_not_static</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span> | 51 | <span class="keyword">fn</span> <span class="function declaration associated trait">t_is_not_static</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span> |
52 | <span class="brace">}</span> | 52 | <span class="brace">}</span> |
53 | 53 | ||
54 | <span class="keyword">impl</span> <span class="trait">t</span> <span class="keyword">for</span> <span class="struct">foo</span> <span class="brace">{</span> | 54 | <span class="keyword">impl</span> <span class="trait">t</span> <span class="keyword">for</span> <span class="struct">foo</span> <span class="brace">{</span> |
55 | <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration static associated">is_static</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span> | 55 | <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration static associated trait">is_static</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span> |
56 | <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration associated">is_not_static</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span> | 56 | <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration associated trait">is_not_static</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span> |
57 | <span class="brace">}</span> | 57 | <span class="brace">}</span> |
58 | </code></pre> \ No newline at end of file | 58 | </code></pre> \ No newline at end of file |
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 045162eb8..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> |
@@ -100,10 +100,18 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
100 | <span class="brace">}</span> | 100 | <span class="brace">}</span> |
101 | 101 | ||
102 | <span class="comment documentation">/// </span><span class="struct documentation intra_doc_link injected">[`Foo`](Foo)</span><span class="comment documentation"> is a struct</span> | 102 | <span class="comment documentation">/// </span><span class="struct documentation intra_doc_link injected">[`Foo`](Foo)</span><span class="comment documentation"> is a struct</span> |
103 | <span class="comment documentation">/// </span><span class="function documentation intra_doc_link injected">[`all_the_links`](all_the_links)</span><span class="comment documentation"> is this function</span> | 103 | <span class="comment documentation">/// This function is > </span><span class="function documentation intra_doc_link injected">[`all_the_links`](all_the_links)</span><span class="comment documentation"> <</span> |
104 | <span class="comment documentation">/// [`noop`](noop) is a macro below</span> | 104 | <span class="comment documentation">/// [`noop`](noop) is a macro below</span> |
105 | <span class="comment documentation">/// </span><span class="struct documentation intra_doc_link injected">[`Item`]</span><span class="comment documentation"> is a struct in the module </span><span class="module documentation intra_doc_link injected">[`module`]</span> | ||
106 | <span class="comment documentation">///</span> | ||
107 | <span class="comment documentation">/// [`Item`]: module::Item</span> | ||
108 | <span class="comment documentation">/// [mix_and_match]: ThisShouldntResolve</span> | ||
105 | <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration">all_the_links</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span> | 109 | <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration">all_the_links</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span> |
106 | 110 | ||
111 | <span class="keyword">pub</span> <span class="keyword">mod</span> <span class="module declaration">module</span> <span class="brace">{</span> | ||
112 | <span class="keyword">pub</span> <span class="keyword">struct</span> <span class="struct declaration">Item</span><span class="semicolon">;</span> | ||
113 | <span class="brace">}</span> | ||
114 | |||
107 | <span class="comment documentation">/// ```</span> | 115 | <span class="comment documentation">/// ```</span> |
108 | <span class="comment documentation">/// </span><span class="macro injected">noop!</span><span class="parenthesis injected">(</span><span class="numeric_literal injected">1</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span> | 116 | <span class="comment documentation">/// </span><span class="macro injected">noop!</span><span class="parenthesis injected">(</span><span class="numeric_literal injected">1</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span> |
109 | <span class="comment documentation">/// ```</span> | 117 | <span class="comment documentation">/// ```</span> |
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html b/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html index 9215ddd9e..7c6694a27 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html | |||
@@ -42,7 +42,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
42 | <span class="keyword">fn</span> <span class="function declaration">main</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span> | 42 | <span class="keyword">fn</span> <span class="function declaration">main</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span> |
43 | <span class="function">fixture</span><span class="parenthesis">(</span><span class="string_literal">r#"</span> | 43 | <span class="function">fixture</span><span class="parenthesis">(</span><span class="string_literal">r#"</span> |
44 | <span class="keyword">trait</span> <span class="trait declaration">Foo</span> <span class="brace">{</span> | 44 | <span class="keyword">trait</span> <span class="trait declaration">Foo</span> <span class="brace">{</span> |
45 | <span class="keyword">fn</span> <span class="function declaration static associated">foo</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span> | 45 | <span class="keyword">fn</span> <span class="function declaration static associated trait">foo</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span> |
46 | <span class="macro">println!</span><span class="parenthesis">(</span><span class="string_literal">"2 + 2 = {}"</span><span class="comma">,</span> <span class="numeric_literal">4</span><span class="parenthesis">)</span><span class="semicolon">;</span> | 46 | <span class="macro">println!</span><span class="parenthesis">(</span><span class="string_literal">"2 + 2 = {}"</span><span class="comma">,</span> <span class="numeric_literal">4</span><span class="parenthesis">)</span><span class="semicolon">;</span> |
47 | <span class="brace">}</span> | 47 | <span class="brace">}</span> |
48 | <span class="brace">}</span><span class="string_literal">"#</span> | 48 | <span class="brace">}</span><span class="string_literal">"#</span> |
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html b/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html index 6a6555208..72910421d 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html | |||
@@ -62,11 +62,11 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
62 | <span class="brace">}</span> | 62 | <span class="brace">}</span> |
63 | 63 | ||
64 | <span class="keyword">trait</span> <span class="trait declaration">DoTheAutoref</span> <span class="brace">{</span> | 64 | <span class="keyword">trait</span> <span class="trait declaration">DoTheAutoref</span> <span class="brace">{</span> |
65 | <span class="keyword">fn</span> <span class="function declaration associated">calls_autoref</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span><span class="semicolon">;</span> | 65 | <span class="keyword">fn</span> <span class="function declaration associated trait">calls_autoref</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span><span class="semicolon">;</span> |
66 | <span class="brace">}</span> | 66 | <span class="brace">}</span> |
67 | 67 | ||
68 | <span class="keyword">impl</span> <span class="trait">DoTheAutoref</span> <span class="keyword">for</span> <span class="builtin_type">u16</span> <span class="brace">{</span> | 68 | <span class="keyword">impl</span> <span class="trait">DoTheAutoref</span> <span class="keyword">for</span> <span class="builtin_type">u16</span> <span class="brace">{</span> |
69 | <span class="keyword">fn</span> <span class="function declaration associated">calls_autoref</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span> | 69 | <span class="keyword">fn</span> <span class="function declaration associated trait">calls_autoref</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span> |
70 | <span class="brace">}</span> | 70 | <span class="brace">}</span> |
71 | 71 | ||
72 | <span class="keyword">fn</span> <span class="function declaration">main</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span> | 72 | <span class="keyword">fn</span> <span class="function declaration">main</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span> |
@@ -96,6 +96,6 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
96 | <span class="keyword">let</span> <span class="struct">Packed</span> <span class="brace">{</span> <span class="field">a</span><span class="colon">:</span> <span class="keyword unsafe">ref</span> <span class="variable declaration">_a</span> <span class="brace">}</span> <span class="operator">=</span> <span class="variable">packed</span><span class="semicolon">;</span> | 96 | <span class="keyword">let</span> <span class="struct">Packed</span> <span class="brace">{</span> <span class="field">a</span><span class="colon">:</span> <span class="keyword unsafe">ref</span> <span class="variable declaration">_a</span> <span class="brace">}</span> <span class="operator">=</span> <span class="variable">packed</span><span class="semicolon">;</span> |
97 | 97 | ||
98 | <span class="comment">// unsafe auto ref of packed field</span> | 98 | <span class="comment">// unsafe auto ref of packed field</span> |
99 | <span class="variable">packed</span><span class="operator">.</span><span class="field">a</span><span class="operator">.</span><span class="function associated unsafe">calls_autoref</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span> | 99 | <span class="variable">packed</span><span class="operator">.</span><span class="field">a</span><span class="operator">.</span><span class="function associated trait unsafe">calls_autoref</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span> |
100 | <span class="brace">}</span> | 100 | <span class="brace">}</span> |
101 | <span class="brace">}</span></code></pre> \ No newline at end of file | 101 | <span class="brace">}</span></code></pre> \ No newline at end of file |
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlighting.html b/crates/ide/src/syntax_highlighting/test_data/highlighting.html index 1eaa7b75b..c43bcb691 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlighting.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlighting.html | |||
@@ -67,11 +67,11 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
67 | <span class="brace">}</span> | 67 | <span class="brace">}</span> |
68 | 68 | ||
69 | <span class="keyword">trait</span> <span class="trait declaration">Bar</span> <span class="brace">{</span> | 69 | <span class="keyword">trait</span> <span class="trait declaration">Bar</span> <span class="brace">{</span> |
70 | <span class="keyword">fn</span> <span class="function declaration associated">bar</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="operator">-></span> <span class="builtin_type">i32</span><span class="semicolon">;</span> | 70 | <span class="keyword">fn</span> <span class="function declaration associated trait">bar</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="operator">-></span> <span class="builtin_type">i32</span><span class="semicolon">;</span> |
71 | <span class="brace">}</span> | 71 | <span class="brace">}</span> |
72 | 72 | ||
73 | <span class="keyword">impl</span> <span class="trait">Bar</span> <span class="keyword">for</span> <span class="struct">Foo</span> <span class="brace">{</span> | 73 | <span class="keyword">impl</span> <span class="trait">Bar</span> <span class="keyword">for</span> <span class="struct">Foo</span> <span class="brace">{</span> |
74 | <span class="keyword">fn</span> <span class="function declaration associated">bar</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="operator">-></span> <span class="builtin_type">i32</span> <span class="brace">{</span> | 74 | <span class="keyword">fn</span> <span class="function declaration associated trait">bar</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="operator">-></span> <span class="builtin_type">i32</span> <span class="brace">{</span> |
75 | <span class="self_keyword">self</span><span class="operator">.</span><span class="field">x</span> | 75 | <span class="self_keyword">self</span><span class="operator">.</span><span class="field">x</span> |
76 | <span class="brace">}</span> | 76 | <span class="brace">}</span> |
77 | <span class="brace">}</span> | 77 | <span class="brace">}</span> |
@@ -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 369ae0972..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; |
@@ -544,10 +576,18 @@ impl Foo { | |||
544 | } | 576 | } |
545 | 577 | ||
546 | /// [`Foo`](Foo) is a struct | 578 | /// [`Foo`](Foo) is a struct |
547 | /// [`all_the_links`](all_the_links) is this function | 579 | /// This function is > [`all_the_links`](all_the_links) < |
548 | /// [`noop`](noop) is a macro below | 580 | /// [`noop`](noop) is a macro below |
581 | /// [`Item`] is a struct in the module [`module`] | ||
582 | /// | ||
583 | /// [`Item`]: module::Item | ||
584 | /// [mix_and_match]: ThisShouldntResolve | ||
549 | pub fn all_the_links() {} | 585 | pub fn all_the_links() {} |
550 | 586 | ||
587 | pub mod module { | ||
588 | pub struct Item; | ||
589 | } | ||
590 | |||
551 | /// ``` | 591 | /// ``` |
552 | /// noop!(1); | 592 | /// noop!(1); |
553 | /// ``` | 593 | /// ``` |
diff --git a/crates/ide/src/syntax_tree.rs b/crates/ide/src/syntax_tree.rs index 8979de528..633878d1c 100644 --- a/crates/ide/src/syntax_tree.rs +++ b/crates/ide/src/syntax_tree.rs | |||
@@ -14,6 +14,7 @@ use syntax::{ | |||
14 | // | 14 | // |
15 | // | VS Code | **Rust Analyzer: Show Syntax Tree** | 15 | // | VS Code | **Rust Analyzer: Show Syntax Tree** |
16 | // |=== | 16 | // |=== |
17 | // image::https://user-images.githubusercontent.com/48062697/113065586-068bdb80-91b1-11eb-9507-fee67f9f45a0.gif[] | ||
17 | pub(crate) fn syntax_tree( | 18 | pub(crate) fn syntax_tree( |
18 | db: &RootDatabase, | 19 | db: &RootDatabase, |
19 | file_id: FileId, | 20 | file_id: FileId, |
diff --git a/crates/ide/src/typing.rs b/crates/ide/src/typing.rs index e10b7d98e..82c732390 100644 --- a/crates/ide/src/typing.rs +++ b/crates/ide/src/typing.rs | |||
@@ -22,18 +22,19 @@ use ide_db::{ | |||
22 | use syntax::{ | 22 | use syntax::{ |
23 | algo::find_node_at_offset, | 23 | algo::find_node_at_offset, |
24 | ast::{self, edit::IndentLevel, AstToken}, | 24 | ast::{self, edit::IndentLevel, AstToken}, |
25 | AstNode, SourceFile, | 25 | AstNode, Parse, SourceFile, |
26 | SyntaxKind::{FIELD_EXPR, METHOD_CALL_EXPR}, | 26 | SyntaxKind::{FIELD_EXPR, METHOD_CALL_EXPR}, |
27 | TextRange, TextSize, | 27 | TextRange, TextSize, |
28 | }; | 28 | }; |
29 | 29 | ||
30 | use text_edit::TextEdit; | 30 | use text_edit::{Indel, TextEdit}; |
31 | 31 | ||
32 | use crate::SourceChange; | 32 | use crate::SourceChange; |
33 | 33 | ||
34 | pub(crate) use on_enter::on_enter; | 34 | pub(crate) use on_enter::on_enter; |
35 | 35 | ||
36 | pub(crate) const TRIGGER_CHARS: &str = ".=>"; | 36 | // Don't forget to add new trigger characters to `server_capabilities` in `caps.rs`. |
37 | pub(crate) const TRIGGER_CHARS: &str = ".=>{"; | ||
37 | 38 | ||
38 | // Feature: On Typing Assists | 39 | // Feature: On Typing Assists |
39 | // | 40 | // |
@@ -41,6 +42,7 @@ pub(crate) const TRIGGER_CHARS: &str = ".=>"; | |||
41 | // | 42 | // |
42 | // - typing `let =` tries to smartly add `;` if `=` is followed by an existing expression | 43 | // - typing `let =` tries to smartly add `;` if `=` is followed by an existing expression |
43 | // - typing `.` in a chain method call auto-indents | 44 | // - typing `.` in a chain method call auto-indents |
45 | // - typing `{` in front of an expression inserts a closing `}` after the expression | ||
44 | // | 46 | // |
45 | // VS Code:: | 47 | // VS Code:: |
46 | // | 48 | // |
@@ -49,33 +51,87 @@ pub(crate) const TRIGGER_CHARS: &str = ".=>"; | |||
49 | // ---- | 51 | // ---- |
50 | // "editor.formatOnType": true, | 52 | // "editor.formatOnType": true, |
51 | // ---- | 53 | // ---- |
54 | // | ||
55 | // image::https://user-images.githubusercontent.com/48062697/113166163-69758500-923a-11eb-81ee-eb33ec380399.gif[] | ||
56 | // image::https://user-images.githubusercontent.com/48062697/113171066-105c2000-923f-11eb-87ab-f4a263346567.gif[] | ||
52 | pub(crate) fn on_char_typed( | 57 | pub(crate) fn on_char_typed( |
53 | db: &RootDatabase, | 58 | db: &RootDatabase, |
54 | position: FilePosition, | 59 | position: FilePosition, |
55 | char_typed: char, | 60 | char_typed: char, |
56 | ) -> Option<SourceChange> { | 61 | ) -> Option<SourceChange> { |
57 | assert!(TRIGGER_CHARS.contains(char_typed)); | 62 | if !stdx::always!(TRIGGER_CHARS.contains(char_typed)) { |
58 | let file = &db.parse(position.file_id).tree(); | 63 | return None; |
59 | assert_eq!(file.syntax().text().char_at(position.offset), Some(char_typed)); | 64 | } |
65 | let file = &db.parse(position.file_id); | ||
66 | if !stdx::always!(file.tree().syntax().text().char_at(position.offset) == Some(char_typed)) { | ||
67 | return None; | ||
68 | } | ||
60 | let edit = on_char_typed_inner(file, position.offset, char_typed)?; | 69 | let edit = on_char_typed_inner(file, position.offset, char_typed)?; |
61 | Some(SourceChange::from_text_edit(position.file_id, edit)) | 70 | Some(SourceChange::from_text_edit(position.file_id, edit)) |
62 | } | 71 | } |
63 | 72 | ||
64 | fn on_char_typed_inner(file: &SourceFile, offset: TextSize, char_typed: char) -> Option<TextEdit> { | 73 | fn on_char_typed_inner( |
65 | assert!(TRIGGER_CHARS.contains(char_typed)); | 74 | file: &Parse<SourceFile>, |
75 | offset: TextSize, | ||
76 | char_typed: char, | ||
77 | ) -> Option<TextEdit> { | ||
78 | if !stdx::always!(TRIGGER_CHARS.contains(char_typed)) { | ||
79 | return None; | ||
80 | } | ||
66 | match char_typed { | 81 | match char_typed { |
67 | '.' => on_dot_typed(file, offset), | 82 | '.' => on_dot_typed(&file.tree(), offset), |
68 | '=' => on_eq_typed(file, offset), | 83 | '=' => on_eq_typed(&file.tree(), offset), |
69 | '>' => on_arrow_typed(file, offset), | 84 | '>' => on_arrow_typed(&file.tree(), offset), |
85 | '{' => on_opening_brace_typed(file, offset), | ||
70 | _ => unreachable!(), | 86 | _ => unreachable!(), |
71 | } | 87 | } |
72 | } | 88 | } |
73 | 89 | ||
90 | /// Inserts a closing `}` when the user types an opening `{`, wrapping an existing expression in a | ||
91 | /// block. | ||
92 | fn on_opening_brace_typed(file: &Parse<SourceFile>, offset: TextSize) -> Option<TextEdit> { | ||
93 | if !stdx::always!(file.tree().syntax().text().char_at(offset) == Some('{')) { | ||
94 | return None; | ||
95 | } | ||
96 | |||
97 | let brace_token = file.tree().syntax().token_at_offset(offset).right_biased()?; | ||
98 | |||
99 | // Remove the `{` to get a better parse tree, and reparse | ||
100 | let file = file.reparse(&Indel::delete(brace_token.text_range())); | ||
101 | |||
102 | let mut expr: ast::Expr = find_node_at_offset(file.tree().syntax(), offset)?; | ||
103 | if expr.syntax().text_range().start() != offset { | ||
104 | return None; | ||
105 | } | ||
106 | |||
107 | // Enclose the outermost expression starting at `offset` | ||
108 | while let Some(parent) = expr.syntax().parent() { | ||
109 | if parent.text_range().start() != expr.syntax().text_range().start() { | ||
110 | break; | ||
111 | } | ||
112 | |||
113 | match ast::Expr::cast(parent) { | ||
114 | Some(parent) => expr = parent, | ||
115 | None => break, | ||
116 | } | ||
117 | } | ||
118 | |||
119 | // If it's a statement in a block, we don't know how many statements should be included | ||
120 | if ast::ExprStmt::can_cast(expr.syntax().parent()?.kind()) { | ||
121 | return None; | ||
122 | } | ||
123 | |||
124 | // Insert `}` right after the expression. | ||
125 | Some(TextEdit::insert(expr.syntax().text_range().end() + TextSize::of("{"), "}".to_string())) | ||
126 | } | ||
127 | |||
74 | /// Returns an edit which should be applied after `=` was typed. Primarily, | 128 | /// Returns an edit which should be applied after `=` was typed. Primarily, |
75 | /// this works when adding `let =`. | 129 | /// this works when adding `let =`. |
76 | // FIXME: use a snippet completion instead of this hack here. | 130 | // FIXME: use a snippet completion instead of this hack here. |
77 | fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> { | 131 | fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> { |
78 | assert_eq!(file.syntax().text().char_at(offset), Some('=')); | 132 | if !stdx::always!(file.syntax().text().char_at(offset) == Some('=')) { |
133 | return None; | ||
134 | } | ||
79 | let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?; | 135 | let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?; |
80 | if let_stmt.semicolon_token().is_some() { | 136 | if let_stmt.semicolon_token().is_some() { |
81 | return None; | 137 | return None; |
@@ -97,7 +153,9 @@ fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> { | |||
97 | 153 | ||
98 | /// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately. | 154 | /// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately. |
99 | fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> { | 155 | fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> { |
100 | assert_eq!(file.syntax().text().char_at(offset), Some('.')); | 156 | if !stdx::always!(file.syntax().text().char_at(offset) == Some('.')) { |
157 | return None; | ||
158 | } | ||
101 | let whitespace = | 159 | let whitespace = |
102 | file.syntax().token_at_offset(offset).left_biased().and_then(ast::Whitespace::cast)?; | 160 | file.syntax().token_at_offset(offset).left_biased().and_then(ast::Whitespace::cast)?; |
103 | 161 | ||
@@ -126,7 +184,9 @@ fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> { | |||
126 | /// Adds a space after an arrow when `fn foo() { ... }` is turned into `fn foo() -> { ... }` | 184 | /// Adds a space after an arrow when `fn foo() { ... }` is turned into `fn foo() -> { ... }` |
127 | fn on_arrow_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> { | 185 | fn on_arrow_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> { |
128 | let file_text = file.syntax().text(); | 186 | let file_text = file.syntax().text(); |
129 | assert_eq!(file_text.char_at(offset), Some('>')); | 187 | if !stdx::always!(file_text.char_at(offset) == Some('>')) { |
188 | return None; | ||
189 | } | ||
130 | let after_arrow = offset + TextSize::of('>'); | 190 | let after_arrow = offset + TextSize::of('>'); |
131 | if file_text.char_at(after_arrow) != Some('{') { | 191 | if file_text.char_at(after_arrow) != Some('{') { |
132 | return None; | 192 | return None; |
@@ -149,7 +209,7 @@ mod tests { | |||
149 | let edit = TextEdit::insert(offset, char_typed.to_string()); | 209 | let edit = TextEdit::insert(offset, char_typed.to_string()); |
150 | edit.apply(&mut before); | 210 | edit.apply(&mut before); |
151 | let parse = SourceFile::parse(&before); | 211 | let parse = SourceFile::parse(&before); |
152 | on_char_typed_inner(&parse.tree(), offset, char_typed).map(|it| { | 212 | on_char_typed_inner(&parse, offset, char_typed).map(|it| { |
153 | it.apply(&mut before); | 213 | it.apply(&mut before); |
154 | before.to_string() | 214 | before.to_string() |
155 | }) | 215 | }) |
@@ -162,8 +222,8 @@ mod tests { | |||
162 | assert_eq_text!(ra_fixture_after, &actual); | 222 | assert_eq_text!(ra_fixture_after, &actual); |
163 | } | 223 | } |
164 | 224 | ||
165 | fn type_char_noop(char_typed: char, before: &str) { | 225 | fn type_char_noop(char_typed: char, ra_fixture_before: &str) { |
166 | let file_change = do_type_char(char_typed, before); | 226 | let file_change = do_type_char(char_typed, ra_fixture_before); |
167 | assert!(file_change.is_none()) | 227 | assert!(file_change.is_none()) |
168 | } | 228 | } |
169 | 229 | ||
@@ -180,16 +240,16 @@ mod tests { | |||
180 | // "); | 240 | // "); |
181 | type_char( | 241 | type_char( |
182 | '=', | 242 | '=', |
183 | r" | 243 | r#" |
184 | fn foo() { | 244 | fn foo() { |
185 | let foo $0 1 + 1 | 245 | let foo $0 1 + 1 |
186 | } | 246 | } |
187 | ", | 247 | "#, |
188 | r" | 248 | r#" |
189 | fn foo() { | 249 | fn foo() { |
190 | let foo = 1 + 1; | 250 | let foo = 1 + 1; |
191 | } | 251 | } |
192 | ", | 252 | "#, |
193 | ); | 253 | ); |
194 | // do_check(r" | 254 | // do_check(r" |
195 | // fn foo() { | 255 | // fn foo() { |
@@ -208,27 +268,27 @@ fn foo() { | |||
208 | fn indents_new_chain_call() { | 268 | fn indents_new_chain_call() { |
209 | type_char( | 269 | type_char( |
210 | '.', | 270 | '.', |
211 | r" | 271 | r#" |
212 | fn main() { | 272 | fn main() { |
213 | xs.foo() | 273 | xs.foo() |
214 | $0 | 274 | $0 |
215 | } | 275 | } |
216 | ", | 276 | "#, |
217 | r" | 277 | r#" |
218 | fn main() { | 278 | fn main() { |
219 | xs.foo() | 279 | xs.foo() |
220 | . | 280 | . |
221 | } | 281 | } |
222 | ", | 282 | "#, |
223 | ); | 283 | ); |
224 | type_char_noop( | 284 | type_char_noop( |
225 | '.', | 285 | '.', |
226 | r" | 286 | r#" |
227 | fn main() { | 287 | fn main() { |
228 | xs.foo() | 288 | xs.foo() |
229 | $0 | 289 | $0 |
230 | } | 290 | } |
231 | ", | 291 | "#, |
232 | ) | 292 | ) |
233 | } | 293 | } |
234 | 294 | ||
@@ -237,26 +297,26 @@ fn foo() { | |||
237 | type_char( | 297 | type_char( |
238 | '.', | 298 | '.', |
239 | r" | 299 | r" |
240 | fn main() { | 300 | fn main() { |
241 | xs.foo() | 301 | xs.foo() |
242 | $0; | 302 | $0; |
243 | } | 303 | } |
244 | ", | ||
245 | r" | ||
246 | fn main() { | ||
247 | xs.foo() | ||
248 | .; | ||
249 | } | ||
250 | ", | 304 | ", |
305 | r#" | ||
306 | fn main() { | ||
307 | xs.foo() | ||
308 | .; | ||
309 | } | ||
310 | "#, | ||
251 | ); | 311 | ); |
252 | type_char_noop( | 312 | type_char_noop( |
253 | '.', | 313 | '.', |
254 | r" | 314 | r#" |
255 | fn main() { | 315 | fn main() { |
256 | xs.foo() | 316 | xs.foo() |
257 | $0; | 317 | $0; |
258 | } | 318 | } |
259 | ", | 319 | "#, |
260 | ) | 320 | ) |
261 | } | 321 | } |
262 | 322 | ||
@@ -285,30 +345,30 @@ fn main() { | |||
285 | fn indents_continued_chain_call() { | 345 | fn indents_continued_chain_call() { |
286 | type_char( | 346 | type_char( |
287 | '.', | 347 | '.', |
288 | r" | 348 | r#" |
289 | fn main() { | 349 | fn main() { |
290 | xs.foo() | 350 | xs.foo() |
291 | .first() | 351 | .first() |
292 | $0 | 352 | $0 |
293 | } | 353 | } |
294 | ", | 354 | "#, |
295 | r" | 355 | r#" |
296 | fn main() { | 356 | fn main() { |
297 | xs.foo() | 357 | xs.foo() |
298 | .first() | 358 | .first() |
299 | . | 359 | . |
300 | } | 360 | } |
301 | ", | 361 | "#, |
302 | ); | 362 | ); |
303 | type_char_noop( | 363 | type_char_noop( |
304 | '.', | 364 | '.', |
305 | r" | 365 | r#" |
306 | fn main() { | 366 | fn main() { |
307 | xs.foo() | 367 | xs.foo() |
308 | .first() | 368 | .first() |
309 | $0 | 369 | $0 |
310 | } | 370 | } |
311 | ", | 371 | "#, |
312 | ); | 372 | ); |
313 | } | 373 | } |
314 | 374 | ||
@@ -316,33 +376,33 @@ fn main() { | |||
316 | fn indents_middle_of_chain_call() { | 376 | fn indents_middle_of_chain_call() { |
317 | type_char( | 377 | type_char( |
318 | '.', | 378 | '.', |
319 | r" | 379 | r#" |
320 | fn source_impl() { | 380 | fn source_impl() { |
321 | let var = enum_defvariant_list().unwrap() | 381 | let var = enum_defvariant_list().unwrap() |
322 | $0 | 382 | $0 |
323 | .nth(92) | 383 | .nth(92) |
324 | .unwrap(); | 384 | .unwrap(); |
325 | } | 385 | } |
326 | ", | 386 | "#, |
327 | r" | 387 | r#" |
328 | fn source_impl() { | 388 | fn source_impl() { |
329 | let var = enum_defvariant_list().unwrap() | 389 | let var = enum_defvariant_list().unwrap() |
330 | . | 390 | . |
331 | .nth(92) | 391 | .nth(92) |
332 | .unwrap(); | 392 | .unwrap(); |
333 | } | 393 | } |
334 | ", | 394 | "#, |
335 | ); | 395 | ); |
336 | type_char_noop( | 396 | type_char_noop( |
337 | '.', | 397 | '.', |
338 | r" | 398 | r#" |
339 | fn source_impl() { | 399 | fn source_impl() { |
340 | let var = enum_defvariant_list().unwrap() | 400 | let var = enum_defvariant_list().unwrap() |
341 | $0 | 401 | $0 |
342 | .nth(92) | 402 | .nth(92) |
343 | .unwrap(); | 403 | .unwrap(); |
344 | } | 404 | } |
345 | ", | 405 | "#, |
346 | ); | 406 | ); |
347 | } | 407 | } |
348 | 408 | ||
@@ -350,24 +410,113 @@ fn main() { | |||
350 | fn dont_indent_freestanding_dot() { | 410 | fn dont_indent_freestanding_dot() { |
351 | type_char_noop( | 411 | type_char_noop( |
352 | '.', | 412 | '.', |
353 | r" | 413 | r#" |
354 | fn main() { | 414 | fn main() { |
355 | $0 | 415 | $0 |
356 | } | 416 | } |
357 | ", | 417 | "#, |
358 | ); | 418 | ); |
359 | type_char_noop( | 419 | type_char_noop( |
360 | '.', | 420 | '.', |
361 | r" | 421 | r#" |
362 | fn main() { | 422 | fn main() { |
363 | $0 | 423 | $0 |
364 | } | 424 | } |
365 | ", | 425 | "#, |
366 | ); | 426 | ); |
367 | } | 427 | } |
368 | 428 | ||
369 | #[test] | 429 | #[test] |
370 | fn adds_space_after_return_type() { | 430 | fn adds_space_after_return_type() { |
371 | 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 | ); | ||
440 | } | ||
441 | |||
442 | #[test] | ||
443 | fn adds_closing_brace() { | ||
444 | type_char( | ||
445 | '{', | ||
446 | r#" | ||
447 | fn f() { match () { _ => $0() } } | ||
448 | "#, | ||
449 | r#" | ||
450 | fn f() { match () { _ => {()} } } | ||
451 | "#, | ||
452 | ); | ||
453 | type_char( | ||
454 | '{', | ||
455 | r#" | ||
456 | fn f() { $0() } | ||
457 | "#, | ||
458 | r#" | ||
459 | fn f() { {()} } | ||
460 | "#, | ||
461 | ); | ||
462 | type_char( | ||
463 | '{', | ||
464 | r#" | ||
465 | fn f() { let x = $0(); } | ||
466 | "#, | ||
467 | r#" | ||
468 | fn f() { let x = {()}; } | ||
469 | "#, | ||
470 | ); | ||
471 | type_char( | ||
472 | '{', | ||
473 | r#" | ||
474 | fn f() { let x = $0a.b(); } | ||
475 | "#, | ||
476 | r#" | ||
477 | fn f() { let x = {a.b()}; } | ||
478 | "#, | ||
479 | ); | ||
480 | type_char( | ||
481 | '{', | ||
482 | r#" | ||
483 | const S: () = $0(); | ||
484 | fn f() {} | ||
485 | "#, | ||
486 | r#" | ||
487 | const S: () = {()}; | ||
488 | fn f() {} | ||
489 | "#, | ||
490 | ); | ||
491 | type_char( | ||
492 | '{', | ||
493 | r#" | ||
494 | const S: () = $0a.b(); | ||
495 | fn f() {} | ||
496 | "#, | ||
497 | r#" | ||
498 | const S: () = {a.b()}; | ||
499 | fn f() {} | ||
500 | "#, | ||
501 | ); | ||
502 | type_char( | ||
503 | '{', | ||
504 | r#" | ||
505 | fn f() { | ||
506 | match x { | ||
507 | 0 => $0(), | ||
508 | 1 => (), | ||
509 | } | ||
510 | } | ||
511 | "#, | ||
512 | r#" | ||
513 | fn f() { | ||
514 | match x { | ||
515 | 0 => {()}, | ||
516 | 1 => (), | ||
517 | } | ||
518 | } | ||
519 | "#, | ||
520 | ); | ||
372 | } | 521 | } |
373 | } | 522 | } |
diff --git a/crates/ide/src/typing/on_enter.rs b/crates/ide/src/typing/on_enter.rs index 978c479de..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 | // |
@@ -32,28 +35,48 @@ use text_edit::TextEdit; | |||
32 | // "when": "editorTextFocus && !suggestWidgetVisible && editorLangId == rust" | 35 | // "when": "editorTextFocus && !suggestWidgetVisible && editorLangId == rust" |
33 | // } | 36 | // } |
34 | // ---- | 37 | // ---- |
38 | // | ||
39 | // image::https://user-images.githubusercontent.com/48062697/113065578-04c21800-91b1-11eb-82b8-22b8c481e645.gif[] | ||
35 | pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<TextEdit> { | 40 | pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<TextEdit> { |
36 | let parse = db.parse(position.file_id); | 41 | let parse = db.parse(position.file_id); |
37 | let file = parse.tree(); | 42 | let file = parse.tree(); |
38 | let comment = file | 43 | let token = file.syntax().token_at_offset(position.offset).left_biased()?; |
39 | .syntax() | 44 | |
40 | .token_at_offset(position.offset) | 45 | if let Some(comment) = ast::Comment::cast(token.clone()) { |
41 | .left_biased() | 46 | return on_enter_in_comment(&comment, &file, position.offset); |
42 | .and_then(ast::Comment::cast)?; | 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 | } | ||
43 | 61 | ||
62 | fn on_enter_in_comment( | ||
63 | comment: &ast::Comment, | ||
64 | file: &ast::SourceFile, | ||
65 | offset: TextSize, | ||
66 | ) -> Option<TextEdit> { | ||
44 | if comment.kind().shape.is_block() { | 67 | if comment.kind().shape.is_block() { |
45 | return None; | 68 | return None; |
46 | } | 69 | } |
47 | 70 | ||
48 | let prefix = comment.prefix(); | 71 | let prefix = comment.prefix(); |
49 | let comment_range = comment.syntax().text_range(); | 72 | let comment_range = comment.syntax().text_range(); |
50 | if position.offset < comment_range.start() + TextSize::of(prefix) { | 73 | if offset < comment_range.start() + TextSize::of(prefix) { |
51 | return None; | 74 | return None; |
52 | } | 75 | } |
53 | 76 | ||
54 | let mut remove_trailing_whitespace = false; | 77 | let mut remove_trailing_whitespace = false; |
55 | // Continuing single-line non-doc comments (like this one :) ) is annoying | 78 | // Continuing single-line non-doc comments (like this one :) ) is annoying |
56 | if prefix == "//" && comment_range.end() == position.offset { | 79 | if prefix == "//" && comment_range.end() == offset { |
57 | if comment.text().ends_with(' ') { | 80 | if comment.text().ends_with(' ') { |
58 | cov_mark::hit!(continues_end_of_line_comment_with_space); | 81 | cov_mark::hit!(continues_end_of_line_comment_with_space); |
59 | remove_trailing_whitespace = true; | 82 | remove_trailing_whitespace = true; |
@@ -67,14 +90,42 @@ pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<Text | |||
67 | let delete = if remove_trailing_whitespace { | 90 | let delete = if remove_trailing_whitespace { |
68 | let trimmed_len = comment.text().trim_end().len() as u32; | 91 | let trimmed_len = comment.text().trim_end().len() as u32; |
69 | let trailing_whitespace_len = comment.text().len() as u32 - trimmed_len; | 92 | let trailing_whitespace_len = comment.text().len() as u32 - trimmed_len; |
70 | TextRange::new(position.offset - TextSize::from(trailing_whitespace_len), position.offset) | 93 | TextRange::new(offset - TextSize::from(trailing_whitespace_len), offset) |
71 | } else { | 94 | } else { |
72 | TextRange::empty(position.offset) | 95 | TextRange::empty(offset) |
73 | }; | 96 | }; |
74 | let edit = TextEdit::replace(delete, inserted); | 97 | let edit = TextEdit::replace(delete, inserted); |
75 | Some(edit) | 98 | Some(edit) |
76 | } | 99 | } |
77 | 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 | |||
78 | fn followed_by_comment(comment: &ast::Comment) -> bool { | 129 | fn followed_by_comment(comment: &ast::Comment) -> bool { |
79 | 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) { |
80 | Some(it) => it, | 131 | Some(it) => it, |
@@ -185,6 +236,25 @@ fn foo() { | |||
185 | } | 236 | } |
186 | 237 | ||
187 | #[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] | ||
188 | fn continues_code_comment_in_the_middle_of_line() { | 258 | fn continues_code_comment_in_the_middle_of_line() { |
189 | do_check( | 259 | do_check( |
190 | r" | 260 | r" |
@@ -274,4 +344,144 @@ fn main() { | |||
274 | ", | 344 | ", |
275 | ); | 345 | ); |
276 | } | 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 | } | ||
277 | } | 487 | } |
diff --git a/crates/ide/src/view_hir.rs b/crates/ide/src/view_hir.rs index f8f3fae3d..7312afe53 100644 --- a/crates/ide/src/view_hir.rs +++ b/crates/ide/src/view_hir.rs | |||
@@ -10,6 +10,7 @@ use syntax::{algo::find_node_at_offset, ast, AstNode}; | |||
10 | // | 10 | // |
11 | // | VS Code | **Rust Analyzer: View Hir** | 11 | // | VS Code | **Rust Analyzer: View Hir** |
12 | // |=== | 12 | // |=== |
13 | // image::https://user-images.githubusercontent.com/48062697/113065588-068bdb80-91b1-11eb-9a78-0b4ef1e972fb.gif[] | ||
13 | pub(crate) fn view_hir(db: &RootDatabase, position: FilePosition) -> String { | 14 | pub(crate) fn view_hir(db: &RootDatabase, position: FilePosition) -> String { |
14 | body_hir(db, position).unwrap_or_else(|| "Not inside a function body".to_string()) | 15 | body_hir(db, position).unwrap_or_else(|| "Not inside a function body".to_string()) |
15 | } | 16 | } |