aboutsummaryrefslogtreecommitdiff
path: root/crates/ide
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide')
-rw-r--r--crates/ide/src/diagnostics.rs62
-rw-r--r--crates/ide/src/diagnostics/fixes.rs38
-rw-r--r--crates/ide/src/diagnostics/unlinked_file.rs2
-rw-r--r--crates/ide/src/goto_definition.rs18
-rw-r--r--crates/ide/src/lib.rs40
-rw-r--r--crates/ide/src/move_item.rs100
-rw-r--r--crates/ide/src/syntax_highlighting/highlight.rs59
-rw-r--r--crates/ide/src/syntax_highlighting/inject.rs16
-rw-r--r--crates/ide/src/syntax_highlighting/tags.rs24
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html2
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlighting.html2
-rw-r--r--crates/ide/src/syntax_highlighting/tests.rs100
-rw-r--r--crates/ide/src/typing.rs200
-rw-r--r--crates/ide/src/typing/on_enter.rs210
14 files changed, 576 insertions, 297 deletions
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs
index 4f0b4a62e..1c911a8b2 100644
--- a/crates/ide/src/diagnostics.rs
+++ b/crates/ide/src/diagnostics.rs
@@ -84,6 +84,7 @@ pub struct DiagnosticsConfig {
84pub(crate) fn diagnostics( 84pub(crate) fn diagnostics(
85 db: &RootDatabase, 85 db: &RootDatabase,
86 config: &DiagnosticsConfig, 86 config: &DiagnosticsConfig,
87 resolve: bool,
87 file_id: FileId, 88 file_id: FileId,
88) -> Vec<Diagnostic> { 89) -> Vec<Diagnostic> {
89 let _p = profile::span("diagnostics"); 90 let _p = profile::span("diagnostics");
@@ -107,25 +108,25 @@ pub(crate) fn diagnostics(
107 let res = RefCell::new(res); 108 let res = RefCell::new(res);
108 let sink_builder = DiagnosticSinkBuilder::new() 109 let sink_builder = DiagnosticSinkBuilder::new()
109 .on::<hir::diagnostics::UnresolvedModule, _>(|d| { 110 .on::<hir::diagnostics::UnresolvedModule, _>(|d| {
110 res.borrow_mut().push(diagnostic_with_fix(d, &sema)); 111 res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve));
111 }) 112 })
112 .on::<hir::diagnostics::MissingFields, _>(|d| { 113 .on::<hir::diagnostics::MissingFields, _>(|d| {
113 res.borrow_mut().push(diagnostic_with_fix(d, &sema)); 114 res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve));
114 }) 115 })
115 .on::<hir::diagnostics::MissingOkOrSomeInTailExpr, _>(|d| { 116 .on::<hir::diagnostics::MissingOkOrSomeInTailExpr, _>(|d| {
116 res.borrow_mut().push(diagnostic_with_fix(d, &sema)); 117 res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve));
117 }) 118 })
118 .on::<hir::diagnostics::NoSuchField, _>(|d| { 119 .on::<hir::diagnostics::NoSuchField, _>(|d| {
119 res.borrow_mut().push(diagnostic_with_fix(d, &sema)); 120 res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve));
120 }) 121 })
121 .on::<hir::diagnostics::RemoveThisSemicolon, _>(|d| { 122 .on::<hir::diagnostics::RemoveThisSemicolon, _>(|d| {
122 res.borrow_mut().push(diagnostic_with_fix(d, &sema)); 123 res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve));
123 }) 124 })
124 .on::<hir::diagnostics::IncorrectCase, _>(|d| { 125 .on::<hir::diagnostics::IncorrectCase, _>(|d| {
125 res.borrow_mut().push(warning_with_fix(d, &sema)); 126 res.borrow_mut().push(warning_with_fix(d, &sema, resolve));
126 }) 127 })
127 .on::<hir::diagnostics::ReplaceFilterMapNextWithFindMap, _>(|d| { 128 .on::<hir::diagnostics::ReplaceFilterMapNextWithFindMap, _>(|d| {
128 res.borrow_mut().push(warning_with_fix(d, &sema)); 129 res.borrow_mut().push(warning_with_fix(d, &sema, resolve));
129 }) 130 })
130 .on::<hir::diagnostics::InactiveCode, _>(|d| { 131 .on::<hir::diagnostics::InactiveCode, _>(|d| {
131 // 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.
@@ -152,7 +153,7 @@ pub(crate) fn diagnostics(
152 // Override severity and mark as unused. 153 // Override severity and mark as unused.
153 res.borrow_mut().push( 154 res.borrow_mut().push(
154 Diagnostic::hint(range, d.message()) 155 Diagnostic::hint(range, d.message())
155 .with_fix(d.fix(&sema)) 156 .with_fix(d.fix(&sema, resolve))
156 .with_code(Some(d.code())), 157 .with_code(Some(d.code())),
157 ); 158 );
158 }) 159 })
@@ -208,15 +209,23 @@ pub(crate) fn diagnostics(
208 res.into_inner() 209 res.into_inner()
209} 210}
210 211
211fn 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 {
212 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())
213 .with_fix(d.fix(&sema)) 218 .with_fix(d.fix(&sema, resolve))
214 .with_code(Some(d.code())) 219 .with_code(Some(d.code()))
215} 220}
216 221
217fn 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 {
218 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())
219 .with_fix(d.fix(&sema)) 228 .with_fix(d.fix(&sema, resolve))
220 .with_code(Some(d.code())) 229 .with_code(Some(d.code()))
221} 230}
222 231
@@ -271,13 +280,19 @@ fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(
271} 280}
272 281
273fn fix(id: &'static str, label: &str, source_change: SourceChange, target: TextRange) -> Assist { 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 {
274 assert!(!id.contains(' ')); 289 assert!(!id.contains(' '));
275 Assist { 290 Assist {
276 id: AssistId(id, AssistKind::QuickFix), 291 id: AssistId(id, AssistKind::QuickFix),
277 label: Label::new(label), 292 label: Label::new(label),
278 group: None, 293 group: None,
279 target, 294 target,
280 source_change: Some(source_change), 295 source_change: None,
281 } 296 }
282} 297}
283 298
@@ -299,7 +314,7 @@ mod tests {
299 314
300 let (analysis, file_position) = fixture::position(ra_fixture_before); 315 let (analysis, file_position) = fixture::position(ra_fixture_before);
301 let diagnostic = analysis 316 let diagnostic = analysis
302 .diagnostics(&DiagnosticsConfig::default(), file_position.file_id) 317 .diagnostics(&DiagnosticsConfig::default(), true, file_position.file_id)
303 .unwrap() 318 .unwrap()
304 .pop() 319 .pop()
305 .unwrap(); 320 .unwrap();
@@ -328,7 +343,7 @@ mod tests {
328 fn check_no_fix(ra_fixture: &str) { 343 fn check_no_fix(ra_fixture: &str) {
329 let (analysis, file_position) = fixture::position(ra_fixture); 344 let (analysis, file_position) = fixture::position(ra_fixture);
330 let diagnostic = analysis 345 let diagnostic = analysis
331 .diagnostics(&DiagnosticsConfig::default(), file_position.file_id) 346 .diagnostics(&DiagnosticsConfig::default(), true, file_position.file_id)
332 .unwrap() 347 .unwrap()
333 .pop() 348 .pop()
334 .unwrap(); 349 .unwrap();
@@ -342,7 +357,7 @@ mod tests {
342 let diagnostics = files 357 let diagnostics = files
343 .into_iter() 358 .into_iter()
344 .flat_map(|file_id| { 359 .flat_map(|file_id| {
345 analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap() 360 analysis.diagnostics(&DiagnosticsConfig::default(), true, file_id).unwrap()
346 }) 361 })
347 .collect::<Vec<_>>(); 362 .collect::<Vec<_>>();
348 assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics); 363 assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics);
@@ -350,7 +365,8 @@ mod tests {
350 365
351 fn check_expect(ra_fixture: &str, expect: Expect) { 366 fn check_expect(ra_fixture: &str, expect: Expect) {
352 let (analysis, file_id) = fixture::file(ra_fixture); 367 let (analysis, file_id) = fixture::file(ra_fixture);
353 let diagnostics = analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap(); 368 let diagnostics =
369 analysis.diagnostics(&DiagnosticsConfig::default(), true, file_id).unwrap();
354 expect.assert_debug_eq(&diagnostics) 370 expect.assert_debug_eq(&diagnostics)
355 } 371 }
356 372
@@ -709,7 +725,7 @@ fn test_fn() {
709 expect![[r#" 725 expect![[r#"
710 [ 726 [
711 Diagnostic { 727 Diagnostic {
712 message: "unresolved macro call", 728 message: "unresolved macro `foo::bar!`",
713 range: 5..8, 729 range: 5..8,
714 severity: Error, 730 severity: Error,
715 fix: None, 731 fix: None,
@@ -895,10 +911,11 @@ struct Foo {
895 911
896 let (analysis, file_id) = fixture::file(r#"mod foo;"#); 912 let (analysis, file_id) = fixture::file(r#"mod foo;"#);
897 913
898 let diagnostics = analysis.diagnostics(&config, file_id).unwrap(); 914 let diagnostics = analysis.diagnostics(&config, true, file_id).unwrap();
899 assert!(diagnostics.is_empty()); 915 assert!(diagnostics.is_empty());
900 916
901 let diagnostics = analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap(); 917 let diagnostics =
918 analysis.diagnostics(&DiagnosticsConfig::default(), true, file_id).unwrap();
902 assert!(!diagnostics.is_empty()); 919 assert!(!diagnostics.is_empty());
903 } 920 }
904 921
@@ -1004,8 +1021,9 @@ impl TestStruct {
1004 let expected = r#"fn foo() {}"#; 1021 let expected = r#"fn foo() {}"#;
1005 1022
1006 let (analysis, file_position) = fixture::position(input); 1023 let (analysis, file_position) = fixture::position(input);
1007 let diagnostics = 1024 let diagnostics = analysis
1008 analysis.diagnostics(&DiagnosticsConfig::default(), file_position.file_id).unwrap(); 1025 .diagnostics(&DiagnosticsConfig::default(), true, file_position.file_id)
1026 .unwrap();
1009 assert_eq!(diagnostics.len(), 1); 1027 assert_eq!(diagnostics.len(), 1);
1010 1028
1011 check_fix(input, expected); 1029 check_fix(input, expected);
diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs
index 69cf5288c..7be8b3459 100644
--- a/crates/ide/src/diagnostics/fixes.rs
+++ b/crates/ide/src/diagnostics/fixes.rs
@@ -20,17 +20,26 @@ use syntax::{
20}; 20};
21use text_edit::TextEdit; 21use text_edit::TextEdit;
22 22
23use crate::{diagnostics::fix, references::rename::rename_with_semantics, Assist, 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<Assist>; 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<Assist> { 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( 45 Some(fix(
@@ -50,7 +59,7 @@ impl DiagnosticWithFix for UnresolvedModule {
50} 59}
51 60
52impl DiagnosticWithFix for NoSuchField { 61impl DiagnosticWithFix for NoSuchField {
53 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist> { 62 fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> {
54 let root = sema.db.parse_or_expand(self.file)?; 63 let root = sema.db.parse_or_expand(self.file)?;
55 missing_record_expr_field_fix( 64 missing_record_expr_field_fix(
56 &sema, 65 &sema,
@@ -61,7 +70,7 @@ impl DiagnosticWithFix for NoSuchField {
61} 70}
62 71
63impl DiagnosticWithFix for MissingFields { 72impl DiagnosticWithFix for MissingFields {
64 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist> { 73 fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> {
65 // Note that although we could add a diagnostics to 74 // Note that although we could add a diagnostics to
66 // fill the missing tuple field, e.g : 75 // fill the missing tuple field, e.g :
67 // `struct A(usize);` 76 // `struct A(usize);`
@@ -97,7 +106,7 @@ impl DiagnosticWithFix for MissingFields {
97} 106}
98 107
99impl DiagnosticWithFix for MissingOkOrSomeInTailExpr { 108impl DiagnosticWithFix for MissingOkOrSomeInTailExpr {
100 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist> { 109 fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> {
101 let root = sema.db.parse_or_expand(self.file)?; 110 let root = sema.db.parse_or_expand(self.file)?;
102 let tail_expr = self.expr.to_node(&root); 111 let tail_expr = self.expr.to_node(&root);
103 let tail_expr_range = tail_expr.syntax().text_range(); 112 let tail_expr_range = tail_expr.syntax().text_range();
@@ -110,7 +119,7 @@ impl DiagnosticWithFix for MissingOkOrSomeInTailExpr {
110} 119}
111 120
112impl DiagnosticWithFix for RemoveThisSemicolon { 121impl DiagnosticWithFix for RemoveThisSemicolon {
113 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist> { 122 fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> {
114 let root = sema.db.parse_or_expand(self.file)?; 123 let root = sema.db.parse_or_expand(self.file)?;
115 124
116 let semicolon = self 125 let semicolon = self
@@ -130,7 +139,7 @@ impl DiagnosticWithFix for RemoveThisSemicolon {
130} 139}
131 140
132impl DiagnosticWithFix for IncorrectCase { 141impl DiagnosticWithFix for IncorrectCase {
133 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist> { 142 fn fix(&self, sema: &Semantics<RootDatabase>, resolve: bool) -> Option<Assist> {
134 let root = sema.db.parse_or_expand(self.file)?; 143 let root = sema.db.parse_or_expand(self.file)?;
135 let name_node = self.ident.to_node(&root); 144 let name_node = self.ident.to_node(&root);
136 145
@@ -138,16 +147,19 @@ impl DiagnosticWithFix for IncorrectCase {
138 let frange = name_node.original_file_range(sema.db); 147 let frange = name_node.original_file_range(sema.db);
139 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() };
140 149
141 let rename_changes =
142 rename_with_semantics(sema, file_position, &self.suggested_text).ok()?;
143
144 let label = format!("Rename to {}", self.suggested_text); 150 let label = format!("Rename to {}", self.suggested_text);
145 Some(fix("change_case", &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)
146 } 158 }
147} 159}
148 160
149impl DiagnosticWithFix for ReplaceFilterMapNextWithFindMap { 161impl DiagnosticWithFix for ReplaceFilterMapNextWithFindMap {
150 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist> { 162 fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> {
151 let root = sema.db.parse_or_expand(self.file)?; 163 let root = sema.db.parse_or_expand(self.file)?;
152 let next_expr = self.next_expr.to_node(&root); 164 let next_expr = self.next_expr.to_node(&root);
153 let next_call = ast::MethodCallExpr::cast(next_expr.syntax().clone())?; 165 let next_call = ast::MethodCallExpr::cast(next_expr.syntax().clone())?;
diff --git a/crates/ide/src/diagnostics/unlinked_file.rs b/crates/ide/src/diagnostics/unlinked_file.rs
index 5482b7287..7d39f4fbe 100644
--- a/crates/ide/src/diagnostics/unlinked_file.rs
+++ b/crates/ide/src/diagnostics/unlinked_file.rs
@@ -50,7 +50,7 @@ impl Diagnostic for UnlinkedFile {
50} 50}
51 51
52impl DiagnosticWithFix for UnlinkedFile { 52impl DiagnosticWithFix for UnlinkedFile {
53 fn fix(&self, sema: &hir::Semantics<RootDatabase>) -> Option<Assist> { 53 fn fix(&self, sema: &hir::Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> {
54 // 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,
55 // suggest that as a fix. 55 // suggest that as a fix.
56 56
diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs
index d057d5402..a04333e63 100644
--- a/crates/ide/src/goto_definition.rs
+++ b/crates/ide/src/goto_definition.rs
@@ -110,6 +110,13 @@ mod tests {
110 assert_eq!(expected, FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() }); 110 assert_eq!(expected, FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() });
111 } 111 }
112 112
113 fn check_unresolved(ra_fixture: &str) {
114 let (analysis, position) = fixture::position(ra_fixture);
115 let navs = analysis.goto_definition(position).unwrap().expect("no definition found").info;
116
117 assert!(navs.is_empty(), "didn't expect this to resolve anywhere: {:?}", navs)
118 }
119
113 #[test] 120 #[test]
114 fn goto_def_for_extern_crate() { 121 fn goto_def_for_extern_crate() {
115 check( 122 check(
@@ -927,17 +934,12 @@ fn f() -> impl Iterator<Item$0 = u8> {}
927 } 934 }
928 935
929 #[test] 936 #[test]
930 #[should_panic = "unresolved reference"]
931 fn unknown_assoc_ty() { 937 fn unknown_assoc_ty() {
932 check( 938 check_unresolved(
933 r#" 939 r#"
934trait Iterator { 940trait Iterator { type Item; }
935 type Item;
936 //^^^^
937}
938
939fn f() -> impl Iterator<Invalid$0 = u8> {} 941fn f() -> impl Iterator<Invalid$0 = u8> {}
940 "#, 942"#,
941 ) 943 )
942 } 944 }
943 945
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index 0615b26d3..99e45633e 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -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};
@@ -244,6 +244,12 @@ impl Analysis {
244 self.with_db(|db| db.parse(file_id).tree()) 244 self.with_db(|db| db.parse(file_id).tree())
245 } 245 }
246 246
247 /// Returns true if this file belongs to an immutable library.
248 pub fn is_library_file(&self, file_id: FileId) -> Cancelable<bool> {
249 use ide_db::base_db::SourceDatabaseExt;
250 self.with_db(|db| db.source_root(db.file_source_root(file_id)).is_library)
251 }
252
247 /// Gets the file's `LineIndex`: data structure to convert between absolute 253 /// Gets the file's `LineIndex`: data structure to convert between absolute
248 /// offsets and line/column representation. 254 /// offsets and line/column representation.
249 pub fn file_line_index(&self, file_id: FileId) -> Cancelable<Arc<LineIndex>> { 255 pub fn file_line_index(&self, file_id: FileId) -> Cancelable<Arc<LineIndex>> {
@@ -526,9 +532,39 @@ impl Analysis {
526 pub fn diagnostics( 532 pub fn diagnostics(
527 &self, 533 &self,
528 config: &DiagnosticsConfig, 534 config: &DiagnosticsConfig,
535 resolve: bool,
529 file_id: FileId, 536 file_id: FileId,
530 ) -> Cancelable<Vec<Diagnostic>> { 537 ) -> Cancelable<Vec<Diagnostic>> {
531 self.with_db(|db| diagnostics::diagnostics(db, config, file_id)) 538 self.with_db(|db| diagnostics::diagnostics(db, config, resolve, file_id))
539 }
540
541 /// Convenience function to return assists + quick fixes for diagnostics
542 pub fn assists_with_fixes(
543 &self,
544 assist_config: &AssistConfig,
545 diagnostics_config: &DiagnosticsConfig,
546 resolve: bool,
547 frange: FileRange,
548 ) -> Cancelable<Vec<Assist>> {
549 let include_fixes = match &assist_config.allowed {
550 Some(it) => it.iter().any(|&it| it == AssistKind::None || it == AssistKind::QuickFix),
551 None => true,
552 };
553
554 self.with_db(|db| {
555 let mut res = Assist::get(db, assist_config, resolve, frange);
556 ssr::add_ssr_assist(db, &mut res, resolve, frange);
557
558 if include_fixes {
559 res.extend(
560 diagnostics::diagnostics(db, diagnostics_config, resolve, frange.file_id)
561 .into_iter()
562 .filter_map(|it| it.fix)
563 .filter(|it| it.target.intersect(frange.range).is_some()),
564 );
565 }
566 res
567 })
532 } 568 }
533 569
534 /// Returns the edit required to rename reference at the position to the new 570 /// Returns the edit required to rename reference at the position to the new
diff --git a/crates/ide/src/move_item.rs b/crates/ide/src/move_item.rs
index 8d37f4f92..246f10a0a 100644
--- a/crates/ide/src/move_item.rs
+++ b/crates/ide/src/move_item.rs
@@ -1,4 +1,4 @@
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};
@@ -102,7 +102,7 @@ fn move_in_direction(
102 ast::GenericArgList(it) => swap_sibling_in_list(node, it.generic_args(), range, direction), 102 ast::GenericArgList(it) => swap_sibling_in_list(node, it.generic_args(), range, direction),
103 ast::VariantList(it) => swap_sibling_in_list(node, it.variants(), range, direction), 103 ast::VariantList(it) => swap_sibling_in_list(node, it.variants(), range, direction),
104 ast::TypeBoundList(it) => swap_sibling_in_list(node, it.bounds(), range, direction), 104 ast::TypeBoundList(it) => swap_sibling_in_list(node, it.bounds(), range, direction),
105 _ => Some(replace_nodes(node, &match direction { 105 _ => Some(replace_nodes(range, node, &match direction {
106 Direction::Up => node.prev_sibling(), 106 Direction::Up => node.prev_sibling(),
107 Direction::Down => node.next_sibling(), 107 Direction::Down => node.next_sibling(),
108 }?)) 108 }?))
@@ -125,7 +125,7 @@ fn swap_sibling_in_list<A: AstNode + Clone, I: Iterator<Item = A>>(
125 .next(); 125 .next();
126 126
127 if let Some((l, r)) = list_lookup { 127 if let Some((l, r)) = list_lookup {
128 Some(replace_nodes(l.syntax(), r.syntax())) 128 Some(replace_nodes(range, l.syntax(), r.syntax()))
129 } else { 129 } else {
130 // Cursor is beyond any movable list item (for example, on curly brace in enum). 130 // Cursor is beyond any movable list item (for example, on curly brace in enum).
131 // It's not necessary, that parent of list is movable (arg list's parent is not, for example), 131 // It's not necessary, that parent of list is movable (arg list's parent is not, for example),
@@ -134,11 +134,38 @@ fn swap_sibling_in_list<A: AstNode + Clone, I: Iterator<Item = A>>(
134 } 134 }
135} 135}
136 136
137fn 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
138 let mut edit = TextEditBuilder::default(); 165 let mut edit = TextEditBuilder::default();
139 166
140 algo::diff(first, second).into_text_edit(&mut edit); 167 algo::diff(first, second).into_text_edit(&mut edit);
141 algo::diff(second, first).into_text_edit(&mut edit); 168 edit.replace(second.text_range(), first_with_cursor);
142 169
143 edit.finish() 170 edit.finish()
144} 171}
@@ -188,7 +215,7 @@ fn main() {
188 expect![[r#" 215 expect![[r#"
189fn main() { 216fn main() {
190 match true { 217 match true {
191 false => { 218 false =>$0 {
192 println!("Test"); 219 println!("Test");
193 }, 220 },
194 true => { 221 true => {
@@ -222,7 +249,7 @@ fn main() {
222 false => { 249 false => {
223 println!("Test"); 250 println!("Test");
224 }, 251 },
225 true => { 252 true =>$0 {
226 println!("Hello, world"); 253 println!("Hello, world");
227 } 254 }
228 }; 255 };
@@ -274,7 +301,7 @@ fn main() {
274 "#, 301 "#,
275 expect![[r#" 302 expect![[r#"
276fn main() { 303fn main() {
277 let test2 = 456; 304 let test2$0 = 456;
278 let test = 123; 305 let test = 123;
279} 306}
280 "#]], 307 "#]],
@@ -293,7 +320,7 @@ fn main() {
293 "#, 320 "#,
294 expect![[r#" 321 expect![[r#"
295fn main() { 322fn main() {
296 println!("All I want to say is..."); 323 println!("All I want to say is...");$0
297 println!("Hello, world"); 324 println!("Hello, world");
298} 325}
299 "#]], 326 "#]],
@@ -313,7 +340,7 @@ fn main() {
313fn main() { 340fn main() {
314 if true { 341 if true {
315 println!("Test"); 342 println!("Test");
316 } 343 }$0
317 344
318 println!("Hello, world"); 345 println!("Hello, world");
319} 346}
@@ -334,7 +361,7 @@ fn main() {
334fn main() { 361fn main() {
335 for i in 0..10 { 362 for i in 0..10 {
336 println!("Test"); 363 println!("Test");
337 } 364 }$0
338 365
339 println!("Hello, world"); 366 println!("Hello, world");
340} 367}
@@ -355,7 +382,7 @@ fn main() {
355fn main() { 382fn main() {
356 loop { 383 loop {
357 println!("Test"); 384 println!("Test");
358 } 385 }$0
359 386
360 println!("Hello, world"); 387 println!("Hello, world");
361} 388}
@@ -376,7 +403,7 @@ fn main() {
376fn main() { 403fn main() {
377 while true { 404 while true {
378 println!("Test"); 405 println!("Test");
379 } 406 }$0
380 407
381 println!("Hello, world"); 408 println!("Hello, world");
382} 409}
@@ -393,7 +420,7 @@ fn main() {
393 "#, 420 "#,
394 expect![[r#" 421 expect![[r#"
395fn main() { 422fn main() {
396 return 123; 423 return 123;$0
397 424
398 println!("Hello, world"); 425 println!("Hello, world");
399} 426}
@@ -430,7 +457,7 @@ fn main() {}
430fn foo() {}$0$0 457fn foo() {}$0$0
431 "#, 458 "#,
432 expect![[r#" 459 expect![[r#"
433fn foo() {} 460fn foo() {}$0
434 461
435fn main() {} 462fn main() {}
436 "#]], 463 "#]],
@@ -451,7 +478,7 @@ impl Wow for Yay $0$0{}
451 expect![[r#" 478 expect![[r#"
452struct Yay; 479struct Yay;
453 480
454impl Wow for Yay {} 481impl Wow for Yay $0{}
455 482
456trait Wow {} 483trait Wow {}
457 "#]], 484 "#]],
@@ -467,7 +494,7 @@ use std::vec::Vec;
467use std::collections::HashMap$0$0; 494use std::collections::HashMap$0$0;
468 "#, 495 "#,
469 expect![[r#" 496 expect![[r#"
470use std::collections::HashMap; 497use std::collections::HashMap$0;
471use std::vec::Vec; 498use std::vec::Vec;
472 "#]], 499 "#]],
473 Direction::Up, 500 Direction::Up,
@@ -502,7 +529,7 @@ fn main() {
502 } 529 }
503 530
504 #[test] 531 #[test]
505 fn test_moves_param_up() { 532 fn test_moves_param() {
506 check( 533 check(
507 r#" 534 r#"
508fn test(one: i32, two$0$0: u32) {} 535fn test(one: i32, two$0$0: u32) {}
@@ -512,7 +539,7 @@ fn main() {
512} 539}
513 "#, 540 "#,
514 expect![[r#" 541 expect![[r#"
515fn test(two: u32, one: i32) {} 542fn test(two$0: u32, one: i32) {}
516 543
517fn main() { 544fn main() {
518 test(123, 456); 545 test(123, 456);
@@ -520,6 +547,15 @@ fn main() {
520 "#]], 547 "#]],
521 Direction::Up, 548 Direction::Up,
522 ); 549 );
550 check(
551 r#"
552fn f($0$0arg: u8, arg2: u16) {}
553 "#,
554 expect![[r#"
555fn f(arg2: u16, $0arg: u8) {}
556 "#]],
557 Direction::Down,
558 );
523 } 559 }
524 560
525 #[test] 561 #[test]
@@ -536,7 +572,7 @@ fn main() {
536fn test(one: i32, two: u32) {} 572fn test(one: i32, two: u32) {}
537 573
538fn main() { 574fn main() {
539 test(456, 123); 575 test(456$0, 123);
540} 576}
541 "#]], 577 "#]],
542 Direction::Up, 578 Direction::Up,
@@ -557,7 +593,7 @@ fn main() {
557fn test(one: i32, two: u32) {} 593fn test(one: i32, two: u32) {}
558 594
559fn main() { 595fn main() {
560 test(456, 123); 596 test(456, 123$0);
561} 597}
562 "#]], 598 "#]],
563 Direction::Down, 599 Direction::Down,
@@ -594,7 +630,7 @@ struct Test<A, B$0$0>(A, B);
594fn main() {} 630fn main() {}
595 "#, 631 "#,
596 expect![[r#" 632 expect![[r#"
597struct Test<B, A>(A, B); 633struct Test<B$0, A>(A, B);
598 634
599fn main() {} 635fn main() {}
600 "#]], 636 "#]],
@@ -616,7 +652,7 @@ fn main() {
616struct Test<A, B>(A, B); 652struct Test<A, B>(A, B);
617 653
618fn main() { 654fn main() {
619 let t = Test::<&str, i32>(123, "yay"); 655 let t = Test::<&str$0, i32>(123, "yay");
620} 656}
621 "#]], 657 "#]],
622 Direction::Up, 658 Direction::Up,
@@ -636,7 +672,7 @@ fn main() {}
636 "#, 672 "#,
637 expect![[r#" 673 expect![[r#"
638enum Hello { 674enum Hello {
639 Two, 675 Two$0,
640 One 676 One
641} 677}
642 678
@@ -663,7 +699,7 @@ trait One {}
663 699
664trait Two {} 700trait Two {}
665 701
666fn test<T: Two + One>(t: T) {} 702fn test<T: Two$0 + One>(t: T) {}
667 703
668fn main() {} 704fn main() {}
669 "#]], 705 "#]],
@@ -709,7 +745,7 @@ trait Yay {
709impl Yay for Test { 745impl Yay for Test {
710 type One = i32; 746 type One = i32;
711 747
712 fn inner() { 748 fn inner() {$0
713 println!("Mmmm"); 749 println!("Mmmm");
714 } 750 }
715 751
@@ -736,7 +772,7 @@ fn test() {
736 "#, 772 "#,
737 expect![[r#" 773 expect![[r#"
738fn test() { 774fn test() {
739 mod hi { 775 mod hi {$0
740 fn inner() {} 776 fn inner() {}
741 } 777 }
742 778
@@ -764,7 +800,7 @@ fn main() {}
764 expect![[r#" 800 expect![[r#"
765fn main() {} 801fn main() {}
766 802
767#[derive(Debug)] 803$0#[derive(Debug)]
768enum FooBar { 804enum FooBar {
769 Foo, 805 Foo,
770 Bar, 806 Bar,
@@ -784,7 +820,7 @@ fn main() {}
784 expect![[r#" 820 expect![[r#"
785fn main() {} 821fn main() {}
786 822
787enum FooBar { 823$0enum FooBar {
788 Foo, 824 Foo,
789 Bar, 825 Bar,
790} 826}
@@ -804,7 +840,7 @@ fn main() {}
804 expect![[r#" 840 expect![[r#"
805struct Test; 841struct Test;
806 842
807impl SomeTrait for Test {} 843$0impl SomeTrait for Test {}
808 844
809trait SomeTrait {} 845trait SomeTrait {}
810 846
@@ -831,7 +867,7 @@ fn main() {}
831enum FooBar { 867enum FooBar {
832 Foo, 868 Foo,
833 Bar, 869 Bar,
834} 870}$0
835 "#]], 871 "#]],
836 Direction::Down, 872 Direction::Down,
837 ); 873 );
@@ -848,7 +884,7 @@ fn main() {}
848 expect![[r#" 884 expect![[r#"
849struct Test; 885struct Test;
850 886
851impl SomeTrait for Test {} 887impl SomeTrait for Test {}$0
852 888
853trait SomeTrait {} 889trait SomeTrait {}
854 890
diff --git a/crates/ide/src/syntax_highlighting/highlight.rs b/crates/ide/src/syntax_highlighting/highlight.rs
index e921784bf..8cc877c1c 100644
--- a/crates/ide/src/syntax_highlighting/highlight.rs
+++ b/crates/ide/src/syntax_highlighting/highlight.rs
@@ -12,7 +12,10 @@ use syntax::{
12 SyntaxNode, SyntaxToken, T, 12 SyntaxNode, SyntaxToken, T,
13}; 13};
14 14
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,
diff --git a/crates/ide/src/syntax_highlighting/inject.rs b/crates/ide/src/syntax_highlighting/inject.rs
index 04fafd244..bc221d599 100644
--- a/crates/ide/src/syntax_highlighting/inject.rs
+++ b/crates/ide/src/syntax_highlighting/inject.rs
@@ -4,7 +4,7 @@ use std::mem;
4 4
5use either::Either; 5use either::Either;
6use hir::{InFile, Semantics}; 6use hir::{InFile, Semantics};
7use ide_db::{call_info::ActiveParameter, SymbolKind}; 7use ide_db::{call_info::ActiveParameter, helpers::rust_doc::is_rust_fence, SymbolKind};
8use syntax::{ 8use syntax::{
9 ast::{self, AstNode}, 9 ast::{self, AstNode},
10 AstToken, NodeOrToken, SyntaxNode, SyntaxToken, TextRange, TextSize, 10 AstToken, NodeOrToken, SyntaxNode, SyntaxToken, TextRange, TextSize,
@@ -78,17 +78,6 @@ pub(super) fn ra_fixture(
78} 78}
79 79
80const RUSTDOC_FENCE: &'static str = "```"; 80const RUSTDOC_FENCE: &'static str = "```";
81const RUSTDOC_FENCE_TOKENS: &[&'static str] = &[
82 "",
83 "rust",
84 "should_panic",
85 "ignore",
86 "no_run",
87 "compile_fail",
88 "edition2015",
89 "edition2018",
90 "edition2021",
91];
92 81
93/// Injection of syntax highlighting of doctests. 82/// Injection of syntax highlighting of doctests.
94pub(super) fn doc_comment( 83pub(super) fn doc_comment(
@@ -174,8 +163,7 @@ pub(super) fn doc_comment(
174 is_codeblock = !is_codeblock; 163 is_codeblock = !is_codeblock;
175 // Check whether code is rust by inspecting fence guards 164 // Check whether code is rust by inspecting fence guards
176 let guards = &line[idx + RUSTDOC_FENCE.len()..]; 165 let guards = &line[idx + RUSTDOC_FENCE.len()..];
177 let is_rust = 166 let is_rust = is_rust_fence(guards);
178 guards.split(',').all(|sub| RUSTDOC_FENCE_TOKENS.contains(&sub.trim()));
179 is_doctest = is_codeblock && is_rust; 167 is_doctest = is_codeblock && is_rust;
180 continue; 168 continue;
181 } 169 }
diff --git a/crates/ide/src/syntax_highlighting/tags.rs b/crates/ide/src/syntax_highlighting/tags.rs
index 1cec991aa..8128d231d 100644
--- a/crates/ide/src/syntax_highlighting/tags.rs
+++ b/crates/ide/src/syntax_highlighting/tags.rs
@@ -28,7 +28,7 @@ pub enum HlTag {
28 FormatSpecifier, 28 FormatSpecifier,
29 Keyword, 29 Keyword,
30 NumericLiteral, 30 NumericLiteral,
31 Operator, 31 Operator(HlOperator),
32 Punctuation(HlPunct), 32 Punctuation(HlPunct),
33 StringLiteral, 33 StringLiteral,
34 UnresolvedReference, 34 UnresolvedReference,
@@ -87,6 +87,20 @@ pub enum HlPunct {
87 Other, 87 Other,
88} 88}
89 89
90#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
91pub enum HlOperator {
92 /// |, &, !, ^, |=, &=, ^=
93 Bitwise,
94 /// +, -, *, /, +=, -=, *=, /=
95 Arithmetic,
96 /// &&, ||, !
97 Logical,
98 /// >, <, ==, >=, <=, !=
99 Comparision,
100 ///
101 Other,
102}
103
90impl HlTag { 104impl HlTag {
91 fn as_str(self) -> &'static str { 105 fn as_str(self) -> &'static str {
92 match self { 106 match self {
@@ -133,7 +147,13 @@ impl HlTag {
133 HlPunct::Other => "punctuation", 147 HlPunct::Other => "punctuation",
134 }, 148 },
135 HlTag::NumericLiteral => "numeric_literal", 149 HlTag::NumericLiteral => "numeric_literal",
136 HlTag::Operator => "operator", 150 HlTag::Operator(op) => match op {
151 HlOperator::Bitwise => "bitwise",
152 HlOperator::Arithmetic => "arithmetic",
153 HlOperator::Logical => "logical",
154 HlOperator::Comparision => "comparision",
155 HlOperator::Other => "operator",
156 },
137 HlTag::StringLiteral => "string_literal", 157 HlTag::StringLiteral => "string_literal",
138 HlTag::UnresolvedReference => "unresolved_reference", 158 HlTag::UnresolvedReference => "unresolved_reference",
139 HlTag::None => "none", 159 HlTag::None => "none",
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
index b6d1cac4e..6ee6d85fb 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
@@ -76,7 +76,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
76 <span class="comment documentation">/// </span><span class="comment injected">// calls bar on foo</span> 76 <span class="comment documentation">/// </span><span class="comment injected">// calls bar on foo</span>
77 <span class="comment documentation">/// </span><span class="macro injected">assert!</span><span class="parenthesis injected">(</span><span class="none injected">foo</span><span class="operator injected">.</span><span class="none injected">bar</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span> 77 <span class="comment documentation">/// </span><span class="macro injected">assert!</span><span class="parenthesis injected">(</span><span class="none injected">foo</span><span class="operator injected">.</span><span class="none injected">bar</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span>
78 <span class="comment documentation">///</span> 78 <span class="comment documentation">///</span>
79 <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="variable declaration injected">bar</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="variable injected">foo</span><span class="operator injected">.</span><span class="field injected">bar</span><span class="none injected"> </span><span class="operator injected">||</span><span class="none injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="constant injected">bar</span><span class="semicolon injected">;</span> 79 <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="variable declaration injected">bar</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="variable injected">foo</span><span class="operator injected">.</span><span class="field injected">bar</span><span class="none injected"> </span><span class="logical injected">||</span><span class="none injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="constant injected">bar</span><span class="semicolon injected">;</span>
80 <span class="comment documentation">///</span> 80 <span class="comment documentation">///</span>
81 <span class="comment documentation">/// </span><span class="comment injected">/* multi-line</span> 81 <span class="comment documentation">/// </span><span class="comment injected">/* multi-line</span>
82 <span class="comment documentation">/// </span><span class="comment injected"> comment */</span> 82 <span class="comment documentation">/// </span><span class="comment injected"> comment */</span>
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlighting.html b/crates/ide/src/syntax_highlighting/test_data/highlighting.html
index 973173254..c43bcb691 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlighting.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlighting.html
@@ -213,7 +213,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
213 <span class="keyword">let</span> <span class="variable declaration">baz</span> <span class="operator">=</span> <span class="numeric_literal">-</span><span class="numeric_literal">42</span><span class="semicolon">;</span> 213 <span class="keyword">let</span> <span class="variable declaration">baz</span> <span class="operator">=</span> <span class="numeric_literal">-</span><span class="numeric_literal">42</span><span class="semicolon">;</span>
214 <span class="keyword">let</span> <span class="variable declaration">baz</span> <span class="operator">=</span> <span class="operator">-</span><span class="variable">baz</span><span class="semicolon">;</span> 214 <span class="keyword">let</span> <span class="variable declaration">baz</span> <span class="operator">=</span> <span class="operator">-</span><span class="variable">baz</span><span class="semicolon">;</span>
215 215
216 <span class="keyword">let</span> <span class="punctuation">_</span> <span class="operator">=</span> <span class="operator">!</span><span class="bool_literal">true</span><span class="semicolon">;</span> 216 <span class="keyword">let</span> <span class="punctuation">_</span> <span class="operator">=</span> <span class="logical">!</span><span class="bool_literal">true</span><span class="semicolon">;</span>
217 217
218 <span class="label declaration">'foo</span><span class="colon">:</span> <span class="keyword control">loop</span> <span class="brace">{</span> 218 <span class="label declaration">'foo</span><span class="colon">:</span> <span class="keyword control">loop</span> <span class="brace">{</span>
219 <span class="keyword control">break</span> <span class="label">'foo</span><span class="semicolon">;</span> 219 <span class="keyword control">break</span> <span class="label">'foo</span><span class="semicolon">;</span>
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs
index b4818060f..17cc6334b 100644
--- a/crates/ide/src/syntax_highlighting/tests.rs
+++ b/crates/ide/src/syntax_highlighting/tests.rs
@@ -2,8 +2,7 @@ use std::time::Instant;
2 2
3use expect_test::{expect_file, ExpectFile}; 3use expect_test::{expect_file, ExpectFile};
4use ide_db::SymbolKind; 4use ide_db::SymbolKind;
5use stdx::format_to; 5use test_utils::{bench, bench_fixture, skip_slow_tests, AssertLinear};
6use test_utils::{bench, bench_fixture, skip_slow_tests};
7 6
8use crate::{fixture, FileRange, HlTag, TextRange}; 7use crate::{fixture, FileRange, HlTag, TextRange};
9 8
@@ -266,90 +265,27 @@ fn syntax_highlighting_not_quadratic() {
266 return; 265 return;
267 } 266 }
268 267
269 let mut measures = Vec::new(); 268 let mut al = AssertLinear::default();
270 for i in 6..=10 { 269 while al.next_round() {
271 let n = 1 << i; 270 for i in 6..=10 {
272 let fixture = bench_fixture::big_struct_n(n); 271 let n = 1 << i;
273 let (analysis, file_id) = fixture::file(&fixture);
274 272
275 let time = Instant::now(); 273 let fixture = bench_fixture::big_struct_n(n);
274 let (analysis, file_id) = fixture::file(&fixture);
276 275
277 let hash = analysis 276 let time = Instant::now();
278 .highlight(file_id)
279 .unwrap()
280 .iter()
281 .filter(|it| it.highlight.tag == HlTag::Symbol(SymbolKind::Struct))
282 .count();
283 assert!(hash > n as usize);
284 277
285 let elapsed = time.elapsed(); 278 let hash = analysis
286 measures.push((n as f64, elapsed.as_millis() as f64)) 279 .highlight(file_id)
287 } 280 .unwrap()
281 .iter()
282 .filter(|it| it.highlight.tag == HlTag::Symbol(SymbolKind::Struct))
283 .count();
284 assert!(hash > n as usize);
288 285
289 assert_linear(&measures) 286 let elapsed = time.elapsed();
290} 287 al.sample(n as f64, elapsed.as_millis() as f64);
291
292/// Checks that a set of measurements looks like a linear function rather than
293/// like a quadratic function. Algorithm:
294///
295/// 1. Linearly scale input to be in [0; 1)
296/// 2. Using linear regression, compute the best linear function approximating
297/// the input.
298/// 3. Compute RMSE and maximal absolute error.
299/// 4. Check that errors are within tolerances and that the constant term is not
300/// too negative.
301///
302/// Ideally, we should use a proper "model selection" to directly compare
303/// quadratic and linear models, but that sounds rather complicated:
304///
305/// https://stats.stackexchange.com/questions/21844/selecting-best-model-based-on-linear-quadratic-and-cubic-fit-of-data
306fn assert_linear(xy: &[(f64, f64)]) {
307 let (mut xs, mut ys): (Vec<_>, Vec<_>) = xy.iter().copied().unzip();
308 normalize(&mut xs);
309 normalize(&mut ys);
310 let xy = xs.iter().copied().zip(ys.iter().copied());
311
312 // Linear regression: finding a and b to fit y = a + b*x.
313
314 let mean_x = mean(&xs);
315 let mean_y = mean(&ys);
316
317 let b = {
318 let mut num = 0.0;
319 let mut denom = 0.0;
320 for (x, y) in xy.clone() {
321 num += (x - mean_x) * (y - mean_y);
322 denom += (x - mean_x).powi(2);
323 } 288 }
324 num / denom
325 };
326
327 let a = mean_y - b * mean_x;
328
329 let mut plot = format!("y_pred = {:.3} + {:.3} * x\n\nx y y_pred\n", a, b);
330
331 let mut se = 0.0;
332 let mut max_error = 0.0f64;
333 for (x, y) in xy {
334 let y_pred = a + b * x;
335 se += (y - y_pred).powi(2);
336 max_error = max_error.max((y_pred - y).abs());
337
338 format_to!(plot, "{:.3} {:.3} {:.3}\n", x, y, y_pred);
339 }
340
341 let rmse = (se / xs.len() as f64).sqrt();
342 format_to!(plot, "\nrmse = {:.3} max error = {:.3}", rmse, max_error);
343
344 assert!(rmse < 0.05 && max_error < 0.1 && a > -0.1, "\nLooks quadratic\n{}", plot);
345
346 fn normalize(xs: &mut Vec<f64>) {
347 let max = xs.iter().copied().max_by(|a, b| a.partial_cmp(b).unwrap()).unwrap();
348 xs.iter_mut().for_each(|it| *it /= max);
349 }
350
351 fn mean(xs: &[f64]) -> f64 {
352 xs.iter().copied().sum::<f64>() / (xs.len() as f64)
353 } 289 }
354} 290}
355 291
@@ -371,7 +307,7 @@ fn benchmark_syntax_highlighting_parser() {
371 .filter(|it| it.highlight.tag == HlTag::Symbol(SymbolKind::Function)) 307 .filter(|it| it.highlight.tag == HlTag::Symbol(SymbolKind::Function))
372 .count() 308 .count()
373 }; 309 };
374 assert_eq!(hash, 1629); 310 assert_eq!(hash, 1632);
375} 311}
376 312
377#[test] 313#[test]
diff --git a/crates/ide/src/typing.rs b/crates/ide/src/typing.rs
index 1378048e5..82c732390 100644
--- a/crates/ide/src/typing.rs
+++ b/crates/ide/src/typing.rs
@@ -222,8 +222,8 @@ mod tests {
222 assert_eq_text!(ra_fixture_after, &actual); 222 assert_eq_text!(ra_fixture_after, &actual);
223 } 223 }
224 224
225 fn type_char_noop(char_typed: char, before: &str) { 225 fn type_char_noop(char_typed: char, ra_fixture_before: &str) {
226 let file_change = do_type_char(char_typed, before); 226 let file_change = do_type_char(char_typed, ra_fixture_before);
227 assert!(file_change.is_none()) 227 assert!(file_change.is_none())
228 } 228 }
229 229
@@ -240,16 +240,16 @@ mod tests {
240 // "); 240 // ");
241 type_char( 241 type_char(
242 '=', 242 '=',
243 r" 243 r#"
244fn foo() { 244fn foo() {
245 let foo $0 1 + 1 245 let foo $0 1 + 1
246} 246}
247", 247"#,
248 r" 248 r#"
249fn foo() { 249fn foo() {
250 let foo = 1 + 1; 250 let foo = 1 + 1;
251} 251}
252", 252"#,
253 ); 253 );
254 // do_check(r" 254 // do_check(r"
255 // fn foo() { 255 // fn foo() {
@@ -268,27 +268,27 @@ fn foo() {
268 fn indents_new_chain_call() { 268 fn indents_new_chain_call() {
269 type_char( 269 type_char(
270 '.', 270 '.',
271 r" 271 r#"
272 fn main() { 272fn main() {
273 xs.foo() 273 xs.foo()
274 $0 274 $0
275 } 275}
276 ", 276 "#,
277 r" 277 r#"
278 fn main() { 278fn main() {
279 xs.foo() 279 xs.foo()
280 . 280 .
281 } 281}
282 ", 282 "#,
283 ); 283 );
284 type_char_noop( 284 type_char_noop(
285 '.', 285 '.',
286 r" 286 r#"
287 fn main() { 287fn main() {
288 xs.foo() 288 xs.foo()
289 $0 289 $0
290 } 290}
291 ", 291 "#,
292 ) 292 )
293 } 293 }
294 294
@@ -297,26 +297,26 @@ fn foo() {
297 type_char( 297 type_char(
298 '.', 298 '.',
299 r" 299 r"
300 fn main() { 300fn main() {
301 xs.foo() 301 xs.foo()
302 $0; 302 $0;
303 } 303}
304 ",
305 r"
306 fn main() {
307 xs.foo()
308 .;
309 }
310 ", 304 ",
305 r#"
306fn main() {
307 xs.foo()
308 .;
309}
310 "#,
311 ); 311 );
312 type_char_noop( 312 type_char_noop(
313 '.', 313 '.',
314 r" 314 r#"
315 fn main() { 315fn main() {
316 xs.foo() 316 xs.foo()
317 $0; 317 $0;
318 } 318}
319 ", 319 "#,
320 ) 320 )
321 } 321 }
322 322
@@ -345,30 +345,30 @@ fn main() {
345 fn indents_continued_chain_call() { 345 fn indents_continued_chain_call() {
346 type_char( 346 type_char(
347 '.', 347 '.',
348 r" 348 r#"
349 fn main() { 349fn main() {
350 xs.foo() 350 xs.foo()
351 .first() 351 .first()
352 $0 352 $0
353 } 353}
354 ", 354 "#,
355 r" 355 r#"
356 fn main() { 356fn main() {
357 xs.foo() 357 xs.foo()
358 .first() 358 .first()
359 . 359 .
360 } 360}
361 ", 361 "#,
362 ); 362 );
363 type_char_noop( 363 type_char_noop(
364 '.', 364 '.',
365 r" 365 r#"
366 fn main() { 366fn main() {
367 xs.foo() 367 xs.foo()
368 .first() 368 .first()
369 $0 369 $0
370 } 370}
371 ", 371 "#,
372 ); 372 );
373 } 373 }
374 374
@@ -376,33 +376,33 @@ fn main() {
376 fn indents_middle_of_chain_call() { 376 fn indents_middle_of_chain_call() {
377 type_char( 377 type_char(
378 '.', 378 '.',
379 r" 379 r#"
380 fn source_impl() { 380fn source_impl() {
381 let var = enum_defvariant_list().unwrap() 381 let var = enum_defvariant_list().unwrap()
382 $0 382 $0
383 .nth(92) 383 .nth(92)
384 .unwrap(); 384 .unwrap();
385 } 385}
386 ", 386 "#,
387 r" 387 r#"
388 fn source_impl() { 388fn source_impl() {
389 let var = enum_defvariant_list().unwrap() 389 let var = enum_defvariant_list().unwrap()
390 . 390 .
391 .nth(92) 391 .nth(92)
392 .unwrap(); 392 .unwrap();
393 } 393}
394 ", 394 "#,
395 ); 395 );
396 type_char_noop( 396 type_char_noop(
397 '.', 397 '.',
398 r" 398 r#"
399 fn source_impl() { 399fn source_impl() {
400 let var = enum_defvariant_list().unwrap() 400 let var = enum_defvariant_list().unwrap()
401 $0 401 $0
402 .nth(92) 402 .nth(92)
403 .unwrap(); 403 .unwrap();
404 } 404}
405 ", 405 "#,
406 ); 406 );
407 } 407 }
408 408
@@ -410,25 +410,33 @@ fn main() {
410 fn dont_indent_freestanding_dot() { 410 fn dont_indent_freestanding_dot() {
411 type_char_noop( 411 type_char_noop(
412 '.', 412 '.',
413 r" 413 r#"
414 fn main() { 414fn main() {
415 $0 415 $0
416 } 416}
417 ", 417 "#,
418 ); 418 );
419 type_char_noop( 419 type_char_noop(
420 '.', 420 '.',
421 r" 421 r#"
422 fn main() { 422fn main() {
423 $0 423$0
424 } 424}
425 ", 425 "#,
426 ); 426 );
427 } 427 }
428 428
429 #[test] 429 #[test]
430 fn adds_space_after_return_type() { 430 fn adds_space_after_return_type() {
431 type_char('>', "fn foo() -$0{ 92 }", "fn foo() -> { 92 }") 431 type_char(
432 '>',
433 r#"
434fn foo() -$0{ 92 }
435"#,
436 r#"
437fn foo() -> { 92 }
438"#,
439 );
432 } 440 }
433 441
434 #[test] 442 #[test]
diff --git a/crates/ide/src/typing/on_enter.rs b/crates/ide/src/typing/on_enter.rs
index 6f1ce3689..7d2db201a 100644
--- a/crates/ide/src/typing/on_enter.rs
+++ b/crates/ide/src/typing/on_enter.rs
@@ -4,10 +4,11 @@
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;
@@ -19,6 +20,7 @@ use text_edit::TextEdit;
19// - kbd:[Enter] inside triple-slash comments automatically inserts `///` 20// - kbd:[Enter] inside triple-slash comments automatically inserts `///`
20// - kbd:[Enter] in the middle or after a trailing space in `//` inserts `//` 21// - kbd:[Enter] in the middle or after a trailing space in `//` inserts `//`
21// - kbd:[Enter] inside `//!` doc comments automatically inserts `//!` 22// - kbd:[Enter] inside `//!` doc comments automatically inserts `//!`
23// - kbd:[Enter] after `{` indents contents and closing `}` of single-line block
22// 24//
23// This action needs to be assigned to shortcut explicitly. 25// This action needs to be assigned to shortcut explicitly.
24// 26//
@@ -38,25 +40,43 @@ use text_edit::TextEdit;
38pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<TextEdit> { 40pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<TextEdit> {
39 let parse = db.parse(position.file_id); 41 let parse = db.parse(position.file_id);
40 let file = parse.tree(); 42 let file = parse.tree();
41 let comment = file 43 let token = file.syntax().token_at_offset(position.offset).left_biased()?;
42 .syntax()
43 .token_at_offset(position.offset)
44 .left_biased()
45 .and_then(ast::Comment::cast)?;
46 44
45 if let Some(comment) = ast::Comment::cast(token.clone()) {
46 return on_enter_in_comment(&comment, &file, position.offset);
47 }
48
49 if token.kind() == L_CURLY {
50 // Typing enter after the `{` of a block expression, where the `}` is on the same line
51 if let Some(edit) = find_node_at_offset(file.syntax(), position.offset - TextSize::of('{'))
52 .and_then(|block| on_enter_in_block(block, position))
53 {
54 cov_mark::hit!(indent_block_contents);
55 return Some(edit);
56 }
57 }
58
59 None
60}
61
62fn on_enter_in_comment(
63 comment: &ast::Comment,
64 file: &ast::SourceFile,
65 offset: TextSize,
66) -> Option<TextEdit> {
47 if comment.kind().shape.is_block() { 67 if comment.kind().shape.is_block() {
48 return None; 68 return None;
49 } 69 }
50 70
51 let prefix = comment.prefix(); 71 let prefix = comment.prefix();
52 let comment_range = comment.syntax().text_range(); 72 let comment_range = comment.syntax().text_range();
53 if position.offset < comment_range.start() + TextSize::of(prefix) { 73 if offset < comment_range.start() + TextSize::of(prefix) {
54 return None; 74 return None;
55 } 75 }
56 76
57 let mut remove_trailing_whitespace = false; 77 let mut remove_trailing_whitespace = false;
58 // Continuing single-line non-doc comments (like this one :) ) is annoying 78 // Continuing single-line non-doc comments (like this one :) ) is annoying
59 if prefix == "//" && comment_range.end() == position.offset { 79 if prefix == "//" && comment_range.end() == offset {
60 if comment.text().ends_with(' ') { 80 if comment.text().ends_with(' ') {
61 cov_mark::hit!(continues_end_of_line_comment_with_space); 81 cov_mark::hit!(continues_end_of_line_comment_with_space);
62 remove_trailing_whitespace = true; 82 remove_trailing_whitespace = true;
@@ -70,14 +90,42 @@ pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<Text
70 let delete = if remove_trailing_whitespace { 90 let delete = if remove_trailing_whitespace {
71 let trimmed_len = comment.text().trim_end().len() as u32; 91 let trimmed_len = comment.text().trim_end().len() as u32;
72 let trailing_whitespace_len = comment.text().len() as u32 - trimmed_len; 92 let trailing_whitespace_len = comment.text().len() as u32 - trimmed_len;
73 TextRange::new(position.offset - TextSize::from(trailing_whitespace_len), position.offset) 93 TextRange::new(offset - TextSize::from(trailing_whitespace_len), offset)
74 } else { 94 } else {
75 TextRange::empty(position.offset) 95 TextRange::empty(offset)
76 }; 96 };
77 let edit = TextEdit::replace(delete, inserted); 97 let edit = TextEdit::replace(delete, inserted);
78 Some(edit) 98 Some(edit)
79} 99}
80 100
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
81fn followed_by_comment(comment: &ast::Comment) -> bool { 129fn followed_by_comment(comment: &ast::Comment) -> bool {
82 let ws = match comment.syntax().next_token().and_then(ast::Whitespace::cast) { 130 let ws = match comment.syntax().next_token().and_then(ast::Whitespace::cast) {
83 Some(it) => it, 131 Some(it) => it,
@@ -296,4 +344,144 @@ fn main() {
296", 344",
297 ); 345 );
298 } 346 }
347
348 #[test]
349 fn indents_fn_body_block() {
350 cov_mark::check!(indent_block_contents);
351 do_check(
352 r#"
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 }
299} 487}