aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide/src')
-rw-r--r--crates/ide/src/annotations.rs2
-rw-r--r--crates/ide/src/diagnostics.rs158
-rw-r--r--crates/ide/src/diagnostics/field_shorthand.rs8
-rw-r--r--crates/ide/src/diagnostics/fixes.rs60
-rw-r--r--crates/ide/src/diagnostics/unlinked_file.rs14
-rw-r--r--crates/ide/src/doc_links.rs197
-rw-r--r--crates/ide/src/expand_macro.rs46
-rw-r--r--crates/ide/src/extend_selection.rs2
-rw-r--r--crates/ide/src/file_structure.rs3
-rw-r--r--crates/ide/src/folding_ranges.rs56
-rw-r--r--crates/ide/src/goto_definition.rs83
-rw-r--r--crates/ide/src/goto_implementation.rs2
-rw-r--r--crates/ide/src/goto_type_definition.rs2
-rw-r--r--crates/ide/src/hover.rs156
-rw-r--r--crates/ide/src/inlay_hints.rs4
-rw-r--r--crates/ide/src/join_lines.rs20
-rw-r--r--crates/ide/src/lib.rs36
-rw-r--r--crates/ide/src/matching_brace.rs2
-rw-r--r--crates/ide/src/move_item.rs348
-rw-r--r--crates/ide/src/parent_module.rs2
-rw-r--r--crates/ide/src/references.rs2
-rw-r--r--crates/ide/src/references/rename.rs4
-rw-r--r--crates/ide/src/runnables.rs3
-rw-r--r--crates/ide/src/status.rs1
-rw-r--r--crates/ide/src/syntax_highlighting.rs3
-rw-r--r--crates/ide/src/syntax_highlighting/highlight.rs108
-rw-r--r--crates/ide/src/syntax_highlighting/inject.rs118
-rw-r--r--crates/ide/src/syntax_highlighting/tags.rs28
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html8
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html12
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_injection.html2
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html6
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlighting.html6
-rw-r--r--crates/ide/src/syntax_highlighting/tests.rs44
-rw-r--r--crates/ide/src/syntax_tree.rs1
-rw-r--r--crates/ide/src/typing.rs371
-rw-r--r--crates/ide/src/typing/on_enter.rs232
-rw-r--r--crates/ide/src/view_hir.rs1
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)]
23pub struct Annotation { 25pub 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;
20use rustc_hash::FxHashSet; 20use rustc_hash::FxHashSet;
21use syntax::{ 21use syntax::{
22 ast::{self, AstNode}, 22 ast::{self, AstNode},
23 SyntaxNode, SyntaxNodePtr, TextRange, 23 SyntaxNode, SyntaxNodePtr, TextRange, TextSize,
24}; 24};
25use text_edit::TextEdit; 25use text_edit::TextEdit;
26use unlinked_file::UnlinkedFile; 26use unlinked_file::UnlinkedFile;
27 27
28use crate::{FileId, Label, SourceChange}; 28use crate::{Assist, AssistId, AssistKind, FileId, Label, SourceChange};
29 29
30use self::fixes::DiagnosticWithFix; 30use 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)]
73pub 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
80impl 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)]
88pub enum Severity { 73pub enum Severity {
89 Error, 74 Error,
@@ -99,6 +84,7 @@ pub struct DiagnosticsConfig {
99pub(crate) fn diagnostics( 84pub(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
225fn diagnostic_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic { 212fn 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
231fn warning_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic { 222fn 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
282fn 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
288fn 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)]
287mod tests { 300mod 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};
5use syntax::{ast, match_ast, AstNode, SyntaxNode}; 5use syntax::{ast, match_ast, AstNode, SyntaxNode};
6use text_edit::TextEdit; 6use text_edit::TextEdit;
7 7
8use crate::{Diagnostic, Fix}; 8use crate::{diagnostics::fix, Diagnostic};
9 9
10pub(super) fn check(acc: &mut Vec<Diagnostic>, file_id: FileId, node: &SyntaxNode) { 10pub(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};
21use text_edit::TextEdit; 21use text_edit::TextEdit;
22 22
23use crate::{diagnostics::Fix, references::rename::rename_with_semantics, FilePosition}; 23use 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
28pub(crate) trait DiagnosticWithFix: Diagnostic { 32pub(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
32impl DiagnosticWithFix for UnresolvedModule { 41impl 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
51impl DiagnosticWithFix for NoSuchField { 61impl 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
62impl DiagnosticWithFix for MissingFields { 72impl 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
97impl DiagnosticWithFix for MissingOkOrSomeInTailExpr { 108impl 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
110impl DiagnosticWithFix for RemoveThisSemicolon { 121impl 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
130impl DiagnosticWithFix for IncorrectCase { 141impl 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
147impl DiagnosticWithFix for ReplaceFilterMapNextWithFindMap { 161impl 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};
17use text_edit::TextEdit; 17use text_edit::TextEdit;
18 18
19use crate::Fix; 19use crate::{
20 20 diagnostics::{fix, fixes::DiagnosticWithFix},
21use 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
51impl DiagnosticWithFix for UnlinkedFile { 52impl 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
3use std::{convert::TryFrom, iter::once, ops::Range}; 3use std::{
4 convert::{TryFrom, TryInto},
5 iter::once,
6};
4 7
5use itertools::Itertools; 8use itertools::Itertools;
6use pulldown_cmark::{BrokenLink, CowStr, Event, InlineStr, LinkType, Options, Parser, Tag}; 9use pulldown_cmark::{BrokenLink, CowStr, Event, InlineStr, LinkType, Options, Parser, Tag};
@@ -16,8 +19,7 @@ use ide_db::{
16 RootDatabase, 19 RootDatabase,
17}; 20};
18use syntax::{ 21use 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
23use crate::{FilePosition, Semantics}; 25use 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)
28pub(crate) fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> String { 30pub(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.
99pub(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.
101pub(crate) fn extract_definitions_from_markdown( 121pub(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.
126pub(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.
146pub(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
173pub(crate) fn resolve_doc_path_for_def( 146pub(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
173pub(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
200fn 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.
332pub(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.
354fn map_links<'e>( 341fn 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 @@
1use std::iter;
2
1use hir::Semantics; 3use hir::Semantics;
2use ide_db::RootDatabase; 4use ide_db::RootDatabase;
3use syntax::{ 5use 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[]
26pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<ExpandedMacro> { 30pub(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[]
27pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange { 29pub(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
38pub(crate) fn file_structure(file: &SourceFile) -> Vec<StructureNode> { 41pub(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#"
474const 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";
502const 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";
512static 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 @@
1use either::Either; 1use either::Either;
2use hir::Semantics; 2use hir::{InFile, Semantics};
3use ide_db::{ 3use 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
9use crate::{ 9use 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[]
24pub(crate) fn goto_definition( 26pub(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#"
940trait Iterator { type Item; }
941fn 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
1178pub 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
1187pub 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#"
1198macro_rules! pat {
1199 ($name:ident) => { Enum::Variant1($name) }
1200}
1201
1202enum Enum {
1203 Variant1(u8),
1204 Variant2,
1205}
1206
1207fn 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[]
19pub(crate) fn goto_implementation( 21pub(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[]
15pub(crate) fn goto_type_definition( 17pub(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 @@
1use either::Either; 1use either::Either;
2use hir::{ 2use 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};
6use ide_db::{ 6use ide_db::{
@@ -16,8 +16,8 @@ use syntax::{ast, match_ast, AstNode, AstToken, SyntaxKind::*, SyntaxToken, Toke
16use crate::{ 16use 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[]
85pub(crate) fn hover( 87pub(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
471fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { 481fn 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
3815fn foo() {} 3826pub 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
3834pub 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#"
3860fn foo<T: A>() where T::Assoc$0: {}
3861
3862trait 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#"
3879fn foo<T: A>() {
3880 let _: <T>::Assoc$0;
3881}
3882
3883trait 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#"
3900trait 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.
3926struct String;
3927
3928fn f() {
3929 let _: String$0;
3930
3931 fn inner() {}
3932}
3933
3934//- /alloc.rs crate:alloc
3935#[prelude_import]
3936pub use string::*;
3937
3938mod 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[]
55pub(crate) fn inlay_hints( 57pub(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[]
22pub(crate) fn join_lines(file: &SourceFile, range: TextRange) -> TextEdit { 24pub(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#"
839fn main() {$0}
840"#,
841 r#"
842fn 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;
69pub use crate::{ 69pub 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[]
17pub(crate) fn matching_brace(file: &SourceFile, offset: TextSize) -> Option<TextSize> { 19pub(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 @@
1use std::iter::once; 1use std::{iter::once, mem};
2 2
3use hir::Semantics; 3use hir::Semantics;
4use ide_db::{base_db::FileRange, RootDatabase}; 4use ide_db::{base_db::FileRange, RootDatabase};
5use itertools::Itertools; 5use itertools::Itertools;
6use syntax::{ 6use 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};
9use text_edit::{TextEdit, TextEditBuilder}; 10use text_edit::{TextEdit, TextEditBuilder};
10 11
12#[derive(Copy, Clone, Debug)]
11pub enum Direction { 13pub 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[]
26pub(crate) fn move_item( 30pub(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
38fn find_ancestors(item: SyntaxElement, direction: Direction, range: TextRange) -> Option<TextEdit> { 47fn 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
99fn swap_sibling_in_list<A: AstNode + Clone, I: Iterator<Item = A>>( 113fn 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
115fn replace_nodes(first: &SyntaxNode, second: &SyntaxNode) -> TextEdit { 137fn 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
173fn 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)]
125mod tests { 186mod tests {
126 use crate::fixture; 187 use crate::fixture;
@@ -154,7 +215,7 @@ fn main() {
154 expect![[r#" 215 expect![[r#"
155fn main() { 216fn 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#"
242fn main() { 303fn 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#"
261fn main() { 322fn 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#"
331fn main() {
332 println!("Hello, world");
333
334 if true {
335 println!("Test");
336 }$0$0
337}
338 "#,
339 expect![[r#"
340fn main() {
341 if true {
342 println!("Test");
343 }$0
344
345 println!("Hello, world");
346}
347 "#]],
348 Direction::Up,
349 );
350 check(
351 r#"
352fn main() {
353 println!("Hello, world");
354
355 for i in 0..10 {
356 println!("Test");
357 }$0$0
358}
359 "#,
360 expect![[r#"
361fn 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#"
373fn main() {
374 println!("Hello, world");
375
376 loop {
377 println!("Test");
378 }$0$0
379}
380 "#,
381 expect![[r#"
382fn main() {
383 loop {
384 println!("Test");
385 }$0
386
387 println!("Hello, world");
388}
389 "#]],
390 Direction::Up,
391 );
392 check(
393 r#"
394fn main() {
395 println!("Hello, world");
396
397 while true {
398 println!("Test");
399 }$0$0
400}
401 "#,
402 expect![[r#"
403fn main() {
404 while true {
405 println!("Test");
406 }$0
407
408 println!("Hello, world");
409}
410 "#]],
411 Direction::Up,
412 );
413 check(
414 r#"
415fn main() {
416 println!("Hello, world");
417
418 return 123;$0$0
419}
420 "#,
421 expect![[r#"
422fn main() {
423 return 123;$0
424
263 println!("Hello, world"); 425 println!("Hello, world");
264} 426}
265 "#]], 427 "#]],
@@ -295,7 +457,7 @@ fn main() {}
295fn foo() {}$0$0 457fn foo() {}$0$0
296 "#, 458 "#,
297 expect![[r#" 459 expect![[r#"
298fn foo() {} 460fn foo() {}$0
299 461
300fn main() {} 462fn main() {}
301 "#]], 463 "#]],
@@ -316,7 +478,7 @@ impl Wow for Yay $0$0{}
316 expect![[r#" 478 expect![[r#"
317struct Yay; 479struct Yay;
318 480
319impl Wow for Yay {} 481impl Wow for Yay $0{}
320 482
321trait Wow {} 483trait Wow {}
322 "#]], 484 "#]],
@@ -332,7 +494,7 @@ use std::vec::Vec;
332use std::collections::HashMap$0$0; 494use std::collections::HashMap$0$0;
333 "#, 495 "#,
334 expect![[r#" 496 expect![[r#"
335use std::collections::HashMap; 497use std::collections::HashMap$0;
336use std::vec::Vec; 498use 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#"
373fn test(one: i32, two$0$0: u32) {} 535fn test(one: i32, two$0$0: u32) {}
@@ -377,7 +539,7 @@ fn main() {
377} 539}
378 "#, 540 "#,
379 expect![[r#" 541 expect![[r#"
380fn test(two: u32, one: i32) {} 542fn test(two$0: u32, one: i32) {}
381 543
382fn main() { 544fn 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#"
552fn f($0$0arg: u8, arg2: u16) {}
553 "#,
554 expect![[r#"
555fn f(arg2: u16, $0arg: u8) {}
556 "#]],
557 Direction::Down,
558 );
388 } 559 }
389 560
390 #[test] 561 #[test]
@@ -401,7 +572,7 @@ fn main() {
401fn test(one: i32, two: u32) {} 572fn test(one: i32, two: u32) {}
402 573
403fn main() { 574fn 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() {
422fn test(one: i32, two: u32) {} 593fn test(one: i32, two: u32) {}
423 594
424fn main() { 595fn 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);
459fn main() {} 630fn main() {}
460 "#, 631 "#,
461 expect![[r#" 632 expect![[r#"
462struct Test<B, A>(A, B); 633struct Test<B$0, A>(A, B);
463 634
464fn main() {} 635fn main() {}
465 "#]], 636 "#]],
@@ -481,7 +652,7 @@ fn main() {
481struct Test<A, B>(A, B); 652struct Test<A, B>(A, B);
482 653
483fn main() { 654fn 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#"
503enum Hello { 674enum Hello {
504 Two, 675 Two$0,
505 One 676 One
506} 677}
507 678
@@ -528,7 +699,7 @@ trait One {}
528 699
529trait Two {} 700trait Two {}
530 701
531fn test<T: Two + One>(t: T) {} 702fn test<T: Two$0 + One>(t: T) {}
532 703
533fn main() {} 704fn main() {}
534 "#]], 705 "#]],
@@ -574,7 +745,7 @@ trait Yay {
574impl Yay for Test { 745impl 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#"
603fn test() { 774fn 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)]
793enum FooBar {
794 Foo,
795 Bar,
796}
797
798fn main() {}
799 "#,
800 expect![[r#"
801fn main() {}
802
803$0#[derive(Debug)]
804enum 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
818fn main() {}
819 "#,
820 expect![[r#"
821fn main() {}
822
823$0enum FooBar {
824 Foo,
825 Bar,
826}
827 "#]],
828 Direction::Down,
829 );
830 check(
831 r#"
832struct Test;
833
834trait SomeTrait {}
835
836$0$0impl SomeTrait for Test {}
837
838fn main() {}
839 "#,
840 expect![[r#"
841struct Test;
842
843$0impl SomeTrait for Test {}
844
845trait SomeTrait {}
846
847fn main() {}
848 "#]],
849 Direction::Up,
850 );
851 }
852
853 #[test]
854 fn test_cursor_at_item_end() {
855 check(
856 r#"
857enum FooBar {
858 Foo,
859 Bar,
860}$0$0
861
862fn main() {}
863 "#,
864 expect![[r#"
865fn main() {}
866
867enum FooBar {
868 Foo,
869 Bar,
870}$0
871 "#]],
872 Direction::Down,
873 );
874 check(
875 r#"
876struct Test;
877
878trait SomeTrait {}
879
880impl SomeTrait for Test {}$0$0
881
882fn main() {}
883 "#,
884 expect![[r#"
885struct Test;
886
887impl SomeTrait for Test {}$0
888
889trait SomeTrait {}
890
891fn 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.
23pub(crate) fn parent_module(db: &RootDatabase, position: FilePosition) -> Vec<NavigationTarget> { 25pub(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[]
51pub(crate) fn find_all_refs( 53pub(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[]
73pub(crate) fn rename( 75pub(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[]
101pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> { 102pub(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[]
34pub(crate) fn status(db: &RootDatabase, file_id: Option<FileId>) -> String { 35pub(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[]
51pub(crate) fn highlight( 54pub(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
3use hir::{AsAssocItem, Semantics, VariantDef}; 3use hir::{AsAssocItem, AssocItemContainer, Semantics, VariantDef};
4use ide_db::{ 4use 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
15use crate::{syntax_highlighting::tags::HlPunct, Highlight, HlMod, HlTag}; 15use crate::{
16 syntax_highlighting::tags::{HlOperator, HlPunct},
17 Highlight, HlMod, HlTag,
18};
16 19
17pub(super) fn element( 20pub(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
3use std::{mem, ops::Range}; 3use std::mem;
4 4
5use either::Either; 5use either::Either;
6use hir::{HasAttrs, InFile, Semantics}; 6use hir::{InFile, Semantics};
7use ide_db::{call_info::ActiveParameter, defs::Definition, SymbolKind}; 7use ide_db::{call_info::ActiveParameter, SymbolKind};
8use syntax::{ 8use 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
13use crate::{ 13use 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
17use super::{highlights::Highlights, injector::Injector}; 18use super::{highlights::Highlights, injector::Injector};
@@ -89,33 +90,6 @@ const RUSTDOC_FENCE_TOKENS: &[&'static str] = &[
89 "edition2021", 90 "edition2021",
90]; 91];
91 92
92fn 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.
120pub(super) fn doc_comment( 94pub(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
286fn 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
313fn module_def_to_hl_tag(def: hir::ModuleDef) -> HlTag { 251fn 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)]
91pub enum HlOperator {
92 /// |, &, !, ^, |=, &=, ^=
93 Bitwise,
94 /// +, -, *, /, +=, -=, *=, /=
95 Arithmetic,
96 /// &&, ||, !
97 Logical,
98 /// >, <, ==, >=, <=, !=
99 Comparision,
100 ///
101 Other,
102}
103
88impl HlTag { 104impl 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 &gt; </span><span class="function documentation intra_doc_link injected">[`all_the_links`](all_the_links)</span><span class="comment documentation"> &lt;</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">-&gt;</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">-&gt;</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">-&gt;</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">-&gt;</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 @@
1use std::time::Instant;
2
1use expect_test::{expect_file, ExpectFile}; 3use expect_test::{expect_file, ExpectFile};
2use ide_db::SymbolKind; 4use ide_db::SymbolKind;
3use test_utils::{bench, bench_fixture, skip_slow_tests}; 5use test_utils::{bench, bench_fixture, skip_slow_tests, AssertLinear};
4 6
5use crate::{fixture, FileRange, HlTag, TextRange}; 7use 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]
263fn 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]
261fn benchmark_syntax_highlighting_parser() { 293fn 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
549pub fn all_the_links() {} 585pub fn all_the_links() {}
550 586
587pub 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[]
17pub(crate) fn syntax_tree( 18pub(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::{
22use syntax::{ 22use syntax::{
23 algo::find_node_at_offset, 23 algo::find_node_at_offset,
24 ast::{self, edit::IndentLevel, AstToken}, 24 ast::{self, edit::IndentLevel, AstToken},
25 AstNode, SourceFile, 25 AstNode, Parse, SourceFile,
26 SyntaxKind::{FIELD_EXPR, METHOD_CALL_EXPR}, 26 SyntaxKind::{FIELD_EXPR, METHOD_CALL_EXPR},
27 TextRange, TextSize, 27 TextRange, TextSize,
28}; 28};
29 29
30use text_edit::TextEdit; 30use text_edit::{Indel, TextEdit};
31 31
32use crate::SourceChange; 32use crate::SourceChange;
33 33
34pub(crate) use on_enter::on_enter; 34pub(crate) use on_enter::on_enter;
35 35
36pub(crate) const TRIGGER_CHARS: &str = ".=>"; 36// Don't forget to add new trigger characters to `server_capabilities` in `caps.rs`.
37pub(crate) const TRIGGER_CHARS: &str = ".=>{";
37 38
38// Feature: On Typing Assists 39// Feature: On Typing Assists
39// 40//
@@ -41,6 +42,7 @@ pub(crate) const TRIGGER_CHARS: &str = ".=>";
41// 42//
42// - typing `let =` tries to smartly add `;` if `=` is followed by an existing expression 43// - typing `let =` tries to smartly add `;` if `=` is followed by an existing expression
43// - typing `.` in a chain method call auto-indents 44// - typing `.` in a chain method call auto-indents
45// - typing `{` in front of an expression inserts a closing `}` after the expression
44// 46//
45// VS Code:: 47// VS Code::
46// 48//
@@ -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[]
52pub(crate) fn on_char_typed( 57pub(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
64fn on_char_typed_inner(file: &SourceFile, offset: TextSize, char_typed: char) -> Option<TextEdit> { 73fn 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.
92fn on_opening_brace_typed(file: &Parse<SourceFile>, offset: TextSize) -> Option<TextEdit> {
93 if !stdx::always!(file.tree().syntax().text().char_at(offset) == Some('{')) {
94 return None;
95 }
96
97 let brace_token = file.tree().syntax().token_at_offset(offset).right_biased()?;
98
99 // Remove the `{` to get a better parse tree, and reparse
100 let file = file.reparse(&Indel::delete(brace_token.text_range()));
101
102 let mut expr: ast::Expr = find_node_at_offset(file.tree().syntax(), offset)?;
103 if expr.syntax().text_range().start() != offset {
104 return None;
105 }
106
107 // Enclose the outermost expression starting at `offset`
108 while let Some(parent) = expr.syntax().parent() {
109 if parent.text_range().start() != expr.syntax().text_range().start() {
110 break;
111 }
112
113 match ast::Expr::cast(parent) {
114 Some(parent) => expr = parent,
115 None => break,
116 }
117 }
118
119 // If it's a statement in a block, we don't know how many statements should be included
120 if ast::ExprStmt::can_cast(expr.syntax().parent()?.kind()) {
121 return None;
122 }
123
124 // Insert `}` right after the expression.
125 Some(TextEdit::insert(expr.syntax().text_range().end() + TextSize::of("{"), "}".to_string()))
126}
127
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.
77fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> { 131fn 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.
99fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> { 155fn 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() -> { ... }`
127fn on_arrow_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> { 185fn 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#"
184fn foo() { 244fn foo() {
185 let foo $0 1 + 1 245 let foo $0 1 + 1
186} 246}
187", 247"#,
188 r" 248 r#"
189fn foo() { 249fn 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() { 272fn main() {
213 xs.foo() 273 xs.foo()
214 $0 274 $0
215 } 275}
216 ", 276 "#,
217 r" 277 r#"
218 fn main() { 278fn 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() { 287fn 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() { 300fn 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#"
306fn 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() { 315fn 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() { 349fn 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() { 356fn 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() { 366fn 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() { 380fn 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() { 388fn 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() { 399fn 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() { 414fn 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() { 422fn 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#"
434fn foo() -$0{ 92 }
435"#,
436 r#"
437fn foo() -> { 92 }
438"#,
439 );
440 }
441
442 #[test]
443 fn adds_closing_brace() {
444 type_char(
445 '{',
446 r#"
447fn f() { match () { _ => $0() } }
448 "#,
449 r#"
450fn f() { match () { _ => {()} } }
451 "#,
452 );
453 type_char(
454 '{',
455 r#"
456fn f() { $0() }
457 "#,
458 r#"
459fn f() { {()} }
460 "#,
461 );
462 type_char(
463 '{',
464 r#"
465fn f() { let x = $0(); }
466 "#,
467 r#"
468fn f() { let x = {()}; }
469 "#,
470 );
471 type_char(
472 '{',
473 r#"
474fn f() { let x = $0a.b(); }
475 "#,
476 r#"
477fn f() { let x = {a.b()}; }
478 "#,
479 );
480 type_char(
481 '{',
482 r#"
483const S: () = $0();
484fn f() {}
485 "#,
486 r#"
487const S: () = {()};
488fn f() {}
489 "#,
490 );
491 type_char(
492 '{',
493 r#"
494const S: () = $0a.b();
495fn f() {}
496 "#,
497 r#"
498const S: () = {a.b()};
499fn f() {}
500 "#,
501 );
502 type_char(
503 '{',
504 r#"
505fn f() {
506 match x {
507 0 => $0(),
508 1 => (),
509 }
510}
511 "#,
512 r#"
513fn 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 @@
4use ide_db::base_db::{FilePosition, SourceDatabase}; 4use ide_db::base_db::{FilePosition, SourceDatabase};
5use ide_db::RootDatabase; 5use ide_db::RootDatabase;
6use syntax::{ 6use 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
13use text_edit::TextEdit; 14use 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[]
35pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<TextEdit> { 40pub(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
62fn 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
101fn 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
114fn 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
78fn followed_by_comment(comment: &ast::Comment) -> bool { 129fn 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#"
242fn main() {
243 //! Documentation for$0 on enter
244 let x = 1 + 1;
245}
246"#,
247 r#"
248fn 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#"
353fn f() {$0()}
354 "#,
355 r#"
356fn f() {
357 $0()
358}
359 "#,
360 );
361 }
362
363 #[test]
364 fn indents_block_expr() {
365 do_check(
366 r#"
367fn f() {
368 let x = {$0()};
369}
370 "#,
371 r#"
372fn f() {
373 let x = {
374 $0()
375 };
376}
377 "#,
378 );
379 }
380
381 #[test]
382 fn indents_match_arm() {
383 do_check(
384 r#"
385fn f() {
386 match 6 {
387 1 => {$0f()},
388 _ => (),
389 }
390}
391 "#,
392 r#"
393fn 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#"
409fn f() {$0a = b}
410 "#,
411 r#"
412fn f() {
413 $0a = b
414}
415 "#,
416 );
417 do_check(
418 r#"
419fn f() {$0fn f() {}}
420 "#,
421 r#"
422fn f() {
423 $0fn f() {}
424}
425 "#,
426 );
427 }
428
429 #[test]
430 fn indents_nested_blocks() {
431 do_check(
432 r#"
433fn f() {$0{}}
434 "#,
435 r#"
436fn f() {
437 $0{}
438}
439 "#,
440 );
441 }
442
443 #[test]
444 fn does_not_indent_empty_block() {
445 do_check_noop(
446 r#"
447fn f() {$0}
448 "#,
449 );
450 do_check_noop(
451 r#"
452fn f() {{$0}}
453 "#,
454 );
455 }
456
457 #[test]
458 fn does_not_indent_block_with_too_much_content() {
459 do_check_noop(
460 r#"
461fn f() {$0 a = b; ()}
462 "#,
463 );
464 do_check_noop(
465 r#"
466fn 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#"
475fn f() {$0
476}
477 "#,
478 );
479 do_check_noop(
480 r#"
481fn 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[]
13pub(crate) fn view_hir(db: &RootDatabase, position: FilePosition) -> String { 14pub(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}