aboutsummaryrefslogtreecommitdiff
path: root/crates/ide
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide')
-rw-r--r--crates/ide/Cargo.toml1
-rw-r--r--crates/ide/src/diagnostics.rs63
-rw-r--r--crates/ide/src/diagnostics/fixes.rs51
-rw-r--r--crates/ide/src/diagnostics/unlinked_file.rs7
-rwxr-xr-x[-rw-r--r--]crates/ide/src/folding_ranges.rs45
-rw-r--r--crates/ide/src/goto_type_definition.rs12
-rw-r--r--crates/ide/src/inlay_hints.rs4
-rw-r--r--crates/ide/src/join_lines.rs69
-rw-r--r--crates/ide/src/lib.rs44
-rw-r--r--crates/ide/src/references.rs64
-rw-r--r--crates/ide/src/references/rename.rs17
-rw-r--r--crates/ide/src/ssr.rs54
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html2
-rw-r--r--crates/ide/src/syntax_highlighting/tests.rs2
-rw-r--r--crates/ide/src/typing.rs150
-rw-r--r--crates/ide/src/typing/on_enter.rs115
-rw-r--r--crates/ide/src/view_crate_graph.rs111
17 files changed, 701 insertions, 110 deletions
diff --git a/crates/ide/Cargo.toml b/crates/ide/Cargo.toml
index f04bcf531..88f3d09d3 100644
--- a/crates/ide/Cargo.toml
+++ b/crates/ide/Cargo.toml
@@ -20,6 +20,7 @@ oorandom = "11.1.2"
20pulldown-cmark-to-cmark = "6.0.0" 20pulldown-cmark-to-cmark = "6.0.0"
21pulldown-cmark = { version = "0.8.0", default-features = false } 21pulldown-cmark = { version = "0.8.0", default-features = false }
22url = "2.1.1" 22url = "2.1.1"
23dot = "0.1.4"
23 24
24stdx = { path = "../stdx", version = "0.0.0" } 25stdx = { path = "../stdx", version = "0.0.0" }
25syntax = { path = "../syntax", version = "0.0.0" } 26syntax = { path = "../syntax", version = "0.0.0" }
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs
index 1c911a8b2..273d8cfbb 100644
--- a/crates/ide/src/diagnostics.rs
+++ b/crates/ide/src/diagnostics.rs
@@ -15,6 +15,7 @@ use hir::{
15 diagnostics::{Diagnostic as _, DiagnosticCode, DiagnosticSinkBuilder}, 15 diagnostics::{Diagnostic as _, DiagnosticCode, DiagnosticSinkBuilder},
16 InFile, Semantics, 16 InFile, Semantics,
17}; 17};
18use ide_assists::AssistResolveStrategy;
18use ide_db::{base_db::SourceDatabase, RootDatabase}; 19use ide_db::{base_db::SourceDatabase, RootDatabase};
19use itertools::Itertools; 20use itertools::Itertools;
20use rustc_hash::FxHashSet; 21use rustc_hash::FxHashSet;
@@ -84,7 +85,7 @@ pub struct DiagnosticsConfig {
84pub(crate) fn diagnostics( 85pub(crate) fn diagnostics(
85 db: &RootDatabase, 86 db: &RootDatabase,
86 config: &DiagnosticsConfig, 87 config: &DiagnosticsConfig,
87 resolve: bool, 88 resolve: &AssistResolveStrategy,
88 file_id: FileId, 89 file_id: FileId,
89) -> Vec<Diagnostic> { 90) -> Vec<Diagnostic> {
90 let _p = profile::span("diagnostics"); 91 let _p = profile::span("diagnostics");
@@ -212,7 +213,7 @@ pub(crate) fn diagnostics(
212fn diagnostic_with_fix<D: DiagnosticWithFix>( 213fn diagnostic_with_fix<D: DiagnosticWithFix>(
213 d: &D, 214 d: &D,
214 sema: &Semantics<RootDatabase>, 215 sema: &Semantics<RootDatabase>,
215 resolve: bool, 216 resolve: &AssistResolveStrategy,
216) -> Diagnostic { 217) -> Diagnostic {
217 Diagnostic::error(sema.diagnostics_display_range(d.display_source()).range, d.message()) 218 Diagnostic::error(sema.diagnostics_display_range(d.display_source()).range, d.message())
218 .with_fix(d.fix(&sema, resolve)) 219 .with_fix(d.fix(&sema, resolve))
@@ -222,7 +223,7 @@ fn diagnostic_with_fix<D: DiagnosticWithFix>(
222fn warning_with_fix<D: DiagnosticWithFix>( 223fn warning_with_fix<D: DiagnosticWithFix>(
223 d: &D, 224 d: &D,
224 sema: &Semantics<RootDatabase>, 225 sema: &Semantics<RootDatabase>,
225 resolve: bool, 226 resolve: &AssistResolveStrategy,
226) -> Diagnostic { 227) -> Diagnostic {
227 Diagnostic::hint(sema.diagnostics_display_range(d.display_source()).range, d.message()) 228 Diagnostic::hint(sema.diagnostics_display_range(d.display_source()).range, d.message())
228 .with_fix(d.fix(&sema, resolve)) 229 .with_fix(d.fix(&sema, resolve))
@@ -299,6 +300,7 @@ fn unresolved_fix(id: &'static str, label: &str, target: TextRange) -> Assist {
299#[cfg(test)] 300#[cfg(test)]
300mod tests { 301mod tests {
301 use expect_test::{expect, Expect}; 302 use expect_test::{expect, Expect};
303 use ide_assists::AssistResolveStrategy;
302 use stdx::trim_indent; 304 use stdx::trim_indent;
303 use test_utils::assert_eq_text; 305 use test_utils::assert_eq_text;
304 306
@@ -314,7 +316,11 @@ mod tests {
314 316
315 let (analysis, file_position) = fixture::position(ra_fixture_before); 317 let (analysis, file_position) = fixture::position(ra_fixture_before);
316 let diagnostic = analysis 318 let diagnostic = analysis
317 .diagnostics(&DiagnosticsConfig::default(), true, file_position.file_id) 319 .diagnostics(
320 &DiagnosticsConfig::default(),
321 AssistResolveStrategy::All,
322 file_position.file_id,
323 )
318 .unwrap() 324 .unwrap()
319 .pop() 325 .pop()
320 .unwrap(); 326 .unwrap();
@@ -343,7 +349,11 @@ mod tests {
343 fn check_no_fix(ra_fixture: &str) { 349 fn check_no_fix(ra_fixture: &str) {
344 let (analysis, file_position) = fixture::position(ra_fixture); 350 let (analysis, file_position) = fixture::position(ra_fixture);
345 let diagnostic = analysis 351 let diagnostic = analysis
346 .diagnostics(&DiagnosticsConfig::default(), true, file_position.file_id) 352 .diagnostics(
353 &DiagnosticsConfig::default(),
354 AssistResolveStrategy::All,
355 file_position.file_id,
356 )
347 .unwrap() 357 .unwrap()
348 .pop() 358 .pop()
349 .unwrap(); 359 .unwrap();
@@ -357,7 +367,9 @@ mod tests {
357 let diagnostics = files 367 let diagnostics = files
358 .into_iter() 368 .into_iter()
359 .flat_map(|file_id| { 369 .flat_map(|file_id| {
360 analysis.diagnostics(&DiagnosticsConfig::default(), true, file_id).unwrap() 370 analysis
371 .diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::All, file_id)
372 .unwrap()
361 }) 373 })
362 .collect::<Vec<_>>(); 374 .collect::<Vec<_>>();
363 assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics); 375 assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics);
@@ -365,8 +377,9 @@ mod tests {
365 377
366 fn check_expect(ra_fixture: &str, expect: Expect) { 378 fn check_expect(ra_fixture: &str, expect: Expect) {
367 let (analysis, file_id) = fixture::file(ra_fixture); 379 let (analysis, file_id) = fixture::file(ra_fixture);
368 let diagnostics = 380 let diagnostics = analysis
369 analysis.diagnostics(&DiagnosticsConfig::default(), true, file_id).unwrap(); 381 .diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::All, file_id)
382 .unwrap();
370 expect.assert_debug_eq(&diagnostics) 383 expect.assert_debug_eq(&diagnostics)
371 } 384 }
372 385
@@ -641,6 +654,26 @@ fn test_fn() {
641 } 654 }
642 655
643 #[test] 656 #[test]
657 fn test_fill_struct_fields_raw_ident() {
658 check_fix(
659 r#"
660struct TestStruct { r#type: u8 }
661
662fn test_fn() {
663 TestStruct { $0 };
664}
665"#,
666 r"
667struct TestStruct { r#type: u8 }
668
669fn test_fn() {
670 TestStruct { r#type: () };
671}
672",
673 );
674 }
675
676 #[test]
644 fn test_fill_struct_fields_no_diagnostic() { 677 fn test_fill_struct_fields_no_diagnostic() {
645 check_no_diagnostics( 678 check_no_diagnostics(
646 r" 679 r"
@@ -911,11 +944,13 @@ struct Foo {
911 944
912 let (analysis, file_id) = fixture::file(r#"mod foo;"#); 945 let (analysis, file_id) = fixture::file(r#"mod foo;"#);
913 946
914 let diagnostics = analysis.diagnostics(&config, true, file_id).unwrap(); 947 let diagnostics =
948 analysis.diagnostics(&config, AssistResolveStrategy::All, file_id).unwrap();
915 assert!(diagnostics.is_empty()); 949 assert!(diagnostics.is_empty());
916 950
917 let diagnostics = 951 let diagnostics = analysis
918 analysis.diagnostics(&DiagnosticsConfig::default(), true, file_id).unwrap(); 952 .diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::All, file_id)
953 .unwrap();
919 assert!(!diagnostics.is_empty()); 954 assert!(!diagnostics.is_empty());
920 } 955 }
921 956
@@ -1022,7 +1057,11 @@ impl TestStruct {
1022 1057
1023 let (analysis, file_position) = fixture::position(input); 1058 let (analysis, file_position) = fixture::position(input);
1024 let diagnostics = analysis 1059 let diagnostics = analysis
1025 .diagnostics(&DiagnosticsConfig::default(), true, file_position.file_id) 1060 .diagnostics(
1061 &DiagnosticsConfig::default(),
1062 AssistResolveStrategy::All,
1063 file_position.file_id,
1064 )
1026 .unwrap(); 1065 .unwrap();
1027 assert_eq!(diagnostics.len(), 1); 1066 assert_eq!(diagnostics.len(), 1);
1028 1067
diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs
index 7be8b3459..15821500f 100644
--- a/crates/ide/src/diagnostics/fixes.rs
+++ b/crates/ide/src/diagnostics/fixes.rs
@@ -8,6 +8,7 @@ use hir::{
8 }, 8 },
9 HasSource, HirDisplay, InFile, Semantics, VariantDef, 9 HasSource, HirDisplay, InFile, Semantics, VariantDef,
10}; 10};
11use ide_assists::AssistResolveStrategy;
11use ide_db::{ 12use ide_db::{
12 base_db::{AnchoredPathBuf, FileId}, 13 base_db::{AnchoredPathBuf, FileId},
13 source_change::{FileSystemEdit, SourceChange}, 14 source_change::{FileSystemEdit, SourceChange},
@@ -35,11 +36,19 @@ pub(crate) trait DiagnosticWithFix: Diagnostic {
35 /// 36 ///
36 /// If `resolve` is false, the edit will be computed later, on demand, and 37 /// If `resolve` is false, the edit will be computed later, on demand, and
37 /// can be omitted. 38 /// can be omitted.
38 fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist>; 39 fn fix(
40 &self,
41 sema: &Semantics<RootDatabase>,
42 _resolve: &AssistResolveStrategy,
43 ) -> Option<Assist>;
39} 44}
40 45
41impl DiagnosticWithFix for UnresolvedModule { 46impl DiagnosticWithFix for UnresolvedModule {
42 fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> { 47 fn fix(
48 &self,
49 sema: &Semantics<RootDatabase>,
50 _resolve: &AssistResolveStrategy,
51 ) -> Option<Assist> {
43 let root = sema.db.parse_or_expand(self.file)?; 52 let root = sema.db.parse_or_expand(self.file)?;
44 let unresolved_module = self.decl.to_node(&root); 53 let unresolved_module = self.decl.to_node(&root);
45 Some(fix( 54 Some(fix(
@@ -59,7 +68,11 @@ impl DiagnosticWithFix for UnresolvedModule {
59} 68}
60 69
61impl DiagnosticWithFix for NoSuchField { 70impl DiagnosticWithFix for NoSuchField {
62 fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> { 71 fn fix(
72 &self,
73 sema: &Semantics<RootDatabase>,
74 _resolve: &AssistResolveStrategy,
75 ) -> Option<Assist> {
63 let root = sema.db.parse_or_expand(self.file)?; 76 let root = sema.db.parse_or_expand(self.file)?;
64 missing_record_expr_field_fix( 77 missing_record_expr_field_fix(
65 &sema, 78 &sema,
@@ -70,7 +83,11 @@ impl DiagnosticWithFix for NoSuchField {
70} 83}
71 84
72impl DiagnosticWithFix for MissingFields { 85impl DiagnosticWithFix for MissingFields {
73 fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> { 86 fn fix(
87 &self,
88 sema: &Semantics<RootDatabase>,
89 _resolve: &AssistResolveStrategy,
90 ) -> Option<Assist> {
74 // Note that although we could add a diagnostics to 91 // Note that although we could add a diagnostics to
75 // fill the missing tuple field, e.g : 92 // fill the missing tuple field, e.g :
76 // `struct A(usize);` 93 // `struct A(usize);`
@@ -106,7 +123,11 @@ impl DiagnosticWithFix for MissingFields {
106} 123}
107 124
108impl DiagnosticWithFix for MissingOkOrSomeInTailExpr { 125impl DiagnosticWithFix for MissingOkOrSomeInTailExpr {
109 fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> { 126 fn fix(
127 &self,
128 sema: &Semantics<RootDatabase>,
129 _resolve: &AssistResolveStrategy,
130 ) -> Option<Assist> {
110 let root = sema.db.parse_or_expand(self.file)?; 131 let root = sema.db.parse_or_expand(self.file)?;
111 let tail_expr = self.expr.to_node(&root); 132 let tail_expr = self.expr.to_node(&root);
112 let tail_expr_range = tail_expr.syntax().text_range(); 133 let tail_expr_range = tail_expr.syntax().text_range();
@@ -119,7 +140,11 @@ impl DiagnosticWithFix for MissingOkOrSomeInTailExpr {
119} 140}
120 141
121impl DiagnosticWithFix for RemoveThisSemicolon { 142impl DiagnosticWithFix for RemoveThisSemicolon {
122 fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> { 143 fn fix(
144 &self,
145 sema: &Semantics<RootDatabase>,
146 _resolve: &AssistResolveStrategy,
147 ) -> Option<Assist> {
123 let root = sema.db.parse_or_expand(self.file)?; 148 let root = sema.db.parse_or_expand(self.file)?;
124 149
125 let semicolon = self 150 let semicolon = self
@@ -139,7 +164,11 @@ impl DiagnosticWithFix for RemoveThisSemicolon {
139} 164}
140 165
141impl DiagnosticWithFix for IncorrectCase { 166impl DiagnosticWithFix for IncorrectCase {
142 fn fix(&self, sema: &Semantics<RootDatabase>, resolve: bool) -> Option<Assist> { 167 fn fix(
168 &self,
169 sema: &Semantics<RootDatabase>,
170 resolve: &AssistResolveStrategy,
171 ) -> Option<Assist> {
143 let root = sema.db.parse_or_expand(self.file)?; 172 let root = sema.db.parse_or_expand(self.file)?;
144 let name_node = self.ident.to_node(&root); 173 let name_node = self.ident.to_node(&root);
145 174
@@ -149,7 +178,7 @@ impl DiagnosticWithFix for IncorrectCase {
149 178
150 let label = format!("Rename to {}", self.suggested_text); 179 let label = format!("Rename to {}", self.suggested_text);
151 let mut res = unresolved_fix("change_case", &label, frange.range); 180 let mut res = unresolved_fix("change_case", &label, frange.range);
152 if resolve { 181 if resolve.should_resolve(&res.id) {
153 let source_change = rename_with_semantics(sema, file_position, &self.suggested_text); 182 let source_change = rename_with_semantics(sema, file_position, &self.suggested_text);
154 res.source_change = Some(source_change.ok().unwrap_or_default()); 183 res.source_change = Some(source_change.ok().unwrap_or_default());
155 } 184 }
@@ -159,7 +188,11 @@ impl DiagnosticWithFix for IncorrectCase {
159} 188}
160 189
161impl DiagnosticWithFix for ReplaceFilterMapNextWithFindMap { 190impl DiagnosticWithFix for ReplaceFilterMapNextWithFindMap {
162 fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> { 191 fn fix(
192 &self,
193 sema: &Semantics<RootDatabase>,
194 _resolve: &AssistResolveStrategy,
195 ) -> Option<Assist> {
163 let root = sema.db.parse_or_expand(self.file)?; 196 let root = sema.db.parse_or_expand(self.file)?;
164 let next_expr = self.next_expr.to_node(&root); 197 let next_expr = self.next_expr.to_node(&root);
165 let next_call = ast::MethodCallExpr::cast(next_expr.syntax().clone())?; 198 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 7d39f4fbe..93fd25dea 100644
--- a/crates/ide/src/diagnostics/unlinked_file.rs
+++ b/crates/ide/src/diagnostics/unlinked_file.rs
@@ -5,6 +5,7 @@ use hir::{
5 diagnostics::{Diagnostic, DiagnosticCode}, 5 diagnostics::{Diagnostic, DiagnosticCode},
6 InFile, 6 InFile,
7}; 7};
8use ide_assists::AssistResolveStrategy;
8use ide_db::{ 9use ide_db::{
9 base_db::{FileId, FileLoader, SourceDatabase, SourceDatabaseExt}, 10 base_db::{FileId, FileLoader, SourceDatabase, SourceDatabaseExt},
10 source_change::SourceChange, 11 source_change::SourceChange,
@@ -50,7 +51,11 @@ impl Diagnostic for UnlinkedFile {
50} 51}
51 52
52impl DiagnosticWithFix for UnlinkedFile { 53impl DiagnosticWithFix for UnlinkedFile {
53 fn fix(&self, sema: &hir::Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> { 54 fn fix(
55 &self,
56 sema: &hir::Semantics<RootDatabase>,
57 _resolve: &AssistResolveStrategy,
58 ) -> Option<Assist> {
54 // If there's an existing module that could add a `mod` item to include the unlinked file, 59 // If there's an existing module that could add a `mod` item to include the unlinked file,
55 // suggest that as a fix. 60 // suggest that as a fix.
56 61
diff --git a/crates/ide/src/folding_ranges.rs b/crates/ide/src/folding_ranges.rs
index 2b9ed123c..a03988778 100644..100755
--- a/crates/ide/src/folding_ranges.rs
+++ b/crates/ide/src/folding_ranges.rs
@@ -20,6 +20,7 @@ pub enum FoldKind {
20 Consts, 20 Consts,
21 Statics, 21 Statics,
22 Array, 22 Array,
23 WhereClause,
23} 24}
24 25
25#[derive(Debug)] 26#[derive(Debug)]
@@ -109,6 +110,13 @@ pub(crate) fn folding_ranges(file: &SourceFile) -> Vec<Fold> {
109 res.push(Fold { range, kind: FoldKind::Statics }) 110 res.push(Fold { range, kind: FoldKind::Statics })
110 } 111 }
111 } 112 }
113
114 // Fold where clause
115 if node.kind() == WHERE_CLAUSE {
116 if let Some(range) = fold_range_for_where_clause(&node) {
117 res.push(Fold { range, kind: FoldKind::WhereClause })
118 }
119 }
112 } 120 }
113 } 121 }
114 } 122 }
@@ -241,6 +249,23 @@ fn contiguous_range_for_comment(
241 } 249 }
242} 250}
243 251
252fn fold_range_for_where_clause(node: &SyntaxNode) -> Option<TextRange> {
253 let first_where_pred = node.first_child();
254 let last_where_pred = node.last_child();
255
256 if first_where_pred != last_where_pred {
257 let mut it = node.descendants_with_tokens();
258 if let (Some(_where_clause), Some(where_kw), Some(last_comma)) =
259 (it.next(), it.next(), it.last())
260 {
261 let start = where_kw.text_range().end();
262 let end = last_comma.text_range().end();
263 return Some(TextRange::new(start, end));
264 }
265 }
266 None
267}
268
244#[cfg(test)] 269#[cfg(test)]
245mod tests { 270mod tests {
246 use test_utils::extract_tags; 271 use test_utils::extract_tags;
@@ -272,6 +297,7 @@ mod tests {
272 FoldKind::Consts => "consts", 297 FoldKind::Consts => "consts",
273 FoldKind::Statics => "statics", 298 FoldKind::Statics => "statics",
274 FoldKind::Array => "array", 299 FoldKind::Array => "array",
300 FoldKind::WhereClause => "whereclause",
275 }; 301 };
276 assert_eq!(kind, &attr.unwrap()); 302 assert_eq!(kind, &attr.unwrap());
277 } 303 }
@@ -513,4 +539,23 @@ static SECOND_STATIC: &str = "second";</fold>
513 "#, 539 "#,
514 ) 540 )
515 } 541 }
542
543 #[test]
544 fn fold_where_clause() {
545 // fold multi-line and don't fold single line.
546 check(
547 r#"
548fn foo()
549where<fold whereclause>
550 A: Foo,
551 B: Foo,
552 C: Foo,
553 D: Foo,</fold> {}
554
555fn bar()
556where
557 A: Bar, {}
558"#,
559 )
560 }
516} 561}
diff --git a/crates/ide/src/goto_type_definition.rs b/crates/ide/src/goto_type_definition.rs
index 9d34b109b..f3284bb96 100644
--- a/crates/ide/src/goto_type_definition.rs
+++ b/crates/ide/src/goto_type_definition.rs
@@ -30,6 +30,7 @@ pub(crate) fn goto_type_definition(
30 ast::Expr(it) => sema.type_of_expr(&it)?, 30 ast::Expr(it) => sema.type_of_expr(&it)?,
31 ast::Pat(it) => sema.type_of_pat(&it)?, 31 ast::Pat(it) => sema.type_of_pat(&it)?,
32 ast::SelfParam(it) => sema.type_of_self(&it)?, 32 ast::SelfParam(it) => sema.type_of_self(&it)?,
33 ast::Type(it) => sema.resolve_type(&it)?,
33 _ => return None, 34 _ => return None,
34 } 35 }
35 }; 36 };
@@ -149,4 +150,15 @@ impl Foo {
149"#, 150"#,
150 ) 151 )
151 } 152 }
153
154 #[test]
155 fn goto_def_for_type_fallback() {
156 check(
157 r#"
158struct Foo;
159 //^^^
160impl Foo$0 {}
161"#,
162 )
163 }
152} 164}
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs
index d5ef054d8..e0bf660c4 100644
--- a/crates/ide/src/inlay_hints.rs
+++ b/crates/ide/src/inlay_hints.rs
@@ -218,9 +218,7 @@ fn hint_iterator(
218 ty: &hir::Type, 218 ty: &hir::Type,
219) -> Option<SmolStr> { 219) -> Option<SmolStr> {
220 let db = sema.db; 220 let db = sema.db;
221 let strukt = std::iter::successors(Some(ty.clone()), |ty| ty.remove_ref()) 221 let strukt = ty.strip_references().as_adt()?;
222 .last()
223 .and_then(|strukt| strukt.as_adt())?;
224 let krate = strukt.krate(db); 222 let krate = strukt.krate(db);
225 if krate != famous_defs.core()? { 223 if krate != famous_defs.core()? {
226 return None; 224 return None;
diff --git a/crates/ide/src/join_lines.rs b/crates/ide/src/join_lines.rs
index fe2a349e6..61dcbb399 100644
--- a/crates/ide/src/join_lines.rs
+++ b/crates/ide/src/join_lines.rs
@@ -1,3 +1,5 @@
1use std::convert::TryFrom;
2
1use ide_assists::utils::extract_trivial_expression; 3use ide_assists::utils::extract_trivial_expression;
2use itertools::Itertools; 4use itertools::Itertools;
3use syntax::{ 5use syntax::{
@@ -65,14 +67,6 @@ fn remove_newlines(edit: &mut TextEditBuilder, token: &SyntaxToken, range: TextR
65 67
66fn remove_newline(edit: &mut TextEditBuilder, token: &SyntaxToken, offset: TextSize) { 68fn remove_newline(edit: &mut TextEditBuilder, token: &SyntaxToken, offset: TextSize) {
67 if token.kind() != WHITESPACE || token.text().bytes().filter(|&b| b == b'\n').count() != 1 { 69 if token.kind() != WHITESPACE || token.text().bytes().filter(|&b| b == b'\n').count() != 1 {
68 let mut string_open_quote = false;
69 if let Some(string) = ast::String::cast(token.clone()) {
70 if let Some(range) = string.open_quote_text_range() {
71 cov_mark::hit!(join_string_literal);
72 string_open_quote = range.end() == offset;
73 }
74 }
75
76 let n_spaces_after_line_break = { 70 let n_spaces_after_line_break = {
77 let suff = &token.text()[TextRange::new( 71 let suff = &token.text()[TextRange::new(
78 offset - token.text_range().start() + TextSize::of('\n'), 72 offset - token.text_range().start() + TextSize::of('\n'),
@@ -81,8 +75,23 @@ fn remove_newline(edit: &mut TextEditBuilder, token: &SyntaxToken, offset: TextS
81 suff.bytes().take_while(|&b| b == b' ').count() 75 suff.bytes().take_while(|&b| b == b' ').count()
82 }; 76 };
83 77
78 let mut no_space = false;
79 if let Some(string) = ast::String::cast(token.clone()) {
80 if let Some(range) = string.open_quote_text_range() {
81 cov_mark::hit!(join_string_literal_open_quote);
82 no_space |= range.end() == offset;
83 }
84 if let Some(range) = string.close_quote_text_range() {
85 cov_mark::hit!(join_string_literal_close_quote);
86 no_space |= range.start()
87 == offset
88 + TextSize::of('\n')
89 + TextSize::try_from(n_spaces_after_line_break).unwrap();
90 }
91 }
92
84 let range = TextRange::at(offset, ((n_spaces_after_line_break + 1) as u32).into()); 93 let range = TextRange::at(offset, ((n_spaces_after_line_break + 1) as u32).into());
85 let replace_with = if string_open_quote { "" } else { " " }; 94 let replace_with = if no_space { "" } else { " " };
86 edit.replace(range, replace_with.to_string()); 95 edit.replace(range, replace_with.to_string());
87 return; 96 return;
88 } 97 }
@@ -797,22 +806,54 @@ fn foo() {
797 806
798 #[test] 807 #[test]
799 fn join_string_literal() { 808 fn join_string_literal() {
800 cov_mark::check!(join_string_literal); 809 {
801 check_join_lines( 810 cov_mark::check!(join_string_literal_open_quote);
802 r#" 811 check_join_lines(
812 r#"
803fn main() { 813fn main() {
804 $0" 814 $0"
805hello 815hello
806"; 816";
807} 817}
808"#, 818"#,
809 r#" 819 r#"
810fn main() { 820fn main() {
811 $0"hello 821 $0"hello
812"; 822";
813} 823}
814"#, 824"#,
815 ); 825 );
826 }
827
828 {
829 cov_mark::check!(join_string_literal_close_quote);
830 check_join_lines(
831 r#"
832fn main() {
833 $0"hello
834";
835}
836"#,
837 r#"
838fn main() {
839 $0"hello";
840}
841"#,
842 );
843 check_join_lines(
844 r#"
845fn main() {
846 $0r"hello
847 ";
848}
849"#,
850 r#"
851fn main() {
852 $0r"hello";
853}
854"#,
855 );
856 }
816 857
817 check_join_lines( 858 check_join_lines(
818 r#" 859 r#"
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index 99e45633e..34360501a 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -49,6 +49,7 @@ mod syntax_tree;
49mod typing; 49mod typing;
50mod markdown_remove; 50mod markdown_remove;
51mod doc_links; 51mod doc_links;
52mod view_crate_graph;
52 53
53use std::sync::Arc; 54use std::sync::Arc;
54 55
@@ -87,7 +88,9 @@ pub use crate::{
87 }, 88 },
88}; 89};
89pub use hir::{Documentation, Semantics}; 90pub use hir::{Documentation, Semantics};
90pub use ide_assists::{Assist, AssistConfig, AssistId, AssistKind}; 91pub use ide_assists::{
92 Assist, AssistConfig, AssistId, AssistKind, AssistResolveStrategy, SingleResolve,
93};
91pub use ide_completion::{ 94pub use ide_completion::{
92 CompletionConfig, CompletionItem, CompletionItemKind, CompletionRelevance, ImportEdit, 95 CompletionConfig, CompletionItem, CompletionItemKind, CompletionRelevance, ImportEdit,
93 InsertTextFormat, 96 InsertTextFormat,
@@ -285,6 +288,10 @@ impl Analysis {
285 self.with_db(|db| view_hir::view_hir(&db, position)) 288 self.with_db(|db| view_hir::view_hir(&db, position))
286 } 289 }
287 290
291 pub fn view_crate_graph(&self) -> Cancelable<Result<String, String>> {
292 self.with_db(|db| view_crate_graph::view_crate_graph(&db))
293 }
294
288 pub fn expand_macro(&self, position: FilePosition) -> Cancelable<Option<ExpandedMacro>> { 295 pub fn expand_macro(&self, position: FilePosition) -> Cancelable<Option<ExpandedMacro>> {
289 self.with_db(|db| expand_macro::expand_macro(db, position)) 296 self.with_db(|db| expand_macro::expand_macro(db, position))
290 } 297 }
@@ -518,12 +525,13 @@ impl Analysis {
518 pub fn assists( 525 pub fn assists(
519 &self, 526 &self,
520 config: &AssistConfig, 527 config: &AssistConfig,
521 resolve: bool, 528 resolve: AssistResolveStrategy,
522 frange: FileRange, 529 frange: FileRange,
523 ) -> Cancelable<Vec<Assist>> { 530 ) -> Cancelable<Vec<Assist>> {
524 self.with_db(|db| { 531 self.with_db(|db| {
532 let ssr_assists = ssr::ssr_assists(db, &resolve, frange);
525 let mut acc = Assist::get(db, config, resolve, frange); 533 let mut acc = Assist::get(db, config, resolve, frange);
526 ssr::add_ssr_assist(db, &mut acc, resolve, frange); 534 acc.extend(ssr_assists.into_iter());
527 acc 535 acc
528 }) 536 })
529 } 537 }
@@ -532,10 +540,10 @@ impl Analysis {
532 pub fn diagnostics( 540 pub fn diagnostics(
533 &self, 541 &self,
534 config: &DiagnosticsConfig, 542 config: &DiagnosticsConfig,
535 resolve: bool, 543 resolve: AssistResolveStrategy,
536 file_id: FileId, 544 file_id: FileId,
537 ) -> Cancelable<Vec<Diagnostic>> { 545 ) -> Cancelable<Vec<Diagnostic>> {
538 self.with_db(|db| diagnostics::diagnostics(db, config, resolve, file_id)) 546 self.with_db(|db| diagnostics::diagnostics(db, config, &resolve, file_id))
539 } 547 }
540 548
541 /// Convenience function to return assists + quick fixes for diagnostics 549 /// Convenience function to return assists + quick fixes for diagnostics
@@ -543,7 +551,7 @@ impl Analysis {
543 &self, 551 &self,
544 assist_config: &AssistConfig, 552 assist_config: &AssistConfig,
545 diagnostics_config: &DiagnosticsConfig, 553 diagnostics_config: &DiagnosticsConfig,
546 resolve: bool, 554 resolve: AssistResolveStrategy,
547 frange: FileRange, 555 frange: FileRange,
548 ) -> Cancelable<Vec<Assist>> { 556 ) -> Cancelable<Vec<Assist>> {
549 let include_fixes = match &assist_config.allowed { 557 let include_fixes = match &assist_config.allowed {
@@ -552,17 +560,21 @@ impl Analysis {
552 }; 560 };
553 561
554 self.with_db(|db| { 562 self.with_db(|db| {
563 let ssr_assists = ssr::ssr_assists(db, &resolve, frange);
564 let diagnostic_assists = if include_fixes {
565 diagnostics::diagnostics(db, diagnostics_config, &resolve, frange.file_id)
566 .into_iter()
567 .filter_map(|it| it.fix)
568 .filter(|it| it.target.intersect(frange.range).is_some())
569 .collect()
570 } else {
571 Vec::new()
572 };
573
555 let mut res = Assist::get(db, assist_config, resolve, frange); 574 let mut res = Assist::get(db, assist_config, resolve, frange);
556 ssr::add_ssr_assist(db, &mut res, resolve, frange); 575 res.extend(ssr_assists.into_iter());
557 576 res.extend(diagnostic_assists.into_iter());
558 if include_fixes { 577
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 578 res
567 }) 579 })
568 } 580 }
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs
index 11ca7ec6b..ae492a264 100644
--- a/crates/ide/src/references.rs
+++ b/crates/ide/src/references.rs
@@ -65,7 +65,7 @@ pub(crate) fn find_all_refs(
65 (find_def(&sema, &syntax, position)?, false) 65 (find_def(&sema, &syntax, position)?, false)
66 }; 66 };
67 67
68 let mut usages = def.usages(sema).set_scope(search_scope).all(); 68 let mut usages = def.usages(sema).set_scope(search_scope).include_self_refs().all();
69 if is_literal_search { 69 if is_literal_search {
70 // filter for constructor-literals 70 // filter for constructor-literals
71 let refs = usages.references.values_mut(); 71 let refs = usages.references.values_mut();
@@ -1163,22 +1163,76 @@ fn foo<const FOO$0: usize>() -> usize {
1163 } 1163 }
1164 1164
1165 #[test] 1165 #[test]
1166 fn test_find_self_ty_in_trait_def() { 1166 fn test_trait() {
1167 check( 1167 check(
1168 r#" 1168 r#"
1169trait Foo { 1169trait Foo$0 where Self: {}
1170 fn f() -> Self$0; 1170
1171impl Foo for () {}
1172"#,
1173 expect![[r#"
1174 Foo Trait FileId(0) 0..24 6..9
1175
1176 FileId(0) 31..34
1177 "#]],
1178 );
1179 }
1180
1181 #[test]
1182 fn test_trait_self() {
1183 check(
1184 r#"
1185trait Foo where Self$0 {
1186 fn f() -> Self;
1171} 1187}
1188
1189impl Foo for () {}
1172"#, 1190"#,
1173 expect![[r#" 1191 expect![[r#"
1174 Self TypeParam FileId(0) 6..9 6..9 1192 Self TypeParam FileId(0) 6..9 6..9
1175 1193
1176 FileId(0) 26..30 1194 FileId(0) 16..20
1195 FileId(0) 37..41
1177 "#]], 1196 "#]],
1178 ); 1197 );
1179 } 1198 }
1180 1199
1181 #[test] 1200 #[test]
1201 fn test_self_ty() {
1202 check(
1203 r#"
1204 struct $0Foo;
1205
1206 impl Foo where Self: {
1207 fn f() -> Self;
1208 }
1209 "#,
1210 expect![[r#"
1211 Foo Struct FileId(0) 0..11 7..10
1212
1213 FileId(0) 18..21
1214 FileId(0) 28..32
1215 FileId(0) 50..54
1216 "#]],
1217 );
1218 check(
1219 r#"
1220struct Foo;
1221
1222impl Foo where Self: {
1223 fn f() -> Self$0;
1224}
1225"#,
1226 expect![[r#"
1227 impl Impl FileId(0) 13..57 18..21
1228
1229 FileId(0) 18..21
1230 FileId(0) 28..32
1231 FileId(0) 50..54
1232 "#]],
1233 );
1234 }
1235 #[test]
1182 fn test_self_variant_with_payload() { 1236 fn test_self_variant_with_payload() {
1183 check( 1237 check(
1184 r#" 1238 r#"
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs
index 175e7a31d..2bf953305 100644
--- a/crates/ide/src/references/rename.rs
+++ b/crates/ide/src/references/rename.rs
@@ -1888,4 +1888,21 @@ impl Foo {
1888 "error: Cannot rename `Self`", 1888 "error: Cannot rename `Self`",
1889 ); 1889 );
1890 } 1890 }
1891
1892 #[test]
1893 fn test_rename_ignores_self_ty() {
1894 check(
1895 "Fo0",
1896 r#"
1897struct $0Foo;
1898
1899impl Foo where Self: {}
1900"#,
1901 r#"
1902struct Fo0;
1903
1904impl Fo0 where Self: {}
1905"#,
1906 );
1907 }
1891} 1908}
diff --git a/crates/ide/src/ssr.rs b/crates/ide/src/ssr.rs
index f3638d928..57ec80261 100644
--- a/crates/ide/src/ssr.rs
+++ b/crates/ide/src/ssr.rs
@@ -2,18 +2,23 @@
2//! assist in ide_assists because that would require the ide_assists crate 2//! assist in ide_assists because that would require the ide_assists crate
3//! depend on the ide_ssr crate. 3//! depend on the ide_ssr crate.
4 4
5use ide_assists::{Assist, AssistId, AssistKind, GroupLabel}; 5use ide_assists::{Assist, AssistId, AssistKind, AssistResolveStrategy, GroupLabel};
6use ide_db::{base_db::FileRange, label::Label, source_change::SourceChange, RootDatabase}; 6use ide_db::{base_db::FileRange, label::Label, source_change::SourceChange, RootDatabase};
7 7
8pub(crate) fn add_ssr_assist( 8pub(crate) fn ssr_assists(
9 db: &RootDatabase, 9 db: &RootDatabase,
10 base: &mut Vec<Assist>, 10 resolve: &AssistResolveStrategy,
11 resolve: bool,
12 frange: FileRange, 11 frange: FileRange,
13) -> Option<()> { 12) -> Vec<Assist> {
14 let (match_finder, comment_range) = ide_ssr::ssr_from_comment(db, frange)?; 13 let mut ssr_assists = Vec::with_capacity(2);
15 14
16 let (source_change_for_file, source_change_for_workspace) = if resolve { 15 let (match_finder, comment_range) = match ide_ssr::ssr_from_comment(db, frange) {
16 Some(ssr_data) => ssr_data,
17 None => return ssr_assists,
18 };
19 let id = AssistId("ssr", AssistKind::RefactorRewrite);
20
21 let (source_change_for_file, source_change_for_workspace) = if resolve.should_resolve(&id) {
17 let edits = match_finder.edits(); 22 let edits = match_finder.edits();
18 23
19 let source_change_for_file = { 24 let source_change_for_file = {
@@ -35,16 +40,17 @@ pub(crate) fn add_ssr_assist(
35 40
36 for (label, source_change) in assists.into_iter() { 41 for (label, source_change) in assists.into_iter() {
37 let assist = Assist { 42 let assist = Assist {
38 id: AssistId("ssr", AssistKind::RefactorRewrite), 43 id,
39 label: Label::new(label), 44 label: Label::new(label),
40 group: Some(GroupLabel("Apply SSR".into())), 45 group: Some(GroupLabel("Apply SSR".into())),
41 target: comment_range, 46 target: comment_range,
42 source_change, 47 source_change,
43 }; 48 };
44 49
45 base.push(assist); 50 ssr_assists.push(assist);
46 } 51 }
47 Some(()) 52
53 ssr_assists
48} 54}
49 55
50#[cfg(test)] 56#[cfg(test)]
@@ -52,7 +58,7 @@ mod tests {
52 use std::sync::Arc; 58 use std::sync::Arc;
53 59
54 use expect_test::expect; 60 use expect_test::expect;
55 use ide_assists::Assist; 61 use ide_assists::{Assist, AssistResolveStrategy};
56 use ide_db::{ 62 use ide_db::{
57 base_db::{fixture::WithFixture, salsa::Durability, FileRange}, 63 base_db::{fixture::WithFixture, salsa::Durability, FileRange},
58 symbol_index::SymbolsDatabase, 64 symbol_index::SymbolsDatabase,
@@ -60,24 +66,14 @@ mod tests {
60 }; 66 };
61 use rustc_hash::FxHashSet; 67 use rustc_hash::FxHashSet;
62 68
63 use super::add_ssr_assist; 69 use super::ssr_assists;
64 70
65 fn get_assists(ra_fixture: &str, resolve: bool) -> Vec<Assist> { 71 fn get_assists(ra_fixture: &str, resolve: AssistResolveStrategy) -> Vec<Assist> {
66 let (mut db, file_id, range_or_offset) = RootDatabase::with_range_or_offset(ra_fixture); 72 let (mut db, file_id, range_or_offset) = RootDatabase::with_range_or_offset(ra_fixture);
67 let mut local_roots = FxHashSet::default(); 73 let mut local_roots = FxHashSet::default();
68 local_roots.insert(ide_db::base_db::fixture::WORKSPACE); 74 local_roots.insert(ide_db::base_db::fixture::WORKSPACE);
69 db.set_local_roots_with_durability(Arc::new(local_roots), Durability::HIGH); 75 db.set_local_roots_with_durability(Arc::new(local_roots), Durability::HIGH);
70 76 ssr_assists(&db, &resolve, FileRange { file_id, range: range_or_offset.into() })
71 let mut assists = vec![];
72
73 add_ssr_assist(
74 &db,
75 &mut assists,
76 resolve,
77 FileRange { file_id, range: range_or_offset.into() },
78 );
79
80 assists
81 } 77 }
82 78
83 #[test] 79 #[test]
@@ -88,16 +84,13 @@ mod tests {
88 // This is foo $0 84 // This is foo $0
89 fn foo() {} 85 fn foo() {}
90 "#; 86 "#;
91 let resolve = true; 87 let assists = get_assists(ra_fixture, AssistResolveStrategy::All);
92
93 let assists = get_assists(ra_fixture, resolve);
94 88
95 assert_eq!(0, assists.len()); 89 assert_eq!(0, assists.len());
96 } 90 }
97 91
98 #[test] 92 #[test]
99 fn resolve_edits_true() { 93 fn resolve_edits_true() {
100 let resolve = true;
101 let assists = get_assists( 94 let assists = get_assists(
102 r#" 95 r#"
103 //- /lib.rs 96 //- /lib.rs
@@ -109,7 +102,7 @@ mod tests {
109 //- /bar.rs 102 //- /bar.rs
110 fn bar() { 2 } 103 fn bar() { 2 }
111 "#, 104 "#,
112 resolve, 105 AssistResolveStrategy::All,
113 ); 106 );
114 107
115 assert_eq!(2, assists.len()); 108 assert_eq!(2, assists.len());
@@ -200,7 +193,6 @@ mod tests {
200 193
201 #[test] 194 #[test]
202 fn resolve_edits_false() { 195 fn resolve_edits_false() {
203 let resolve = false;
204 let assists = get_assists( 196 let assists = get_assists(
205 r#" 197 r#"
206 //- /lib.rs 198 //- /lib.rs
@@ -212,7 +204,7 @@ mod tests {
212 //- /bar.rs 204 //- /bar.rs
213 fn bar() { 2 } 205 fn bar() { 2 }
214 "#, 206 "#,
215 resolve, 207 AssistResolveStrategy::None,
216 ); 208 );
217 209
218 assert_eq!(2, assists.len()); 210 assert_eq!(2, assists.len());
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 638f42c2f..8d83ba206 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
@@ -142,6 +142,7 @@ It is beyond me why you'd use these when you got ///
142```rust 142```rust
143</span><span class="keyword injected">let</span><span class="none injected"> </span><span class="punctuation injected">_</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="function injected">example</span><span class="parenthesis injected">(</span><span class="operator injected">&</span><span class="bracket injected">[</span><span class="numeric_literal injected">1</span><span class="comma injected">,</span><span class="none injected"> </span><span class="numeric_literal injected">2</span><span class="comma injected">,</span><span class="none injected"> </span><span class="numeric_literal injected">3</span><span class="bracket injected">]</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span><span class="comment documentation"> 143</span><span class="keyword injected">let</span><span class="none injected"> </span><span class="punctuation injected">_</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="function injected">example</span><span class="parenthesis injected">(</span><span class="operator injected">&</span><span class="bracket injected">[</span><span class="numeric_literal injected">1</span><span class="comma injected">,</span><span class="none injected"> </span><span class="numeric_literal injected">2</span><span class="comma injected">,</span><span class="none injected"> </span><span class="numeric_literal injected">3</span><span class="bracket injected">]</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span><span class="comment documentation">
144``` 144```
145</span><span class="function documentation injected intra_doc_link">[`block_comments2`]</span><span class="comment documentation"> tests these with indentation
145 */</span> 146 */</span>
146<span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration">block_comments</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span> 147<span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration">block_comments</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
147 148
@@ -150,5 +151,6 @@ It is beyond me why you'd use these when you got ///
150 ```rust 151 ```rust
151</span><span class="comment documentation"> </span><span class="none injected"> </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="punctuation injected">_</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="function injected">example</span><span class="parenthesis injected">(</span><span class="operator injected">&</span><span class="bracket injected">[</span><span class="numeric_literal injected">1</span><span class="comma injected">,</span><span class="none injected"> </span><span class="numeric_literal injected">2</span><span class="comma injected">,</span><span class="none injected"> </span><span class="numeric_literal injected">3</span><span class="bracket injected">]</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span><span class="comment documentation"> 152</span><span class="comment documentation"> </span><span class="none injected"> </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="punctuation injected">_</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="function injected">example</span><span class="parenthesis injected">(</span><span class="operator injected">&</span><span class="bracket injected">[</span><span class="numeric_literal injected">1</span><span class="comma injected">,</span><span class="none injected"> </span><span class="numeric_literal injected">2</span><span class="comma injected">,</span><span class="none injected"> </span><span class="numeric_literal injected">3</span><span class="bracket injected">]</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span><span class="comment documentation">
152 ``` 153 ```
154 </span><span class="function documentation injected intra_doc_link">[`block_comments`]</span><span class="comment documentation"> tests these without indentation
153*/</span> 155*/</span>
154<span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration">block_comments2</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span></code></pre> \ No newline at end of file 156<span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration">block_comments2</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span></code></pre> \ No newline at end of file
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs
index 17cc6334b..b6e952b08 100644
--- a/crates/ide/src/syntax_highlighting/tests.rs
+++ b/crates/ide/src/syntax_highlighting/tests.rs
@@ -618,6 +618,7 @@ It is beyond me why you'd use these when you got ///
618```rust 618```rust
619let _ = example(&[1, 2, 3]); 619let _ = example(&[1, 2, 3]);
620``` 620```
621[`block_comments2`] tests these with indentation
621 */ 622 */
622pub fn block_comments() {} 623pub fn block_comments() {}
623 624
@@ -626,6 +627,7 @@ pub fn block_comments() {}
626 ```rust 627 ```rust
627 let _ = example(&[1, 2, 3]); 628 let _ = example(&[1, 2, 3]);
628 ``` 629 ```
630 [`block_comments`] tests these without indentation
629*/ 631*/
630pub fn block_comments2() {} 632pub fn block_comments2() {}
631"# 633"#
diff --git a/crates/ide/src/typing.rs b/crates/ide/src/typing.rs
index 82c732390..4ad49eca0 100644
--- a/crates/ide/src/typing.rs
+++ b/crates/ide/src/typing.rs
@@ -88,7 +88,7 @@ fn on_char_typed_inner(
88} 88}
89 89
90/// Inserts a closing `}` when the user types an opening `{`, wrapping an existing expression in a 90/// Inserts a closing `}` when the user types an opening `{`, wrapping an existing expression in a
91/// block. 91/// block, or a part of a `use` item.
92fn on_opening_brace_typed(file: &Parse<SourceFile>, offset: TextSize) -> Option<TextEdit> { 92fn on_opening_brace_typed(file: &Parse<SourceFile>, offset: TextSize) -> Option<TextEdit> {
93 if !stdx::always!(file.tree().syntax().text().char_at(offset) == Some('{')) { 93 if !stdx::always!(file.tree().syntax().text().char_at(offset) == Some('{')) {
94 return None; 94 return None;
@@ -99,30 +99,59 @@ fn on_opening_brace_typed(file: &Parse<SourceFile>, offset: TextSize) -> Option<
99 // Remove the `{` to get a better parse tree, and reparse 99 // Remove the `{` to get a better parse tree, and reparse
100 let file = file.reparse(&Indel::delete(brace_token.text_range())); 100 let file = file.reparse(&Indel::delete(brace_token.text_range()));
101 101
102 let mut expr: ast::Expr = find_node_at_offset(file.tree().syntax(), offset)?; 102 if let Some(edit) = brace_expr(&file.tree(), offset) {
103 if expr.syntax().text_range().start() != offset { 103 return Some(edit);
104 return None;
105 } 104 }
106 105
107 // Enclose the outermost expression starting at `offset` 106 if let Some(edit) = brace_use_path(&file.tree(), offset) {
108 while let Some(parent) = expr.syntax().parent() { 107 return Some(edit);
109 if parent.text_range().start() != expr.syntax().text_range().start() { 108 }
110 break;
111 }
112 109
113 match ast::Expr::cast(parent) { 110 return None;
114 Some(parent) => expr = parent, 111
115 None => break, 112 fn brace_use_path(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
113 let segment: ast::PathSegment = find_node_at_offset(file.syntax(), offset)?;
114 if segment.syntax().text_range().start() != offset {
115 return None;
116 } 116 }
117 }
118 117
119 // If it's a statement in a block, we don't know how many statements should be included 118 let tree: ast::UseTree = find_node_at_offset(file.syntax(), offset)?;
120 if ast::ExprStmt::can_cast(expr.syntax().parent()?.kind()) { 119
121 return None; 120 Some(TextEdit::insert(
121 tree.syntax().text_range().end() + TextSize::of("{"),
122 "}".to_string(),
123 ))
122 } 124 }
123 125
124 // Insert `}` right after the expression. 126 fn brace_expr(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
125 Some(TextEdit::insert(expr.syntax().text_range().end() + TextSize::of("{"), "}".to_string())) 127 let mut expr: ast::Expr = find_node_at_offset(file.syntax(), offset)?;
128 if expr.syntax().text_range().start() != offset {
129 return None;
130 }
131
132 // Enclose the outermost expression starting at `offset`
133 while let Some(parent) = expr.syntax().parent() {
134 if parent.text_range().start() != expr.syntax().text_range().start() {
135 break;
136 }
137
138 match ast::Expr::cast(parent) {
139 Some(parent) => expr = parent,
140 None => break,
141 }
142 }
143
144 // If it's a statement in a block, we don't know how many statements should be included
145 if ast::ExprStmt::can_cast(expr.syntax().parent()?.kind()) {
146 return None;
147 }
148
149 // Insert `}` right after the expression.
150 Some(TextEdit::insert(
151 expr.syntax().text_range().end() + TextSize::of("{"),
152 "}".to_string(),
153 ))
154 }
126} 155}
127 156
128/// Returns an edit which should be applied after `=` was typed. Primarily, 157/// Returns an edit which should be applied after `=` was typed. Primarily,
@@ -440,7 +469,7 @@ fn foo() -> { 92 }
440 } 469 }
441 470
442 #[test] 471 #[test]
443 fn adds_closing_brace() { 472 fn adds_closing_brace_for_expr() {
444 type_char( 473 type_char(
445 '{', 474 '{',
446 r#" 475 r#"
@@ -519,4 +548,87 @@ fn f() {
519 "#, 548 "#,
520 ); 549 );
521 } 550 }
551
552 #[test]
553 fn adds_closing_brace_for_use_tree() {
554 type_char(
555 '{',
556 r#"
557use some::$0Path;
558 "#,
559 r#"
560use some::{Path};
561 "#,
562 );
563 type_char(
564 '{',
565 r#"
566use some::{Path, $0Other};
567 "#,
568 r#"
569use some::{Path, {Other}};
570 "#,
571 );
572 type_char(
573 '{',
574 r#"
575use some::{$0Path, Other};
576 "#,
577 r#"
578use some::{{Path}, Other};
579 "#,
580 );
581 type_char(
582 '{',
583 r#"
584use some::path::$0to::Item;
585 "#,
586 r#"
587use some::path::{to::Item};
588 "#,
589 );
590 type_char(
591 '{',
592 r#"
593use some::$0path::to::Item;
594 "#,
595 r#"
596use some::{path::to::Item};
597 "#,
598 );
599 type_char(
600 '{',
601 r#"
602use $0some::path::to::Item;
603 "#,
604 r#"
605use {some::path::to::Item};
606 "#,
607 );
608 type_char(
609 '{',
610 r#"
611use some::path::$0to::{Item};
612 "#,
613 r#"
614use some::path::{to::{Item}};
615 "#,
616 );
617 type_char(
618 '{',
619 r#"
620use $0Thing as _;
621 "#,
622 r#"
623use {Thing as _};
624 "#,
625 );
626
627 type_char_noop(
628 '{',
629 r#"
630use some::pa$0th::to::Item;
631 "#,
632 );
633 }
522} 634}
diff --git a/crates/ide/src/typing/on_enter.rs b/crates/ide/src/typing/on_enter.rs
index 7d2db201a..81c4d95b1 100644
--- a/crates/ide/src/typing/on_enter.rs
+++ b/crates/ide/src/typing/on_enter.rs
@@ -54,6 +54,14 @@ pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<Text
54 cov_mark::hit!(indent_block_contents); 54 cov_mark::hit!(indent_block_contents);
55 return Some(edit); 55 return Some(edit);
56 } 56 }
57
58 // Typing enter after the `{` of a use tree list.
59 if let Some(edit) = find_node_at_offset(file.syntax(), position.offset - TextSize::of('{'))
60 .and_then(|list| on_enter_in_use_tree_list(list, position))
61 {
62 cov_mark::hit!(indent_block_contents);
63 return Some(edit);
64 }
57 } 65 }
58 66
59 None 67 None
@@ -111,6 +119,21 @@ fn on_enter_in_block(block: ast::BlockExpr, position: FilePosition) -> Option<Te
111 Some(edit) 119 Some(edit)
112} 120}
113 121
122fn on_enter_in_use_tree_list(list: ast::UseTreeList, position: FilePosition) -> Option<TextEdit> {
123 if list.syntax().text().contains_char('\n') {
124 return None;
125 }
126
127 let indent = IndentLevel::from_node(list.syntax());
128 let mut edit = TextEdit::insert(position.offset, format!("\n{}$0", indent + 1));
129 edit.union(TextEdit::insert(
130 list.r_curly_token()?.text_range().start(),
131 format!("\n{}", indent),
132 ))
133 .ok()?;
134 Some(edit)
135}
136
114fn block_contents(block: &ast::BlockExpr) -> Option<SyntaxNode> { 137fn block_contents(block: &ast::BlockExpr) -> Option<SyntaxNode> {
115 let mut node = block.tail_expr().map(|e| e.syntax().clone()); 138 let mut node = block.tail_expr().map(|e| e.syntax().clone());
116 139
@@ -484,4 +507,96 @@ fn f() {$0
484 "#, 507 "#,
485 ); 508 );
486 } 509 }
510
511 #[test]
512 fn indents_use_tree_list() {
513 do_check(
514 r#"
515use crate::{$0};
516 "#,
517 r#"
518use crate::{
519 $0
520};
521 "#,
522 );
523 do_check(
524 r#"
525use crate::{$0Object, path::to::OtherThing};
526 "#,
527 r#"
528use crate::{
529 $0Object, path::to::OtherThing
530};
531 "#,
532 );
533 do_check(
534 r#"
535use {crate::{$0Object, path::to::OtherThing}};
536 "#,
537 r#"
538use {crate::{
539 $0Object, path::to::OtherThing
540}};
541 "#,
542 );
543 do_check(
544 r#"
545use {
546 crate::{$0Object, path::to::OtherThing}
547};
548 "#,
549 r#"
550use {
551 crate::{
552 $0Object, path::to::OtherThing
553 }
554};
555 "#,
556 );
557 }
558
559 #[test]
560 fn does_not_indent_use_tree_list_when_not_at_curly_brace() {
561 do_check_noop(
562 r#"
563use path::{Thing$0};
564 "#,
565 );
566 }
567
568 #[test]
569 fn does_not_indent_use_tree_list_without_curly_braces() {
570 do_check_noop(
571 r#"
572use path::Thing$0;
573 "#,
574 );
575 do_check_noop(
576 r#"
577use path::$0Thing;
578 "#,
579 );
580 do_check_noop(
581 r#"
582use path::Thing$0};
583 "#,
584 );
585 do_check_noop(
586 r#"
587use path::{$0Thing;
588 "#,
589 );
590 }
591
592 #[test]
593 fn does_not_indent_multiline_use_tree_list() {
594 do_check_noop(
595 r#"
596use path::{$0
597 Thing
598};
599 "#,
600 );
601 }
487} 602}
diff --git a/crates/ide/src/view_crate_graph.rs b/crates/ide/src/view_crate_graph.rs
new file mode 100644
index 000000000..5e4ba881e
--- /dev/null
+++ b/crates/ide/src/view_crate_graph.rs
@@ -0,0 +1,111 @@
1use std::{
2 error::Error,
3 io::{Read, Write},
4 process::{Command, Stdio},
5 sync::Arc,
6};
7
8use dot::{Id, LabelText};
9use ide_db::{
10 base_db::{CrateGraph, CrateId, Dependency, SourceDatabase, SourceDatabaseExt},
11 RootDatabase,
12};
13use rustc_hash::FxHashSet;
14
15// Feature: View Crate Graph
16//
17// Renders the currently loaded crate graph as an SVG graphic. Requires the `dot` tool, which
18// is part of graphviz, to be installed.
19//
20// Only workspace crates are included, no crates.io dependencies or sysroot crates.
21//
22// |===
23// | Editor | Action Name
24//
25// | VS Code | **Rust Analyzer: View Crate Graph**
26// |===
27pub(crate) fn view_crate_graph(db: &RootDatabase) -> Result<String, String> {
28 let crate_graph = db.crate_graph();
29 let crates_to_render = crate_graph
30 .iter()
31 .filter(|krate| {
32 // Only render workspace crates
33 let root_id = db.file_source_root(crate_graph[*krate].root_file_id);
34 !db.source_root(root_id).is_library
35 })
36 .collect();
37 let graph = DotCrateGraph { graph: crate_graph, crates_to_render };
38
39 let mut dot = Vec::new();
40 dot::render(&graph, &mut dot).unwrap();
41
42 render_svg(&dot).map_err(|e| e.to_string())
43}
44
45fn render_svg(dot: &[u8]) -> Result<String, Box<dyn Error>> {
46 // We shell out to `dot` to render to SVG, as there does not seem to be a pure-Rust renderer.
47 let child = Command::new("dot")
48 .arg("-Tsvg")
49 .stdin(Stdio::piped())
50 .stdout(Stdio::piped())
51 .spawn()
52 .map_err(|err| format!("failed to spawn `dot`: {}", err))?;
53 child.stdin.unwrap().write_all(&dot)?;
54
55 let mut svg = String::new();
56 child.stdout.unwrap().read_to_string(&mut svg)?;
57 Ok(svg)
58}
59
60struct DotCrateGraph {
61 graph: Arc<CrateGraph>,
62 crates_to_render: FxHashSet<CrateId>,
63}
64
65type Edge<'a> = (CrateId, &'a Dependency);
66
67impl<'a> dot::GraphWalk<'a, CrateId, Edge<'a>> for DotCrateGraph {
68 fn nodes(&'a self) -> dot::Nodes<'a, CrateId> {
69 self.crates_to_render.iter().copied().collect()
70 }
71
72 fn edges(&'a self) -> dot::Edges<'a, Edge<'a>> {
73 self.crates_to_render
74 .iter()
75 .flat_map(|krate| {
76 self.graph[*krate]
77 .dependencies
78 .iter()
79 .filter(|dep| self.crates_to_render.contains(&dep.crate_id))
80 .map(move |dep| (*krate, dep))
81 })
82 .collect()
83 }
84
85 fn source(&'a self, edge: &Edge<'a>) -> CrateId {
86 edge.0
87 }
88
89 fn target(&'a self, edge: &Edge<'a>) -> CrateId {
90 edge.1.crate_id
91 }
92}
93
94impl<'a> dot::Labeller<'a, CrateId, Edge<'a>> for DotCrateGraph {
95 fn graph_id(&'a self) -> Id<'a> {
96 Id::new("rust_analyzer_crate_graph").unwrap()
97 }
98
99 fn node_id(&'a self, n: &CrateId) -> Id<'a> {
100 Id::new(format!("_{}", n.0)).unwrap()
101 }
102
103 fn node_shape(&'a self, _node: &CrateId) -> Option<LabelText<'a>> {
104 Some(LabelText::LabelStr("box".into()))
105 }
106
107 fn node_label(&'a self, n: &CrateId) -> LabelText<'a> {
108 let name = self.graph[*n].display_name.as_ref().map_or("(unnamed crate)", |name| &*name);
109 LabelText::LabelStr(name.into())
110 }
111}