aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide')
-rw-r--r--crates/ra_ide/src/completion.rs78
-rw-r--r--crates/ra_ide/src/diagnostics.rs75
-rw-r--r--crates/ra_ide/src/display/function_signature.rs13
-rw-r--r--crates/ra_ide/src/display/navigation_target.rs66
-rw-r--r--crates/ra_ide/src/goto_definition.rs26
-rw-r--r--crates/ra_ide/src/hover.rs411
-rw-r--r--crates/ra_ide/src/inlay_hints.rs7
-rw-r--r--crates/ra_ide/src/lib.rs43
-rw-r--r--crates/ra_ide/src/references/rename.rs6
-rw-r--r--crates/ra_ide/src/runnables.rs342
-rw-r--r--crates/ra_ide/src/snapshots/highlight_doctest.html71
-rw-r--r--crates/ra_ide/src/snapshots/highlight_injection.html2
-rw-r--r--crates/ra_ide/src/snapshots/highlight_strings.html5
-rw-r--r--crates/ra_ide/src/snapshots/highlight_unsafe.html49
-rw-r--r--crates/ra_ide/src/snapshots/highlighting.html6
-rw-r--r--crates/ra_ide/src/snapshots/rainbow_highlighting.html2
-rw-r--r--crates/ra_ide/src/syntax_highlighting.rs148
-rw-r--r--crates/ra_ide/src/syntax_highlighting/html.rs2
-rw-r--r--crates/ra_ide/src/syntax_highlighting/injection.rs168
-rw-r--r--crates/ra_ide/src/syntax_highlighting/tags.rs8
-rw-r--r--crates/ra_ide/src/syntax_highlighting/tests.rs123
-rw-r--r--crates/ra_ide/src/typing.rs44
22 files changed, 1438 insertions, 257 deletions
diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs
index fa37b6955..e1fcf379d 100644
--- a/crates/ra_ide/src/completion.rs
+++ b/crates/ra_ide/src/completion.rs
@@ -126,3 +126,81 @@ pub(crate) fn completions(
126 126
127 Some(acc) 127 Some(acc)
128} 128}
129
130#[cfg(test)]
131mod tests {
132 use crate::completion::completion_config::CompletionConfig;
133 use crate::mock_analysis::analysis_and_position;
134
135 struct DetailAndDocumentation<'a> {
136 detail: &'a str,
137 documentation: &'a str,
138 }
139
140 fn check_detail_and_documentation(fixture: &str, expected: DetailAndDocumentation) {
141 let (analysis, position) = analysis_and_position(fixture);
142 let config = CompletionConfig::default();
143 let completions = analysis.completions(&config, position).unwrap().unwrap();
144 for item in completions {
145 if item.detail() == Some(expected.detail) {
146 let opt = item.documentation();
147 let doc = opt.as_ref().map(|it| it.as_str());
148 assert_eq!(doc, Some(expected.documentation));
149 return;
150 }
151 }
152 panic!("completion detail not found: {}", expected.detail)
153 }
154
155 #[test]
156 fn test_completion_detail_from_macro_generated_struct_fn_doc_attr() {
157 check_detail_and_documentation(
158 r#"
159 //- /lib.rs
160 macro_rules! bar {
161 () => {
162 struct Bar;
163 impl Bar {
164 #[doc = "Do the foo"]
165 fn foo(&self) {}
166 }
167 }
168 }
169
170 bar!();
171
172 fn foo() {
173 let bar = Bar;
174 bar.fo<|>;
175 }
176 "#,
177 DetailAndDocumentation { detail: "fn foo(&self)", documentation: "Do the foo" },
178 );
179 }
180
181 #[test]
182 fn test_completion_detail_from_macro_generated_struct_fn_doc_comment() {
183 check_detail_and_documentation(
184 r#"
185 //- /lib.rs
186 macro_rules! bar {
187 () => {
188 struct Bar;
189 impl Bar {
190 /// Do the foo
191 fn foo(&self) {}
192 }
193 }
194 }
195
196 bar!();
197
198 fn foo() {
199 let bar = Bar;
200 bar.fo<|>;
201 }
202 "#,
203 DetailAndDocumentation { detail: "fn foo(&self)", documentation: " Do the foo" },
204 );
205 }
206}
diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs
index 15dc50cf1..e1bfd72f9 100644
--- a/crates/ra_ide/src/diagnostics.rs
+++ b/crates/ra_ide/src/diagnostics.rs
@@ -21,7 +21,7 @@ use ra_syntax::{
21}; 21};
22use ra_text_edit::{TextEdit, TextEditBuilder}; 22use ra_text_edit::{TextEdit, TextEditBuilder};
23 23
24use crate::{Diagnostic, FileId, FileSystemEdit, Fix, SourceChange, SourceFileEdit}; 24use crate::{Diagnostic, FileId, FileSystemEdit, Fix, SourceFileEdit};
25 25
26#[derive(Debug, Copy, Clone)] 26#[derive(Debug, Copy, Clone)]
27pub enum Severity { 27pub enum Severity {
@@ -115,7 +115,7 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
115 let node = d.ast(db); 115 let node = d.ast(db);
116 let replacement = format!("Ok({})", node.syntax()); 116 let replacement = format!("Ok({})", node.syntax());
117 let edit = TextEdit::replace(node.syntax().text_range(), replacement); 117 let edit = TextEdit::replace(node.syntax().text_range(), replacement);
118 let source_change = SourceChange::source_file_edit_from(file_id, edit); 118 let source_change = SourceFileEdit { file_id, edit }.into();
119 let fix = Fix::new("Wrap with ok", source_change); 119 let fix = Fix::new("Wrap with ok", source_change);
120 res.borrow_mut().push(Diagnostic { 120 res.borrow_mut().push(Diagnostic {
121 range: sema.diagnostics_range(d).range, 121 range: sema.diagnostics_range(d).range,
@@ -187,7 +187,8 @@ fn check_struct_shorthand_initialization(
187 if let (Some(name_ref), Some(expr)) = (record_field.name_ref(), record_field.expr()) { 187 if let (Some(name_ref), Some(expr)) = (record_field.name_ref(), record_field.expr()) {
188 let field_name = name_ref.syntax().text().to_string(); 188 let field_name = name_ref.syntax().text().to_string();
189 let field_expr = expr.syntax().text().to_string(); 189 let field_expr = expr.syntax().text().to_string();
190 if field_name == field_expr { 190 let field_name_is_tup_index = name_ref.as_tuple_field().is_some();
191 if field_name == field_expr && !field_name_is_tup_index {
191 let mut edit_builder = TextEditBuilder::default(); 192 let mut edit_builder = TextEditBuilder::default();
192 edit_builder.delete(record_field.syntax().text_range()); 193 edit_builder.delete(record_field.syntax().text_range());
193 edit_builder.insert(record_field.syntax().text_range().start(), field_name); 194 edit_builder.insert(record_field.syntax().text_range().start(), field_name);
@@ -321,29 +322,26 @@ mod tests {
321 fn test_wrap_return_type() { 322 fn test_wrap_return_type() {
322 let before = r#" 323 let before = r#"
323 //- /main.rs 324 //- /main.rs
324 use std::{string::String, result::Result::{self, Ok, Err}}; 325 use core::result::Result::{self, Ok, Err};
325 326
326 fn div(x: i32, y: i32) -> Result<i32, String> { 327 fn div(x: i32, y: i32) -> Result<i32, ()> {
327 if y == 0 { 328 if y == 0 {
328 return Err("div by zero".into()); 329 return Err(());
329 } 330 }
330 x / y<|> 331 x / y<|>
331 } 332 }
332 333
333 //- /std/lib.rs 334 //- /core/lib.rs
334 pub mod string {
335 pub struct String { }
336 }
337 pub mod result { 335 pub mod result {
338 pub enum Result<T, E> { Ok(T), Err(E) } 336 pub enum Result<T, E> { Ok(T), Err(E) }
339 } 337 }
340 "#; 338 "#;
341 let after = r#" 339 let after = r#"
342 use std::{string::String, result::Result::{self, Ok, Err}}; 340 use core::result::Result::{self, Ok, Err};
343 341
344 fn div(x: i32, y: i32) -> Result<i32, String> { 342 fn div(x: i32, y: i32) -> Result<i32, ()> {
345 if y == 0 { 343 if y == 0 {
346 return Err("div by zero".into()); 344 return Err(());
347 } 345 }
348 Ok(x / y) 346 Ok(x / y)
349 } 347 }
@@ -355,7 +353,7 @@ mod tests {
355 fn test_wrap_return_type_handles_generic_functions() { 353 fn test_wrap_return_type_handles_generic_functions() {
356 let before = r#" 354 let before = r#"
357 //- /main.rs 355 //- /main.rs
358 use std::result::Result::{self, Ok, Err}; 356 use core::result::Result::{self, Ok, Err};
359 357
360 fn div<T>(x: T) -> Result<T, i32> { 358 fn div<T>(x: T) -> Result<T, i32> {
361 if x == 0 { 359 if x == 0 {
@@ -364,13 +362,13 @@ mod tests {
364 <|>x 362 <|>x
365 } 363 }
366 364
367 //- /std/lib.rs 365 //- /core/lib.rs
368 pub mod result { 366 pub mod result {
369 pub enum Result<T, E> { Ok(T), Err(E) } 367 pub enum Result<T, E> { Ok(T), Err(E) }
370 } 368 }
371 "#; 369 "#;
372 let after = r#" 370 let after = r#"
373 use std::result::Result::{self, Ok, Err}; 371 use core::result::Result::{self, Ok, Err};
374 372
375 fn div<T>(x: T) -> Result<T, i32> { 373 fn div<T>(x: T) -> Result<T, i32> {
376 if x == 0 { 374 if x == 0 {
@@ -386,32 +384,29 @@ mod tests {
386 fn test_wrap_return_type_handles_type_aliases() { 384 fn test_wrap_return_type_handles_type_aliases() {
387 let before = r#" 385 let before = r#"
388 //- /main.rs 386 //- /main.rs
389 use std::{string::String, result::Result::{self, Ok, Err}}; 387 use core::result::Result::{self, Ok, Err};
390 388
391 type MyResult<T> = Result<T, String>; 389 type MyResult<T> = Result<T, ()>;
392 390
393 fn div(x: i32, y: i32) -> MyResult<i32> { 391 fn div(x: i32, y: i32) -> MyResult<i32> {
394 if y == 0 { 392 if y == 0 {
395 return Err("div by zero".into()); 393 return Err(());
396 } 394 }
397 x <|>/ y 395 x <|>/ y
398 } 396 }
399 397
400 //- /std/lib.rs 398 //- /core/lib.rs
401 pub mod string {
402 pub struct String { }
403 }
404 pub mod result { 399 pub mod result {
405 pub enum Result<T, E> { Ok(T), Err(E) } 400 pub enum Result<T, E> { Ok(T), Err(E) }
406 } 401 }
407 "#; 402 "#;
408 let after = r#" 403 let after = r#"
409 use std::{string::String, result::Result::{self, Ok, Err}}; 404 use core::result::Result::{self, Ok, Err};
410 405
411 type MyResult<T> = Result<T, String>; 406 type MyResult<T> = Result<T, ()>;
412 fn div(x: i32, y: i32) -> MyResult<i32> { 407 fn div(x: i32, y: i32) -> MyResult<i32> {
413 if y == 0 { 408 if y == 0 {
414 return Err("div by zero".into()); 409 return Err(());
415 } 410 }
416 Ok(x / y) 411 Ok(x / y)
417 } 412 }
@@ -423,16 +418,13 @@ mod tests {
423 fn test_wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type() { 418 fn test_wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type() {
424 let content = r#" 419 let content = r#"
425 //- /main.rs 420 //- /main.rs
426 use std::{string::String, result::Result::{self, Ok, Err}}; 421 use core::result::Result::{self, Ok, Err};
427 422
428 fn foo() -> Result<String, i32> { 423 fn foo() -> Result<(), i32> {
429 0<|> 424 0<|>
430 } 425 }
431 426
432 //- /std/lib.rs 427 //- /core/lib.rs
433 pub mod string {
434 pub struct String { }
435 }
436 pub mod result { 428 pub mod result {
437 pub enum Result<T, E> { Ok(T), Err(E) } 429 pub enum Result<T, E> { Ok(T), Err(E) }
438 } 430 }
@@ -444,7 +436,7 @@ mod tests {
444 fn test_wrap_return_type_not_applicable_when_return_type_is_not_result() { 436 fn test_wrap_return_type_not_applicable_when_return_type_is_not_result() {
445 let content = r#" 437 let content = r#"
446 //- /main.rs 438 //- /main.rs
447 use std::{string::String, result::Result::{self, Ok, Err}}; 439 use core::result::Result::{self, Ok, Err};
448 440
449 enum SomeOtherEnum { 441 enum SomeOtherEnum {
450 Ok(i32), 442 Ok(i32),
@@ -455,10 +447,7 @@ mod tests {
455 0<|> 447 0<|>
456 } 448 }
457 449
458 //- /std/lib.rs 450 //- /core/lib.rs
459 pub mod string {
460 pub struct String { }
461 }
462 pub mod result { 451 pub mod result {
463 pub enum Result<T, E> { Ok(T), Err(E) } 452 pub enum Result<T, E> { Ok(T), Err(E) }
464 } 453 }
@@ -731,6 +720,18 @@ mod tests {
731 "#, 720 "#,
732 check_struct_shorthand_initialization, 721 check_struct_shorthand_initialization,
733 ); 722 );
723 check_not_applicable(
724 r#"
725 struct A(usize);
726
727 fn main() {
728 A {
729 0: 0
730 }
731 }
732 "#,
733 check_struct_shorthand_initialization,
734 );
734 735
735 check_apply( 736 check_apply(
736 r#" 737 r#"
diff --git a/crates/ra_ide/src/display/function_signature.rs b/crates/ra_ide/src/display/function_signature.rs
index 9572debd8..ca8a6a650 100644
--- a/crates/ra_ide/src/display/function_signature.rs
+++ b/crates/ra_ide/src/display/function_signature.rs
@@ -10,7 +10,7 @@ use std::{
10use hir::{Docs, Documentation, HasSource, HirDisplay}; 10use hir::{Docs, Documentation, HasSource, HirDisplay};
11use ra_ide_db::RootDatabase; 11use ra_ide_db::RootDatabase;
12use ra_syntax::ast::{self, AstNode, NameOwner, VisibilityOwner}; 12use ra_syntax::ast::{self, AstNode, NameOwner, VisibilityOwner};
13use stdx::SepBy; 13use stdx::{split1, SepBy};
14 14
15use crate::display::{generic_parameters, where_predicates}; 15use crate::display::{generic_parameters, where_predicates};
16 16
@@ -207,7 +207,16 @@ impl From<&'_ ast::FnDef> for FunctionSignature {
207 res.push(raw_param); 207 res.push(raw_param);
208 } 208 }
209 209
210 res.extend(param_list.params().map(|param| param.syntax().text().to_string())); 210 // macro-generated functions are missing whitespace
211 fn fmt_param(param: ast::Param) -> String {
212 let text = param.syntax().text().to_string();
213 match split1(&text, ':') {
214 Some((left, right)) => format!("{}: {}", left.trim(), right.trim()),
215 _ => text,
216 }
217 }
218
219 res.extend(param_list.params().map(fmt_param));
211 res_types.extend(param_list.params().map(|param| { 220 res_types.extend(param_list.params().map(|param| {
212 let param_text = param.syntax().text().to_string(); 221 let param_text = param.syntax().text().to_string();
213 match param_text.split(':').nth(1).and_then(|it| it.get(1..)) { 222 match param_text.split(':').nth(1).and_then(|it| it.get(1..)) {
diff --git a/crates/ra_ide/src/display/navigation_target.rs b/crates/ra_ide/src/display/navigation_target.rs
index 5da28edd2..c7bb1e69f 100644
--- a/crates/ra_ide/src/display/navigation_target.rs
+++ b/crates/ra_ide/src/display/navigation_target.rs
@@ -92,15 +92,16 @@ impl NavigationTarget {
92 let name = module.name(db).map(|it| it.to_string().into()).unwrap_or_default(); 92 let name = module.name(db).map(|it| it.to_string().into()).unwrap_or_default();
93 if let Some(src) = module.declaration_source(db) { 93 if let Some(src) = module.declaration_source(db) {
94 let frange = original_range(db, src.as_ref().map(|it| it.syntax())); 94 let frange = original_range(db, src.as_ref().map(|it| it.syntax()));
95 return NavigationTarget::from_syntax( 95 let mut res = NavigationTarget::from_syntax(
96 frange.file_id, 96 frange.file_id,
97 name, 97 name,
98 None, 98 None,
99 frange.range, 99 frange.range,
100 src.value.syntax().kind(), 100 src.value.syntax().kind(),
101 src.value.doc_comment_text(),
102 src.value.short_label(),
103 ); 101 );
102 res.docs = src.value.doc_comment_text();
103 res.description = src.value.short_label();
104 return res;
104 } 105 }
105 module.to_nav(db) 106 module.to_nav(db)
106 } 107 }
@@ -130,11 +131,9 @@ impl NavigationTarget {
130 } 131 }
131 132
132 /// Allows `NavigationTarget` to be created from a `NameOwner` 133 /// Allows `NavigationTarget` to be created from a `NameOwner`
133 fn from_named( 134 pub(crate) fn from_named(
134 db: &RootDatabase, 135 db: &RootDatabase,
135 node: InFile<&dyn ast::NameOwner>, 136 node: InFile<&dyn ast::NameOwner>,
136 docs: Option<String>,
137 description: Option<String>,
138 ) -> NavigationTarget { 137 ) -> NavigationTarget {
139 //FIXME: use `_` instead of empty string 138 //FIXME: use `_` instead of empty string
140 let name = node.value.name().map(|it| it.text().clone()).unwrap_or_default(); 139 let name = node.value.name().map(|it| it.text().clone()).unwrap_or_default();
@@ -148,8 +147,6 @@ impl NavigationTarget {
148 focus_range, 147 focus_range,
149 frange.range, 148 frange.range,
150 node.value.syntax().kind(), 149 node.value.syntax().kind(),
151 docs,
152 description,
153 ) 150 )
154 } 151 }
155 152
@@ -159,8 +156,6 @@ impl NavigationTarget {
159 focus_range: Option<TextRange>, 156 focus_range: Option<TextRange>,
160 full_range: TextRange, 157 full_range: TextRange,
161 kind: SyntaxKind, 158 kind: SyntaxKind,
162 docs: Option<String>,
163 description: Option<String>,
164 ) -> NavigationTarget { 159 ) -> NavigationTarget {
165 NavigationTarget { 160 NavigationTarget {
166 file_id, 161 file_id,
@@ -169,8 +164,8 @@ impl NavigationTarget {
169 full_range, 164 full_range,
170 focus_range, 165 focus_range,
171 container_name: None, 166 container_name: None,
172 description, 167 description: None,
173 docs, 168 docs: None,
174 } 169 }
175 } 170 }
176} 171}
@@ -238,12 +233,11 @@ where
238{ 233{
239 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { 234 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
240 let src = self.source(db); 235 let src = self.source(db);
241 NavigationTarget::from_named( 236 let mut res =
242 db, 237 NavigationTarget::from_named(db, src.as_ref().map(|it| it as &dyn ast::NameOwner));
243 src.as_ref().map(|it| it as &dyn ast::NameOwner), 238 res.docs = src.value.doc_comment_text();
244 src.value.doc_comment_text(), 239 res.description = src.value.short_label();
245 src.value.short_label(), 240 res
246 )
247 } 241 }
248} 242}
249 243
@@ -258,15 +252,7 @@ impl ToNav for hir::Module {
258 } 252 }
259 }; 253 };
260 let frange = original_range(db, src.with_value(syntax)); 254 let frange = original_range(db, src.with_value(syntax));
261 NavigationTarget::from_syntax( 255 NavigationTarget::from_syntax(frange.file_id, name, focus, frange.range, syntax.kind())
262 frange.file_id,
263 name,
264 focus,
265 frange.range,
266 syntax.kind(),
267 None,
268 None,
269 )
270 } 256 }
271} 257}
272 258
@@ -285,8 +271,6 @@ impl ToNav for hir::ImplDef {
285 None, 271 None,
286 frange.range, 272 frange.range,
287 src.value.syntax().kind(), 273 src.value.syntax().kind(),
288 None,
289 None,
290 ) 274 )
291 } 275 }
292} 276}
@@ -296,12 +280,12 @@ impl ToNav for hir::Field {
296 let src = self.source(db); 280 let src = self.source(db);
297 281
298 match &src.value { 282 match &src.value {
299 FieldSource::Named(it) => NavigationTarget::from_named( 283 FieldSource::Named(it) => {
300 db, 284 let mut res = NavigationTarget::from_named(db, src.with_value(it));
301 src.with_value(it), 285 res.docs = it.doc_comment_text();
302 it.doc_comment_text(), 286 res.description = it.short_label();
303 it.short_label(), 287 res
304 ), 288 }
305 FieldSource::Pos(it) => { 289 FieldSource::Pos(it) => {
306 let frange = original_range(db, src.with_value(it.syntax())); 290 let frange = original_range(db, src.with_value(it.syntax()));
307 NavigationTarget::from_syntax( 291 NavigationTarget::from_syntax(
@@ -310,8 +294,6 @@ impl ToNav for hir::Field {
310 None, 294 None,
311 frange.range, 295 frange.range,
312 it.syntax().kind(), 296 it.syntax().kind(),
313 None,
314 None,
315 ) 297 )
316 } 298 }
317 } 299 }
@@ -322,12 +304,10 @@ impl ToNav for hir::MacroDef {
322 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { 304 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
323 let src = self.source(db); 305 let src = self.source(db);
324 log::debug!("nav target {:#?}", src.value.syntax()); 306 log::debug!("nav target {:#?}", src.value.syntax());
325 NavigationTarget::from_named( 307 let mut res =
326 db, 308 NavigationTarget::from_named(db, src.as_ref().map(|it| it as &dyn ast::NameOwner));
327 src.as_ref().map(|it| it as &dyn ast::NameOwner), 309 res.docs = src.value.doc_comment_text();
328 src.value.doc_comment_text(), 310 res
329 None,
330 )
331 } 311 }
332} 312}
333 313
diff --git a/crates/ra_ide/src/goto_definition.rs b/crates/ra_ide/src/goto_definition.rs
index a6c86e99c..0798d2c36 100644
--- a/crates/ra_ide/src/goto_definition.rs
+++ b/crates/ra_ide/src/goto_definition.rs
@@ -1,6 +1,6 @@
1use hir::Semantics; 1use hir::Semantics;
2use ra_ide_db::{ 2use ra_ide_db::{
3 defs::{classify_name, classify_name_ref}, 3 defs::{classify_name, classify_name_ref, NameClass},
4 symbol_index, RootDatabase, 4 symbol_index, RootDatabase,
5}; 5};
6use ra_syntax::{ 6use ra_syntax::{
@@ -39,7 +39,10 @@ pub(crate) fn goto_definition(
39 reference_definition(&sema, &name_ref).to_vec() 39 reference_definition(&sema, &name_ref).to_vec()
40 }, 40 },
41 ast::Name(name) => { 41 ast::Name(name) => {
42 let def = classify_name(&sema, &name)?.definition(); 42 let def = match classify_name(&sema, &name)? {
43 NameClass::Definition(def) | NameClass::ConstReference(def) => def,
44 NameClass::FieldShorthand { local: _, field } => field,
45 };
43 let nav = def.try_to_nav(sema.db)?; 46 let nav = def.try_to_nav(sema.db)?;
44 vec![nav] 47 vec![nav]
45 }, 48 },
@@ -886,4 +889,23 @@ mod tests {
886 "x", 889 "x",
887 ) 890 )
888 } 891 }
892
893 #[test]
894 fn goto_def_for_enum_variant_field() {
895 check_goto(
896 "
897 //- /lib.rs
898 enum Foo {
899 Bar { x: i32 }
900 }
901 fn baz(foo: Foo) {
902 match foo {
903 Foo::Bar { x<|> } => x
904 };
905 }
906 ",
907 "x RECORD_FIELD_DEF FileId(1) 21..27 21..22",
908 "x: i32|x",
909 );
910 }
889} 911}
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs
index d96cb5596..ad78b7671 100644
--- a/crates/ra_ide/src/hover.rs
+++ b/crates/ra_ide/src/hover.rs
@@ -1,8 +1,8 @@
1use std::iter::once; 1use std::iter::once;
2 2
3use hir::{ 3use hir::{
4 Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef, 4 Adt, AsAssocItem, AssocItemContainer, Documentation, FieldSource, HasSource, HirDisplay,
5 ModuleSource, Semantics, 5 ModuleDef, ModuleSource, Semantics,
6}; 6};
7use itertools::Itertools; 7use itertools::Itertools;
8use ra_db::SourceDatabase; 8use ra_db::SourceDatabase;
@@ -10,22 +10,55 @@ use ra_ide_db::{
10 defs::{classify_name, classify_name_ref, Definition}, 10 defs::{classify_name, classify_name_ref, Definition},
11 RootDatabase, 11 RootDatabase,
12}; 12};
13use ra_syntax::{ 13use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset};
14 ast::{self, DocCommentsOwner},
15 match_ast, AstNode,
16 SyntaxKind::*,
17 SyntaxToken, TokenAtOffset,
18};
19 14
20use crate::{ 15use crate::{
21 display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel}, 16 display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel, ToNav},
22 FilePosition, RangeInfo, 17 runnables::runnable,
18 FileId, FilePosition, NavigationTarget, RangeInfo, Runnable,
23}; 19};
20use test_utils::mark;
21
22#[derive(Clone, Debug, PartialEq, Eq)]
23pub struct HoverConfig {
24 pub implementations: bool,
25 pub run: bool,
26 pub debug: bool,
27}
28
29impl Default for HoverConfig {
30 fn default() -> Self {
31 Self { implementations: true, run: true, debug: true }
32 }
33}
34
35impl HoverConfig {
36 pub const NO_ACTIONS: Self = Self { implementations: false, run: false, debug: false };
37
38 pub fn any(&self) -> bool {
39 self.implementations || self.runnable()
40 }
41
42 pub fn none(&self) -> bool {
43 !self.any()
44 }
45
46 pub fn runnable(&self) -> bool {
47 self.run || self.debug
48 }
49}
50
51#[derive(Debug, Clone)]
52pub enum HoverAction {
53 Runnable(Runnable),
54 Implementaion(FilePosition),
55}
24 56
25/// Contains the results when hovering over an item 57/// Contains the results when hovering over an item
26#[derive(Debug, Default)] 58#[derive(Debug, Default)]
27pub struct HoverResult { 59pub struct HoverResult {
28 results: Vec<String>, 60 results: Vec<String>,
61 actions: Vec<HoverAction>,
29} 62}
30 63
31impl HoverResult { 64impl HoverResult {
@@ -53,10 +86,20 @@ impl HoverResult {
53 &self.results 86 &self.results
54 } 87 }
55 88
89 pub fn actions(&self) -> &[HoverAction] {
90 &self.actions
91 }
92
93 pub fn push_action(&mut self, action: HoverAction) {
94 self.actions.push(action);
95 }
96
56 /// Returns the results converted into markup 97 /// Returns the results converted into markup
57 /// for displaying in a UI 98 /// for displaying in a UI
99 ///
100 /// Does not process actions!
58 pub fn to_markup(&self) -> String { 101 pub fn to_markup(&self) -> String {
59 self.results.join("\n\n---\n") 102 self.results.join("\n\n___\n")
60 } 103 }
61} 104}
62 105
@@ -87,6 +130,14 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn
87 res.extend(hover_text_from_name_kind(db, name_kind)); 130 res.extend(hover_text_from_name_kind(db, name_kind));
88 131
89 if !res.is_empty() { 132 if !res.is_empty() {
133 if let Some(action) = show_implementations_action(db, name_kind) {
134 res.push_action(action);
135 }
136
137 if let Some(action) = runnable_action(&sema, name_kind, position.file_id) {
138 res.push_action(action);
139 }
140
90 return Some(RangeInfo::new(range, res)); 141 return Some(RangeInfo::new(range, res));
91 } 142 }
92 } 143 }
@@ -117,6 +168,56 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn
117 Some(RangeInfo::new(range, res)) 168 Some(RangeInfo::new(range, res))
118} 169}
119 170
171fn show_implementations_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
172 fn to_action(nav_target: NavigationTarget) -> HoverAction {
173 HoverAction::Implementaion(FilePosition {
174 file_id: nav_target.file_id(),
175 offset: nav_target.range().start(),
176 })
177 }
178
179 match def {
180 Definition::ModuleDef(it) => match it {
181 ModuleDef::Adt(Adt::Struct(it)) => Some(to_action(it.to_nav(db))),
182 ModuleDef::Adt(Adt::Union(it)) => Some(to_action(it.to_nav(db))),
183 ModuleDef::Adt(Adt::Enum(it)) => Some(to_action(it.to_nav(db))),
184 ModuleDef::Trait(it) => Some(to_action(it.to_nav(db))),
185 _ => None,
186 },
187 _ => None,
188 }
189}
190
191fn runnable_action(
192 sema: &Semantics<RootDatabase>,
193 def: Definition,
194 file_id: FileId,
195) -> Option<HoverAction> {
196 match def {
197 Definition::ModuleDef(it) => match it {
198 ModuleDef::Module(it) => match it.definition_source(sema.db).value {
199 ModuleSource::Module(it) => runnable(&sema, it.syntax().clone(), file_id)
200 .map(|it| HoverAction::Runnable(it)),
201 _ => None,
202 },
203 ModuleDef::Function(it) => {
204 let src = it.source(sema.db);
205 if src.file_id != file_id.into() {
206 mark::hit!(hover_macro_generated_struct_fn_doc_comment);
207 mark::hit!(hover_macro_generated_struct_fn_doc_attr);
208
209 return None;
210 }
211
212 runnable(&sema, src.value.syntax().clone(), file_id)
213 .map(|it| HoverAction::Runnable(it))
214 }
215 _ => None,
216 },
217 _ => None,
218 }
219}
220
120fn hover_text( 221fn hover_text(
121 docs: Option<String>, 222 docs: Option<String>,
122 desc: Option<String>, 223 desc: Option<String>,
@@ -169,13 +270,15 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<Strin
169 return match def { 270 return match def {
170 Definition::Macro(it) => { 271 Definition::Macro(it) => {
171 let src = it.source(db); 272 let src = it.source(db);
172 hover_text(src.value.doc_comment_text(), Some(macro_label(&src.value)), mod_path) 273 let docs = Documentation::from_ast(&src.value).map(Into::into);
274 hover_text(docs, Some(macro_label(&src.value)), mod_path)
173 } 275 }
174 Definition::Field(it) => { 276 Definition::Field(it) => {
175 let src = it.source(db); 277 let src = it.source(db);
176 match src.value { 278 match src.value {
177 FieldSource::Named(it) => { 279 FieldSource::Named(it) => {
178 hover_text(it.doc_comment_text(), it.short_label(), mod_path) 280 let docs = Documentation::from_ast(&it).map(Into::into);
281 hover_text(docs, it.short_label(), mod_path)
179 } 282 }
180 _ => None, 283 _ => None,
181 } 284 }
@@ -183,7 +286,8 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<Strin
183 Definition::ModuleDef(it) => match it { 286 Definition::ModuleDef(it) => match it {
184 ModuleDef::Module(it) => match it.definition_source(db).value { 287 ModuleDef::Module(it) => match it.definition_source(db).value {
185 ModuleSource::Module(it) => { 288 ModuleSource::Module(it) => {
186 hover_text(it.doc_comment_text(), it.short_label(), mod_path) 289 let docs = Documentation::from_ast(&it).map(Into::into);
290 hover_text(docs, it.short_label(), mod_path)
187 } 291 }
188 _ => None, 292 _ => None,
189 }, 293 },
@@ -208,10 +312,11 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<Strin
208 fn from_def_source<A, D>(db: &RootDatabase, def: D, mod_path: Option<String>) -> Option<String> 312 fn from_def_source<A, D>(db: &RootDatabase, def: D, mod_path: Option<String>) -> Option<String>
209 where 313 where
210 D: HasSource<Ast = A>, 314 D: HasSource<Ast = A>,
211 A: ast::DocCommentsOwner + ast::NameOwner + ShortLabel, 315 A: ast::DocCommentsOwner + ast::NameOwner + ShortLabel + ast::AttrsOwner,
212 { 316 {
213 let src = def.source(db); 317 let src = def.source(db);
214 hover_text(src.value.doc_comment_text(), src.value.short_label(), mod_path) 318 let docs = Documentation::from_ast(&src.value).map(Into::into);
319 hover_text(docs, src.value.short_label(), mod_path)
215 } 320 }
216} 321}
217 322
@@ -229,6 +334,9 @@ fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
229 334
230#[cfg(test)] 335#[cfg(test)]
231mod tests { 336mod tests {
337 use super::*;
338 use insta::assert_debug_snapshot;
339
232 use ra_db::FileLoader; 340 use ra_db::FileLoader;
233 use ra_syntax::TextRange; 341 use ra_syntax::TextRange;
234 342
@@ -242,7 +350,15 @@ mod tests {
242 s.map(trim_markup) 350 s.map(trim_markup)
243 } 351 }
244 352
245 fn check_hover_result(fixture: &str, expected: &[&str]) -> String { 353 fn assert_impl_action(action: &HoverAction, position: u32) {
354 let offset = match action {
355 HoverAction::Implementaion(pos) => pos.offset,
356 it => panic!("Unexpected hover action: {:#?}", it),
357 };
358 assert_eq!(offset, position.into());
359 }
360
361 fn check_hover_result(fixture: &str, expected: &[&str]) -> (String, Vec<HoverAction>) {
246 let (analysis, position) = analysis_and_position(fixture); 362 let (analysis, position) = analysis_and_position(fixture);
247 let hover = analysis.hover(position).unwrap().unwrap(); 363 let hover = analysis.hover(position).unwrap().unwrap();
248 let mut results = Vec::from(hover.info.results()); 364 let mut results = Vec::from(hover.info.results());
@@ -257,7 +373,7 @@ mod tests {
257 assert_eq!(hover.info.len(), expected.len()); 373 assert_eq!(hover.info.len(), expected.len());
258 374
259 let content = analysis.db.file_text(position.file_id); 375 let content = analysis.db.file_text(position.file_id);
260 content[hover.range].to_string() 376 (content[hover.range].to_string(), hover.info.actions().to_vec())
261 } 377 }
262 378
263 fn check_hover_no_result(fixture: &str) { 379 fn check_hover_no_result(fixture: &str) {
@@ -458,7 +574,7 @@ struct Test<K, T = u8> {
458} 574}
459 575
460fn main() { 576fn main() {
461 let zz<|> = Test { t: 23, k: 33 }; 577 let zz<|> = Test { t: 23u8, k: 33 };
462}"#, 578}"#,
463 &["Test<i32, u8>"], 579 &["Test<i32, u8>"],
464 ); 580 );
@@ -747,7 +863,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
747 863
748 #[test] 864 #[test]
749 fn test_hover_through_macro() { 865 fn test_hover_through_macro() {
750 let hover_on = check_hover_result( 866 let (hover_on, _) = check_hover_result(
751 " 867 "
752 //- /lib.rs 868 //- /lib.rs
753 macro_rules! id { 869 macro_rules! id {
@@ -768,7 +884,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
768 884
769 #[test] 885 #[test]
770 fn test_hover_through_expr_in_macro() { 886 fn test_hover_through_expr_in_macro() {
771 let hover_on = check_hover_result( 887 let (hover_on, _) = check_hover_result(
772 " 888 "
773 //- /lib.rs 889 //- /lib.rs
774 macro_rules! id { 890 macro_rules! id {
@@ -786,7 +902,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
786 902
787 #[test] 903 #[test]
788 fn test_hover_through_expr_in_macro_recursive() { 904 fn test_hover_through_expr_in_macro_recursive() {
789 let hover_on = check_hover_result( 905 let (hover_on, _) = check_hover_result(
790 " 906 "
791 //- /lib.rs 907 //- /lib.rs
792 macro_rules! id_deep { 908 macro_rules! id_deep {
@@ -807,7 +923,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
807 923
808 #[test] 924 #[test]
809 fn test_hover_through_func_in_macro_recursive() { 925 fn test_hover_through_func_in_macro_recursive() {
810 let hover_on = check_hover_result( 926 let (hover_on, _) = check_hover_result(
811 " 927 "
812 //- /lib.rs 928 //- /lib.rs
813 macro_rules! id_deep { 929 macro_rules! id_deep {
@@ -831,7 +947,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
831 947
832 #[test] 948 #[test]
833 fn test_hover_through_literal_string_in_macro() { 949 fn test_hover_through_literal_string_in_macro() {
834 let hover_on = check_hover_result( 950 let (hover_on, _) = check_hover_result(
835 r#" 951 r#"
836 //- /lib.rs 952 //- /lib.rs
837 macro_rules! arr { 953 macro_rules! arr {
@@ -850,7 +966,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
850 966
851 #[test] 967 #[test]
852 fn test_hover_through_assert_macro() { 968 fn test_hover_through_assert_macro() {
853 let hover_on = check_hover_result( 969 let (hover_on, _) = check_hover_result(
854 r#" 970 r#"
855 //- /lib.rs 971 //- /lib.rs
856 #[rustc_builtin_macro] 972 #[rustc_builtin_macro]
@@ -926,13 +1042,14 @@ fn func(foo: i32) { if true { <|>foo; }; }
926 1042
927 #[test] 1043 #[test]
928 fn test_hover_trait_show_qualifiers() { 1044 fn test_hover_trait_show_qualifiers() {
929 check_hover_result( 1045 let (_, actions) = check_hover_result(
930 " 1046 "
931 //- /lib.rs 1047 //- /lib.rs
932 unsafe trait foo<|>() {} 1048 unsafe trait foo<|>() {}
933 ", 1049 ",
934 &["unsafe trait foo"], 1050 &["unsafe trait foo"],
935 ); 1051 );
1052 assert_impl_action(&actions[0], 13);
936 } 1053 }
937 1054
938 #[test] 1055 #[test]
@@ -951,4 +1068,246 @@ fn func(foo: i32) { if true { <|>foo; }; }
951 &["mod my"], 1068 &["mod my"],
952 ); 1069 );
953 } 1070 }
1071
1072 #[test]
1073 fn test_hover_struct_doc_comment() {
1074 check_hover_result(
1075 r#"
1076 //- /lib.rs
1077 /// bar docs
1078 struct Bar;
1079
1080 fn foo() {
1081 let bar = Ba<|>r;
1082 }
1083 "#,
1084 &["struct Bar\n```\n___\n\nbar docs"],
1085 );
1086 }
1087
1088 #[test]
1089 fn test_hover_struct_doc_attr() {
1090 check_hover_result(
1091 r#"
1092 //- /lib.rs
1093 #[doc = "bar docs"]
1094 struct Bar;
1095
1096 fn foo() {
1097 let bar = Ba<|>r;
1098 }
1099 "#,
1100 &["struct Bar\n```\n___\n\nbar docs"],
1101 );
1102 }
1103
1104 #[test]
1105 fn test_hover_struct_doc_attr_multiple_and_mixed() {
1106 check_hover_result(
1107 r#"
1108 //- /lib.rs
1109 /// bar docs 0
1110 #[doc = "bar docs 1"]
1111 #[doc = "bar docs 2"]
1112 struct Bar;
1113
1114 fn foo() {
1115 let bar = Ba<|>r;
1116 }
1117 "#,
1118 &["struct Bar\n```\n___\n\nbar docs 0\n\nbar docs 1\n\nbar docs 2"],
1119 );
1120 }
1121
1122 #[test]
1123 fn test_hover_macro_generated_struct_fn_doc_comment() {
1124 mark::check!(hover_macro_generated_struct_fn_doc_comment);
1125
1126 check_hover_result(
1127 r#"
1128 //- /lib.rs
1129 macro_rules! bar {
1130 () => {
1131 struct Bar;
1132 impl Bar {
1133 /// Do the foo
1134 fn foo(&self) {}
1135 }
1136 }
1137 }
1138
1139 bar!();
1140
1141 fn foo() {
1142 let bar = Bar;
1143 bar.fo<|>o();
1144 }
1145 "#,
1146 &["Bar\n```\n\n```rust\nfn foo(&self)\n```\n___\n\n Do the foo"],
1147 );
1148 }
1149
1150 #[test]
1151 fn test_hover_macro_generated_struct_fn_doc_attr() {
1152 mark::check!(hover_macro_generated_struct_fn_doc_attr);
1153
1154 check_hover_result(
1155 r#"
1156 //- /lib.rs
1157 macro_rules! bar {
1158 () => {
1159 struct Bar;
1160 impl Bar {
1161 #[doc = "Do the foo"]
1162 fn foo(&self) {}
1163 }
1164 }
1165 }
1166
1167 bar!();
1168
1169 fn foo() {
1170 let bar = Bar;
1171 bar.fo<|>o();
1172 }
1173 "#,
1174 &["Bar\n```\n\n```rust\nfn foo(&self)\n```\n___\n\nDo the foo"],
1175 );
1176 }
1177
1178 #[test]
1179 fn test_hover_trait_has_impl_action() {
1180 let (_, actions) = check_hover_result(
1181 "
1182 //- /lib.rs
1183 trait foo<|>() {}
1184 ",
1185 &["trait foo"],
1186 );
1187 assert_impl_action(&actions[0], 6);
1188 }
1189
1190 #[test]
1191 fn test_hover_struct_has_impl_action() {
1192 let (_, actions) = check_hover_result(
1193 "
1194 //- /lib.rs
1195 struct foo<|>() {}
1196 ",
1197 &["struct foo"],
1198 );
1199 assert_impl_action(&actions[0], 7);
1200 }
1201
1202 #[test]
1203 fn test_hover_union_has_impl_action() {
1204 let (_, actions) = check_hover_result(
1205 "
1206 //- /lib.rs
1207 union foo<|>() {}
1208 ",
1209 &["union foo"],
1210 );
1211 assert_impl_action(&actions[0], 6);
1212 }
1213
1214 #[test]
1215 fn test_hover_enum_has_impl_action() {
1216 let (_, actions) = check_hover_result(
1217 "
1218 //- /lib.rs
1219 enum foo<|>() {
1220 A,
1221 B
1222 }
1223 ",
1224 &["enum foo"],
1225 );
1226 assert_impl_action(&actions[0], 5);
1227 }
1228
1229 #[test]
1230 fn test_hover_test_has_action() {
1231 let (_, actions) = check_hover_result(
1232 "
1233 //- /lib.rs
1234 #[test]
1235 fn foo_<|>test() {}
1236 ",
1237 &["fn foo_test()"],
1238 );
1239 assert_debug_snapshot!(actions,
1240 @r###"
1241 [
1242 Runnable(
1243 Runnable {
1244 nav: NavigationTarget {
1245 file_id: FileId(
1246 1,
1247 ),
1248 full_range: 0..24,
1249 name: "foo_test",
1250 kind: FN_DEF,
1251 focus_range: Some(
1252 11..19,
1253 ),
1254 container_name: None,
1255 description: None,
1256 docs: None,
1257 },
1258 kind: Test {
1259 test_id: Path(
1260 "foo_test",
1261 ),
1262 attr: TestAttr {
1263 ignore: false,
1264 },
1265 },
1266 cfg_exprs: [],
1267 },
1268 ),
1269 ]
1270 "###);
1271 }
1272
1273 #[test]
1274 fn test_hover_test_mod_has_action() {
1275 let (_, actions) = check_hover_result(
1276 "
1277 //- /lib.rs
1278 mod tests<|> {
1279 #[test]
1280 fn foo_test() {}
1281 }
1282 ",
1283 &["mod tests"],
1284 );
1285 assert_debug_snapshot!(actions,
1286 @r###"
1287 [
1288 Runnable(
1289 Runnable {
1290 nav: NavigationTarget {
1291 file_id: FileId(
1292 1,
1293 ),
1294 full_range: 0..46,
1295 name: "tests",
1296 kind: MODULE,
1297 focus_range: Some(
1298 4..9,
1299 ),
1300 container_name: None,
1301 description: None,
1302 docs: None,
1303 },
1304 kind: TestMod {
1305 path: "tests",
1306 },
1307 cfg_exprs: [],
1308 },
1309 ),
1310 ]
1311 "###);
1312 }
954} 1313}
diff --git a/crates/ra_ide/src/inlay_hints.rs b/crates/ra_ide/src/inlay_hints.rs
index 75bd3c96b..7eb2cef73 100644
--- a/crates/ra_ide/src/inlay_hints.rs
+++ b/crates/ra_ide/src/inlay_hints.rs
@@ -149,11 +149,10 @@ fn get_param_name_hints(
149 ast::Expr::MethodCallExpr(expr) => expr.arg_list()?.args(), 149 ast::Expr::MethodCallExpr(expr) => expr.arg_list()?.args(),
150 _ => return None, 150 _ => return None,
151 }; 151 };
152 let args_count = args.clone().count();
153 152
154 let fn_signature = get_fn_signature(sema, &expr)?; 153 let fn_signature = get_fn_signature(sema, &expr)?;
155 let n_params_to_skip = 154 let n_params_to_skip =
156 if fn_signature.has_self_param && fn_signature.parameter_names.len() > args_count { 155 if fn_signature.has_self_param && matches!(&expr, ast::Expr::MethodCallExpr(_)) {
157 1 156 1
158 } else { 157 } else {
159 0 158 0
@@ -416,7 +415,7 @@ struct Test<K, T = u8> {
416} 415}
417 416
418fn main() { 417fn main() {
419 let zz = Test { t: 23, k: 33 }; 418 let zz = Test { t: 23u8, k: 33 };
420 let zz_ref = &zz; 419 let zz_ref = &zz;
421}"#, 420}"#,
422 ); 421 );
@@ -429,7 +428,7 @@ fn main() {
429 label: "Test<i32>", 428 label: "Test<i32>",
430 }, 429 },
431 InlayHint { 430 InlayHint {
432 range: 105..111, 431 range: 107..113,
433 kind: TypeHint, 432 kind: TypeHint,
434 label: "&Test<i32>", 433 label: "&Test<i32>",
435 }, 434 },
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs
index 12d5716e8..28f686767 100644
--- a/crates/ra_ide/src/lib.rs
+++ b/crates/ra_ide/src/lib.rs
@@ -66,7 +66,7 @@ pub use crate::{
66 display::{file_structure, FunctionSignature, NavigationTarget, StructureNode}, 66 display::{file_structure, FunctionSignature, NavigationTarget, StructureNode},
67 expand_macro::ExpandedMacro, 67 expand_macro::ExpandedMacro,
68 folding_ranges::{Fold, FoldKind}, 68 folding_ranges::{Fold, FoldKind},
69 hover::HoverResult, 69 hover::{HoverAction, HoverConfig, HoverResult},
70 inlay_hints::{InlayHint, InlayHintsConfig, InlayKind}, 70 inlay_hints::{InlayHint, InlayHintsConfig, InlayKind},
71 references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult}, 71 references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult},
72 runnables::{Runnable, RunnableKind, TestId}, 72 runnables::{Runnable, RunnableKind, TestId},
@@ -77,7 +77,7 @@ pub use crate::{
77}; 77};
78 78
79pub use hir::Documentation; 79pub use hir::Documentation;
80pub use ra_assists::{AssistConfig, AssistId}; 80pub use ra_assists::{Assist, AssistConfig, AssistId, ResolvedAssist};
81pub use ra_db::{ 81pub use ra_db::{
82 Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRootId, 82 Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRootId,
83}; 83};
@@ -142,14 +142,6 @@ pub struct AnalysisHost {
142 db: RootDatabase, 142 db: RootDatabase,
143} 143}
144 144
145#[derive(Debug)]
146pub struct Assist {
147 pub id: AssistId,
148 pub label: String,
149 pub group_label: Option<String>,
150 pub source_change: SourceChange,
151}
152
153impl AnalysisHost { 145impl AnalysisHost {
154 pub fn new(lru_capacity: Option<usize>) -> AnalysisHost { 146 pub fn new(lru_capacity: Option<usize>) -> AnalysisHost {
155 AnalysisHost { db: RootDatabase::new(lru_capacity) } 147 AnalysisHost { db: RootDatabase::new(lru_capacity) }
@@ -470,20 +462,23 @@ impl Analysis {
470 self.with_db(|db| completion::completions(db, config, position).map(Into::into)) 462 self.with_db(|db| completion::completions(db, config, position).map(Into::into))
471 } 463 }
472 464
473 /// Computes assists (aka code actions aka intentions) for the given 465 /// Computes resolved assists with source changes for the given position.
466 pub fn resolved_assists(
467 &self,
468 config: &AssistConfig,
469 frange: FileRange,
470 ) -> Cancelable<Vec<ResolvedAssist>> {
471 self.with_db(|db| ra_assists::Assist::resolved(db, config, frange))
472 }
473
474 /// Computes unresolved assists (aka code actions aka intentions) for the given
474 /// position. 475 /// position.
475 pub fn assists(&self, config: &AssistConfig, frange: FileRange) -> Cancelable<Vec<Assist>> { 476 pub fn unresolved_assists(
476 self.with_db(|db| { 477 &self,
477 ra_assists::Assist::resolved(db, config, frange) 478 config: &AssistConfig,
478 .into_iter() 479 frange: FileRange,
479 .map(|assist| Assist { 480 ) -> Cancelable<Vec<Assist>> {
480 id: assist.assist.id, 481 self.with_db(|db| Assist::unresolved(db, config, frange))
481 label: assist.assist.label,
482 group_label: assist.assist.group.map(|it| it.0),
483 source_change: assist.source_change,
484 })
485 .collect()
486 })
487 } 482 }
488 483
489 /// Computes the set of diagnostics for the given file. 484 /// Computes the set of diagnostics for the given file.
@@ -508,7 +503,7 @@ impl Analysis {
508 ) -> Cancelable<Result<SourceChange, SsrError>> { 503 ) -> Cancelable<Result<SourceChange, SsrError>> {
509 self.with_db(|db| { 504 self.with_db(|db| {
510 let edits = ssr::parse_search_replace(query, parse_only, db)?; 505 let edits = ssr::parse_search_replace(query, parse_only, db)?;
511 Ok(SourceChange::source_file_edits(edits)) 506 Ok(SourceChange::from(edits))
512 }) 507 })
513 } 508 }
514 509
diff --git a/crates/ra_ide/src/references/rename.rs b/crates/ra_ide/src/references/rename.rs
index 28c6349b1..915d4f4d3 100644
--- a/crates/ra_ide/src/references/rename.rs
+++ b/crates/ra_ide/src/references/rename.rs
@@ -171,7 +171,7 @@ fn rename_to_self(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo
171 ), 171 ),
172 }); 172 });
173 173
174 Some(RangeInfo::new(range, SourceChange::source_file_edits(edits))) 174 Some(RangeInfo::new(range, SourceChange::from(edits)))
175} 175}
176 176
177fn text_edit_from_self_param( 177fn text_edit_from_self_param(
@@ -234,7 +234,7 @@ fn rename_self_to_param(
234 let range = ast::SelfParam::cast(self_token.parent()) 234 let range = ast::SelfParam::cast(self_token.parent())
235 .map_or(self_token.text_range(), |p| p.syntax().text_range()); 235 .map_or(self_token.text_range(), |p| p.syntax().text_range());
236 236
237 Some(RangeInfo::new(range, SourceChange::source_file_edits(edits))) 237 Some(RangeInfo::new(range, SourceChange::from(edits)))
238} 238}
239 239
240fn rename_reference( 240fn rename_reference(
@@ -253,7 +253,7 @@ fn rename_reference(
253 return None; 253 return None;
254 } 254 }
255 255
256 Some(RangeInfo::new(range, SourceChange::source_file_edits(edit))) 256 Some(RangeInfo::new(range, SourceChange::from(edit)))
257} 257}
258 258
259#[cfg(test)] 259#[cfg(test)]
diff --git a/crates/ra_ide/src/runnables.rs b/crates/ra_ide/src/runnables.rs
index 286d45eee..fc57dc33d 100644
--- a/crates/ra_ide/src/runnables.rs
+++ b/crates/ra_ide/src/runnables.rs
@@ -1,31 +1,31 @@
1use std::fmt;
2
1use hir::{AsAssocItem, Attrs, HirFileId, InFile, Semantics}; 3use hir::{AsAssocItem, Attrs, HirFileId, InFile, Semantics};
2use itertools::Itertools; 4use itertools::Itertools;
5use ra_cfg::CfgExpr;
3use ra_ide_db::RootDatabase; 6use ra_ide_db::RootDatabase;
4use ra_syntax::{ 7use ra_syntax::{
5 ast::{self, AstNode, AttrsOwner, ModuleItemOwner, NameOwner}, 8 ast::{self, AstNode, AttrsOwner, DocCommentsOwner, ModuleItemOwner, NameOwner},
6 match_ast, SyntaxNode, TextRange, 9 match_ast, SyntaxNode,
7}; 10};
8 11
9use crate::FileId; 12use crate::{display::ToNav, FileId, NavigationTarget};
10use ast::DocCommentsOwner;
11use ra_cfg::CfgExpr;
12use std::fmt::Display;
13 13
14#[derive(Debug)] 14#[derive(Debug, Clone)]
15pub struct Runnable { 15pub struct Runnable {
16 pub range: TextRange, 16 pub nav: NavigationTarget,
17 pub kind: RunnableKind, 17 pub kind: RunnableKind,
18 pub cfg_exprs: Vec<CfgExpr>, 18 pub cfg_exprs: Vec<CfgExpr>,
19} 19}
20 20
21#[derive(Debug)] 21#[derive(Debug, Clone)]
22pub enum TestId { 22pub enum TestId {
23 Name(String), 23 Name(String),
24 Path(String), 24 Path(String),
25} 25}
26 26
27impl Display for TestId { 27impl fmt::Display for TestId {
28 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 28 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
29 match self { 29 match self {
30 TestId::Name(name) => write!(f, "{}", name), 30 TestId::Name(name) => write!(f, "{}", name),
31 TestId::Path(path) => write!(f, "{}", path), 31 TestId::Path(path) => write!(f, "{}", path),
@@ -33,7 +33,7 @@ impl Display for TestId {
33 } 33 }
34} 34}
35 35
36#[derive(Debug)] 36#[derive(Debug, Clone)]
37pub enum RunnableKind { 37pub enum RunnableKind {
38 Test { test_id: TestId, attr: TestAttr }, 38 Test { test_id: TestId, attr: TestAttr },
39 TestMod { path: String }, 39 TestMod { path: String },
@@ -42,6 +42,42 @@ pub enum RunnableKind {
42 Bin, 42 Bin,
43} 43}
44 44
45#[derive(Debug, Eq, PartialEq)]
46pub struct RunnableAction {
47 pub run_title: &'static str,
48 pub debugee: bool,
49}
50
51const TEST: RunnableAction = RunnableAction { run_title: "▶\u{fe0e} Run Test", debugee: true };
52const DOCTEST: RunnableAction =
53 RunnableAction { run_title: "▶\u{fe0e} Run Doctest", debugee: false };
54const BENCH: RunnableAction = RunnableAction { run_title: "▶\u{fe0e} Run Bench", debugee: true };
55const BIN: RunnableAction = RunnableAction { run_title: "▶\u{fe0e} Run", debugee: true };
56
57impl Runnable {
58 // test package::module::testname
59 pub fn label(&self, target: Option<String>) -> String {
60 match &self.kind {
61 RunnableKind::Test { test_id, .. } => format!("test {}", test_id),
62 RunnableKind::TestMod { path } => format!("test-mod {}", path),
63 RunnableKind::Bench { test_id } => format!("bench {}", test_id),
64 RunnableKind::DocTest { test_id, .. } => format!("doctest {}", test_id),
65 RunnableKind::Bin => {
66 target.map_or_else(|| "run binary".to_string(), |t| format!("run {}", t))
67 }
68 }
69 }
70
71 pub fn action(&self) -> &'static RunnableAction {
72 match &self.kind {
73 RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => &TEST,
74 RunnableKind::DocTest { .. } => &DOCTEST,
75 RunnableKind::Bench { .. } => &BENCH,
76 RunnableKind::Bin => &BIN,
77 }
78 }
79}
80
45// Feature: Run 81// Feature: Run
46// 82//
47// Shows a popup suggesting to run a test/benchmark/binary **at the current cursor 83// Shows a popup suggesting to run a test/benchmark/binary **at the current cursor
@@ -59,7 +95,11 @@ pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
59 source_file.syntax().descendants().filter_map(|i| runnable(&sema, i, file_id)).collect() 95 source_file.syntax().descendants().filter_map(|i| runnable(&sema, i, file_id)).collect()
60} 96}
61 97
62fn runnable(sema: &Semantics<RootDatabase>, item: SyntaxNode, file_id: FileId) -> Option<Runnable> { 98pub(crate) fn runnable(
99 sema: &Semantics<RootDatabase>,
100 item: SyntaxNode,
101 file_id: FileId,
102) -> Option<Runnable> {
63 match_ast! { 103 match_ast! {
64 match item { 104 match item {
65 ast::FnDef(it) => runnable_fn(sema, it, file_id), 105 ast::FnDef(it) => runnable_fn(sema, it, file_id),
@@ -131,10 +171,11 @@ fn runnable_fn(
131 let cfg_exprs = 171 let cfg_exprs =
132 attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect(); 172 attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect();
133 173
134 Some(Runnable { range: fn_def.syntax().text_range(), kind, cfg_exprs }) 174 let nav = NavigationTarget::from_named(sema.db, InFile::new(file_id.into(), &fn_def));
175 Some(Runnable { nav, kind, cfg_exprs })
135} 176}
136 177
137#[derive(Debug)] 178#[derive(Debug, Copy, Clone)]
138pub struct TestAttr { 179pub struct TestAttr {
139 pub ignore: bool, 180 pub ignore: bool,
140} 181}
@@ -183,7 +224,6 @@ fn runnable_mod(
183 if !has_test_function { 224 if !has_test_function {
184 return None; 225 return None;
185 } 226 }
186 let range = module.syntax().text_range();
187 let module_def = sema.to_def(&module)?; 227 let module_def = sema.to_def(&module)?;
188 228
189 let path = module_def 229 let path = module_def
@@ -197,7 +237,8 @@ fn runnable_mod(
197 let cfg_exprs = 237 let cfg_exprs =
198 attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect(); 238 attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect();
199 239
200 Some(Runnable { range, kind: RunnableKind::TestMod { path }, cfg_exprs }) 240 let nav = module_def.to_nav(sema.db);
241 Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg_exprs })
201} 242}
202 243
203#[cfg(test)] 244#[cfg(test)]
@@ -206,6 +247,15 @@ mod tests {
206 247
207 use crate::mock_analysis::analysis_and_position; 248 use crate::mock_analysis::analysis_and_position;
208 249
250 use super::{Runnable, RunnableAction, BENCH, BIN, DOCTEST, TEST};
251
252 fn assert_actions(runnables: &[Runnable], actions: &[&RunnableAction]) {
253 assert_eq!(
254 actions,
255 runnables.into_iter().map(|it| it.action()).collect::<Vec<_>>().as_slice()
256 );
257 }
258
209 #[test] 259 #[test]
210 fn test_runnables() { 260 fn test_runnables() {
211 let (analysis, pos) = analysis_and_position( 261 let (analysis, pos) = analysis_and_position(
@@ -220,6 +270,9 @@ mod tests {
220 #[test] 270 #[test]
221 #[ignore] 271 #[ignore]
222 fn test_foo() {} 272 fn test_foo() {}
273
274 #[bench]
275 fn bench() {}
223 "#, 276 "#,
224 ); 277 );
225 let runnables = analysis.runnables(pos.file_id).unwrap(); 278 let runnables = analysis.runnables(pos.file_id).unwrap();
@@ -227,12 +280,38 @@ mod tests {
227 @r###" 280 @r###"
228 [ 281 [
229 Runnable { 282 Runnable {
230 range: 1..21, 283 nav: NavigationTarget {
284 file_id: FileId(
285 1,
286 ),
287 full_range: 1..21,
288 name: "main",
289 kind: FN_DEF,
290 focus_range: Some(
291 12..16,
292 ),
293 container_name: None,
294 description: None,
295 docs: None,
296 },
231 kind: Bin, 297 kind: Bin,
232 cfg_exprs: [], 298 cfg_exprs: [],
233 }, 299 },
234 Runnable { 300 Runnable {
235 range: 22..46, 301 nav: NavigationTarget {
302 file_id: FileId(
303 1,
304 ),
305 full_range: 22..46,
306 name: "test_foo",
307 kind: FN_DEF,
308 focus_range: Some(
309 33..41,
310 ),
311 container_name: None,
312 description: None,
313 docs: None,
314 },
236 kind: Test { 315 kind: Test {
237 test_id: Path( 316 test_id: Path(
238 "test_foo", 317 "test_foo",
@@ -244,7 +323,20 @@ mod tests {
244 cfg_exprs: [], 323 cfg_exprs: [],
245 }, 324 },
246 Runnable { 325 Runnable {
247 range: 47..81, 326 nav: NavigationTarget {
327 file_id: FileId(
328 1,
329 ),
330 full_range: 47..81,
331 name: "test_foo",
332 kind: FN_DEF,
333 focus_range: Some(
334 68..76,
335 ),
336 container_name: None,
337 description: None,
338 docs: None,
339 },
248 kind: Test { 340 kind: Test {
249 test_id: Path( 341 test_id: Path(
250 "test_foo", 342 "test_foo",
@@ -255,9 +347,32 @@ mod tests {
255 }, 347 },
256 cfg_exprs: [], 348 cfg_exprs: [],
257 }, 349 },
350 Runnable {
351 nav: NavigationTarget {
352 file_id: FileId(
353 1,
354 ),
355 full_range: 82..104,
356 name: "bench",
357 kind: FN_DEF,
358 focus_range: Some(
359 94..99,
360 ),
361 container_name: None,
362 description: None,
363 docs: None,
364 },
365 kind: Bench {
366 test_id: Path(
367 "bench",
368 ),
369 },
370 cfg_exprs: [],
371 },
258 ] 372 ]
259 "### 373 "###
260 ); 374 );
375 assert_actions(&runnables, &[&BIN, &TEST, &TEST, &BENCH]);
261 } 376 }
262 377
263 #[test] 378 #[test]
@@ -279,12 +394,38 @@ mod tests {
279 @r###" 394 @r###"
280 [ 395 [
281 Runnable { 396 Runnable {
282 range: 1..21, 397 nav: NavigationTarget {
398 file_id: FileId(
399 1,
400 ),
401 full_range: 1..21,
402 name: "main",
403 kind: FN_DEF,
404 focus_range: Some(
405 12..16,
406 ),
407 container_name: None,
408 description: None,
409 docs: None,
410 },
283 kind: Bin, 411 kind: Bin,
284 cfg_exprs: [], 412 cfg_exprs: [],
285 }, 413 },
286 Runnable { 414 Runnable {
287 range: 22..64, 415 nav: NavigationTarget {
416 file_id: FileId(
417 1,
418 ),
419 full_range: 22..64,
420 name: "foo",
421 kind: FN_DEF,
422 focus_range: Some(
423 56..59,
424 ),
425 container_name: None,
426 description: None,
427 docs: None,
428 },
288 kind: DocTest { 429 kind: DocTest {
289 test_id: Path( 430 test_id: Path(
290 "foo", 431 "foo",
@@ -295,6 +436,7 @@ mod tests {
295 ] 436 ]
296 "### 437 "###
297 ); 438 );
439 assert_actions(&runnables, &[&BIN, &DOCTEST]);
298 } 440 }
299 441
300 #[test] 442 #[test]
@@ -319,12 +461,38 @@ mod tests {
319 @r###" 461 @r###"
320 [ 462 [
321 Runnable { 463 Runnable {
322 range: 1..21, 464 nav: NavigationTarget {
465 file_id: FileId(
466 1,
467 ),
468 full_range: 1..21,
469 name: "main",
470 kind: FN_DEF,
471 focus_range: Some(
472 12..16,
473 ),
474 container_name: None,
475 description: None,
476 docs: None,
477 },
323 kind: Bin, 478 kind: Bin,
324 cfg_exprs: [], 479 cfg_exprs: [],
325 }, 480 },
326 Runnable { 481 Runnable {
327 range: 51..105, 482 nav: NavigationTarget {
483 file_id: FileId(
484 1,
485 ),
486 full_range: 51..105,
487 name: "foo",
488 kind: FN_DEF,
489 focus_range: Some(
490 97..100,
491 ),
492 container_name: None,
493 description: None,
494 docs: None,
495 },
328 kind: DocTest { 496 kind: DocTest {
329 test_id: Path( 497 test_id: Path(
330 "Data::foo", 498 "Data::foo",
@@ -335,6 +503,7 @@ mod tests {
335 ] 503 ]
336 "### 504 "###
337 ); 505 );
506 assert_actions(&runnables, &[&BIN, &DOCTEST]);
338 } 507 }
339 508
340 #[test] 509 #[test]
@@ -354,14 +523,40 @@ mod tests {
354 @r###" 523 @r###"
355 [ 524 [
356 Runnable { 525 Runnable {
357 range: 1..59, 526 nav: NavigationTarget {
527 file_id: FileId(
528 1,
529 ),
530 full_range: 1..59,
531 name: "test_mod",
532 kind: MODULE,
533 focus_range: Some(
534 13..21,
535 ),
536 container_name: None,
537 description: None,
538 docs: None,
539 },
358 kind: TestMod { 540 kind: TestMod {
359 path: "test_mod", 541 path: "test_mod",
360 }, 542 },
361 cfg_exprs: [], 543 cfg_exprs: [],
362 }, 544 },
363 Runnable { 545 Runnable {
364 range: 28..57, 546 nav: NavigationTarget {
547 file_id: FileId(
548 1,
549 ),
550 full_range: 28..57,
551 name: "test_foo1",
552 kind: FN_DEF,
553 focus_range: Some(
554 43..52,
555 ),
556 container_name: None,
557 description: None,
558 docs: None,
559 },
365 kind: Test { 560 kind: Test {
366 test_id: Path( 561 test_id: Path(
367 "test_mod::test_foo1", 562 "test_mod::test_foo1",
@@ -375,6 +570,7 @@ mod tests {
375 ] 570 ]
376 "### 571 "###
377 ); 572 );
573 assert_actions(&runnables, &[&TEST, &TEST]);
378 } 574 }
379 575
380 #[test] 576 #[test]
@@ -396,14 +592,40 @@ mod tests {
396 @r###" 592 @r###"
397 [ 593 [
398 Runnable { 594 Runnable {
399 range: 23..85, 595 nav: NavigationTarget {
596 file_id: FileId(
597 1,
598 ),
599 full_range: 23..85,
600 name: "test_mod",
601 kind: MODULE,
602 focus_range: Some(
603 27..35,
604 ),
605 container_name: None,
606 description: None,
607 docs: None,
608 },
400 kind: TestMod { 609 kind: TestMod {
401 path: "foo::test_mod", 610 path: "foo::test_mod",
402 }, 611 },
403 cfg_exprs: [], 612 cfg_exprs: [],
404 }, 613 },
405 Runnable { 614 Runnable {
406 range: 46..79, 615 nav: NavigationTarget {
616 file_id: FileId(
617 1,
618 ),
619 full_range: 46..79,
620 name: "test_foo1",
621 kind: FN_DEF,
622 focus_range: Some(
623 65..74,
624 ),
625 container_name: None,
626 description: None,
627 docs: None,
628 },
407 kind: Test { 629 kind: Test {
408 test_id: Path( 630 test_id: Path(
409 "foo::test_mod::test_foo1", 631 "foo::test_mod::test_foo1",
@@ -417,6 +639,7 @@ mod tests {
417 ] 639 ]
418 "### 640 "###
419 ); 641 );
642 assert_actions(&runnables, &[&TEST, &TEST]);
420 } 643 }
421 644
422 #[test] 645 #[test]
@@ -440,14 +663,40 @@ mod tests {
440 @r###" 663 @r###"
441 [ 664 [
442 Runnable { 665 Runnable {
443 range: 41..115, 666 nav: NavigationTarget {
667 file_id: FileId(
668 1,
669 ),
670 full_range: 41..115,
671 name: "test_mod",
672 kind: MODULE,
673 focus_range: Some(
674 45..53,
675 ),
676 container_name: None,
677 description: None,
678 docs: None,
679 },
444 kind: TestMod { 680 kind: TestMod {
445 path: "foo::bar::test_mod", 681 path: "foo::bar::test_mod",
446 }, 682 },
447 cfg_exprs: [], 683 cfg_exprs: [],
448 }, 684 },
449 Runnable { 685 Runnable {
450 range: 68..105, 686 nav: NavigationTarget {
687 file_id: FileId(
688 1,
689 ),
690 full_range: 68..105,
691 name: "test_foo1",
692 kind: FN_DEF,
693 focus_range: Some(
694 91..100,
695 ),
696 container_name: None,
697 description: None,
698 docs: None,
699 },
451 kind: Test { 700 kind: Test {
452 test_id: Path( 701 test_id: Path(
453 "foo::bar::test_mod::test_foo1", 702 "foo::bar::test_mod::test_foo1",
@@ -461,6 +710,7 @@ mod tests {
461 ] 710 ]
462 "### 711 "###
463 ); 712 );
713 assert_actions(&runnables, &[&TEST, &TEST]);
464 } 714 }
465 715
466 #[test] 716 #[test]
@@ -479,7 +729,20 @@ mod tests {
479 @r###" 729 @r###"
480 [ 730 [
481 Runnable { 731 Runnable {
482 range: 1..58, 732 nav: NavigationTarget {
733 file_id: FileId(
734 1,
735 ),
736 full_range: 1..58,
737 name: "test_foo1",
738 kind: FN_DEF,
739 focus_range: Some(
740 44..53,
741 ),
742 container_name: None,
743 description: None,
744 docs: None,
745 },
483 kind: Test { 746 kind: Test {
484 test_id: Path( 747 test_id: Path(
485 "test_foo1", 748 "test_foo1",
@@ -498,6 +761,7 @@ mod tests {
498 ] 761 ]
499 "### 762 "###
500 ); 763 );
764 assert_actions(&runnables, &[&TEST]);
501 } 765 }
502 766
503 #[test] 767 #[test]
@@ -516,7 +780,20 @@ mod tests {
516 @r###" 780 @r###"
517 [ 781 [
518 Runnable { 782 Runnable {
519 range: 1..80, 783 nav: NavigationTarget {
784 file_id: FileId(
785 1,
786 ),
787 full_range: 1..80,
788 name: "test_foo1",
789 kind: FN_DEF,
790 focus_range: Some(
791 66..75,
792 ),
793 container_name: None,
794 description: None,
795 docs: None,
796 },
520 kind: Test { 797 kind: Test {
521 test_id: Path( 798 test_id: Path(
522 "test_foo1", 799 "test_foo1",
@@ -543,6 +820,7 @@ mod tests {
543 ] 820 ]
544 "### 821 "###
545 ); 822 );
823 assert_actions(&runnables, &[&TEST]);
546 } 824 }
547 825
548 #[test] 826 #[test]
diff --git a/crates/ra_ide/src/snapshots/highlight_doctest.html b/crates/ra_ide/src/snapshots/highlight_doctest.html
new file mode 100644
index 000000000..0ae8c7efc
--- /dev/null
+++ b/crates/ra_ide/src/snapshots/highlight_doctest.html
@@ -0,0 +1,71 @@
1
2<style>
3body { margin: 0; }
4pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
5
6.lifetime { color: #DFAF8F; font-style: italic; }
7.comment { color: #7F9F7F; }
8.struct, .enum { color: #7CB8BB; }
9.enum_variant { color: #BDE0F3; }
10.string_literal { color: #CC9393; }
11.field { color: #94BFF3; }
12.function { color: #93E0E3; }
13.function.unsafe { color: #BC8383; }
14.operator.unsafe { color: #BC8383; }
15.parameter { color: #94BFF3; }
16.text { color: #DCDCCC; }
17.type { color: #7CB8BB; }
18.builtin_type { color: #8CD0D3; }
19.type_param { color: #DFAF8F; }
20.attribute { color: #94BFF3; }
21.numeric_literal { color: #BFEBBF; }
22.bool_literal { color: #BFE6EB; }
23.macro { color: #94BFF3; }
24.module { color: #AFD8AF; }
25.variable { color: #DCDCCC; }
26.format_specifier { color: #CC696B; }
27.mutable { text-decoration: underline; }
28
29.keyword { color: #F0DFAF; font-weight: bold; }
30.keyword.unsafe { color: #BC8383; font-weight: bold; }
31.control { font-style: italic; }
32</style>
33<pre><code><span class="keyword">impl</span> <span class="unresolved_reference">Foo</span> {
34 <span class="comment">/// Constructs a new `Foo`.</span>
35 <span class="comment">///</span>
36 <span class="comment">/// # Examples</span>
37 <span class="comment">///</span>
38 <span class="comment">/// ```</span>
39 <span class="comment">/// #</span> <span class="attribute">#![</span><span class="function attribute">allow</span><span class="attribute">(unused_mut)]</span>
40 <span class="comment">/// </span><span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">foo</span>: <span class="unresolved_reference">Foo</span> = <span class="unresolved_reference">Foo</span>::<span class="unresolved_reference">new</span>();
41 <span class="comment">/// ```</span>
42 <span class="keyword">pub</span> <span class="keyword">const</span> <span class="keyword">fn</span> <span class="function declaration">new</span>() -&gt; <span class="unresolved_reference">Foo</span> {
43 <span class="unresolved_reference">Foo</span> { }
44 }
45
46 <span class="comment">/// `bar` method on `Foo`.</span>
47 <span class="comment">///</span>
48 <span class="comment">/// # Examples</span>
49 <span class="comment">///</span>
50 <span class="comment">/// ```</span>
51 <span class="comment">/// </span><span class="keyword">let</span> <span class="variable declaration">foo</span> = <span class="unresolved_reference">Foo</span>::<span class="unresolved_reference">new</span>();
52 <span class="comment">///</span>
53 <span class="comment">/// </span><span class="comment">// calls bar on foo</span>
54 <span class="comment">/// </span><span class="macro">assert!</span>(foo.bar());
55 <span class="comment">///</span>
56 <span class="comment">/// </span><span class="comment">/* multi-line
57 </span><span class="comment">/// </span><span class="comment"> comment */</span>
58 <span class="comment">///</span>
59 <span class="comment">/// </span><span class="keyword">let</span> <span class="variable declaration">multi_line_string</span> = <span class="string_literal">"Foo
60 </span><span class="comment">/// </span><span class="string_literal"> bar
61 </span><span class="comment">/// </span><span class="string_literal"> "</span>;
62 <span class="comment">///</span>
63 <span class="comment">/// ```</span>
64 <span class="comment">///</span>
65 <span class="comment">/// ```</span>
66 <span class="comment">/// </span><span class="keyword">let</span> <span class="variable declaration">foobar</span> = <span class="unresolved_reference">Foo</span>::<span class="unresolved_reference">new</span>().<span class="unresolved_reference">bar</span>();
67 <span class="comment">/// ```</span>
68 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration">foo</span>(&<span class="self_keyword">self</span>) -&gt; <span class="builtin_type">bool</span> {
69 <span class="bool_literal">true</span>
70 }
71}</code></pre> \ No newline at end of file
diff --git a/crates/ra_ide/src/snapshots/highlight_injection.html b/crates/ra_ide/src/snapshots/highlight_injection.html
index 68fc589bc..dec06eb51 100644
--- a/crates/ra_ide/src/snapshots/highlight_injection.html
+++ b/crates/ra_ide/src/snapshots/highlight_injection.html
@@ -10,6 +10,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
10.string_literal { color: #CC9393; } 10.string_literal { color: #CC9393; }
11.field { color: #94BFF3; } 11.field { color: #94BFF3; }
12.function { color: #93E0E3; } 12.function { color: #93E0E3; }
13.function.unsafe { color: #BC8383; }
14.operator.unsafe { color: #BC8383; }
13.parameter { color: #94BFF3; } 15.parameter { color: #94BFF3; }
14.text { color: #DCDCCC; } 16.text { color: #DCDCCC; }
15.type { color: #7CB8BB; } 17.type { color: #7CB8BB; }
diff --git a/crates/ra_ide/src/snapshots/highlight_strings.html b/crates/ra_ide/src/snapshots/highlight_strings.html
index 326744361..849eb3b73 100644
--- a/crates/ra_ide/src/snapshots/highlight_strings.html
+++ b/crates/ra_ide/src/snapshots/highlight_strings.html
@@ -10,6 +10,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
10.string_literal { color: #CC9393; } 10.string_literal { color: #CC9393; }
11.field { color: #94BFF3; } 11.field { color: #94BFF3; }
12.function { color: #93E0E3; } 12.function { color: #93E0E3; }
13.function.unsafe { color: #BC8383; }
14.operator.unsafe { color: #BC8383; }
13.parameter { color: #94BFF3; } 15.parameter { color: #94BFF3; }
14.text { color: #DCDCCC; } 16.text { color: #DCDCCC; }
15.type { color: #7CB8BB; } 17.type { color: #7CB8BB; }
@@ -52,6 +54,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
52 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">argument</span><span class="format_specifier">}</span><span class="string_literal">"</span>, argument = <span class="string_literal">"test"</span>); <span class="comment">// =&gt; "test"</span> 54 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">argument</span><span class="format_specifier">}</span><span class="string_literal">"</span>, argument = <span class="string_literal">"test"</span>); <span class="comment">// =&gt; "test"</span>
53 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">name</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">"</span>, <span class="numeric_literal">1</span>, name = <span class="numeric_literal">2</span>); <span class="comment">// =&gt; "2 1"</span> 55 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">name</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">"</span>, <span class="numeric_literal">1</span>, name = <span class="numeric_literal">2</span>); <span class="comment">// =&gt; "2 1"</span>
54 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">a</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="variable">c</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="variable">b</span><span class="format_specifier">}</span><span class="string_literal">"</span>, a=<span class="string_literal">"a"</span>, b=<span class="char_literal">'b'</span>, c=<span class="numeric_literal">3</span>); <span class="comment">// =&gt; "a 3 b"</span> 56 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">a</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="variable">c</span><span class="format_specifier">}</span><span class="string_literal"> </span><span class="format_specifier">{</span><span class="variable">b</span><span class="format_specifier">}</span><span class="string_literal">"</span>, a=<span class="string_literal">"a"</span>, b=<span class="char_literal">'b'</span>, c=<span class="numeric_literal">3</span>); <span class="comment">// =&gt; "a 3 b"</span>
57 <span class="macro">println!</span>(<span class="string_literal">"{{</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">}}"</span>, <span class="numeric_literal">2</span>); <span class="comment">// =&gt; "{2}"</span>
55 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>); 58 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>);
56 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">1</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>, <span class="numeric_literal">5</span>); 59 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">1</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>, <span class="numeric_literal">5</span>);
57 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="numeric_literal">1</span><span class="format_specifier">:</span><span class="numeric_literal">0</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">5</span>, <span class="string_literal">"x"</span>); 60 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="numeric_literal">1</span><span class="format_specifier">:</span><span class="numeric_literal">0</span><span class="format_specifier">$</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">5</span>, <span class="string_literal">"x"</span>);
@@ -61,7 +64,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
61 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">^</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>); 64 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">^</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>);
62 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">&gt;</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>); 65 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">&gt;</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>);
63 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">+</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">5</span>); 66 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">+</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">5</span>);
64 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">#</span><span class="variable">x</span><span class="string_literal">}!"</span>, <span class="numeric_literal">27</span>); 67 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">#</span><span class="variable">x</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">27</span>);
65 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">0</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">5</span>); 68 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">0</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">5</span>);
66 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">0</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, -<span class="numeric_literal">5</span>); 69 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="numeric_literal">0</span><span class="numeric_literal">5</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, -<span class="numeric_literal">5</span>);
67 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">#</span><span class="numeric_literal">0</span><span class="numeric_literal">10</span><span class="variable">x</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">27</span>); 70 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="format_specifier">#</span><span class="numeric_literal">0</span><span class="numeric_literal">10</span><span class="variable">x</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">27</span>);
diff --git a/crates/ra_ide/src/snapshots/highlight_unsafe.html b/crates/ra_ide/src/snapshots/highlight_unsafe.html
new file mode 100644
index 000000000..bd24e6e38
--- /dev/null
+++ b/crates/ra_ide/src/snapshots/highlight_unsafe.html
@@ -0,0 +1,49 @@
1
2<style>
3body { margin: 0; }
4pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
5
6.lifetime { color: #DFAF8F; font-style: italic; }
7.comment { color: #7F9F7F; }
8.struct, .enum { color: #7CB8BB; }
9.enum_variant { color: #BDE0F3; }
10.string_literal { color: #CC9393; }
11.field { color: #94BFF3; }
12.function { color: #93E0E3; }
13.function.unsafe { color: #BC8383; }
14.operator.unsafe { color: #BC8383; }
15.parameter { color: #94BFF3; }
16.text { color: #DCDCCC; }
17.type { color: #7CB8BB; }
18.builtin_type { color: #8CD0D3; }
19.type_param { color: #DFAF8F; }
20.attribute { color: #94BFF3; }
21.numeric_literal { color: #BFEBBF; }
22.bool_literal { color: #BFE6EB; }
23.macro { color: #94BFF3; }
24.module { color: #AFD8AF; }
25.variable { color: #DCDCCC; }
26.format_specifier { color: #CC696B; }
27.mutable { text-decoration: underline; }
28
29.keyword { color: #F0DFAF; font-weight: bold; }
30.keyword.unsafe { color: #BC8383; font-weight: bold; }
31.control { font-style: italic; }
32</style>
33<pre><code><span class="keyword unsafe">unsafe</span> <span class="keyword">fn</span> <span class="function declaration unsafe">unsafe_fn</span>() {}
34
35<span class="keyword">struct</span> <span class="struct declaration">HasUnsafeFn</span>;
36
37<span class="keyword">impl</span> <span class="struct">HasUnsafeFn</span> {
38 <span class="keyword unsafe">unsafe</span> <span class="keyword">fn</span> <span class="function declaration unsafe">unsafe_method</span>(&<span class="self_keyword">self</span>) {}
39}
40
41<span class="keyword">fn</span> <span class="function declaration">main</span>() {
42 <span class="keyword">let</span> <span class="variable declaration">x</span> = &<span class="numeric_literal">5</span> <span class="keyword">as</span> *<span class="keyword">const</span> <span class="builtin_type">usize</span>;
43 <span class="keyword unsafe">unsafe</span> {
44 <span class="function unsafe">unsafe_fn</span>();
45 <span class="struct">HasUnsafeFn</span>.<span class="function unsafe">unsafe_method</span>();
46 <span class="keyword">let</span> <span class="variable declaration">y</span> = <span class="operator unsafe">*</span>(<span class="variable">x</span>);
47 <span class="keyword">let</span> <span class="variable declaration">z</span> = -<span class="variable">x</span>;
48 }
49}</code></pre> \ No newline at end of file
diff --git a/crates/ra_ide/src/snapshots/highlighting.html b/crates/ra_ide/src/snapshots/highlighting.html
index 352e35095..33548d43c 100644
--- a/crates/ra_ide/src/snapshots/highlighting.html
+++ b/crates/ra_ide/src/snapshots/highlighting.html
@@ -10,6 +10,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
10.string_literal { color: #CC9393; } 10.string_literal { color: #CC9393; }
11.field { color: #94BFF3; } 11.field { color: #94BFF3; }
12.function { color: #93E0E3; } 12.function { color: #93E0E3; }
13.function.unsafe { color: #BC8383; }
14.operator.unsafe { color: #BC8383; }
13.parameter { color: #94BFF3; } 15.parameter { color: #94BFF3; }
14.text { color: #DCDCCC; } 16.text { color: #DCDCCC; }
15.type { color: #7CB8BB; } 17.type { color: #7CB8BB; }
@@ -82,7 +84,9 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
82 <span class="keyword">let</span> <span class="variable declaration mutable">y</span> = &<span class="keyword">mut</span> <span class="variable mutable">x</span>; 84 <span class="keyword">let</span> <span class="variable declaration mutable">y</span> = &<span class="keyword">mut</span> <span class="variable mutable">x</span>;
83 <span class="keyword">let</span> <span class="variable declaration">z</span> = &<span class="variable mutable">y</span>; 85 <span class="keyword">let</span> <span class="variable declaration">z</span> = &<span class="variable mutable">y</span>;
84 86
85 <span class="variable mutable">y</span>; 87 <span class="keyword">let</span> <span class="struct">Foo</span> { <span class="field">x</span>: <span class="variable declaration">z</span>, <span class="field">y</span> } = <span class="struct">Foo</span> { <span class="field">x</span>: <span class="variable">z</span>, <span class="field">y</span> };
88
89 <span class="variable">y</span>;
86} 90}
87 91
88<span class="keyword">enum</span> <span class="enum declaration">Option</span>&lt;<span class="type_param declaration">T</span>&gt; { 92<span class="keyword">enum</span> <span class="enum declaration">Option</span>&lt;<span class="type_param declaration">T</span>&gt; {
diff --git a/crates/ra_ide/src/snapshots/rainbow_highlighting.html b/crates/ra_ide/src/snapshots/rainbow_highlighting.html
index 2a0294f71..1ab06182c 100644
--- a/crates/ra_ide/src/snapshots/rainbow_highlighting.html
+++ b/crates/ra_ide/src/snapshots/rainbow_highlighting.html
@@ -10,6 +10,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
10.string_literal { color: #CC9393; } 10.string_literal { color: #CC9393; }
11.field { color: #94BFF3; } 11.field { color: #94BFF3; }
12.function { color: #93E0E3; } 12.function { color: #93E0E3; }
13.function.unsafe { color: #BC8383; }
14.operator.unsafe { color: #BC8383; }
13.parameter { color: #94BFF3; } 15.parameter { color: #94BFF3; }
14.text { color: #DCDCCC; } 16.text { color: #DCDCCC; }
15.type { color: #7CB8BB; } 17.type { color: #7CB8BB; }
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs
index 0b53ebe69..ab45c364a 100644
--- a/crates/ra_ide/src/syntax_highlighting.rs
+++ b/crates/ra_ide/src/syntax_highlighting.rs
@@ -1,5 +1,6 @@
1mod tags; 1mod tags;
2mod html; 2mod html;
3mod injection;
3#[cfg(test)] 4#[cfg(test)]
4mod tests; 5mod tests;
5 6
@@ -10,14 +11,14 @@ use ra_ide_db::{
10}; 11};
11use ra_prof::profile; 12use ra_prof::profile;
12use ra_syntax::{ 13use ra_syntax::{
13 ast::{self, HasFormatSpecifier, HasQuotes, HasStringValue}, 14 ast::{self, HasFormatSpecifier},
14 AstNode, AstToken, Direction, NodeOrToken, SyntaxElement, 15 AstNode, AstToken, Direction, NodeOrToken, SyntaxElement,
15 SyntaxKind::*, 16 SyntaxKind::*,
16 SyntaxToken, TextRange, WalkEvent, T, 17 TextRange, WalkEvent, T,
17}; 18};
18use rustc_hash::FxHashMap; 19use rustc_hash::FxHashMap;
19 20
20use crate::{call_info::ActiveParameter, Analysis, FileId}; 21use crate::FileId;
21 22
22use ast::FormatSpecifier; 23use ast::FormatSpecifier;
23pub(crate) use html::highlight_as_html; 24pub(crate) use html::highlight_as_html;
@@ -123,6 +124,23 @@ pub(crate) fn highlight(
123 _ => (), 124 _ => (),
124 } 125 }
125 126
127 // Check for Rust code in documentation
128 match &event {
129 WalkEvent::Leave(NodeOrToken::Node(node)) => {
130 if let Some((doctest, range_mapping, new_comments)) =
131 injection::extract_doc_comments(node)
132 {
133 injection::highlight_doc_comment(
134 doctest,
135 range_mapping,
136 new_comments,
137 &mut stack,
138 );
139 }
140 }
141 _ => (),
142 }
143
126 let element = match event { 144 let element = match event {
127 WalkEvent::Enter(it) => it, 145 WalkEvent::Enter(it) => it,
128 WalkEvent::Leave(_) => continue, 146 WalkEvent::Leave(_) => continue,
@@ -173,7 +191,7 @@ pub(crate) fn highlight(
173 191
174 if let Some(token) = element.as_token().cloned().and_then(ast::RawString::cast) { 192 if let Some(token) = element.as_token().cloned().and_then(ast::RawString::cast) {
175 let expanded = element_to_highlight.as_token().unwrap().clone(); 193 let expanded = element_to_highlight.as_token().unwrap().clone();
176 if highlight_injection(&mut stack, &sema, token, expanded).is_some() { 194 if injection::highlight_injection(&mut stack, &sema, token, expanded).is_some() {
177 continue; 195 continue;
178 } 196 }
179 } 197 }
@@ -259,9 +277,8 @@ impl HighlightedRangeStack {
259 let mut parent = prev.pop().unwrap(); 277 let mut parent = prev.pop().unwrap();
260 for ele in children { 278 for ele in children {
261 assert!(parent.range.contains_range(ele.range)); 279 assert!(parent.range.contains_range(ele.range));
262 let mut cloned = parent.clone(); 280
263 parent.range = TextRange::new(parent.range.start(), ele.range.start()); 281 let cloned = Self::intersect(&mut parent, &ele);
264 cloned.range = TextRange::new(ele.range.end(), cloned.range.end());
265 if !parent.range.is_empty() { 282 if !parent.range.is_empty() {
266 prev.push(parent); 283 prev.push(parent);
267 } 284 }
@@ -274,6 +291,62 @@ impl HighlightedRangeStack {
274 } 291 }
275 } 292 }
276 293
294 /// Intersects the `HighlightedRange` `parent` with `child`.
295 /// `parent` is mutated in place, becoming the range before `child`.
296 /// Returns the range (of the same type as `parent`) *after* `child`.
297 fn intersect(parent: &mut HighlightedRange, child: &HighlightedRange) -> HighlightedRange {
298 assert!(parent.range.contains_range(child.range));
299
300 let mut cloned = parent.clone();
301 parent.range = TextRange::new(parent.range.start(), child.range.start());
302 cloned.range = TextRange::new(child.range.end(), cloned.range.end());
303
304 cloned
305 }
306
307 /// Similar to `pop`, but can modify arbitrary prior ranges (where `pop`)
308 /// can only modify the last range currently on the stack.
309 /// Can be used to do injections that span multiple ranges, like the
310 /// doctest injection below.
311 /// If `delete` is set to true, the parent range is deleted instead of
312 /// intersected.
313 ///
314 /// Note that `pop` can be simulated by `pop_and_inject(false)` but the
315 /// latter is computationally more expensive.
316 fn pop_and_inject(&mut self, delete: bool) {
317 let mut children = self.stack.pop().unwrap();
318 let prev = self.stack.last_mut().unwrap();
319 children.sort_by_key(|range| range.range.start());
320 prev.sort_by_key(|range| range.range.start());
321
322 for child in children {
323 if let Some(idx) =
324 prev.iter().position(|parent| parent.range.contains_range(child.range))
325 {
326 let cloned = Self::intersect(&mut prev[idx], &child);
327 let insert_idx = if delete || prev[idx].range.is_empty() {
328 prev.remove(idx);
329 idx
330 } else {
331 idx + 1
332 };
333 prev.insert(insert_idx, child);
334 if !delete && !cloned.range.is_empty() {
335 prev.insert(insert_idx + 1, cloned);
336 }
337 } else if let Some(_idx) =
338 prev.iter().position(|parent| parent.range.contains(child.range.start()))
339 {
340 unreachable!("child range should be completely contained in parent range");
341 } else {
342 let idx = prev
343 .binary_search_by_key(&child.range.start(), |range| range.range.start())
344 .unwrap_or_else(|x| x);
345 prev.insert(idx, child);
346 }
347 }
348 }
349
277 fn add(&mut self, range: HighlightedRange) { 350 fn add(&mut self, range: HighlightedRange) {
278 self.stack 351 self.stack
279 .last_mut() 352 .last_mut()
@@ -363,6 +436,7 @@ fn highlight_element(
363 highlight_name(db, def) | HighlightModifier::Definition 436 highlight_name(db, def) | HighlightModifier::Definition
364 } 437 }
365 Some(NameClass::ConstReference(def)) => highlight_name(db, def), 438 Some(NameClass::ConstReference(def)) => highlight_name(db, def),
439 Some(NameClass::FieldShorthand { .. }) => HighlightTag::Field.into(),
366 None => highlight_name_by_syntax(name) | HighlightModifier::Definition, 440 None => highlight_name_by_syntax(name) | HighlightModifier::Definition,
367 } 441 }
368 } 442 }
@@ -406,6 +480,19 @@ fn highlight_element(
406 _ => h, 480 _ => h,
407 } 481 }
408 } 482 }
483 T![*] => {
484 let prefix_expr = element.parent().and_then(ast::PrefixExpr::cast)?;
485
486 let expr = prefix_expr.expr()?;
487 let ty = sema.type_of_expr(&expr)?;
488 if !ty.is_raw_ptr() {
489 return None;
490 }
491
492 let mut h = Highlight::new(HighlightTag::Operator);
493 h |= HighlightModifier::Unsafe;
494 h
495 }
409 496
410 k if k.is_keyword() => { 497 k if k.is_keyword() => {
411 let h = Highlight::new(HighlightTag::Keyword); 498 let h = Highlight::new(HighlightTag::Keyword);
@@ -458,7 +545,13 @@ fn highlight_name(db: &RootDatabase, def: Definition) -> Highlight {
458 Definition::Field(_) => HighlightTag::Field, 545 Definition::Field(_) => HighlightTag::Field,
459 Definition::ModuleDef(def) => match def { 546 Definition::ModuleDef(def) => match def {
460 hir::ModuleDef::Module(_) => HighlightTag::Module, 547 hir::ModuleDef::Module(_) => HighlightTag::Module,
461 hir::ModuleDef::Function(_) => HighlightTag::Function, 548 hir::ModuleDef::Function(func) => {
549 let mut h = HighlightTag::Function.into();
550 if func.is_unsafe(db) {
551 h |= HighlightModifier::Unsafe;
552 }
553 return h;
554 }
462 hir::ModuleDef::Adt(hir::Adt::Struct(_)) => HighlightTag::Struct, 555 hir::ModuleDef::Adt(hir::Adt::Struct(_)) => HighlightTag::Struct,
463 hir::ModuleDef::Adt(hir::Adt::Enum(_)) => HighlightTag::Enum, 556 hir::ModuleDef::Adt(hir::Adt::Enum(_)) => HighlightTag::Enum,
464 hir::ModuleDef::Adt(hir::Adt::Union(_)) => HighlightTag::Union, 557 hir::ModuleDef::Adt(hir::Adt::Union(_)) => HighlightTag::Union,
@@ -516,42 +609,3 @@ fn highlight_name_by_syntax(name: ast::Name) -> Highlight {
516 609
517 tag.into() 610 tag.into()
518} 611}
519
520fn highlight_injection(
521 acc: &mut HighlightedRangeStack,
522 sema: &Semantics<RootDatabase>,
523 literal: ast::RawString,
524 expanded: SyntaxToken,
525) -> Option<()> {
526 let active_parameter = ActiveParameter::at_token(&sema, expanded)?;
527 if !active_parameter.name.starts_with("ra_fixture") {
528 return None;
529 }
530 let value = literal.value()?;
531 let (analysis, tmp_file_id) = Analysis::from_single_file(value);
532
533 if let Some(range) = literal.open_quote_text_range() {
534 acc.add(HighlightedRange {
535 range,
536 highlight: HighlightTag::StringLiteral.into(),
537 binding_hash: None,
538 })
539 }
540
541 for mut h in analysis.highlight(tmp_file_id).unwrap() {
542 if let Some(r) = literal.map_range_up(h.range) {
543 h.range = r;
544 acc.add(h)
545 }
546 }
547
548 if let Some(range) = literal.close_quote_text_range() {
549 acc.add(HighlightedRange {
550 range,
551 highlight: HighlightTag::StringLiteral.into(),
552 binding_hash: None,
553 })
554 }
555
556 Some(())
557}
diff --git a/crates/ra_ide/src/syntax_highlighting/html.rs b/crates/ra_ide/src/syntax_highlighting/html.rs
index edfe61f39..5bada6252 100644
--- a/crates/ra_ide/src/syntax_highlighting/html.rs
+++ b/crates/ra_ide/src/syntax_highlighting/html.rs
@@ -69,6 +69,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
69.string_literal { color: #CC9393; } 69.string_literal { color: #CC9393; }
70.field { color: #94BFF3; } 70.field { color: #94BFF3; }
71.function { color: #93E0E3; } 71.function { color: #93E0E3; }
72.function.unsafe { color: #BC8383; }
73.operator.unsafe { color: #BC8383; }
72.parameter { color: #94BFF3; } 74.parameter { color: #94BFF3; }
73.text { color: #DCDCCC; } 75.text { color: #DCDCCC; }
74.type { color: #7CB8BB; } 76.type { color: #7CB8BB; }
diff --git a/crates/ra_ide/src/syntax_highlighting/injection.rs b/crates/ra_ide/src/syntax_highlighting/injection.rs
new file mode 100644
index 000000000..3575a0fc6
--- /dev/null
+++ b/crates/ra_ide/src/syntax_highlighting/injection.rs
@@ -0,0 +1,168 @@
1//! Syntax highlighting injections such as highlighting of documentation tests.
2
3use std::{collections::BTreeMap, convert::TryFrom};
4
5use ast::{HasQuotes, HasStringValue};
6use hir::Semantics;
7use ra_syntax::{ast, AstToken, SyntaxNode, SyntaxToken, TextRange, TextSize};
8use stdx::SepBy;
9
10use crate::{call_info::ActiveParameter, Analysis, HighlightTag, HighlightedRange, RootDatabase};
11
12use super::HighlightedRangeStack;
13
14pub(super) fn highlight_injection(
15 acc: &mut HighlightedRangeStack,
16 sema: &Semantics<RootDatabase>,
17 literal: ast::RawString,
18 expanded: SyntaxToken,
19) -> Option<()> {
20 let active_parameter = ActiveParameter::at_token(&sema, expanded)?;
21 if !active_parameter.name.starts_with("ra_fixture") {
22 return None;
23 }
24 let value = literal.value()?;
25 let (analysis, tmp_file_id) = Analysis::from_single_file(value);
26
27 if let Some(range) = literal.open_quote_text_range() {
28 acc.add(HighlightedRange {
29 range,
30 highlight: HighlightTag::StringLiteral.into(),
31 binding_hash: None,
32 })
33 }
34
35 for mut h in analysis.highlight(tmp_file_id).unwrap() {
36 if let Some(r) = literal.map_range_up(h.range) {
37 h.range = r;
38 acc.add(h)
39 }
40 }
41
42 if let Some(range) = literal.close_quote_text_range() {
43 acc.add(HighlightedRange {
44 range,
45 highlight: HighlightTag::StringLiteral.into(),
46 binding_hash: None,
47 })
48 }
49
50 Some(())
51}
52
53/// Mapping from extracted documentation code to original code
54type RangesMap = BTreeMap<TextSize, TextSize>;
55
56/// Extracts Rust code from documentation comments as well as a mapping from
57/// the extracted source code back to the original source ranges.
58/// Lastly, a vector of new comment highlight ranges (spanning only the
59/// comment prefix) is returned which is used in the syntax highlighting
60/// injection to replace the previous (line-spanning) comment ranges.
61pub(super) fn extract_doc_comments(
62 node: &SyntaxNode,
63) -> Option<(String, RangesMap, Vec<HighlightedRange>)> {
64 // wrap the doctest into function body to get correct syntax highlighting
65 let prefix = "fn doctest() {\n";
66 let suffix = "}\n";
67 // Mapping from extracted documentation code to original code
68 let mut range_mapping: RangesMap = BTreeMap::new();
69 let mut line_start = TextSize::try_from(prefix.len()).unwrap();
70 let mut is_doctest = false;
71 // Replace the original, line-spanning comment ranges by new, only comment-prefix
72 // spanning comment ranges.
73 let mut new_comments = Vec::new();
74 let doctest = node
75 .children_with_tokens()
76 .filter_map(|el| el.into_token().and_then(ast::Comment::cast))
77 .filter(|comment| comment.kind().doc.is_some())
78 .filter(|comment| {
79 if comment.text().contains("```") {
80 is_doctest = !is_doctest;
81 false
82 } else {
83 is_doctest
84 }
85 })
86 .map(|comment| {
87 let prefix_len = comment.prefix().len();
88 let line: &str = comment.text().as_str();
89 let range = comment.syntax().text_range();
90
91 // whitespace after comment is ignored
92 let pos = if let Some(ws) = line.chars().nth(prefix_len).filter(|c| c.is_whitespace()) {
93 prefix_len + ws.len_utf8()
94 } else {
95 prefix_len
96 };
97
98 // lines marked with `#` should be ignored in output, we skip the `#` char
99 let pos = if let Some(ws) = line.chars().nth(pos).filter(|&c| c == '#') {
100 pos + ws.len_utf8()
101 } else {
102 pos
103 };
104
105 range_mapping.insert(line_start, range.start() + TextSize::try_from(pos).unwrap());
106 new_comments.push(HighlightedRange {
107 range: TextRange::new(
108 range.start(),
109 range.start() + TextSize::try_from(pos).unwrap(),
110 ),
111 highlight: HighlightTag::Comment.into(),
112 binding_hash: None,
113 });
114 line_start += range.len() - TextSize::try_from(pos).unwrap();
115 line_start += TextSize::try_from('\n'.len_utf8()).unwrap();
116
117 line[pos..].to_owned()
118 })
119 .sep_by("\n")
120 .to_string();
121
122 if doctest.is_empty() {
123 return None;
124 }
125
126 let doctest = format!("{}{}{}", prefix, doctest, suffix);
127 Some((doctest, range_mapping, new_comments))
128}
129
130/// Injection of syntax highlighting of doctests.
131pub(super) fn highlight_doc_comment(
132 text: String,
133 range_mapping: RangesMap,
134 new_comments: Vec<HighlightedRange>,
135 stack: &mut HighlightedRangeStack,
136) {
137 let (analysis, tmp_file_id) = Analysis::from_single_file(text);
138
139 stack.push();
140 for mut h in analysis.highlight(tmp_file_id).unwrap() {
141 // Determine start offset and end offset in case of multi-line ranges
142 let mut start_offset = None;
143 let mut end_offset = None;
144 for (line_start, orig_line_start) in range_mapping.range(..h.range.end()).rev() {
145 if line_start <= &h.range.start() {
146 start_offset.get_or_insert(orig_line_start - line_start);
147 break;
148 } else {
149 end_offset.get_or_insert(orig_line_start - line_start);
150 }
151 }
152 if let Some(start_offset) = start_offset {
153 h.range = TextRange::new(
154 h.range.start() + start_offset,
155 h.range.end() + end_offset.unwrap_or(start_offset),
156 );
157 stack.add(h);
158 }
159 }
160
161 // Inject the comment prefix highlight ranges
162 stack.push();
163 for comment in new_comments {
164 stack.add(comment);
165 }
166 stack.pop_and_inject(false);
167 stack.pop_and_inject(true);
168}
diff --git a/crates/ra_ide/src/syntax_highlighting/tags.rs b/crates/ra_ide/src/syntax_highlighting/tags.rs
index 1514531de..94f466966 100644
--- a/crates/ra_ide/src/syntax_highlighting/tags.rs
+++ b/crates/ra_ide/src/syntax_highlighting/tags.rs
@@ -24,12 +24,14 @@ pub enum HighlightTag {
24 Enum, 24 Enum,
25 EnumVariant, 25 EnumVariant,
26 Field, 26 Field,
27 FormatSpecifier,
27 Function, 28 Function,
28 Keyword, 29 Keyword,
29 Lifetime, 30 Lifetime,
30 Macro, 31 Macro,
31 Module, 32 Module,
32 NumericLiteral, 33 NumericLiteral,
34 Operator,
33 SelfKeyword, 35 SelfKeyword,
34 SelfType, 36 SelfType,
35 Static, 37 Static,
@@ -41,8 +43,6 @@ pub enum HighlightTag {
41 Union, 43 Union,
42 Local, 44 Local,
43 UnresolvedReference, 45 UnresolvedReference,
44 FormatSpecifier,
45 Operator,
46} 46}
47 47
48#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] 48#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
@@ -72,12 +72,14 @@ impl HighlightTag {
72 HighlightTag::Enum => "enum", 72 HighlightTag::Enum => "enum",
73 HighlightTag::EnumVariant => "enum_variant", 73 HighlightTag::EnumVariant => "enum_variant",
74 HighlightTag::Field => "field", 74 HighlightTag::Field => "field",
75 HighlightTag::FormatSpecifier => "format_specifier",
75 HighlightTag::Function => "function", 76 HighlightTag::Function => "function",
76 HighlightTag::Keyword => "keyword", 77 HighlightTag::Keyword => "keyword",
77 HighlightTag::Lifetime => "lifetime", 78 HighlightTag::Lifetime => "lifetime",
78 HighlightTag::Macro => "macro", 79 HighlightTag::Macro => "macro",
79 HighlightTag::Module => "module", 80 HighlightTag::Module => "module",
80 HighlightTag::NumericLiteral => "numeric_literal", 81 HighlightTag::NumericLiteral => "numeric_literal",
82 HighlightTag::Operator => "operator",
81 HighlightTag::SelfKeyword => "self_keyword", 83 HighlightTag::SelfKeyword => "self_keyword",
82 HighlightTag::SelfType => "self_type", 84 HighlightTag::SelfType => "self_type",
83 HighlightTag::Static => "static", 85 HighlightTag::Static => "static",
@@ -89,8 +91,6 @@ impl HighlightTag {
89 HighlightTag::Union => "union", 91 HighlightTag::Union => "union",
90 HighlightTag::Local => "variable", 92 HighlightTag::Local => "variable",
91 HighlightTag::UnresolvedReference => "unresolved_reference", 93 HighlightTag::UnresolvedReference => "unresolved_reference",
92 HighlightTag::FormatSpecifier => "format_specifier",
93 HighlightTag::Operator => "operator",
94 } 94 }
95 } 95 }
96} 96}
diff --git a/crates/ra_ide/src/syntax_highlighting/tests.rs b/crates/ra_ide/src/syntax_highlighting/tests.rs
index eb43a23da..949bf59a0 100644
--- a/crates/ra_ide/src/syntax_highlighting/tests.rs
+++ b/crates/ra_ide/src/syntax_highlighting/tests.rs
@@ -9,7 +9,7 @@ use crate::{
9 9
10#[test] 10#[test]
11fn test_highlighting() { 11fn test_highlighting() {
12 let (analysis, file_id) = single_file( 12 check_highlighting(
13 r#" 13 r#"
14#[derive(Clone, Debug)] 14#[derive(Clone, Debug)]
15struct Foo { 15struct Foo {
@@ -65,6 +65,8 @@ fn main() {
65 let y = &mut x; 65 let y = &mut x;
66 let z = &y; 66 let z = &y;
67 67
68 let Foo { x: z, y } = Foo { x: z, y };
69
68 y; 70 y;
69} 71}
70 72
@@ -84,17 +86,14 @@ impl<T> Option<T> {
84} 86}
85"# 87"#
86 .trim(), 88 .trim(),
89 "crates/ra_ide/src/snapshots/highlighting.html",
90 false,
87 ); 91 );
88 let dst_file = project_dir().join("crates/ra_ide/src/snapshots/highlighting.html");
89 let actual_html = &analysis.highlight_as_html(file_id, false).unwrap();
90 let expected_html = &read_text(&dst_file);
91 fs::write(dst_file, &actual_html).unwrap();
92 assert_eq_text!(expected_html, actual_html);
93} 92}
94 93
95#[test] 94#[test]
96fn test_rainbow_highlighting() { 95fn test_rainbow_highlighting() {
97 let (analysis, file_id) = single_file( 96 check_highlighting(
98 r#" 97 r#"
99fn main() { 98fn main() {
100 let hello = "hello"; 99 let hello = "hello";
@@ -110,12 +109,9 @@ fn bar() {
110} 109}
111"# 110"#
112 .trim(), 111 .trim(),
112 "crates/ra_ide/src/snapshots/rainbow_highlighting.html",
113 true,
113 ); 114 );
114 let dst_file = project_dir().join("crates/ra_ide/src/snapshots/rainbow_highlighting.html");
115 let actual_html = &analysis.highlight_as_html(file_id, true).unwrap();
116 let expected_html = &read_text(&dst_file);
117 fs::write(dst_file, &actual_html).unwrap();
118 assert_eq_text!(expected_html, actual_html);
119} 115}
120 116
121#[test] 117#[test]
@@ -153,7 +149,7 @@ fn test_ranges() {
153 149
154#[test] 150#[test]
155fn test_flattening() { 151fn test_flattening() {
156 let (analysis, file_id) = single_file( 152 check_highlighting(
157 r##" 153 r##"
158fn fixture(ra_fixture: &str) {} 154fn fixture(ra_fixture: &str) {}
159 155
@@ -167,13 +163,9 @@ fn main() {
167 ); 163 );
168}"## 164}"##
169 .trim(), 165 .trim(),
166 "crates/ra_ide/src/snapshots/highlight_injection.html",
167 false,
170 ); 168 );
171
172 let dst_file = project_dir().join("crates/ra_ide/src/snapshots/highlight_injection.html");
173 let actual_html = &analysis.highlight_as_html(file_id, false).unwrap();
174 let expected_html = &read_text(&dst_file);
175 fs::write(dst_file, &actual_html).unwrap();
176 assert_eq_text!(expected_html, actual_html);
177} 169}
178 170
179#[test] 171#[test]
@@ -192,7 +184,7 @@ macro_rules! test {}
192fn test_string_highlighting() { 184fn test_string_highlighting() {
193 // The format string detection is based on macro-expansion, 185 // The format string detection is based on macro-expansion,
194 // thus, we have to copy the macro definition from `std` 186 // thus, we have to copy the macro definition from `std`
195 let (analysis, file_id) = single_file( 187 check_highlighting(
196 r#" 188 r#"
197macro_rules! println { 189macro_rules! println {
198 ($($arg:tt)*) => ({ 190 ($($arg:tt)*) => ({
@@ -218,6 +210,7 @@ fn main() {
218 println!("{argument}", argument = "test"); // => "test" 210 println!("{argument}", argument = "test"); // => "test"
219 println!("{name} {}", 1, name = 2); // => "2 1" 211 println!("{name} {}", 1, name = 2); // => "2 1"
220 println!("{a} {c} {b}", a="a", b='b', c=3); // => "a 3 b" 212 println!("{a} {c} {b}", a="a", b='b', c=3); // => "a 3 b"
213 println!("{{{}}}", 2); // => "{2}"
221 println!("Hello {:5}!", "x"); 214 println!("Hello {:5}!", "x");
222 println!("Hello {:1$}!", "x", 5); 215 println!("Hello {:1$}!", "x", 5);
223 println!("Hello {1:0$}!", 5, "x"); 216 println!("Hello {1:0$}!", 5, "x");
@@ -249,10 +242,96 @@ fn main() {
249 println!("{ничоси}", ничоси = 92); 242 println!("{ничоси}", ничоси = 92);
250}"# 243}"#
251 .trim(), 244 .trim(),
245 "crates/ra_ide/src/snapshots/highlight_strings.html",
246 false,
252 ); 247 );
248}
249
250#[test]
251fn test_unsafe_highlighting() {
252 check_highlighting(
253 r#"
254unsafe fn unsafe_fn() {}
255
256struct HasUnsafeFn;
257
258impl HasUnsafeFn {
259 unsafe fn unsafe_method(&self) {}
260}
261
262fn main() {
263 let x = &5 as *const usize;
264 unsafe {
265 unsafe_fn();
266 HasUnsafeFn.unsafe_method();
267 let y = *(x);
268 let z = -x;
269 }
270}
271"#
272 .trim(),
273 "crates/ra_ide/src/snapshots/highlight_unsafe.html",
274 false,
275 );
276}
277
278#[test]
279fn test_highlight_doctest() {
280 check_highlighting(
281 r#"
282impl Foo {
283 /// Constructs a new `Foo`.
284 ///
285 /// # Examples
286 ///
287 /// ```
288 /// # #![allow(unused_mut)]
289 /// let mut foo: Foo = Foo::new();
290 /// ```
291 pub const fn new() -> Foo {
292 Foo { }
293 }
294
295 /// `bar` method on `Foo`.
296 ///
297 /// # Examples
298 ///
299 /// ```
300 /// let foo = Foo::new();
301 ///
302 /// // calls bar on foo
303 /// assert!(foo.bar());
304 ///
305 /// /* multi-line
306 /// comment */
307 ///
308 /// let multi_line_string = "Foo
309 /// bar
310 /// ";
311 ///
312 /// ```
313 ///
314 /// ```
315 /// let foobar = Foo::new().bar();
316 /// ```
317 pub fn foo(&self) -> bool {
318 true
319 }
320}
321"#
322 .trim(),
323 "crates/ra_ide/src/snapshots/highlight_doctest.html",
324 false,
325 )
326}
253 327
254 let dst_file = project_dir().join("crates/ra_ide/src/snapshots/highlight_strings.html"); 328/// Highlights the code given by the `ra_fixture` argument, renders the
255 let actual_html = &analysis.highlight_as_html(file_id, false).unwrap(); 329/// result as HTML, and compares it with the HTML file given as `snapshot`.
330/// Note that the `snapshot` file is overwritten by the rendered HTML.
331fn check_highlighting(ra_fixture: &str, snapshot: &str, rainbow: bool) {
332 let (analysis, file_id) = single_file(ra_fixture);
333 let dst_file = project_dir().join(snapshot);
334 let actual_html = &analysis.highlight_as_html(file_id, rainbow).unwrap();
256 let expected_html = &read_text(&dst_file); 335 let expected_html = &read_text(&dst_file);
257 fs::write(dst_file, &actual_html).unwrap(); 336 fs::write(dst_file, &actual_html).unwrap();
258 assert_eq_text!(expected_html, actual_html); 337 assert_eq_text!(expected_html, actual_html);
diff --git a/crates/ra_ide/src/typing.rs b/crates/ra_ide/src/typing.rs
index 67e2c33a0..83776d2b6 100644
--- a/crates/ra_ide/src/typing.rs
+++ b/crates/ra_ide/src/typing.rs
@@ -17,11 +17,13 @@ mod on_enter;
17 17
18use ra_db::{FilePosition, SourceDatabase}; 18use ra_db::{FilePosition, SourceDatabase};
19use ra_fmt::leading_indent; 19use ra_fmt::leading_indent;
20use ra_ide_db::RootDatabase; 20use ra_ide_db::{source_change::SourceFileEdit, RootDatabase};
21use ra_syntax::{ 21use ra_syntax::{
22 algo::find_node_at_offset, 22 algo::find_node_at_offset,
23 ast::{self, AstToken}, 23 ast::{self, AstToken},
24 AstNode, SourceFile, TextRange, TextSize, 24 AstNode, SourceFile,
25 SyntaxKind::{FIELD_EXPR, METHOD_CALL_EXPR},
26 TextRange, TextSize,
25}; 27};
26 28
27use ra_text_edit::TextEdit; 29use ra_text_edit::TextEdit;
@@ -47,8 +49,8 @@ pub(crate) fn on_char_typed(
47 assert!(TRIGGER_CHARS.contains(char_typed)); 49 assert!(TRIGGER_CHARS.contains(char_typed));
48 let file = &db.parse(position.file_id).tree(); 50 let file = &db.parse(position.file_id).tree();
49 assert_eq!(file.syntax().text().char_at(position.offset), Some(char_typed)); 51 assert_eq!(file.syntax().text().char_at(position.offset), Some(char_typed));
50 let text_edit = on_char_typed_inner(file, position.offset, char_typed)?; 52 let edit = on_char_typed_inner(file, position.offset, char_typed)?;
51 Some(SourceChange::source_file_edit_from(position.file_id, text_edit)) 53 Some(SourceFileEdit { file_id: position.file_id, edit }.into())
52} 54}
53 55
54fn on_char_typed_inner(file: &SourceFile, offset: TextSize, char_typed: char) -> Option<TextEdit> { 56fn on_char_typed_inner(file: &SourceFile, offset: TextSize, char_typed: char) -> Option<TextEdit> {
@@ -98,9 +100,12 @@ fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
98 }; 100 };
99 let current_indent_len = TextSize::of(current_indent); 101 let current_indent_len = TextSize::of(current_indent);
100 102
103 let parent = whitespace.syntax().parent();
101 // Make sure dot is a part of call chain 104 // Make sure dot is a part of call chain
102 let field_expr = ast::FieldExpr::cast(whitespace.syntax().parent())?; 105 if !matches!(parent.kind(), FIELD_EXPR | METHOD_CALL_EXPR) {
103 let prev_indent = leading_indent(field_expr.syntax())?; 106 return None;
107 }
108 let prev_indent = leading_indent(&parent)?;
104 let target_indent = format!(" {}", prev_indent); 109 let target_indent = format!(" {}", prev_indent);
105 let target_indent_len = TextSize::of(&target_indent); 110 let target_indent_len = TextSize::of(&target_indent);
106 if current_indent_len == target_indent_len { 111 if current_indent_len == target_indent_len {
@@ -143,11 +148,11 @@ mod tests {
143 }) 148 })
144 } 149 }
145 150
146 fn type_char(char_typed: char, before: &str, after: &str) { 151 fn type_char(char_typed: char, ra_fixture_before: &str, ra_fixture_after: &str) {
147 let actual = do_type_char(char_typed, before) 152 let actual = do_type_char(char_typed, ra_fixture_before)
148 .unwrap_or_else(|| panic!("typing `{}` did nothing", char_typed)); 153 .unwrap_or_else(|| panic!("typing `{}` did nothing", char_typed));
149 154
150 assert_eq_text!(after, &actual); 155 assert_eq_text!(ra_fixture_after, &actual);
151 } 156 }
152 157
153 fn type_char_noop(char_typed: char, before: &str) { 158 fn type_char_noop(char_typed: char, before: &str) {
@@ -249,6 +254,27 @@ fn foo() {
249 } 254 }
250 255
251 #[test] 256 #[test]
257 fn indents_new_chain_call_with_let() {
258 type_char(
259 '.',
260 r#"
261fn main() {
262 let _ = foo
263 <|>
264 bar()
265}
266"#,
267 r#"
268fn main() {
269 let _ = foo
270 .
271 bar()
272}
273"#,
274 );
275 }
276
277 #[test]
252 fn indents_continued_chain_call() { 278 fn indents_continued_chain_call() {
253 type_char( 279 type_char(
254 '.', 280 '.',