diff options
author | Mikhail Rakhmanov <[email protected]> | 2020-06-13 07:42:15 +0100 |
---|---|---|
committer | Mikhail Rakhmanov <[email protected]> | 2020-06-13 07:42:15 +0100 |
commit | 16bbf4ab7f132e6e5e5318dccdef9a5d71afdd7f (patch) | |
tree | 4b79fa8c046be56b02427ba843e70cdf3ac05767 /crates/ra_ide | |
parent | eeb8b9e236796da8734ba81a49164864497f7226 (diff) | |
parent | b56ad148db0c69eb279c225f45d324b4e80e7367 (diff) |
Merge branch 'master' into keyword_completion
# Conflicts:
# docs/user/generated_features.adoc
Diffstat (limited to 'crates/ra_ide')
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)] | ||
131 | mod 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 | }; |
22 | use ra_text_edit::{TextEdit, TextEditBuilder}; | 22 | use ra_text_edit::{TextEdit, TextEditBuilder}; |
23 | 23 | ||
24 | use crate::{Diagnostic, FileId, FileSystemEdit, Fix, SourceChange, SourceFileEdit}; | 24 | use crate::{Diagnostic, FileId, FileSystemEdit, Fix, SourceFileEdit}; |
25 | 25 | ||
26 | #[derive(Debug, Copy, Clone)] | 26 | #[derive(Debug, Copy, Clone)] |
27 | pub enum Severity { | 27 | pub 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::{ | |||
10 | use hir::{Docs, Documentation, HasSource, HirDisplay}; | 10 | use hir::{Docs, Documentation, HasSource, HirDisplay}; |
11 | use ra_ide_db::RootDatabase; | 11 | use ra_ide_db::RootDatabase; |
12 | use ra_syntax::ast::{self, AstNode, NameOwner, VisibilityOwner}; | 12 | use ra_syntax::ast::{self, AstNode, NameOwner, VisibilityOwner}; |
13 | use stdx::SepBy; | 13 | use stdx::{split1, SepBy}; |
14 | 14 | ||
15 | use crate::display::{generic_parameters, where_predicates}; | 15 | use 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 @@ | |||
1 | use hir::Semantics; | 1 | use hir::Semantics; |
2 | use ra_ide_db::{ | 2 | use 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 | }; |
6 | use ra_syntax::{ | 6 | use 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 @@ | |||
1 | use std::iter::once; | 1 | use std::iter::once; |
2 | 2 | ||
3 | use hir::{ | 3 | use 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 | }; |
7 | use itertools::Itertools; | 7 | use itertools::Itertools; |
8 | use ra_db::SourceDatabase; | 8 | use 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 | }; |
13 | use ra_syntax::{ | 13 | use 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 | ||
20 | use crate::{ | 15 | use 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 | }; |
20 | use test_utils::mark; | ||
21 | |||
22 | #[derive(Clone, Debug, PartialEq, Eq)] | ||
23 | pub struct HoverConfig { | ||
24 | pub implementations: bool, | ||
25 | pub run: bool, | ||
26 | pub debug: bool, | ||
27 | } | ||
28 | |||
29 | impl Default for HoverConfig { | ||
30 | fn default() -> Self { | ||
31 | Self { implementations: true, run: true, debug: true } | ||
32 | } | ||
33 | } | ||
34 | |||
35 | impl 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)] | ||
52 | pub 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)] |
27 | pub struct HoverResult { | 59 | pub struct HoverResult { |
28 | results: Vec<String>, | 60 | results: Vec<String>, |
61 | actions: Vec<HoverAction>, | ||
29 | } | 62 | } |
30 | 63 | ||
31 | impl HoverResult { | 64 | impl 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 | ||
171 | fn 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 | |||
191 | fn 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 | |||
120 | fn hover_text( | 221 | fn 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)] |
231 | mod tests { | 336 | mod 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 | ||
460 | fn main() { | 576 | fn 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 | ||
418 | fn main() { | 417 | fn 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 | ||
79 | pub use hir::Documentation; | 79 | pub use hir::Documentation; |
80 | pub use ra_assists::{AssistConfig, AssistId}; | 80 | pub use ra_assists::{Assist, AssistConfig, AssistId, ResolvedAssist}; |
81 | pub use ra_db::{ | 81 | pub 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)] | ||
146 | pub struct Assist { | ||
147 | pub id: AssistId, | ||
148 | pub label: String, | ||
149 | pub group_label: Option<String>, | ||
150 | pub source_change: SourceChange, | ||
151 | } | ||
152 | |||
153 | impl AnalysisHost { | 145 | impl 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 | ||
177 | fn text_edit_from_self_param( | 177 | fn 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 | ||
240 | fn rename_reference( | 240 | fn 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 @@ | |||
1 | use std::fmt; | ||
2 | |||
1 | use hir::{AsAssocItem, Attrs, HirFileId, InFile, Semantics}; | 3 | use hir::{AsAssocItem, Attrs, HirFileId, InFile, Semantics}; |
2 | use itertools::Itertools; | 4 | use itertools::Itertools; |
5 | use ra_cfg::CfgExpr; | ||
3 | use ra_ide_db::RootDatabase; | 6 | use ra_ide_db::RootDatabase; |
4 | use ra_syntax::{ | 7 | use 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 | ||
9 | use crate::FileId; | 12 | use crate::{display::ToNav, FileId, NavigationTarget}; |
10 | use ast::DocCommentsOwner; | ||
11 | use ra_cfg::CfgExpr; | ||
12 | use std::fmt::Display; | ||
13 | 13 | ||
14 | #[derive(Debug)] | 14 | #[derive(Debug, Clone)] |
15 | pub struct Runnable { | 15 | pub 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)] |
22 | pub enum TestId { | 22 | pub enum TestId { |
23 | Name(String), | 23 | Name(String), |
24 | Path(String), | 24 | Path(String), |
25 | } | 25 | } |
26 | 26 | ||
27 | impl Display for TestId { | 27 | impl 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)] |
37 | pub enum RunnableKind { | 37 | pub 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)] | ||
46 | pub struct RunnableAction { | ||
47 | pub run_title: &'static str, | ||
48 | pub debugee: bool, | ||
49 | } | ||
50 | |||
51 | const TEST: RunnableAction = RunnableAction { run_title: "▶\u{fe0e} Run Test", debugee: true }; | ||
52 | const DOCTEST: RunnableAction = | ||
53 | RunnableAction { run_title: "▶\u{fe0e} Run Doctest", debugee: false }; | ||
54 | const BENCH: RunnableAction = RunnableAction { run_title: "▶\u{fe0e} Run Bench", debugee: true }; | ||
55 | const BIN: RunnableAction = RunnableAction { run_title: "▶\u{fe0e} Run", debugee: true }; | ||
56 | |||
57 | impl 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 | ||
62 | fn runnable(sema: &Semantics<RootDatabase>, item: SyntaxNode, file_id: FileId) -> Option<Runnable> { | 98 | pub(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)] |
138 | pub struct TestAttr { | 179 | pub 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> | ||
3 | body { margin: 0; } | ||
4 | pre { 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>() -> <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>) -> <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">// => "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">// => "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">// => "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">// => "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">// => "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">// => "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">// => "{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">></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">></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> | ||
3 | body { margin: 0; } | ||
4 | pre { 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><<span class="type_param declaration">T</span>> { | 92 | <span class="keyword">enum</span> <span class="enum declaration">Option</span><<span class="type_param declaration">T</span>> { |
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 @@ | |||
1 | mod tags; | 1 | mod tags; |
2 | mod html; | 2 | mod html; |
3 | mod injection; | ||
3 | #[cfg(test)] | 4 | #[cfg(test)] |
4 | mod tests; | 5 | mod tests; |
5 | 6 | ||
@@ -10,14 +11,14 @@ use ra_ide_db::{ | |||
10 | }; | 11 | }; |
11 | use ra_prof::profile; | 12 | use ra_prof::profile; |
12 | use ra_syntax::{ | 13 | use 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 | }; |
18 | use rustc_hash::FxHashMap; | 19 | use rustc_hash::FxHashMap; |
19 | 20 | ||
20 | use crate::{call_info::ActiveParameter, Analysis, FileId}; | 21 | use crate::FileId; |
21 | 22 | ||
22 | use ast::FormatSpecifier; | 23 | use ast::FormatSpecifier; |
23 | pub(crate) use html::highlight_as_html; | 24 | pub(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 | |||
520 | fn 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 | |||
3 | use std::{collections::BTreeMap, convert::TryFrom}; | ||
4 | |||
5 | use ast::{HasQuotes, HasStringValue}; | ||
6 | use hir::Semantics; | ||
7 | use ra_syntax::{ast, AstToken, SyntaxNode, SyntaxToken, TextRange, TextSize}; | ||
8 | use stdx::SepBy; | ||
9 | |||
10 | use crate::{call_info::ActiveParameter, Analysis, HighlightTag, HighlightedRange, RootDatabase}; | ||
11 | |||
12 | use super::HighlightedRangeStack; | ||
13 | |||
14 | pub(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 | ||
54 | type 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. | ||
61 | pub(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. | ||
131 | pub(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] |
11 | fn test_highlighting() { | 11 | fn test_highlighting() { |
12 | let (analysis, file_id) = single_file( | 12 | check_highlighting( |
13 | r#" | 13 | r#" |
14 | #[derive(Clone, Debug)] | 14 | #[derive(Clone, Debug)] |
15 | struct Foo { | 15 | struct 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] |
96 | fn test_rainbow_highlighting() { | 95 | fn test_rainbow_highlighting() { |
97 | let (analysis, file_id) = single_file( | 96 | check_highlighting( |
98 | r#" | 97 | r#" |
99 | fn main() { | 98 | fn 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] |
155 | fn test_flattening() { | 151 | fn test_flattening() { |
156 | let (analysis, file_id) = single_file( | 152 | check_highlighting( |
157 | r##" | 153 | r##" |
158 | fn fixture(ra_fixture: &str) {} | 154 | fn 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 {} | |||
192 | fn test_string_highlighting() { | 184 | fn 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#" |
197 | macro_rules! println { | 189 | macro_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] | ||
251 | fn test_unsafe_highlighting() { | ||
252 | check_highlighting( | ||
253 | r#" | ||
254 | unsafe fn unsafe_fn() {} | ||
255 | |||
256 | struct HasUnsafeFn; | ||
257 | |||
258 | impl HasUnsafeFn { | ||
259 | unsafe fn unsafe_method(&self) {} | ||
260 | } | ||
261 | |||
262 | fn 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] | ||
279 | fn test_highlight_doctest() { | ||
280 | check_highlighting( | ||
281 | r#" | ||
282 | impl 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. | ||
331 | fn 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 | ||
18 | use ra_db::{FilePosition, SourceDatabase}; | 18 | use ra_db::{FilePosition, SourceDatabase}; |
19 | use ra_fmt::leading_indent; | 19 | use ra_fmt::leading_indent; |
20 | use ra_ide_db::RootDatabase; | 20 | use ra_ide_db::{source_change::SourceFileEdit, RootDatabase}; |
21 | use ra_syntax::{ | 21 | use 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 | ||
27 | use ra_text_edit::TextEdit; | 29 | use 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 | ||
54 | fn on_char_typed_inner(file: &SourceFile, offset: TextSize, char_typed: char) -> Option<TextEdit> { | 56 | fn 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#" | ||
261 | fn main() { | ||
262 | let _ = foo | ||
263 | <|> | ||
264 | bar() | ||
265 | } | ||
266 | "#, | ||
267 | r#" | ||
268 | fn 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 | '.', |