diff options
Diffstat (limited to 'crates/ra_ide/src')
41 files changed, 1505 insertions, 391 deletions
diff --git a/crates/ra_ide/src/call_hierarchy.rs b/crates/ra_ide/src/call_hierarchy.rs index 85d1f0cb1..defd8176f 100644 --- a/crates/ra_ide/src/call_hierarchy.rs +++ b/crates/ra_ide/src/call_hierarchy.rs | |||
| @@ -246,6 +246,35 @@ mod tests { | |||
| 246 | } | 246 | } |
| 247 | 247 | ||
| 248 | #[test] | 248 | #[test] |
| 249 | fn test_call_hierarchy_in_tests_mod() { | ||
| 250 | check_hierarchy( | ||
| 251 | r#" | ||
| 252 | //- /lib.rs cfg:test | ||
| 253 | fn callee() {} | ||
| 254 | fn caller1() { | ||
| 255 | call<|>ee(); | ||
| 256 | } | ||
| 257 | |||
| 258 | #[cfg(test)] | ||
| 259 | mod tests { | ||
| 260 | use super::*; | ||
| 261 | |||
| 262 | #[test] | ||
| 263 | fn test_caller() { | ||
| 264 | callee(); | ||
| 265 | } | ||
| 266 | } | ||
| 267 | "#, | ||
| 268 | "callee FN_DEF FileId(1) 0..14 3..9", | ||
| 269 | &[ | ||
| 270 | "caller1 FN_DEF FileId(1) 15..45 18..25 : [34..40]", | ||
| 271 | "test_caller FN_DEF FileId(1) 93..147 108..119 : [132..138]", | ||
| 272 | ], | ||
| 273 | &[], | ||
| 274 | ); | ||
| 275 | } | ||
| 276 | |||
| 277 | #[test] | ||
| 249 | fn test_call_hierarchy_in_different_files() { | 278 | fn test_call_hierarchy_in_different_files() { |
| 250 | check_hierarchy( | 279 | check_hierarchy( |
| 251 | r#" | 280 | r#" |
diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs index 191300704..a721e23c6 100644 --- a/crates/ra_ide/src/completion.rs +++ b/crates/ra_ide/src/completion.rs | |||
| @@ -1,5 +1,3 @@ | |||
| 1 | //! FIXME: write short doc here | ||
| 2 | |||
| 3 | mod completion_config; | 1 | mod completion_config; |
| 4 | mod completion_item; | 2 | mod completion_item; |
| 5 | mod completion_context; | 3 | mod completion_context; |
| @@ -35,6 +33,51 @@ pub use crate::completion::{ | |||
| 35 | completion_item::{CompletionItem, CompletionItemKind, CompletionScore, InsertTextFormat}, | 33 | completion_item::{CompletionItem, CompletionItemKind, CompletionScore, InsertTextFormat}, |
| 36 | }; | 34 | }; |
| 37 | 35 | ||
| 36 | //FIXME: split the following feature into fine-grained features. | ||
| 37 | |||
| 38 | // Feature: Magic Completions | ||
| 39 | // | ||
| 40 | // In addition to usual reference completion, rust-analyzer provides some ✨magic✨ | ||
| 41 | // completions as well: | ||
| 42 | // | ||
| 43 | // Keywords like `if`, `else` `while`, `loop` are completed with braces, and cursor | ||
| 44 | // is placed at the appropriate position. Even though `if` is easy to type, you | ||
| 45 | // still want to complete it, to get ` { }` for free! `return` is inserted with a | ||
| 46 | // space or `;` depending on the return type of the function. | ||
| 47 | // | ||
| 48 | // When completing a function call, `()` are automatically inserted. If a function | ||
| 49 | // takes arguments, the cursor is positioned inside the parenthesis. | ||
| 50 | // | ||
| 51 | // There are postfix completions, which can be triggered by typing something like | ||
| 52 | // `foo().if`. The word after `.` determines postfix completion. Possible variants are: | ||
| 53 | // | ||
| 54 | // - `expr.if` -> `if expr {}` or `if let ... {}` for `Option` or `Result` | ||
| 55 | // - `expr.match` -> `match expr {}` | ||
| 56 | // - `expr.while` -> `while expr {}` or `while let ... {}` for `Option` or `Result` | ||
| 57 | // - `expr.ref` -> `&expr` | ||
| 58 | // - `expr.refm` -> `&mut expr` | ||
| 59 | // - `expr.not` -> `!expr` | ||
| 60 | // - `expr.dbg` -> `dbg!(expr)` | ||
| 61 | // | ||
| 62 | // There also snippet completions: | ||
| 63 | // | ||
| 64 | // .Expressions | ||
| 65 | // - `pd` -> `println!("{:?}")` | ||
| 66 | // - `ppd` -> `println!("{:#?}")` | ||
| 67 | // | ||
| 68 | // .Items | ||
| 69 | // - `tfn` -> `#[test] fn f(){}` | ||
| 70 | // - `tmod` -> | ||
| 71 | // ```rust | ||
| 72 | // #[cfg(test)] | ||
| 73 | // mod tests { | ||
| 74 | // use super::*; | ||
| 75 | // | ||
| 76 | // #[test] | ||
| 77 | // fn test_fn() {} | ||
| 78 | // } | ||
| 79 | // ``` | ||
| 80 | |||
| 38 | /// Main entry point for completion. We run completion as a two-phase process. | 81 | /// Main entry point for completion. We run completion as a two-phase process. |
| 39 | /// | 82 | /// |
| 40 | /// First, we look at the position and collect a so-called `CompletionContext. | 83 | /// First, we look at the position and collect a so-called `CompletionContext. |
| @@ -82,3 +125,81 @@ pub(crate) fn completions( | |||
| 82 | 125 | ||
| 83 | Some(acc) | 126 | Some(acc) |
| 84 | } | 127 | } |
| 128 | |||
| 129 | #[cfg(test)] | ||
| 130 | mod tests { | ||
| 131 | use crate::completion::completion_config::CompletionConfig; | ||
| 132 | use crate::mock_analysis::analysis_and_position; | ||
| 133 | |||
| 134 | struct DetailAndDocumentation<'a> { | ||
| 135 | detail: &'a str, | ||
| 136 | documentation: &'a str, | ||
| 137 | } | ||
| 138 | |||
| 139 | fn check_detail_and_documentation(fixture: &str, expected: DetailAndDocumentation) { | ||
| 140 | let (analysis, position) = analysis_and_position(fixture); | ||
| 141 | let config = CompletionConfig::default(); | ||
| 142 | let completions = analysis.completions(&config, position).unwrap().unwrap(); | ||
| 143 | for item in completions { | ||
| 144 | if item.detail() == Some(expected.detail) { | ||
| 145 | let opt = item.documentation(); | ||
| 146 | let doc = opt.as_ref().map(|it| it.as_str()); | ||
| 147 | assert_eq!(doc, Some(expected.documentation)); | ||
| 148 | return; | ||
| 149 | } | ||
| 150 | } | ||
| 151 | panic!("completion detail not found: {}", expected.detail) | ||
| 152 | } | ||
| 153 | |||
| 154 | #[test] | ||
| 155 | fn test_completion_detail_from_macro_generated_struct_fn_doc_attr() { | ||
| 156 | check_detail_and_documentation( | ||
| 157 | r#" | ||
| 158 | //- /lib.rs | ||
| 159 | macro_rules! bar { | ||
| 160 | () => { | ||
| 161 | struct Bar; | ||
| 162 | impl Bar { | ||
| 163 | #[doc = "Do the foo"] | ||
| 164 | fn foo(&self) {} | ||
| 165 | } | ||
| 166 | } | ||
| 167 | } | ||
| 168 | |||
| 169 | bar!(); | ||
| 170 | |||
| 171 | fn foo() { | ||
| 172 | let bar = Bar; | ||
| 173 | bar.fo<|>; | ||
| 174 | } | ||
| 175 | "#, | ||
| 176 | DetailAndDocumentation { detail: "fn foo(&self)", documentation: "Do the foo" }, | ||
| 177 | ); | ||
| 178 | } | ||
| 179 | |||
| 180 | #[test] | ||
| 181 | fn test_completion_detail_from_macro_generated_struct_fn_doc_comment() { | ||
| 182 | check_detail_and_documentation( | ||
| 183 | r#" | ||
| 184 | //- /lib.rs | ||
| 185 | macro_rules! bar { | ||
| 186 | () => { | ||
| 187 | struct Bar; | ||
| 188 | impl Bar { | ||
| 189 | /// Do the foo | ||
| 190 | fn foo(&self) {} | ||
| 191 | } | ||
| 192 | } | ||
| 193 | } | ||
| 194 | |||
| 195 | bar!(); | ||
| 196 | |||
| 197 | fn foo() { | ||
| 198 | let bar = Bar; | ||
| 199 | bar.fo<|>; | ||
| 200 | } | ||
| 201 | "#, | ||
| 202 | DetailAndDocumentation { detail: "fn foo(&self)", documentation: " Do the foo" }, | ||
| 203 | ); | ||
| 204 | } | ||
| 205 | } | ||
diff --git a/crates/ra_ide/src/completion/complete_attribute.rs b/crates/ra_ide/src/completion/complete_attribute.rs index f17266221..fb3f0b743 100644 --- a/crates/ra_ide/src/completion/complete_attribute.rs +++ b/crates/ra_ide/src/completion/complete_attribute.rs | |||
| @@ -112,7 +112,7 @@ const ATTRIBUTES: &[AttrCompletion] = &[ | |||
| 112 | AttrCompletion { label: "repr", snippet: Some("repr(${0:C})"), should_be_inner: false }, | 112 | AttrCompletion { label: "repr", snippet: Some("repr(${0:C})"), should_be_inner: false }, |
| 113 | AttrCompletion { | 113 | AttrCompletion { |
| 114 | label: "should_panic", | 114 | label: "should_panic", |
| 115 | snippet: Some(r#"expected = "${0:reason}""#), | 115 | snippet: Some(r#"should_panic(expected = "${0:reason}")"#), |
| 116 | should_be_inner: false, | 116 | should_be_inner: false, |
| 117 | }, | 117 | }, |
| 118 | AttrCompletion { | 118 | AttrCompletion { |
| @@ -571,7 +571,7 @@ mod tests { | |||
| 571 | label: "should_panic", | 571 | label: "should_panic", |
| 572 | source_range: 19..19, | 572 | source_range: 19..19, |
| 573 | delete: 19..19, | 573 | delete: 19..19, |
| 574 | insert: "expected = \"${0:reason}\"", | 574 | insert: "should_panic(expected = \"${0:reason}\")", |
| 575 | kind: Attribute, | 575 | kind: Attribute, |
| 576 | }, | 576 | }, |
| 577 | CompletionItem { | 577 | CompletionItem { |
| @@ -810,7 +810,7 @@ mod tests { | |||
| 810 | label: "should_panic", | 810 | label: "should_panic", |
| 811 | source_range: 20..20, | 811 | source_range: 20..20, |
| 812 | delete: 20..20, | 812 | delete: 20..20, |
| 813 | insert: "expected = \"${0:reason}\"", | 813 | insert: "should_panic(expected = \"${0:reason}\")", |
| 814 | kind: Attribute, | 814 | kind: Attribute, |
| 815 | }, | 815 | }, |
| 816 | CompletionItem { | 816 | CompletionItem { |
diff --git a/crates/ra_ide/src/completion/complete_postfix.rs b/crates/ra_ide/src/completion/complete_postfix.rs index f2a52a407..59b58bf98 100644 --- a/crates/ra_ide/src/completion/complete_postfix.rs +++ b/crates/ra_ide/src/completion/complete_postfix.rs | |||
| @@ -1,12 +1,11 @@ | |||
| 1 | //! FIXME: write short doc here | 1 | //! FIXME: write short doc here |
| 2 | 2 | use ra_assists::utils::TryEnum; | |
| 3 | use ra_syntax::{ | 3 | use ra_syntax::{ |
| 4 | ast::{self, AstNode}, | 4 | ast::{self, AstNode}, |
| 5 | TextRange, TextSize, | 5 | TextRange, TextSize, |
| 6 | }; | 6 | }; |
| 7 | use ra_text_edit::TextEdit; | 7 | use ra_text_edit::TextEdit; |
| 8 | 8 | ||
| 9 | use super::completion_config::SnippetCap; | ||
| 10 | use crate::{ | 9 | use crate::{ |
| 11 | completion::{ | 10 | completion::{ |
| 12 | completion_context::CompletionContext, | 11 | completion_context::CompletionContext, |
| @@ -14,7 +13,8 @@ use crate::{ | |||
| 14 | }, | 13 | }, |
| 15 | CompletionItem, | 14 | CompletionItem, |
| 16 | }; | 15 | }; |
| 17 | use ra_assists::utils::TryEnum; | 16 | |
| 17 | use super::completion_config::SnippetCap; | ||
| 18 | 18 | ||
| 19 | pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { | 19 | pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { |
| 20 | if !ctx.config.enable_postfix_completions { | 20 | if !ctx.config.enable_postfix_completions { |
| @@ -184,6 +184,16 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { | |||
| 184 | &format!("dbg!({})", receiver_text), | 184 | &format!("dbg!({})", receiver_text), |
| 185 | ) | 185 | ) |
| 186 | .add_to(acc); | 186 | .add_to(acc); |
| 187 | |||
| 188 | postfix_snippet( | ||
| 189 | ctx, | ||
| 190 | cap, | ||
| 191 | &dot_receiver, | ||
| 192 | "call", | ||
| 193 | "function(expr)", | ||
| 194 | &format!("${{1}}({})", receiver_text), | ||
| 195 | ) | ||
| 196 | .add_to(acc); | ||
| 187 | } | 197 | } |
| 188 | 198 | ||
| 189 | fn get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal: bool) -> String { | 199 | fn get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal: bool) -> String { |
| @@ -256,6 +266,13 @@ mod tests { | |||
| 256 | detail: "Box::new(expr)", | 266 | detail: "Box::new(expr)", |
| 257 | }, | 267 | }, |
| 258 | CompletionItem { | 268 | CompletionItem { |
| 269 | label: "call", | ||
| 270 | source_range: 89..89, | ||
| 271 | delete: 85..89, | ||
| 272 | insert: "${1}(bar)", | ||
| 273 | detail: "function(expr)", | ||
| 274 | }, | ||
| 275 | CompletionItem { | ||
| 259 | label: "dbg", | 276 | label: "dbg", |
| 260 | source_range: 89..89, | 277 | source_range: 89..89, |
| 261 | delete: 85..89, | 278 | delete: 85..89, |
| @@ -335,6 +352,13 @@ mod tests { | |||
| 335 | detail: "Box::new(expr)", | 352 | detail: "Box::new(expr)", |
| 336 | }, | 353 | }, |
| 337 | CompletionItem { | 354 | CompletionItem { |
| 355 | label: "call", | ||
| 356 | source_range: 210..210, | ||
| 357 | delete: 206..210, | ||
| 358 | insert: "${1}(bar)", | ||
| 359 | detail: "function(expr)", | ||
| 360 | }, | ||
| 361 | CompletionItem { | ||
| 338 | label: "dbg", | 362 | label: "dbg", |
| 339 | source_range: 210..210, | 363 | source_range: 210..210, |
| 340 | delete: 206..210, | 364 | delete: 206..210, |
| @@ -414,6 +438,13 @@ mod tests { | |||
| 414 | detail: "Box::new(expr)", | 438 | detail: "Box::new(expr)", |
| 415 | }, | 439 | }, |
| 416 | CompletionItem { | 440 | CompletionItem { |
| 441 | label: "call", | ||
| 442 | source_range: 211..211, | ||
| 443 | delete: 207..211, | ||
| 444 | insert: "${1}(bar)", | ||
| 445 | detail: "function(expr)", | ||
| 446 | }, | ||
| 447 | CompletionItem { | ||
| 417 | label: "dbg", | 448 | label: "dbg", |
| 418 | source_range: 211..211, | 449 | source_range: 211..211, |
| 419 | delete: 207..211, | 450 | delete: 207..211, |
| @@ -488,6 +519,13 @@ mod tests { | |||
| 488 | detail: "Box::new(expr)", | 519 | detail: "Box::new(expr)", |
| 489 | }, | 520 | }, |
| 490 | CompletionItem { | 521 | CompletionItem { |
| 522 | label: "call", | ||
| 523 | source_range: 91..91, | ||
| 524 | delete: 87..91, | ||
| 525 | insert: "${1}(bar)", | ||
| 526 | detail: "function(expr)", | ||
| 527 | }, | ||
| 528 | CompletionItem { | ||
| 491 | label: "dbg", | 529 | label: "dbg", |
| 492 | source_range: 91..91, | 530 | source_range: 91..91, |
| 493 | delete: 87..91, | 531 | delete: 87..91, |
| @@ -547,6 +585,13 @@ mod tests { | |||
| 547 | detail: "Box::new(expr)", | 585 | detail: "Box::new(expr)", |
| 548 | }, | 586 | }, |
| 549 | CompletionItem { | 587 | CompletionItem { |
| 588 | label: "call", | ||
| 589 | source_range: 52..52, | ||
| 590 | delete: 49..52, | ||
| 591 | insert: "${1}(42)", | ||
| 592 | detail: "function(expr)", | ||
| 593 | }, | ||
| 594 | CompletionItem { | ||
| 550 | label: "dbg", | 595 | label: "dbg", |
| 551 | source_range: 52..52, | 596 | source_range: 52..52, |
| 552 | delete: 49..52, | 597 | delete: 49..52, |
| @@ -608,6 +653,13 @@ mod tests { | |||
| 608 | detail: "Box::new(expr)", | 653 | detail: "Box::new(expr)", |
| 609 | }, | 654 | }, |
| 610 | CompletionItem { | 655 | CompletionItem { |
| 656 | label: "call", | ||
| 657 | source_range: 149..150, | ||
| 658 | delete: 145..150, | ||
| 659 | insert: "${1}(bar)", | ||
| 660 | detail: "function(expr)", | ||
| 661 | }, | ||
| 662 | CompletionItem { | ||
| 611 | label: "dbg", | 663 | label: "dbg", |
| 612 | source_range: 149..150, | 664 | source_range: 149..150, |
| 613 | delete: 145..150, | 665 | delete: 145..150, |
| @@ -667,6 +719,13 @@ mod tests { | |||
| 667 | detail: "Box::new(expr)", | 719 | detail: "Box::new(expr)", |
| 668 | }, | 720 | }, |
| 669 | CompletionItem { | 721 | CompletionItem { |
| 722 | label: "call", | ||
| 723 | source_range: 56..56, | ||
| 724 | delete: 49..56, | ||
| 725 | insert: "${1}(&&&&42)", | ||
| 726 | detail: "function(expr)", | ||
| 727 | }, | ||
| 728 | CompletionItem { | ||
| 670 | label: "dbg", | 729 | label: "dbg", |
| 671 | source_range: 56..56, | 730 | source_range: 56..56, |
| 672 | delete: 49..56, | 731 | delete: 49..56, |
diff --git a/crates/ra_ide/src/completion/complete_trait_impl.rs b/crates/ra_ide/src/completion/complete_trait_impl.rs index 039df03e0..21c9316e6 100644 --- a/crates/ra_ide/src/completion/complete_trait_impl.rs +++ b/crates/ra_ide/src/completion/complete_trait_impl.rs | |||
| @@ -49,56 +49,53 @@ use crate::{ | |||
| 49 | pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext) { | 49 | pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext) { |
| 50 | if let Some((trigger, impl_def)) = completion_match(ctx) { | 50 | if let Some((trigger, impl_def)) = completion_match(ctx) { |
| 51 | match trigger.kind() { | 51 | match trigger.kind() { |
| 52 | SyntaxKind::NAME_REF => { | 52 | SyntaxKind::NAME_REF => get_missing_assoc_items(&ctx.sema, &impl_def) |
| 53 | get_missing_assoc_items(&ctx.sema, &impl_def).iter().for_each(|item| match item { | 53 | .into_iter() |
| 54 | .for_each(|item| match item { | ||
| 54 | hir::AssocItem::Function(fn_item) => { | 55 | hir::AssocItem::Function(fn_item) => { |
| 55 | add_function_impl(&trigger, acc, ctx, &fn_item) | 56 | add_function_impl(&trigger, acc, ctx, fn_item) |
| 56 | } | 57 | } |
| 57 | hir::AssocItem::TypeAlias(type_item) => { | 58 | hir::AssocItem::TypeAlias(type_item) => { |
| 58 | add_type_alias_impl(&trigger, acc, ctx, &type_item) | 59 | add_type_alias_impl(&trigger, acc, ctx, type_item) |
| 59 | } | 60 | } |
| 60 | hir::AssocItem::Const(const_item) => { | 61 | hir::AssocItem::Const(const_item) => { |
| 61 | add_const_impl(&trigger, acc, ctx, &const_item) | 62 | add_const_impl(&trigger, acc, ctx, const_item) |
| 62 | } | 63 | } |
| 63 | }) | 64 | }), |
| 64 | } | ||
| 65 | 65 | ||
| 66 | SyntaxKind::FN_DEF => { | 66 | SyntaxKind::FN_DEF => { |
| 67 | for missing_fn in | 67 | for missing_fn in get_missing_assoc_items(&ctx.sema, &impl_def) |
| 68 | get_missing_assoc_items(&ctx.sema, &impl_def).iter().filter_map(|item| { | 68 | .into_iter() |
| 69 | match item { | 69 | .filter_map(|item| match item { |
| 70 | hir::AssocItem::Function(fn_item) => Some(fn_item), | 70 | hir::AssocItem::Function(fn_item) => Some(fn_item), |
| 71 | _ => None, | 71 | _ => None, |
| 72 | } | ||
| 73 | }) | 72 | }) |
| 74 | { | 73 | { |
| 75 | add_function_impl(&trigger, acc, ctx, &missing_fn); | 74 | add_function_impl(&trigger, acc, ctx, missing_fn); |
| 76 | } | 75 | } |
| 77 | } | 76 | } |
| 78 | 77 | ||
| 79 | SyntaxKind::TYPE_ALIAS_DEF => { | 78 | SyntaxKind::TYPE_ALIAS_DEF => { |
| 80 | for missing_fn in | 79 | for missing_fn in get_missing_assoc_items(&ctx.sema, &impl_def) |
| 81 | get_missing_assoc_items(&ctx.sema, &impl_def).iter().filter_map(|item| { | 80 | .into_iter() |
| 82 | match item { | 81 | .filter_map(|item| match item { |
| 83 | hir::AssocItem::TypeAlias(type_item) => Some(type_item), | 82 | hir::AssocItem::TypeAlias(type_item) => Some(type_item), |
| 84 | _ => None, | 83 | _ => None, |
| 85 | } | ||
| 86 | }) | 84 | }) |
| 87 | { | 85 | { |
| 88 | add_type_alias_impl(&trigger, acc, ctx, &missing_fn); | 86 | add_type_alias_impl(&trigger, acc, ctx, missing_fn); |
| 89 | } | 87 | } |
| 90 | } | 88 | } |
| 91 | 89 | ||
| 92 | SyntaxKind::CONST_DEF => { | 90 | SyntaxKind::CONST_DEF => { |
| 93 | for missing_fn in | 91 | for missing_fn in get_missing_assoc_items(&ctx.sema, &impl_def) |
| 94 | get_missing_assoc_items(&ctx.sema, &impl_def).iter().filter_map(|item| { | 92 | .into_iter() |
| 95 | match item { | 93 | .filter_map(|item| match item { |
| 96 | hir::AssocItem::Const(const_item) => Some(const_item), | 94 | hir::AssocItem::Const(const_item) => Some(const_item), |
| 97 | _ => None, | 95 | _ => None, |
| 98 | } | ||
| 99 | }) | 96 | }) |
| 100 | { | 97 | { |
| 101 | add_const_impl(&trigger, acc, ctx, &missing_fn); | 98 | add_const_impl(&trigger, acc, ctx, missing_fn); |
| 102 | } | 99 | } |
| 103 | } | 100 | } |
| 104 | 101 | ||
| @@ -126,9 +123,9 @@ fn add_function_impl( | |||
| 126 | fn_def_node: &SyntaxNode, | 123 | fn_def_node: &SyntaxNode, |
| 127 | acc: &mut Completions, | 124 | acc: &mut Completions, |
| 128 | ctx: &CompletionContext, | 125 | ctx: &CompletionContext, |
| 129 | func: &hir::Function, | 126 | func: hir::Function, |
| 130 | ) { | 127 | ) { |
| 131 | let signature = FunctionSignature::from_hir(ctx.db, *func); | 128 | let signature = FunctionSignature::from_hir(ctx.db, func); |
| 132 | 129 | ||
| 133 | let fn_name = func.name(ctx.db).to_string(); | 130 | let fn_name = func.name(ctx.db).to_string(); |
| 134 | 131 | ||
| @@ -167,7 +164,7 @@ fn add_type_alias_impl( | |||
| 167 | type_def_node: &SyntaxNode, | 164 | type_def_node: &SyntaxNode, |
| 168 | acc: &mut Completions, | 165 | acc: &mut Completions, |
| 169 | ctx: &CompletionContext, | 166 | ctx: &CompletionContext, |
| 170 | type_alias: &hir::TypeAlias, | 167 | type_alias: hir::TypeAlias, |
| 171 | ) { | 168 | ) { |
| 172 | let alias_name = type_alias.name(ctx.db).to_string(); | 169 | let alias_name = type_alias.name(ctx.db).to_string(); |
| 173 | 170 | ||
| @@ -187,7 +184,7 @@ fn add_const_impl( | |||
| 187 | const_def_node: &SyntaxNode, | 184 | const_def_node: &SyntaxNode, |
| 188 | acc: &mut Completions, | 185 | acc: &mut Completions, |
| 189 | ctx: &CompletionContext, | 186 | ctx: &CompletionContext, |
| 190 | const_: &hir::Const, | 187 | const_: hir::Const, |
| 191 | ) { | 188 | ) { |
| 192 | let const_name = const_.name(ctx.db).map(|n| n.to_string()); | 189 | let const_name = const_.name(ctx.db).map(|n| n.to_string()); |
| 193 | 190 | ||
diff --git a/crates/ra_ide/src/completion/complete_unqualified_path.rs b/crates/ra_ide/src/completion/complete_unqualified_path.rs index db791660a..68032c37e 100644 --- a/crates/ra_ide/src/completion/complete_unqualified_path.rs +++ b/crates/ra_ide/src/completion/complete_unqualified_path.rs | |||
| @@ -298,6 +298,42 @@ mod tests { | |||
| 298 | } | 298 | } |
| 299 | 299 | ||
| 300 | #[test] | 300 | #[test] |
| 301 | fn completes_bindings_from_for_with_in_prefix() { | ||
| 302 | mark::check!(completes_bindings_from_for_with_in_prefix); | ||
| 303 | assert_debug_snapshot!( | ||
| 304 | do_reference_completion( | ||
| 305 | r" | ||
| 306 | fn test() { | ||
| 307 | for index in &[1, 2, 3] { | ||
| 308 | let t = in<|> | ||
| 309 | } | ||
| 310 | } | ||
| 311 | " | ||
| 312 | ), | ||
| 313 | @r###" | ||
| 314 | [ | ||
| 315 | CompletionItem { | ||
| 316 | label: "index", | ||
| 317 | source_range: 107..107, | ||
| 318 | delete: 107..107, | ||
| 319 | insert: "index", | ||
| 320 | kind: Binding, | ||
| 321 | }, | ||
| 322 | CompletionItem { | ||
| 323 | label: "test()", | ||
| 324 | source_range: 107..107, | ||
| 325 | delete: 107..107, | ||
| 326 | insert: "test()$0", | ||
| 327 | kind: Function, | ||
| 328 | lookup: "test", | ||
| 329 | detail: "fn test()", | ||
| 330 | }, | ||
| 331 | ] | ||
| 332 | "### | ||
| 333 | ); | ||
| 334 | } | ||
| 335 | |||
| 336 | #[test] | ||
| 301 | fn completes_generic_params() { | 337 | fn completes_generic_params() { |
| 302 | assert_debug_snapshot!( | 338 | assert_debug_snapshot!( |
| 303 | do_reference_completion( | 339 | do_reference_completion( |
diff --git a/crates/ra_ide/src/completion/completion_context.rs b/crates/ra_ide/src/completion/completion_context.rs index da336973c..c4646b727 100644 --- a/crates/ra_ide/src/completion/completion_context.rs +++ b/crates/ra_ide/src/completion/completion_context.rs | |||
| @@ -12,6 +12,7 @@ use ra_syntax::{ | |||
| 12 | use ra_text_edit::Indel; | 12 | use ra_text_edit::Indel; |
| 13 | 13 | ||
| 14 | use crate::{call_info::ActiveParameter, completion::CompletionConfig, FilePosition}; | 14 | use crate::{call_info::ActiveParameter, completion::CompletionConfig, FilePosition}; |
| 15 | use test_utils::mark; | ||
| 15 | 16 | ||
| 16 | /// `CompletionContext` is created early during completion to figure out, where | 17 | /// `CompletionContext` is created early during completion to figure out, where |
| 17 | /// exactly is the cursor, syntax-wise. | 18 | /// exactly is the cursor, syntax-wise. |
| @@ -169,7 +170,17 @@ impl<'a> CompletionContext<'a> { | |||
| 169 | match self.token.kind() { | 170 | match self.token.kind() { |
| 170 | // workaroud when completion is triggered by trigger characters. | 171 | // workaroud when completion is triggered by trigger characters. |
| 171 | IDENT => self.original_token.text_range(), | 172 | IDENT => self.original_token.text_range(), |
| 172 | _ => TextRange::empty(self.offset), | 173 | _ => { |
| 174 | // If we haven't characters between keyword and our cursor we take the keyword start range to edit | ||
| 175 | if self.token.kind().is_keyword() | ||
| 176 | && self.offset == self.original_token.text_range().end() | ||
| 177 | { | ||
| 178 | mark::hit!(completes_bindings_from_for_with_in_prefix); | ||
| 179 | TextRange::empty(self.original_token.text_range().start()) | ||
| 180 | } else { | ||
| 181 | TextRange::empty(self.offset) | ||
| 182 | } | ||
| 183 | } | ||
| 173 | } | 184 | } |
| 174 | } | 185 | } |
| 175 | 186 | ||
diff --git a/crates/ra_ide/src/completion/presentation.rs b/crates/ra_ide/src/completion/presentation.rs index 440ffa31d..61565c84f 100644 --- a/crates/ra_ide/src/completion/presentation.rs +++ b/crates/ra_ide/src/completion/presentation.rs | |||
| @@ -211,7 +211,7 @@ impl Completions { | |||
| 211 | .parameter_names | 211 | .parameter_names |
| 212 | .iter() | 212 | .iter() |
| 213 | .skip(if function_signature.has_self_param { 1 } else { 0 }) | 213 | .skip(if function_signature.has_self_param { 1 } else { 0 }) |
| 214 | .cloned() | 214 | .map(|name| name.trim_start_matches('_').into()) |
| 215 | .collect(); | 215 | .collect(); |
| 216 | 216 | ||
| 217 | builder = builder.add_call_parens(ctx, name, Params::Named(params)); | 217 | builder = builder.add_call_parens(ctx, name, Params::Named(params)); |
| @@ -672,6 +672,37 @@ mod tests { | |||
| 672 | assert_debug_snapshot!( | 672 | assert_debug_snapshot!( |
| 673 | do_reference_completion( | 673 | do_reference_completion( |
| 674 | r" | 674 | r" |
| 675 | fn with_ignored_args(_foo: i32, ___bar: bool, ho_ge_: String) {} | ||
| 676 | fn main() { with_<|> } | ||
| 677 | " | ||
| 678 | ), | ||
| 679 | @r###" | ||
| 680 | [ | ||
| 681 | CompletionItem { | ||
| 682 | label: "main()", | ||
| 683 | source_range: 110..115, | ||
| 684 | delete: 110..115, | ||
| 685 | insert: "main()$0", | ||
| 686 | kind: Function, | ||
| 687 | lookup: "main", | ||
| 688 | detail: "fn main()", | ||
| 689 | }, | ||
| 690 | CompletionItem { | ||
| 691 | label: "with_ignored_args(…)", | ||
| 692 | source_range: 110..115, | ||
| 693 | delete: 110..115, | ||
| 694 | insert: "with_ignored_args(${1:foo}, ${2:bar}, ${3:ho_ge_})$0", | ||
| 695 | kind: Function, | ||
| 696 | lookup: "with_ignored_args", | ||
| 697 | detail: "fn with_ignored_args(_foo: i32, ___bar: bool, ho_ge_: String)", | ||
| 698 | trigger_call_info: true, | ||
| 699 | }, | ||
| 700 | ] | ||
| 701 | "### | ||
| 702 | ); | ||
| 703 | assert_debug_snapshot!( | ||
| 704 | do_reference_completion( | ||
| 705 | r" | ||
| 675 | struct S {} | 706 | struct S {} |
| 676 | impl S { | 707 | impl S { |
| 677 | fn foo(&self) {} | 708 | fn foo(&self) {} |
| @@ -695,6 +726,33 @@ mod tests { | |||
| 695 | ] | 726 | ] |
| 696 | "### | 727 | "### |
| 697 | ); | 728 | ); |
| 729 | assert_debug_snapshot!( | ||
| 730 | do_reference_completion( | ||
| 731 | r" | ||
| 732 | struct S {} | ||
| 733 | impl S { | ||
| 734 | fn foo_ignored_args(&self, _a: bool, b: i32) {} | ||
| 735 | } | ||
| 736 | fn bar(s: &S) { | ||
| 737 | s.f<|> | ||
| 738 | } | ||
| 739 | " | ||
| 740 | ), | ||
| 741 | @r###" | ||
| 742 | [ | ||
| 743 | CompletionItem { | ||
| 744 | label: "foo_ignored_args(…)", | ||
| 745 | source_range: 194..195, | ||
| 746 | delete: 194..195, | ||
| 747 | insert: "foo_ignored_args(${1:a}, ${2:b})$0", | ||
| 748 | kind: Method, | ||
| 749 | lookup: "foo_ignored_args", | ||
| 750 | detail: "fn foo_ignored_args(&self, _a: bool, b: i32)", | ||
| 751 | trigger_call_info: true, | ||
| 752 | }, | ||
| 753 | ] | ||
| 754 | "### | ||
| 755 | ); | ||
| 698 | } | 756 | } |
| 699 | 757 | ||
| 700 | #[test] | 758 | #[test] |
diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs index 3d83c0f71..15dc50cf1 100644 --- a/crates/ra_ide/src/diagnostics.rs +++ b/crates/ra_ide/src/diagnostics.rs | |||
| @@ -664,7 +664,7 @@ mod tests { | |||
| 664 | assert_debug_snapshot!(diagnostics, @r###" | 664 | assert_debug_snapshot!(diagnostics, @r###" |
| 665 | [ | 665 | [ |
| 666 | Diagnostic { | 666 | Diagnostic { |
| 667 | message: "Missing structure fields:\n- b", | 667 | message: "Missing structure fields:\n- b\n", |
| 668 | range: 224..233, | 668 | range: 224..233, |
| 669 | severity: Error, | 669 | severity: Error, |
| 670 | fix: Some( | 670 | fix: Some( |
diff --git a/crates/ra_ide/src/display.rs b/crates/ra_ide/src/display.rs index 722092de9..827c094e7 100644 --- a/crates/ra_ide/src/display.rs +++ b/crates/ra_ide/src/display.rs | |||
| @@ -79,16 +79,17 @@ pub(crate) fn rust_code_markup_with_doc( | |||
| 79 | doc: Option<&str>, | 79 | doc: Option<&str>, |
| 80 | mod_path: Option<&str>, | 80 | mod_path: Option<&str>, |
| 81 | ) -> String { | 81 | ) -> String { |
| 82 | let mut buf = "```rust\n".to_owned(); | 82 | let mut buf = String::new(); |
| 83 | 83 | ||
| 84 | if let Some(mod_path) = mod_path { | 84 | if let Some(mod_path) = mod_path { |
| 85 | if !mod_path.is_empty() { | 85 | if !mod_path.is_empty() { |
| 86 | format_to!(buf, "{}\n", mod_path); | 86 | format_to!(buf, "```rust\n{}\n```\n\n", mod_path); |
| 87 | } | 87 | } |
| 88 | } | 88 | } |
| 89 | format_to!(buf, "{}\n```", code); | 89 | format_to!(buf, "```rust\n{}\n```", code); |
| 90 | 90 | ||
| 91 | if let Some(doc) = doc { | 91 | if let Some(doc) = doc { |
| 92 | format_to!(buf, "\n___"); | ||
| 92 | format_to!(buf, "\n\n{}", doc); | 93 | format_to!(buf, "\n\n{}", doc); |
| 93 | } | 94 | } |
| 94 | 95 | ||
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/display/structure.rs b/crates/ra_ide/src/display/structure.rs index 967eee5d2..aad5a8e4d 100644 --- a/crates/ra_ide/src/display/structure.rs +++ b/crates/ra_ide/src/display/structure.rs | |||
| @@ -1,10 +1,6 @@ | |||
| 1 | //! FIXME: write short doc here | ||
| 2 | |||
| 3 | use crate::TextRange; | ||
| 4 | |||
| 5 | use ra_syntax::{ | 1 | use ra_syntax::{ |
| 6 | ast::{self, AttrsOwner, NameOwner, TypeAscriptionOwner, TypeParamsOwner}, | 2 | ast::{self, AttrsOwner, NameOwner, TypeAscriptionOwner, TypeParamsOwner}, |
| 7 | match_ast, AstNode, SourceFile, SyntaxKind, SyntaxNode, WalkEvent, | 3 | match_ast, AstNode, SourceFile, SyntaxKind, SyntaxNode, TextRange, WalkEvent, |
| 8 | }; | 4 | }; |
| 9 | 5 | ||
| 10 | #[derive(Debug, Clone)] | 6 | #[derive(Debug, Clone)] |
| @@ -18,6 +14,19 @@ pub struct StructureNode { | |||
| 18 | pub deprecated: bool, | 14 | pub deprecated: bool, |
| 19 | } | 15 | } |
| 20 | 16 | ||
| 17 | // Feature: File Structure | ||
| 18 | // | ||
| 19 | // Provides a tree of the symbols defined in the file. Can be used to | ||
| 20 | // | ||
| 21 | // * fuzzy search symbol in a file (super useful) | ||
| 22 | // * draw breadcrumbs to describe the context around the cursor | ||
| 23 | // * draw outline of the file | ||
| 24 | // | ||
| 25 | // |=== | ||
| 26 | // | Editor | Shortcut | ||
| 27 | // | ||
| 28 | // | VS Code | kbd:[Ctrl+Shift+O] | ||
| 29 | // |=== | ||
| 21 | pub fn file_structure(file: &SourceFile) -> Vec<StructureNode> { | 30 | pub fn file_structure(file: &SourceFile) -> Vec<StructureNode> { |
| 22 | let mut res = Vec::new(); | 31 | let mut res = Vec::new(); |
| 23 | let mut stack = Vec::new(); | 32 | let mut stack = Vec::new(); |
diff --git a/crates/ra_ide/src/expand_macro.rs b/crates/ra_ide/src/expand_macro.rs index f536ba3e7..54a47aac0 100644 --- a/crates/ra_ide/src/expand_macro.rs +++ b/crates/ra_ide/src/expand_macro.rs | |||
| @@ -1,5 +1,3 @@ | |||
| 1 | //! This modules implements "expand macro" functionality in the IDE | ||
| 2 | |||
| 3 | use hir::Semantics; | 1 | use hir::Semantics; |
| 4 | use ra_ide_db::RootDatabase; | 2 | use ra_ide_db::RootDatabase; |
| 5 | use ra_syntax::{ | 3 | use ra_syntax::{ |
| @@ -14,6 +12,15 @@ pub struct ExpandedMacro { | |||
| 14 | pub expansion: String, | 12 | pub expansion: String, |
| 15 | } | 13 | } |
| 16 | 14 | ||
| 15 | // Feature: Expand Macro Recursively | ||
| 16 | // | ||
| 17 | // Shows the full macro expansion of the macro at current cursor. | ||
| 18 | // | ||
| 19 | // |=== | ||
| 20 | // | Editor | Action Name | ||
| 21 | // | ||
| 22 | // | VS Code | **Rust Analyzer: Expand macro recursively** | ||
| 23 | // |=== | ||
| 17 | pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<ExpandedMacro> { | 24 | pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<ExpandedMacro> { |
| 18 | let sema = Semantics::new(db); | 25 | let sema = Semantics::new(db); |
| 19 | let file = sema.parse(position.file_id); | 26 | let file = sema.parse(position.file_id); |
diff --git a/crates/ra_ide/src/extend_selection.rs b/crates/ra_ide/src/extend_selection.rs index 554594a43..a4bc93cdb 100644 --- a/crates/ra_ide/src/extend_selection.rs +++ b/crates/ra_ide/src/extend_selection.rs | |||
| @@ -1,5 +1,3 @@ | |||
| 1 | //! FIXME: write short doc here | ||
| 2 | |||
| 3 | use std::iter::successors; | 1 | use std::iter::successors; |
| 4 | 2 | ||
| 5 | use hir::Semantics; | 3 | use hir::Semantics; |
| @@ -14,6 +12,16 @@ use ra_syntax::{ | |||
| 14 | 12 | ||
| 15 | use crate::FileRange; | 13 | use crate::FileRange; |
| 16 | 14 | ||
| 15 | // Feature: Extend Selection | ||
| 16 | // | ||
| 17 | // Extends the current selection to the encompassing syntactic construct | ||
| 18 | // (expression, statement, item, module, etc). It works with multiple cursors. | ||
| 19 | // | ||
| 20 | // |=== | ||
| 21 | // | Editor | Shortcut | ||
| 22 | // | ||
| 23 | // | VS Code | kbd:[Ctrl+Shift+→] | ||
| 24 | // |=== | ||
| 17 | pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange { | 25 | pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange { |
| 18 | let sema = Semantics::new(db); | 26 | let sema = Semantics::new(db); |
| 19 | let src = sema.parse(frange.file_id); | 27 | let src = sema.parse(frange.file_id); |
diff --git a/crates/ra_ide/src/goto_definition.rs b/crates/ra_ide/src/goto_definition.rs index 90e85d419..a6c86e99c 100644 --- a/crates/ra_ide/src/goto_definition.rs +++ b/crates/ra_ide/src/goto_definition.rs | |||
| @@ -1,5 +1,3 @@ | |||
| 1 | //! FIXME: write short doc here | ||
| 2 | |||
| 3 | use hir::Semantics; | 1 | use hir::Semantics; |
| 4 | use ra_ide_db::{ | 2 | use ra_ide_db::{ |
| 5 | defs::{classify_name, classify_name_ref}, | 3 | defs::{classify_name, classify_name_ref}, |
| @@ -17,6 +15,15 @@ use crate::{ | |||
| 17 | FilePosition, NavigationTarget, RangeInfo, | 15 | FilePosition, NavigationTarget, RangeInfo, |
| 18 | }; | 16 | }; |
| 19 | 17 | ||
| 18 | // Feature: Go to Definition | ||
| 19 | // | ||
| 20 | // Navigates to the definition of an identifier. | ||
| 21 | // | ||
| 22 | // |=== | ||
| 23 | // | Editor | Shortcut | ||
| 24 | // | ||
| 25 | // | VS Code | kbd:[F12] | ||
| 26 | // |=== | ||
| 20 | pub(crate) fn goto_definition( | 27 | pub(crate) fn goto_definition( |
| 21 | db: &RootDatabase, | 28 | db: &RootDatabase, |
| 22 | position: FilePosition, | 29 | position: FilePosition, |
diff --git a/crates/ra_ide/src/impls.rs b/crates/ra_ide/src/goto_implementation.rs index ea2225f70..0cec0657e 100644 --- a/crates/ra_ide/src/impls.rs +++ b/crates/ra_ide/src/goto_implementation.rs | |||
| @@ -1,11 +1,18 @@ | |||
| 1 | //! FIXME: write short doc here | ||
| 2 | |||
| 3 | use hir::{Crate, ImplDef, Semantics}; | 1 | use hir::{Crate, ImplDef, Semantics}; |
| 4 | use ra_ide_db::RootDatabase; | 2 | use ra_ide_db::RootDatabase; |
| 5 | use ra_syntax::{algo::find_node_at_offset, ast, AstNode}; | 3 | use ra_syntax::{algo::find_node_at_offset, ast, AstNode}; |
| 6 | 4 | ||
| 7 | use crate::{display::ToNav, FilePosition, NavigationTarget, RangeInfo}; | 5 | use crate::{display::ToNav, FilePosition, NavigationTarget, RangeInfo}; |
| 8 | 6 | ||
| 7 | // Feature: Go to Implementation | ||
| 8 | // | ||
| 9 | // Navigates to the impl block of structs, enums or traits. Also implemented as a code lens. | ||
| 10 | // | ||
| 11 | // |=== | ||
| 12 | // | Editor | Shortcut | ||
| 13 | // | ||
| 14 | // | VS Code | kbd:[Ctrl+F12] | ||
| 15 | // |=== | ||
| 9 | pub(crate) fn goto_implementation( | 16 | pub(crate) fn goto_implementation( |
| 10 | db: &RootDatabase, | 17 | db: &RootDatabase, |
| 11 | position: FilePosition, | 18 | position: FilePosition, |
diff --git a/crates/ra_ide/src/goto_type_definition.rs b/crates/ra_ide/src/goto_type_definition.rs index a84637489..91a3097fb 100644 --- a/crates/ra_ide/src/goto_type_definition.rs +++ b/crates/ra_ide/src/goto_type_definition.rs | |||
| @@ -1,10 +1,17 @@ | |||
| 1 | //! FIXME: write short doc here | ||
| 2 | |||
| 3 | use ra_ide_db::RootDatabase; | 1 | use ra_ide_db::RootDatabase; |
| 4 | use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; | 2 | use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; |
| 5 | 3 | ||
| 6 | use crate::{display::ToNav, FilePosition, NavigationTarget, RangeInfo}; | 4 | use crate::{display::ToNav, FilePosition, NavigationTarget, RangeInfo}; |
| 7 | 5 | ||
| 6 | // Feature: Go to Type Definition | ||
| 7 | // | ||
| 8 | // Navigates to the type of an identifier. | ||
| 9 | // | ||
| 10 | // |=== | ||
| 11 | // | Editor | Action Name | ||
| 12 | // | ||
| 13 | // | VS Code | **Go to Type Definition* | ||
| 14 | // |=== | ||
| 8 | pub(crate) fn goto_type_definition( | 15 | pub(crate) fn goto_type_definition( |
| 9 | db: &RootDatabase, | 16 | db: &RootDatabase, |
| 10 | position: FilePosition, | 17 | position: FilePosition, |
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index befa977c7..9636cd0d6 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs | |||
| @@ -1,28 +1,21 @@ | |||
| 1 | //! Logic for computing info that is displayed when the user hovers over any | 1 | use std::iter::once; |
| 2 | //! source code items (e.g. function call, struct field, variable symbol...) | ||
| 3 | 2 | ||
| 4 | use hir::{ | 3 | use hir::{ |
| 5 | Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef, | 4 | Adt, AsAssocItem, AssocItemContainer, Documentation, FieldSource, HasSource, HirDisplay, |
| 6 | ModuleSource, Semantics, | 5 | ModuleDef, ModuleSource, Semantics, |
| 7 | }; | 6 | }; |
| 7 | use itertools::Itertools; | ||
| 8 | use ra_db::SourceDatabase; | 8 | use ra_db::SourceDatabase; |
| 9 | use ra_ide_db::{ | 9 | 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}, |
| 22 | FilePosition, RangeInfo, | 17 | FilePosition, RangeInfo, |
| 23 | }; | 18 | }; |
| 24 | use itertools::Itertools; | ||
| 25 | use std::iter::once; | ||
| 26 | 19 | ||
| 27 | /// Contains the results when hovering over an item | 20 | /// Contains the results when hovering over an item |
| 28 | #[derive(Debug, Default)] | 21 | #[derive(Debug, Default)] |
| @@ -62,6 +55,63 @@ impl HoverResult { | |||
| 62 | } | 55 | } |
| 63 | } | 56 | } |
| 64 | 57 | ||
| 58 | // Feature: Hover | ||
| 59 | // | ||
| 60 | // Shows additional information, like type of an expression or documentation for definition when "focusing" code. | ||
| 61 | // Focusing is usually hovering with a mouse, but can also be triggered with a shortcut. | ||
| 62 | pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<HoverResult>> { | ||
| 63 | let sema = Semantics::new(db); | ||
| 64 | let file = sema.parse(position.file_id).syntax().clone(); | ||
| 65 | let token = pick_best(file.token_at_offset(position.offset))?; | ||
| 66 | let token = sema.descend_into_macros(token); | ||
| 67 | |||
| 68 | let mut res = HoverResult::new(); | ||
| 69 | |||
| 70 | if let Some((node, name_kind)) = match_ast! { | ||
| 71 | match (token.parent()) { | ||
| 72 | ast::NameRef(name_ref) => { | ||
| 73 | classify_name_ref(&sema, &name_ref).map(|d| (name_ref.syntax().clone(), d.definition())) | ||
| 74 | }, | ||
| 75 | ast::Name(name) => { | ||
| 76 | classify_name(&sema, &name).map(|d| (name.syntax().clone(), d.definition())) | ||
| 77 | }, | ||
| 78 | _ => None, | ||
| 79 | } | ||
| 80 | } { | ||
| 81 | let range = sema.original_range(&node).range; | ||
| 82 | res.extend(hover_text_from_name_kind(db, name_kind)); | ||
| 83 | |||
| 84 | if !res.is_empty() { | ||
| 85 | return Some(RangeInfo::new(range, res)); | ||
| 86 | } | ||
| 87 | } | ||
| 88 | |||
| 89 | let node = token | ||
| 90 | .ancestors() | ||
| 91 | .find(|n| ast::Expr::cast(n.clone()).is_some() || ast::Pat::cast(n.clone()).is_some())?; | ||
| 92 | |||
| 93 | let ty = match_ast! { | ||
| 94 | match node { | ||
| 95 | ast::MacroCall(_it) => { | ||
| 96 | // If this node is a MACRO_CALL, it means that `descend_into_macros` failed to resolve. | ||
| 97 | // (e.g expanding a builtin macro). So we give up here. | ||
| 98 | return None; | ||
| 99 | }, | ||
| 100 | ast::Expr(it) => { | ||
| 101 | sema.type_of_expr(&it) | ||
| 102 | }, | ||
| 103 | ast::Pat(it) => { | ||
| 104 | sema.type_of_pat(&it) | ||
| 105 | }, | ||
| 106 | _ => None, | ||
| 107 | } | ||
| 108 | }?; | ||
| 109 | |||
| 110 | res.extend(Some(rust_code_markup(&ty.display(db)))); | ||
| 111 | let range = sema.original_range(&node).range; | ||
| 112 | Some(RangeInfo::new(range, res)) | ||
| 113 | } | ||
| 114 | |||
| 65 | fn hover_text( | 115 | fn hover_text( |
| 66 | docs: Option<String>, | 116 | docs: Option<String>, |
| 67 | desc: Option<String>, | 117 | desc: Option<String>, |
| @@ -114,13 +164,15 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<Strin | |||
| 114 | return match def { | 164 | return match def { |
| 115 | Definition::Macro(it) => { | 165 | Definition::Macro(it) => { |
| 116 | let src = it.source(db); | 166 | let src = it.source(db); |
| 117 | hover_text(src.value.doc_comment_text(), Some(macro_label(&src.value)), mod_path) | 167 | let docs = Documentation::from_ast(&src.value).map(Into::into); |
| 168 | hover_text(docs, Some(macro_label(&src.value)), mod_path) | ||
| 118 | } | 169 | } |
| 119 | Definition::Field(it) => { | 170 | Definition::Field(it) => { |
| 120 | let src = it.source(db); | 171 | let src = it.source(db); |
| 121 | match src.value { | 172 | match src.value { |
| 122 | FieldSource::Named(it) => { | 173 | FieldSource::Named(it) => { |
| 123 | hover_text(it.doc_comment_text(), it.short_label(), mod_path) | 174 | let docs = Documentation::from_ast(&it).map(Into::into); |
| 175 | hover_text(docs, it.short_label(), mod_path) | ||
| 124 | } | 176 | } |
| 125 | _ => None, | 177 | _ => None, |
| 126 | } | 178 | } |
| @@ -128,7 +180,8 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<Strin | |||
| 128 | Definition::ModuleDef(it) => match it { | 180 | Definition::ModuleDef(it) => match it { |
| 129 | ModuleDef::Module(it) => match it.definition_source(db).value { | 181 | ModuleDef::Module(it) => match it.definition_source(db).value { |
| 130 | ModuleSource::Module(it) => { | 182 | ModuleSource::Module(it) => { |
| 131 | hover_text(it.doc_comment_text(), it.short_label(), mod_path) | 183 | let docs = Documentation::from_ast(&it).map(Into::into); |
| 184 | hover_text(docs, it.short_label(), mod_path) | ||
| 132 | } | 185 | } |
| 133 | _ => None, | 186 | _ => None, |
| 134 | }, | 187 | }, |
| @@ -153,66 +206,14 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<Strin | |||
| 153 | fn from_def_source<A, D>(db: &RootDatabase, def: D, mod_path: Option<String>) -> Option<String> | 206 | fn from_def_source<A, D>(db: &RootDatabase, def: D, mod_path: Option<String>) -> Option<String> |
| 154 | where | 207 | where |
| 155 | D: HasSource<Ast = A>, | 208 | D: HasSource<Ast = A>, |
| 156 | A: ast::DocCommentsOwner + ast::NameOwner + ShortLabel, | 209 | A: ast::DocCommentsOwner + ast::NameOwner + ShortLabel + ast::AttrsOwner, |
| 157 | { | 210 | { |
| 158 | let src = def.source(db); | 211 | let src = def.source(db); |
| 159 | hover_text(src.value.doc_comment_text(), src.value.short_label(), mod_path) | 212 | let docs = Documentation::from_ast(&src.value).map(Into::into); |
| 213 | hover_text(docs, src.value.short_label(), mod_path) | ||
| 160 | } | 214 | } |
| 161 | } | 215 | } |
| 162 | 216 | ||
| 163 | pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<HoverResult>> { | ||
| 164 | let sema = Semantics::new(db); | ||
| 165 | let file = sema.parse(position.file_id).syntax().clone(); | ||
| 166 | let token = pick_best(file.token_at_offset(position.offset))?; | ||
| 167 | let token = sema.descend_into_macros(token); | ||
| 168 | |||
| 169 | let mut res = HoverResult::new(); | ||
| 170 | |||
| 171 | if let Some((node, name_kind)) = match_ast! { | ||
| 172 | match (token.parent()) { | ||
| 173 | ast::NameRef(name_ref) => { | ||
| 174 | classify_name_ref(&sema, &name_ref).map(|d| (name_ref.syntax().clone(), d.definition())) | ||
| 175 | }, | ||
| 176 | ast::Name(name) => { | ||
| 177 | classify_name(&sema, &name).map(|d| (name.syntax().clone(), d.definition())) | ||
| 178 | }, | ||
| 179 | _ => None, | ||
| 180 | } | ||
| 181 | } { | ||
| 182 | let range = sema.original_range(&node).range; | ||
| 183 | res.extend(hover_text_from_name_kind(db, name_kind)); | ||
| 184 | |||
| 185 | if !res.is_empty() { | ||
| 186 | return Some(RangeInfo::new(range, res)); | ||
| 187 | } | ||
| 188 | } | ||
| 189 | |||
| 190 | let node = token | ||
| 191 | .ancestors() | ||
| 192 | .find(|n| ast::Expr::cast(n.clone()).is_some() || ast::Pat::cast(n.clone()).is_some())?; | ||
| 193 | |||
| 194 | let ty = match_ast! { | ||
| 195 | match node { | ||
| 196 | ast::MacroCall(_it) => { | ||
| 197 | // If this node is a MACRO_CALL, it means that `descend_into_macros` failed to resolve. | ||
| 198 | // (e.g expanding a builtin macro). So we give up here. | ||
| 199 | return None; | ||
| 200 | }, | ||
| 201 | ast::Expr(it) => { | ||
| 202 | sema.type_of_expr(&it) | ||
| 203 | }, | ||
| 204 | ast::Pat(it) => { | ||
| 205 | sema.type_of_pat(&it) | ||
| 206 | }, | ||
| 207 | _ => None, | ||
| 208 | } | ||
| 209 | }?; | ||
| 210 | |||
| 211 | res.extend(Some(rust_code_markup(&ty.display(db)))); | ||
| 212 | let range = sema.original_range(&node).range; | ||
| 213 | Some(RangeInfo::new(range, res)) | ||
| 214 | } | ||
| 215 | |||
| 216 | fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { | 217 | fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { |
| 217 | return tokens.max_by_key(priority); | 218 | return tokens.max_by_key(priority); |
| 218 | fn priority(n: &SyntaxToken) -> usize { | 219 | fn priority(n: &SyntaxToken) -> usize { |
| @@ -405,7 +406,7 @@ mod tests { | |||
| 405 | }; | 406 | }; |
| 406 | } | 407 | } |
| 407 | "#, | 408 | "#, |
| 408 | &["Foo\nfield_a: u32"], | 409 | &["Foo\n```\n\n```rust\nfield_a: u32"], |
| 409 | ); | 410 | ); |
| 410 | 411 | ||
| 411 | // Hovering over the field in the definition | 412 | // Hovering over the field in the definition |
| @@ -422,7 +423,7 @@ mod tests { | |||
| 422 | }; | 423 | }; |
| 423 | } | 424 | } |
| 424 | "#, | 425 | "#, |
| 425 | &["Foo\nfield_a: u32"], | 426 | &["Foo\n```\n\n```rust\nfield_a: u32"], |
| 426 | ); | 427 | ); |
| 427 | } | 428 | } |
| 428 | 429 | ||
| @@ -475,7 +476,7 @@ fn main() { | |||
| 475 | ", | 476 | ", |
| 476 | ); | 477 | ); |
| 477 | let hover = analysis.hover(position).unwrap().unwrap(); | 478 | let hover = analysis.hover(position).unwrap().unwrap(); |
| 478 | assert_eq!(trim_markup_opt(hover.info.first()), Some("Option\nSome")); | 479 | assert_eq!(trim_markup_opt(hover.info.first()), Some("Option\n```\n\n```rust\nSome")); |
| 479 | 480 | ||
| 480 | let (analysis, position) = single_file_with_position( | 481 | let (analysis, position) = single_file_with_position( |
| 481 | " | 482 | " |
| @@ -503,8 +504,12 @@ fn main() { | |||
| 503 | "#, | 504 | "#, |
| 504 | &[" | 505 | &[" |
| 505 | Option | 506 | Option |
| 507 | ``` | ||
| 508 | |||
| 509 | ```rust | ||
| 506 | None | 510 | None |
| 507 | ``` | 511 | ``` |
| 512 | ___ | ||
| 508 | 513 | ||
| 509 | The None variant | 514 | The None variant |
| 510 | " | 515 | " |
| @@ -524,8 +529,12 @@ The None variant | |||
| 524 | "#, | 529 | "#, |
| 525 | &[" | 530 | &[" |
| 526 | Option | 531 | Option |
| 532 | ``` | ||
| 533 | |||
| 534 | ```rust | ||
| 527 | Some | 535 | Some |
| 528 | ``` | 536 | ``` |
| 537 | ___ | ||
| 529 | 538 | ||
| 530 | The Some variant | 539 | The Some variant |
| 531 | " | 540 | " |
| @@ -606,7 +615,10 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
| 606 | ", | 615 | ", |
| 607 | ); | 616 | ); |
| 608 | let hover = analysis.hover(position).unwrap().unwrap(); | 617 | let hover = analysis.hover(position).unwrap().unwrap(); |
| 609 | assert_eq!(trim_markup_opt(hover.info.first()), Some("wrapper::Thing\nfn new() -> Thing")); | 618 | assert_eq!( |
| 619 | trim_markup_opt(hover.info.first()), | ||
| 620 | Some("wrapper::Thing\n```\n\n```rust\nfn new() -> Thing") | ||
| 621 | ); | ||
| 610 | } | 622 | } |
| 611 | 623 | ||
| 612 | #[test] | 624 | #[test] |
| @@ -882,7 +894,7 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
| 882 | fo<|>o(); | 894 | fo<|>o(); |
| 883 | } | 895 | } |
| 884 | ", | 896 | ", |
| 885 | &["fn foo()\n```\n\n<- `\u{3000}` here"], | 897 | &["fn foo()\n```\n___\n\n<- `\u{3000}` here"], |
| 886 | ); | 898 | ); |
| 887 | } | 899 | } |
| 888 | 900 | ||
| @@ -938,4 +950,106 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
| 938 | &["mod my"], | 950 | &["mod my"], |
| 939 | ); | 951 | ); |
| 940 | } | 952 | } |
| 953 | |||
| 954 | #[test] | ||
| 955 | fn test_hover_struct_doc_comment() { | ||
| 956 | check_hover_result( | ||
| 957 | r#" | ||
| 958 | //- /lib.rs | ||
| 959 | /// bar docs | ||
| 960 | struct Bar; | ||
| 961 | |||
| 962 | fn foo() { | ||
| 963 | let bar = Ba<|>r; | ||
| 964 | } | ||
| 965 | "#, | ||
| 966 | &["struct Bar\n```\n___\n\nbar docs"], | ||
| 967 | ); | ||
| 968 | } | ||
| 969 | |||
| 970 | #[test] | ||
| 971 | fn test_hover_struct_doc_attr() { | ||
| 972 | check_hover_result( | ||
| 973 | r#" | ||
| 974 | //- /lib.rs | ||
| 975 | #[doc = "bar docs"] | ||
| 976 | struct Bar; | ||
| 977 | |||
| 978 | fn foo() { | ||
| 979 | let bar = Ba<|>r; | ||
| 980 | } | ||
| 981 | "#, | ||
| 982 | &["struct Bar\n```\n___\n\nbar docs"], | ||
| 983 | ); | ||
| 984 | } | ||
| 985 | |||
| 986 | #[test] | ||
| 987 | fn test_hover_struct_doc_attr_multiple_and_mixed() { | ||
| 988 | check_hover_result( | ||
| 989 | r#" | ||
| 990 | //- /lib.rs | ||
| 991 | /// bar docs 0 | ||
| 992 | #[doc = "bar docs 1"] | ||
| 993 | #[doc = "bar docs 2"] | ||
| 994 | struct Bar; | ||
| 995 | |||
| 996 | fn foo() { | ||
| 997 | let bar = Ba<|>r; | ||
| 998 | } | ||
| 999 | "#, | ||
| 1000 | &["struct Bar\n```\n___\n\nbar docs 0\n\nbar docs 1\n\nbar docs 2"], | ||
| 1001 | ); | ||
| 1002 | } | ||
| 1003 | |||
| 1004 | #[test] | ||
| 1005 | fn test_hover_macro_generated_struct_fn_doc_comment() { | ||
| 1006 | check_hover_result( | ||
| 1007 | r#" | ||
| 1008 | //- /lib.rs | ||
| 1009 | macro_rules! bar { | ||
| 1010 | () => { | ||
| 1011 | struct Bar; | ||
| 1012 | impl Bar { | ||
| 1013 | /// Do the foo | ||
| 1014 | fn foo(&self) {} | ||
| 1015 | } | ||
| 1016 | } | ||
| 1017 | } | ||
| 1018 | |||
| 1019 | bar!(); | ||
| 1020 | |||
| 1021 | fn foo() { | ||
| 1022 | let bar = Bar; | ||
| 1023 | bar.fo<|>o(); | ||
| 1024 | } | ||
| 1025 | "#, | ||
| 1026 | &["Bar\n```\n\n```rust\nfn foo(&self)\n```\n___\n\n Do the foo"], | ||
| 1027 | ); | ||
| 1028 | } | ||
| 1029 | |||
| 1030 | #[test] | ||
| 1031 | fn test_hover_macro_generated_struct_fn_doc_attr() { | ||
| 1032 | check_hover_result( | ||
| 1033 | r#" | ||
| 1034 | //- /lib.rs | ||
| 1035 | macro_rules! bar { | ||
| 1036 | () => { | ||
| 1037 | struct Bar; | ||
| 1038 | impl Bar { | ||
| 1039 | #[doc = "Do the foo"] | ||
| 1040 | fn foo(&self) {} | ||
| 1041 | } | ||
| 1042 | } | ||
| 1043 | } | ||
| 1044 | |||
| 1045 | bar!(); | ||
| 1046 | |||
| 1047 | fn foo() { | ||
| 1048 | let bar = Bar; | ||
| 1049 | bar.fo<|>o(); | ||
| 1050 | } | ||
| 1051 | "#, | ||
| 1052 | &["Bar\n```\n\n```rust\nfn foo(&self)\n```\n___\n\nDo the foo"], | ||
| 1053 | ); | ||
| 1054 | } | ||
| 941 | } | 1055 | } |
diff --git a/crates/ra_ide/src/inlay_hints.rs b/crates/ra_ide/src/inlay_hints.rs index b391f903a..75bd3c96b 100644 --- a/crates/ra_ide/src/inlay_hints.rs +++ b/crates/ra_ide/src/inlay_hints.rs | |||
| @@ -1,5 +1,3 @@ | |||
| 1 | //! This module defines multiple types of inlay hints and their visibility | ||
| 2 | |||
| 3 | use hir::{Adt, HirDisplay, Semantics, Type}; | 1 | use hir::{Adt, HirDisplay, Semantics, Type}; |
| 4 | use ra_ide_db::RootDatabase; | 2 | use ra_ide_db::RootDatabase; |
| 5 | use ra_prof::profile; | 3 | use ra_prof::profile; |
| @@ -39,6 +37,26 @@ pub struct InlayHint { | |||
| 39 | pub label: SmolStr, | 37 | pub label: SmolStr, |
| 40 | } | 38 | } |
| 41 | 39 | ||
| 40 | // Feature: Inlay Hints | ||
| 41 | // | ||
| 42 | // rust-analyzer shows additional information inline with the source code. | ||
| 43 | // Editors usually render this using read-only virtual text snippets interspersed with code. | ||
| 44 | // | ||
| 45 | // rust-analyzer shows hits for | ||
| 46 | // | ||
| 47 | // * types of local variables | ||
| 48 | // * names of function arguments | ||
| 49 | // * types of chained expressions | ||
| 50 | // | ||
| 51 | // **Note:** VS Code does not have native support for inlay hints https://github.com/microsoft/vscode/issues/16221[yet] and the hints are implemented using decorations. | ||
| 52 | // This approach has limitations, the caret movement and bracket highlighting near the edges of the hint may be weird: | ||
| 53 | // https://github.com/rust-analyzer/rust-analyzer/issues/1623[1], https://github.com/rust-analyzer/rust-analyzer/issues/3453[2]. | ||
| 54 | // | ||
| 55 | // |=== | ||
| 56 | // | Editor | Action Name | ||
| 57 | // | ||
| 58 | // | VS Code | **Rust Analyzer: Toggle inlay hints* | ||
| 59 | // |=== | ||
| 42 | pub(crate) fn inlay_hints( | 60 | pub(crate) fn inlay_hints( |
| 43 | db: &RootDatabase, | 61 | db: &RootDatabase, |
| 44 | file_id: FileId, | 62 | file_id: FileId, |
diff --git a/crates/ra_ide/src/join_lines.rs b/crates/ra_ide/src/join_lines.rs index af1ade8a1..5036c1fb0 100644 --- a/crates/ra_ide/src/join_lines.rs +++ b/crates/ra_ide/src/join_lines.rs | |||
| @@ -1,5 +1,3 @@ | |||
| 1 | //! FIXME: write short doc here | ||
| 2 | |||
| 3 | use itertools::Itertools; | 1 | use itertools::Itertools; |
| 4 | use ra_fmt::{compute_ws, extract_trivial_expression}; | 2 | use ra_fmt::{compute_ws, extract_trivial_expression}; |
| 5 | use ra_syntax::{ | 3 | use ra_syntax::{ |
| @@ -11,6 +9,15 @@ use ra_syntax::{ | |||
| 11 | }; | 9 | }; |
| 12 | use ra_text_edit::{TextEdit, TextEditBuilder}; | 10 | use ra_text_edit::{TextEdit, TextEditBuilder}; |
| 13 | 11 | ||
| 12 | // Feature: Join Lines | ||
| 13 | // | ||
| 14 | // Join selected lines into one, smartly fixing up whitespace, trailing commas, and braces. | ||
| 15 | // | ||
| 16 | // |=== | ||
| 17 | // | Editor | Action Name | ||
| 18 | // | ||
| 19 | // | VS Code | **Rust Analyzer: Join lines** | ||
| 20 | // |=== | ||
| 14 | pub fn join_lines(file: &SourceFile, range: TextRange) -> TextEdit { | 21 | pub fn join_lines(file: &SourceFile, range: TextRange) -> TextEdit { |
| 15 | let range = if range.is_empty() { | 22 | let range = if range.is_empty() { |
| 16 | let syntax = file.syntax(); | 23 | let syntax = file.syntax(); |
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs index 5ac002d82..12d5716e8 100644 --- a/crates/ra_ide/src/lib.rs +++ b/crates/ra_ide/src/lib.rs | |||
| @@ -23,6 +23,7 @@ mod completion; | |||
| 23 | mod runnables; | 23 | mod runnables; |
| 24 | mod goto_definition; | 24 | mod goto_definition; |
| 25 | mod goto_type_definition; | 25 | mod goto_type_definition; |
| 26 | mod goto_implementation; | ||
| 26 | mod extend_selection; | 27 | mod extend_selection; |
| 27 | mod hover; | 28 | mod hover; |
| 28 | mod call_hierarchy; | 29 | mod call_hierarchy; |
| @@ -30,7 +31,6 @@ mod call_info; | |||
| 30 | mod syntax_highlighting; | 31 | mod syntax_highlighting; |
| 31 | mod parent_module; | 32 | mod parent_module; |
| 32 | mod references; | 33 | mod references; |
| 33 | mod impls; | ||
| 34 | mod diagnostics; | 34 | mod diagnostics; |
| 35 | mod syntax_tree; | 35 | mod syntax_tree; |
| 36 | mod folding_ranges; | 36 | mod folding_ranges; |
| @@ -309,7 +309,8 @@ impl Analysis { | |||
| 309 | 309 | ||
| 310 | /// Returns an edit which should be applied when opening a new line, fixing | 310 | /// Returns an edit which should be applied when opening a new line, fixing |
| 311 | /// up minor stuff like continuing the comment. | 311 | /// up minor stuff like continuing the comment. |
| 312 | pub fn on_enter(&self, position: FilePosition) -> Cancelable<Option<SourceChange>> { | 312 | /// The edit will be a snippet (with `$0`). |
| 313 | pub fn on_enter(&self, position: FilePosition) -> Cancelable<Option<TextEdit>> { | ||
| 313 | self.with_db(|db| typing::on_enter(&db, position)) | 314 | self.with_db(|db| typing::on_enter(&db, position)) |
| 314 | } | 315 | } |
| 315 | 316 | ||
| @@ -372,7 +373,7 @@ impl Analysis { | |||
| 372 | &self, | 373 | &self, |
| 373 | position: FilePosition, | 374 | position: FilePosition, |
| 374 | ) -> Cancelable<Option<RangeInfo<Vec<NavigationTarget>>>> { | 375 | ) -> Cancelable<Option<RangeInfo<Vec<NavigationTarget>>>> { |
| 375 | self.with_db(|db| impls::goto_implementation(db, position)) | 376 | self.with_db(|db| goto_implementation::goto_implementation(db, position)) |
| 376 | } | 377 | } |
| 377 | 378 | ||
| 378 | /// Returns the type definitions for the symbol at `position`. | 379 | /// Returns the type definitions for the symbol at `position`. |
diff --git a/crates/ra_ide/src/matching_brace.rs b/crates/ra_ide/src/matching_brace.rs index b85348706..407a9636d 100644 --- a/crates/ra_ide/src/matching_brace.rs +++ b/crates/ra_ide/src/matching_brace.rs | |||
| @@ -1,7 +1,16 @@ | |||
| 1 | //! FIXME: write short doc here | ||
| 2 | |||
| 3 | use ra_syntax::{ast::AstNode, SourceFile, SyntaxKind, TextSize, T}; | 1 | use ra_syntax::{ast::AstNode, SourceFile, SyntaxKind, TextSize, T}; |
| 4 | 2 | ||
| 3 | // Feature: Matching Brace | ||
| 4 | // | ||
| 5 | // If the cursor is on any brace (`<>(){}[]`) which is a part of a brace-pair, | ||
| 6 | // moves cursor to the matching brace. It uses the actual parser to determine | ||
| 7 | // braces, so it won't confuse generics with comparisons. | ||
| 8 | // | ||
| 9 | // |=== | ||
| 10 | // | Editor | Action Name | ||
| 11 | // | ||
| 12 | // | VS Code | **Rust Analyzer: Find matching brace** | ||
| 13 | // |=== | ||
| 5 | pub fn matching_brace(file: &SourceFile, offset: TextSize) -> Option<TextSize> { | 14 | pub fn matching_brace(file: &SourceFile, offset: TextSize) -> Option<TextSize> { |
| 6 | const BRACES: &[SyntaxKind] = | 15 | const BRACES: &[SyntaxKind] = |
| 7 | &[T!['{'], T!['}'], T!['['], T![']'], T!['('], T![')'], T![<], T![>]]; | 16 | &[T!['{'], T!['}'], T!['['], T![']'], T!['('], T![')'], T![<], T![>]]; |
diff --git a/crates/ra_ide/src/mock_analysis.rs b/crates/ra_ide/src/mock_analysis.rs index 2c13f206a..ad78d2d93 100644 --- a/crates/ra_ide/src/mock_analysis.rs +++ b/crates/ra_ide/src/mock_analysis.rs | |||
| @@ -1,21 +1,81 @@ | |||
| 1 | //! FIXME: write short doc here | 1 | //! FIXME: write short doc here |
| 2 | 2 | ||
| 3 | use std::str::FromStr; | ||
| 3 | use std::sync::Arc; | 4 | use std::sync::Arc; |
| 4 | 5 | ||
| 5 | use ra_cfg::CfgOptions; | 6 | use ra_cfg::CfgOptions; |
| 6 | use ra_db::{CrateName, Env, RelativePathBuf}; | 7 | use ra_db::{CrateName, Env, RelativePathBuf}; |
| 7 | use test_utils::{extract_offset, extract_range, parse_fixture, CURSOR_MARKER}; | 8 | use test_utils::{extract_offset, extract_range, parse_fixture, FixtureEntry, CURSOR_MARKER}; |
| 8 | 9 | ||
| 9 | use crate::{ | 10 | use crate::{ |
| 10 | Analysis, AnalysisChange, AnalysisHost, CrateGraph, Edition::Edition2018, FileId, FilePosition, | 11 | Analysis, AnalysisChange, AnalysisHost, CrateGraph, Edition, FileId, FilePosition, FileRange, |
| 11 | FileRange, SourceRootId, | 12 | SourceRootId, |
| 12 | }; | 13 | }; |
| 13 | 14 | ||
| 15 | #[derive(Debug)] | ||
| 16 | enum MockFileData { | ||
| 17 | Plain { path: String, content: String }, | ||
| 18 | Fixture(FixtureEntry), | ||
| 19 | } | ||
| 20 | |||
| 21 | impl MockFileData { | ||
| 22 | fn new(path: String, content: String) -> Self { | ||
| 23 | // `Self::Plain` causes a false warning: 'variant is never constructed: `Plain` ' | ||
| 24 | // see https://github.com/rust-lang/rust/issues/69018 | ||
| 25 | MockFileData::Plain { path, content } | ||
| 26 | } | ||
| 27 | |||
| 28 | fn path(&self) -> &str { | ||
| 29 | match self { | ||
| 30 | MockFileData::Plain { path, .. } => path.as_str(), | ||
| 31 | MockFileData::Fixture(f) => f.meta.path().as_str(), | ||
| 32 | } | ||
| 33 | } | ||
| 34 | |||
| 35 | fn content(&self) -> &str { | ||
| 36 | match self { | ||
| 37 | MockFileData::Plain { content, .. } => content, | ||
| 38 | MockFileData::Fixture(f) => f.text.as_str(), | ||
| 39 | } | ||
| 40 | } | ||
| 41 | |||
| 42 | fn cfg_options(&self) -> CfgOptions { | ||
| 43 | match self { | ||
| 44 | MockFileData::Fixture(f) => { | ||
| 45 | f.meta.cfg_options().map_or_else(Default::default, |o| o.clone()) | ||
| 46 | } | ||
| 47 | _ => CfgOptions::default(), | ||
| 48 | } | ||
| 49 | } | ||
| 50 | |||
| 51 | fn edition(&self) -> Edition { | ||
| 52 | match self { | ||
| 53 | MockFileData::Fixture(f) => { | ||
| 54 | f.meta.edition().map_or(Edition::Edition2018, |v| Edition::from_str(v).unwrap()) | ||
| 55 | } | ||
| 56 | _ => Edition::Edition2018, | ||
| 57 | } | ||
| 58 | } | ||
| 59 | |||
| 60 | fn env(&self) -> Env { | ||
| 61 | match self { | ||
| 62 | MockFileData::Fixture(f) => Env::from(f.meta.env()), | ||
| 63 | _ => Env::default(), | ||
| 64 | } | ||
| 65 | } | ||
| 66 | } | ||
| 67 | |||
| 68 | impl From<FixtureEntry> for MockFileData { | ||
| 69 | fn from(fixture: FixtureEntry) -> Self { | ||
| 70 | Self::Fixture(fixture) | ||
| 71 | } | ||
| 72 | } | ||
| 73 | |||
| 14 | /// Mock analysis is used in test to bootstrap an AnalysisHost/Analysis | 74 | /// Mock analysis is used in test to bootstrap an AnalysisHost/Analysis |
| 15 | /// from a set of in-memory files. | 75 | /// from a set of in-memory files. |
| 16 | #[derive(Debug, Default)] | 76 | #[derive(Debug, Default)] |
| 17 | pub struct MockAnalysis { | 77 | pub struct MockAnalysis { |
| 18 | files: Vec<(String, String)>, | 78 | files: Vec<MockFileData>, |
| 19 | } | 79 | } |
| 20 | 80 | ||
| 21 | impl MockAnalysis { | 81 | impl MockAnalysis { |
| @@ -35,7 +95,7 @@ impl MockAnalysis { | |||
| 35 | pub fn with_files(fixture: &str) -> MockAnalysis { | 95 | pub fn with_files(fixture: &str) -> MockAnalysis { |
| 36 | let mut res = MockAnalysis::new(); | 96 | let mut res = MockAnalysis::new(); |
| 37 | for entry in parse_fixture(fixture) { | 97 | for entry in parse_fixture(fixture) { |
| 38 | res.add_file(&entry.meta, &entry.text); | 98 | res.add_file_fixture(entry); |
| 39 | } | 99 | } |
| 40 | res | 100 | res |
| 41 | } | 101 | } |
| @@ -48,30 +108,44 @@ impl MockAnalysis { | |||
| 48 | for entry in parse_fixture(fixture) { | 108 | for entry in parse_fixture(fixture) { |
| 49 | if entry.text.contains(CURSOR_MARKER) { | 109 | if entry.text.contains(CURSOR_MARKER) { |
| 50 | assert!(position.is_none(), "only one marker (<|>) per fixture is allowed"); | 110 | assert!(position.is_none(), "only one marker (<|>) per fixture is allowed"); |
| 51 | position = Some(res.add_file_with_position(&entry.meta, &entry.text)); | 111 | position = Some(res.add_file_fixture_with_position(entry)); |
| 52 | } else { | 112 | } else { |
| 53 | res.add_file(&entry.meta, &entry.text); | 113 | res.add_file_fixture(entry); |
| 54 | } | 114 | } |
| 55 | } | 115 | } |
| 56 | let position = position.expect("expected a marker (<|>)"); | 116 | let position = position.expect("expected a marker (<|>)"); |
| 57 | (res, position) | 117 | (res, position) |
| 58 | } | 118 | } |
| 59 | 119 | ||
| 120 | pub fn add_file_fixture(&mut self, fixture: FixtureEntry) -> FileId { | ||
| 121 | let file_id = self.next_id(); | ||
| 122 | self.files.push(MockFileData::from(fixture)); | ||
| 123 | file_id | ||
| 124 | } | ||
| 125 | |||
| 126 | pub fn add_file_fixture_with_position(&mut self, mut fixture: FixtureEntry) -> FilePosition { | ||
| 127 | let (offset, text) = extract_offset(&fixture.text); | ||
| 128 | fixture.text = text; | ||
| 129 | let file_id = self.next_id(); | ||
| 130 | self.files.push(MockFileData::from(fixture)); | ||
| 131 | FilePosition { file_id, offset } | ||
| 132 | } | ||
| 133 | |||
| 60 | pub fn add_file(&mut self, path: &str, text: &str) -> FileId { | 134 | pub fn add_file(&mut self, path: &str, text: &str) -> FileId { |
| 61 | let file_id = FileId((self.files.len() + 1) as u32); | 135 | let file_id = self.next_id(); |
| 62 | self.files.push((path.to_string(), text.to_string())); | 136 | self.files.push(MockFileData::new(path.to_string(), text.to_string())); |
| 63 | file_id | 137 | file_id |
| 64 | } | 138 | } |
| 65 | pub fn add_file_with_position(&mut self, path: &str, text: &str) -> FilePosition { | 139 | pub fn add_file_with_position(&mut self, path: &str, text: &str) -> FilePosition { |
| 66 | let (offset, text) = extract_offset(text); | 140 | let (offset, text) = extract_offset(text); |
| 67 | let file_id = FileId((self.files.len() + 1) as u32); | 141 | let file_id = self.next_id(); |
| 68 | self.files.push((path.to_string(), text)); | 142 | self.files.push(MockFileData::new(path.to_string(), text)); |
| 69 | FilePosition { file_id, offset } | 143 | FilePosition { file_id, offset } |
| 70 | } | 144 | } |
| 71 | pub fn add_file_with_range(&mut self, path: &str, text: &str) -> FileRange { | 145 | pub fn add_file_with_range(&mut self, path: &str, text: &str) -> FileRange { |
| 72 | let (range, text) = extract_range(text); | 146 | let (range, text) = extract_range(text); |
| 73 | let file_id = FileId((self.files.len() + 1) as u32); | 147 | let file_id = self.next_id(); |
| 74 | self.files.push((path.to_string(), text)); | 148 | self.files.push(MockFileData::new(path.to_string(), text)); |
| 75 | FileRange { file_id, range } | 149 | FileRange { file_id, range } |
| 76 | } | 150 | } |
| 77 | pub fn id_of(&self, path: &str) -> FileId { | 151 | pub fn id_of(&self, path: &str) -> FileId { |
| @@ -79,7 +153,7 @@ impl MockAnalysis { | |||
| 79 | .files | 153 | .files |
| 80 | .iter() | 154 | .iter() |
| 81 | .enumerate() | 155 | .enumerate() |
| 82 | .find(|(_, (p, _text))| path == p) | 156 | .find(|(_, data)| path == data.path()) |
| 83 | .expect("no file in this mock"); | 157 | .expect("no file in this mock"); |
| 84 | FileId(idx as u32 + 1) | 158 | FileId(idx as u32 + 1) |
| 85 | } | 159 | } |
| @@ -90,18 +164,21 @@ impl MockAnalysis { | |||
| 90 | change.add_root(source_root, true); | 164 | change.add_root(source_root, true); |
| 91 | let mut crate_graph = CrateGraph::default(); | 165 | let mut crate_graph = CrateGraph::default(); |
| 92 | let mut root_crate = None; | 166 | let mut root_crate = None; |
| 93 | for (i, (path, contents)) in self.files.into_iter().enumerate() { | 167 | for (i, data) in self.files.into_iter().enumerate() { |
| 168 | let path = data.path(); | ||
| 94 | assert!(path.starts_with('/')); | 169 | assert!(path.starts_with('/')); |
| 95 | let path = RelativePathBuf::from_path(&path[1..]).unwrap(); | 170 | let path = RelativePathBuf::from_path(&path[1..]).unwrap(); |
| 171 | let cfg_options = data.cfg_options(); | ||
| 96 | let file_id = FileId(i as u32 + 1); | 172 | let file_id = FileId(i as u32 + 1); |
| 97 | let cfg_options = CfgOptions::default(); | 173 | let edition = data.edition(); |
| 174 | let env = data.env(); | ||
| 98 | if path == "/lib.rs" || path == "/main.rs" { | 175 | if path == "/lib.rs" || path == "/main.rs" { |
| 99 | root_crate = Some(crate_graph.add_crate_root( | 176 | root_crate = Some(crate_graph.add_crate_root( |
| 100 | file_id, | 177 | file_id, |
| 101 | Edition2018, | 178 | edition, |
| 102 | None, | 179 | None, |
| 103 | cfg_options, | 180 | cfg_options, |
| 104 | Env::default(), | 181 | env, |
| 105 | Default::default(), | 182 | Default::default(), |
| 106 | Default::default(), | 183 | Default::default(), |
| 107 | )); | 184 | )); |
| @@ -109,10 +186,10 @@ impl MockAnalysis { | |||
| 109 | let crate_name = path.parent().unwrap().file_name().unwrap(); | 186 | let crate_name = path.parent().unwrap().file_name().unwrap(); |
| 110 | let other_crate = crate_graph.add_crate_root( | 187 | let other_crate = crate_graph.add_crate_root( |
| 111 | file_id, | 188 | file_id, |
| 112 | Edition2018, | 189 | edition, |
| 113 | Some(CrateName::new(crate_name).unwrap()), | 190 | Some(CrateName::new(crate_name).unwrap()), |
| 114 | cfg_options, | 191 | cfg_options, |
| 115 | Env::default(), | 192 | env, |
| 116 | Default::default(), | 193 | Default::default(), |
| 117 | Default::default(), | 194 | Default::default(), |
| 118 | ); | 195 | ); |
| @@ -122,7 +199,7 @@ impl MockAnalysis { | |||
| 122 | .unwrap(); | 199 | .unwrap(); |
| 123 | } | 200 | } |
| 124 | } | 201 | } |
| 125 | change.add_file(source_root, file_id, path, Arc::new(contents)); | 202 | change.add_file(source_root, file_id, path, Arc::new(data.content().to_owned())); |
| 126 | } | 203 | } |
| 127 | change.set_crate_graph(crate_graph); | 204 | change.set_crate_graph(crate_graph); |
| 128 | host.apply_change(change); | 205 | host.apply_change(change); |
| @@ -131,6 +208,10 @@ impl MockAnalysis { | |||
| 131 | pub fn analysis(self) -> Analysis { | 208 | pub fn analysis(self) -> Analysis { |
| 132 | self.analysis_host().analysis() | 209 | self.analysis_host().analysis() |
| 133 | } | 210 | } |
| 211 | |||
| 212 | fn next_id(&self) -> FileId { | ||
| 213 | FileId((self.files.len() + 1) as u32) | ||
| 214 | } | ||
| 134 | } | 215 | } |
| 135 | 216 | ||
| 136 | /// Creates analysis from a multi-file fixture, returns positions marked with <|>. | 217 | /// Creates analysis from a multi-file fixture, returns positions marked with <|>. |
diff --git a/crates/ra_ide/src/parent_module.rs b/crates/ra_ide/src/parent_module.rs index a083fb1eb..fa1535da5 100644 --- a/crates/ra_ide/src/parent_module.rs +++ b/crates/ra_ide/src/parent_module.rs | |||
| @@ -1,5 +1,3 @@ | |||
| 1 | //! FIXME: write short doc here | ||
| 2 | |||
| 3 | use hir::Semantics; | 1 | use hir::Semantics; |
| 4 | use ra_db::{CrateId, FileId, FilePosition}; | 2 | use ra_db::{CrateId, FileId, FilePosition}; |
| 5 | use ra_ide_db::RootDatabase; | 3 | use ra_ide_db::RootDatabase; |
| @@ -11,6 +9,16 @@ use test_utils::mark; | |||
| 11 | 9 | ||
| 12 | use crate::NavigationTarget; | 10 | use crate::NavigationTarget; |
| 13 | 11 | ||
| 12 | // Feature: Parent Module | ||
| 13 | // | ||
| 14 | // Navigates to the parent module of the current module. | ||
| 15 | // | ||
| 16 | // |=== | ||
| 17 | // | Editor | Action Name | ||
| 18 | // | ||
| 19 | // | VS Code | **Rust Analyzer: Locate parent module** | ||
| 20 | // |=== | ||
| 21 | |||
| 14 | /// This returns `Vec` because a module may be included from several places. We | 22 | /// This returns `Vec` because a module may be included from several places. We |
| 15 | /// don't handle this case yet though, so the Vec has length at most one. | 23 | /// don't handle this case yet though, so the Vec has length at most one. |
| 16 | pub(crate) fn parent_module(db: &RootDatabase, position: FilePosition) -> Vec<NavigationTarget> { | 24 | pub(crate) fn parent_module(db: &RootDatabase, position: FilePosition) -> Vec<NavigationTarget> { |
diff --git a/crates/ra_ide/src/references.rs b/crates/ra_ide/src/references.rs index 96444bf6a..bb40d2043 100644 --- a/crates/ra_ide/src/references.rs +++ b/crates/ra_ide/src/references.rs | |||
| @@ -615,6 +615,33 @@ mod tests { | |||
| 615 | ); | 615 | ); |
| 616 | } | 616 | } |
| 617 | 617 | ||
| 618 | #[test] | ||
| 619 | fn test_find_all_refs_nested_module() { | ||
| 620 | let code = r#" | ||
| 621 | //- /lib.rs | ||
| 622 | mod foo { | ||
| 623 | mod bar; | ||
| 624 | } | ||
| 625 | |||
| 626 | fn f<|>() {} | ||
| 627 | |||
| 628 | //- /foo/bar.rs | ||
| 629 | use crate::f; | ||
| 630 | |||
| 631 | fn g() { | ||
| 632 | f(); | ||
| 633 | } | ||
| 634 | "#; | ||
| 635 | |||
| 636 | let (analysis, pos) = analysis_and_position(code); | ||
| 637 | let refs = analysis.find_all_refs(pos, None).unwrap().unwrap(); | ||
| 638 | check_result( | ||
| 639 | refs, | ||
| 640 | "f FN_DEF FileId(1) 25..34 28..29 Other", | ||
| 641 | &["FileId(2) 11..12 Other", "FileId(2) 27..28 StructLiteral"], | ||
| 642 | ); | ||
| 643 | } | ||
| 644 | |||
| 618 | fn get_all_refs(text: &str) -> ReferenceSearchResult { | 645 | fn get_all_refs(text: &str) -> ReferenceSearchResult { |
| 619 | let (analysis, position) = single_file_with_position(text); | 646 | let (analysis, position) = single_file_with_position(text); |
| 620 | analysis.find_all_refs(position, None).unwrap().unwrap() | 647 | analysis.find_all_refs(position, None).unwrap().unwrap() |
diff --git a/crates/ra_ide/src/runnables.rs b/crates/ra_ide/src/runnables.rs index 131b8f307..f32ce0d22 100644 --- a/crates/ra_ide/src/runnables.rs +++ b/crates/ra_ide/src/runnables.rs | |||
| @@ -1,21 +1,21 @@ | |||
| 1 | //! FIXME: write short doc here | 1 | use std::fmt; |
| 2 | 2 | ||
| 3 | use hir::{AsAssocItem, Semantics}; | 3 | use hir::{AsAssocItem, Attrs, HirFileId, InFile, Semantics}; |
| 4 | use itertools::Itertools; | 4 | use itertools::Itertools; |
| 5 | use ra_cfg::CfgExpr; | ||
| 5 | use ra_ide_db::RootDatabase; | 6 | use ra_ide_db::RootDatabase; |
| 6 | use ra_syntax::{ | 7 | use ra_syntax::{ |
| 7 | ast::{self, AstNode, AttrsOwner, ModuleItemOwner, NameOwner}, | 8 | ast::{self, AstNode, AttrsOwner, DocCommentsOwner, ModuleItemOwner, NameOwner}, |
| 8 | match_ast, SyntaxNode, TextRange, | 9 | match_ast, SyntaxNode, |
| 9 | }; | 10 | }; |
| 10 | 11 | ||
| 11 | use crate::FileId; | 12 | use crate::{display::ToNav, FileId, NavigationTarget}; |
| 12 | use ast::DocCommentsOwner; | ||
| 13 | use std::fmt::Display; | ||
| 14 | 13 | ||
| 15 | #[derive(Debug)] | 14 | #[derive(Debug)] |
| 16 | pub struct Runnable { | 15 | pub struct Runnable { |
| 17 | pub range: TextRange, | 16 | pub nav: NavigationTarget, |
| 18 | pub kind: RunnableKind, | 17 | pub kind: RunnableKind, |
| 18 | pub cfg_exprs: Vec<CfgExpr>, | ||
| 19 | } | 19 | } |
| 20 | 20 | ||
| 21 | #[derive(Debug)] | 21 | #[derive(Debug)] |
| @@ -24,8 +24,8 @@ pub enum TestId { | |||
| 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), |
| @@ -42,32 +42,47 @@ pub enum RunnableKind { | |||
| 42 | Bin, | 42 | Bin, |
| 43 | } | 43 | } |
| 44 | 44 | ||
| 45 | // Feature: Run | ||
| 46 | // | ||
| 47 | // Shows a popup suggesting to run a test/benchmark/binary **at the current cursor | ||
| 48 | // location**. Super useful for repeatedly running just a single test. Do bind this | ||
| 49 | // to a shortcut! | ||
| 50 | // | ||
| 51 | // |=== | ||
| 52 | // | Editor | Action Name | ||
| 53 | // | ||
| 54 | // | VS Code | **Rust Analyzer: Run** | ||
| 55 | // |=== | ||
| 45 | pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> { | 56 | pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> { |
| 46 | let sema = Semantics::new(db); | 57 | let sema = Semantics::new(db); |
| 47 | let source_file = sema.parse(file_id); | 58 | let source_file = sema.parse(file_id); |
| 48 | source_file.syntax().descendants().filter_map(|i| runnable(&sema, i)).collect() | 59 | source_file.syntax().descendants().filter_map(|i| runnable(&sema, i, file_id)).collect() |
| 49 | } | 60 | } |
| 50 | 61 | ||
| 51 | fn runnable(sema: &Semantics<RootDatabase>, item: SyntaxNode) -> Option<Runnable> { | 62 | fn runnable(sema: &Semantics<RootDatabase>, item: SyntaxNode, file_id: FileId) -> Option<Runnable> { |
| 52 | match_ast! { | 63 | match_ast! { |
| 53 | match item { | 64 | match item { |
| 54 | ast::FnDef(it) => runnable_fn(sema, it), | 65 | ast::FnDef(it) => runnable_fn(sema, it, file_id), |
| 55 | ast::Module(it) => runnable_mod(sema, it), | 66 | ast::Module(it) => runnable_mod(sema, it, file_id), |
| 56 | _ => None, | 67 | _ => None, |
| 57 | } | 68 | } |
| 58 | } | 69 | } |
| 59 | } | 70 | } |
| 60 | 71 | ||
| 61 | fn runnable_fn(sema: &Semantics<RootDatabase>, fn_def: ast::FnDef) -> Option<Runnable> { | 72 | fn runnable_fn( |
| 73 | sema: &Semantics<RootDatabase>, | ||
| 74 | fn_def: ast::FnDef, | ||
| 75 | file_id: FileId, | ||
| 76 | ) -> Option<Runnable> { | ||
| 62 | let name_string = fn_def.name()?.text().to_string(); | 77 | let name_string = fn_def.name()?.text().to_string(); |
| 63 | 78 | ||
| 64 | let kind = if name_string == "main" { | 79 | let kind = if name_string == "main" { |
| 65 | RunnableKind::Bin | 80 | RunnableKind::Bin |
| 66 | } else { | 81 | } else { |
| 67 | let test_id = if let Some(module) = sema.to_def(&fn_def).map(|def| def.module(sema.db)) { | 82 | let test_id = match sema.to_def(&fn_def).map(|def| def.module(sema.db)) { |
| 68 | let def = sema.to_def(&fn_def)?; | 83 | Some(module) => { |
| 69 | let impl_trait_name = | 84 | let def = sema.to_def(&fn_def)?; |
| 70 | def.as_assoc_item(sema.db).and_then(|assoc_item| { | 85 | let impl_trait_name = def.as_assoc_item(sema.db).and_then(|assoc_item| { |
| 71 | match assoc_item.container(sema.db) { | 86 | match assoc_item.container(sema.db) { |
| 72 | hir::AssocItemContainer::Trait(trait_item) => { | 87 | hir::AssocItemContainer::Trait(trait_item) => { |
| 73 | Some(trait_item.name(sema.db).to_string()) | 88 | Some(trait_item.name(sema.db).to_string()) |
| @@ -79,25 +94,25 @@ fn runnable_fn(sema: &Semantics<RootDatabase>, fn_def: ast::FnDef) -> Option<Run | |||
| 79 | } | 94 | } |
| 80 | }); | 95 | }); |
| 81 | 96 | ||
| 82 | let path_iter = module | 97 | let path_iter = module |
| 83 | .path_to_root(sema.db) | 98 | .path_to_root(sema.db) |
| 84 | .into_iter() | 99 | .into_iter() |
| 85 | .rev() | 100 | .rev() |
| 86 | .filter_map(|it| it.name(sema.db)) | 101 | .filter_map(|it| it.name(sema.db)) |
| 87 | .map(|name| name.to_string()); | 102 | .map(|name| name.to_string()); |
| 88 | 103 | ||
| 89 | let path = if let Some(impl_trait_name) = impl_trait_name { | 104 | let path = if let Some(impl_trait_name) = impl_trait_name { |
| 90 | path_iter | 105 | path_iter |
| 91 | .chain(std::iter::once(impl_trait_name)) | 106 | .chain(std::iter::once(impl_trait_name)) |
| 92 | .chain(std::iter::once(name_string)) | 107 | .chain(std::iter::once(name_string)) |
| 93 | .join("::") | 108 | .join("::") |
| 94 | } else { | 109 | } else { |
| 95 | path_iter.chain(std::iter::once(name_string)).join("::") | 110 | path_iter.chain(std::iter::once(name_string)).join("::") |
| 96 | }; | 111 | }; |
| 97 | 112 | ||
| 98 | TestId::Path(path) | 113 | TestId::Path(path) |
| 99 | } else { | 114 | } |
| 100 | TestId::Name(name_string) | 115 | None => TestId::Name(name_string), |
| 101 | }; | 116 | }; |
| 102 | 117 | ||
| 103 | if has_test_related_attribute(&fn_def) { | 118 | if has_test_related_attribute(&fn_def) { |
| @@ -111,7 +126,13 @@ fn runnable_fn(sema: &Semantics<RootDatabase>, fn_def: ast::FnDef) -> Option<Run | |||
| 111 | return None; | 126 | return None; |
| 112 | } | 127 | } |
| 113 | }; | 128 | }; |
| 114 | Some(Runnable { range: fn_def.syntax().text_range(), kind }) | 129 | |
| 130 | let attrs = Attrs::from_attrs_owner(sema.db, InFile::new(HirFileId::from(file_id), &fn_def)); | ||
| 131 | let cfg_exprs = | ||
| 132 | attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect(); | ||
| 133 | |||
| 134 | let nav = NavigationTarget::from_named(sema.db, InFile::new(file_id.into(), &fn_def)); | ||
| 135 | Some(Runnable { nav, kind, cfg_exprs }) | ||
| 115 | } | 136 | } |
| 116 | 137 | ||
| 117 | #[derive(Debug)] | 138 | #[derive(Debug)] |
| @@ -147,7 +168,11 @@ fn has_doc_test(fn_def: &ast::FnDef) -> bool { | |||
| 147 | fn_def.doc_comment_text().map_or(false, |comment| comment.contains("```")) | 168 | fn_def.doc_comment_text().map_or(false, |comment| comment.contains("```")) |
| 148 | } | 169 | } |
| 149 | 170 | ||
| 150 | fn runnable_mod(sema: &Semantics<RootDatabase>, module: ast::Module) -> Option<Runnable> { | 171 | fn runnable_mod( |
| 172 | sema: &Semantics<RootDatabase>, | ||
| 173 | module: ast::Module, | ||
| 174 | file_id: FileId, | ||
| 175 | ) -> Option<Runnable> { | ||
| 151 | let has_test_function = module | 176 | let has_test_function = module |
| 152 | .item_list()? | 177 | .item_list()? |
| 153 | .items() | 178 | .items() |
| @@ -159,12 +184,21 @@ fn runnable_mod(sema: &Semantics<RootDatabase>, module: ast::Module) -> Option<R | |||
| 159 | if !has_test_function { | 184 | if !has_test_function { |
| 160 | return None; | 185 | return None; |
| 161 | } | 186 | } |
| 162 | let range = module.syntax().text_range(); | 187 | let module_def = sema.to_def(&module)?; |
| 163 | let module = sema.to_def(&module)?; | ||
| 164 | 188 | ||
| 165 | let path = | 189 | let path = module_def |
| 166 | module.path_to_root(sema.db).into_iter().rev().filter_map(|it| it.name(sema.db)).join("::"); | 190 | .path_to_root(sema.db) |
| 167 | Some(Runnable { range, kind: RunnableKind::TestMod { path } }) | 191 | .into_iter() |
| 192 | .rev() | ||
| 193 | .filter_map(|it| it.name(sema.db)) | ||
| 194 | .join("::"); | ||
| 195 | |||
| 196 | let attrs = Attrs::from_attrs_owner(sema.db, InFile::new(HirFileId::from(file_id), &module)); | ||
| 197 | let cfg_exprs = | ||
| 198 | attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect(); | ||
| 199 | |||
| 200 | let nav = module_def.to_nav(sema.db); | ||
| 201 | Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg_exprs }) | ||
| 168 | } | 202 | } |
| 169 | 203 | ||
| 170 | #[cfg(test)] | 204 | #[cfg(test)] |
| @@ -194,11 +228,38 @@ mod tests { | |||
| 194 | @r###" | 228 | @r###" |
| 195 | [ | 229 | [ |
| 196 | Runnable { | 230 | Runnable { |
| 197 | range: 1..21, | 231 | nav: NavigationTarget { |
| 232 | file_id: FileId( | ||
| 233 | 1, | ||
| 234 | ), | ||
| 235 | full_range: 1..21, | ||
| 236 | name: "main", | ||
| 237 | kind: FN_DEF, | ||
| 238 | focus_range: Some( | ||
| 239 | 12..16, | ||
| 240 | ), | ||
| 241 | container_name: None, | ||
| 242 | description: None, | ||
| 243 | docs: None, | ||
| 244 | }, | ||
| 198 | kind: Bin, | 245 | kind: Bin, |
| 246 | cfg_exprs: [], | ||
| 199 | }, | 247 | }, |
| 200 | Runnable { | 248 | Runnable { |
| 201 | range: 22..46, | 249 | nav: NavigationTarget { |
| 250 | file_id: FileId( | ||
| 251 | 1, | ||
| 252 | ), | ||
| 253 | full_range: 22..46, | ||
| 254 | name: "test_foo", | ||
| 255 | kind: FN_DEF, | ||
| 256 | focus_range: Some( | ||
| 257 | 33..41, | ||
| 258 | ), | ||
| 259 | container_name: None, | ||
| 260 | description: None, | ||
| 261 | docs: None, | ||
| 262 | }, | ||
| 202 | kind: Test { | 263 | kind: Test { |
| 203 | test_id: Path( | 264 | test_id: Path( |
| 204 | "test_foo", | 265 | "test_foo", |
| @@ -207,9 +268,23 @@ mod tests { | |||
| 207 | ignore: false, | 268 | ignore: false, |
| 208 | }, | 269 | }, |
| 209 | }, | 270 | }, |
| 271 | cfg_exprs: [], | ||
| 210 | }, | 272 | }, |
| 211 | Runnable { | 273 | Runnable { |
| 212 | range: 47..81, | 274 | nav: NavigationTarget { |
| 275 | file_id: FileId( | ||
| 276 | 1, | ||
| 277 | ), | ||
| 278 | full_range: 47..81, | ||
| 279 | name: "test_foo", | ||
| 280 | kind: FN_DEF, | ||
| 281 | focus_range: Some( | ||
| 282 | 68..76, | ||
| 283 | ), | ||
| 284 | container_name: None, | ||
| 285 | description: None, | ||
| 286 | docs: None, | ||
| 287 | }, | ||
| 213 | kind: Test { | 288 | kind: Test { |
| 214 | test_id: Path( | 289 | test_id: Path( |
| 215 | "test_foo", | 290 | "test_foo", |
| @@ -218,6 +293,7 @@ mod tests { | |||
| 218 | ignore: true, | 293 | ignore: true, |
| 219 | }, | 294 | }, |
| 220 | }, | 295 | }, |
| 296 | cfg_exprs: [], | ||
| 221 | }, | 297 | }, |
| 222 | ] | 298 | ] |
| 223 | "### | 299 | "### |
| @@ -243,16 +319,44 @@ mod tests { | |||
| 243 | @r###" | 319 | @r###" |
| 244 | [ | 320 | [ |
| 245 | Runnable { | 321 | Runnable { |
| 246 | range: 1..21, | 322 | nav: NavigationTarget { |
| 323 | file_id: FileId( | ||
| 324 | 1, | ||
| 325 | ), | ||
| 326 | full_range: 1..21, | ||
| 327 | name: "main", | ||
| 328 | kind: FN_DEF, | ||
| 329 | focus_range: Some( | ||
| 330 | 12..16, | ||
| 331 | ), | ||
| 332 | container_name: None, | ||
| 333 | description: None, | ||
| 334 | docs: None, | ||
| 335 | }, | ||
| 247 | kind: Bin, | 336 | kind: Bin, |
| 337 | cfg_exprs: [], | ||
| 248 | }, | 338 | }, |
| 249 | Runnable { | 339 | Runnable { |
| 250 | range: 22..64, | 340 | nav: NavigationTarget { |
| 341 | file_id: FileId( | ||
| 342 | 1, | ||
| 343 | ), | ||
| 344 | full_range: 22..64, | ||
| 345 | name: "foo", | ||
| 346 | kind: FN_DEF, | ||
| 347 | focus_range: Some( | ||
| 348 | 56..59, | ||
| 349 | ), | ||
| 350 | container_name: None, | ||
| 351 | description: None, | ||
| 352 | docs: None, | ||
| 353 | }, | ||
| 251 | kind: DocTest { | 354 | kind: DocTest { |
| 252 | test_id: Path( | 355 | test_id: Path( |
| 253 | "foo", | 356 | "foo", |
| 254 | ), | 357 | ), |
| 255 | }, | 358 | }, |
| 359 | cfg_exprs: [], | ||
| 256 | }, | 360 | }, |
| 257 | ] | 361 | ] |
| 258 | "### | 362 | "### |
| @@ -281,16 +385,44 @@ mod tests { | |||
| 281 | @r###" | 385 | @r###" |
| 282 | [ | 386 | [ |
| 283 | Runnable { | 387 | Runnable { |
| 284 | range: 1..21, | 388 | nav: NavigationTarget { |
| 389 | file_id: FileId( | ||
| 390 | 1, | ||
| 391 | ), | ||
| 392 | full_range: 1..21, | ||
| 393 | name: "main", | ||
| 394 | kind: FN_DEF, | ||
| 395 | focus_range: Some( | ||
| 396 | 12..16, | ||
| 397 | ), | ||
| 398 | container_name: None, | ||
| 399 | description: None, | ||
| 400 | docs: None, | ||
| 401 | }, | ||
| 285 | kind: Bin, | 402 | kind: Bin, |
| 403 | cfg_exprs: [], | ||
| 286 | }, | 404 | }, |
| 287 | Runnable { | 405 | Runnable { |
| 288 | range: 51..105, | 406 | nav: NavigationTarget { |
| 407 | file_id: FileId( | ||
| 408 | 1, | ||
| 409 | ), | ||
| 410 | full_range: 51..105, | ||
| 411 | name: "foo", | ||
| 412 | kind: FN_DEF, | ||
| 413 | focus_range: Some( | ||
| 414 | 97..100, | ||
| 415 | ), | ||
| 416 | container_name: None, | ||
| 417 | description: None, | ||
| 418 | docs: None, | ||
| 419 | }, | ||
| 289 | kind: DocTest { | 420 | kind: DocTest { |
| 290 | test_id: Path( | 421 | test_id: Path( |
| 291 | "Data::foo", | 422 | "Data::foo", |
| 292 | ), | 423 | ), |
| 293 | }, | 424 | }, |
| 425 | cfg_exprs: [], | ||
| 294 | }, | 426 | }, |
| 295 | ] | 427 | ] |
| 296 | "### | 428 | "### |
| @@ -314,13 +446,40 @@ mod tests { | |||
| 314 | @r###" | 446 | @r###" |
| 315 | [ | 447 | [ |
| 316 | Runnable { | 448 | Runnable { |
| 317 | range: 1..59, | 449 | nav: NavigationTarget { |
| 450 | file_id: FileId( | ||
| 451 | 1, | ||
| 452 | ), | ||
| 453 | full_range: 1..59, | ||
| 454 | name: "test_mod", | ||
| 455 | kind: MODULE, | ||
| 456 | focus_range: Some( | ||
| 457 | 13..21, | ||
| 458 | ), | ||
| 459 | container_name: None, | ||
| 460 | description: None, | ||
| 461 | docs: None, | ||
| 462 | }, | ||
| 318 | kind: TestMod { | 463 | kind: TestMod { |
| 319 | path: "test_mod", | 464 | path: "test_mod", |
| 320 | }, | 465 | }, |
| 466 | cfg_exprs: [], | ||
| 321 | }, | 467 | }, |
| 322 | Runnable { | 468 | Runnable { |
| 323 | range: 28..57, | 469 | nav: NavigationTarget { |
| 470 | file_id: FileId( | ||
| 471 | 1, | ||
| 472 | ), | ||
| 473 | full_range: 28..57, | ||
| 474 | name: "test_foo1", | ||
| 475 | kind: FN_DEF, | ||
| 476 | focus_range: Some( | ||
| 477 | 43..52, | ||
| 478 | ), | ||
| 479 | container_name: None, | ||
| 480 | description: None, | ||
| 481 | docs: None, | ||
| 482 | }, | ||
| 324 | kind: Test { | 483 | kind: Test { |
| 325 | test_id: Path( | 484 | test_id: Path( |
| 326 | "test_mod::test_foo1", | 485 | "test_mod::test_foo1", |
| @@ -329,6 +488,7 @@ mod tests { | |||
| 329 | ignore: false, | 488 | ignore: false, |
| 330 | }, | 489 | }, |
| 331 | }, | 490 | }, |
| 491 | cfg_exprs: [], | ||
| 332 | }, | 492 | }, |
| 333 | ] | 493 | ] |
| 334 | "### | 494 | "### |
| @@ -354,13 +514,40 @@ mod tests { | |||
| 354 | @r###" | 514 | @r###" |
| 355 | [ | 515 | [ |
| 356 | Runnable { | 516 | Runnable { |
| 357 | range: 23..85, | 517 | nav: NavigationTarget { |
| 518 | file_id: FileId( | ||
| 519 | 1, | ||
| 520 | ), | ||
| 521 | full_range: 23..85, | ||
| 522 | name: "test_mod", | ||
| 523 | kind: MODULE, | ||
| 524 | focus_range: Some( | ||
| 525 | 27..35, | ||
| 526 | ), | ||
| 527 | container_name: None, | ||
| 528 | description: None, | ||
| 529 | docs: None, | ||
| 530 | }, | ||
| 358 | kind: TestMod { | 531 | kind: TestMod { |
| 359 | path: "foo::test_mod", | 532 | path: "foo::test_mod", |
| 360 | }, | 533 | }, |
| 534 | cfg_exprs: [], | ||
| 361 | }, | 535 | }, |
| 362 | Runnable { | 536 | Runnable { |
| 363 | range: 46..79, | 537 | nav: NavigationTarget { |
| 538 | file_id: FileId( | ||
| 539 | 1, | ||
| 540 | ), | ||
| 541 | full_range: 46..79, | ||
| 542 | name: "test_foo1", | ||
| 543 | kind: FN_DEF, | ||
| 544 | focus_range: Some( | ||
| 545 | 65..74, | ||
| 546 | ), | ||
| 547 | container_name: None, | ||
| 548 | description: None, | ||
| 549 | docs: None, | ||
| 550 | }, | ||
| 364 | kind: Test { | 551 | kind: Test { |
| 365 | test_id: Path( | 552 | test_id: Path( |
| 366 | "foo::test_mod::test_foo1", | 553 | "foo::test_mod::test_foo1", |
| @@ -369,6 +556,7 @@ mod tests { | |||
| 369 | ignore: false, | 556 | ignore: false, |
| 370 | }, | 557 | }, |
| 371 | }, | 558 | }, |
| 559 | cfg_exprs: [], | ||
| 372 | }, | 560 | }, |
| 373 | ] | 561 | ] |
| 374 | "### | 562 | "### |
| @@ -396,13 +584,40 @@ mod tests { | |||
| 396 | @r###" | 584 | @r###" |
| 397 | [ | 585 | [ |
| 398 | Runnable { | 586 | Runnable { |
| 399 | range: 41..115, | 587 | nav: NavigationTarget { |
| 588 | file_id: FileId( | ||
| 589 | 1, | ||
| 590 | ), | ||
| 591 | full_range: 41..115, | ||
| 592 | name: "test_mod", | ||
| 593 | kind: MODULE, | ||
| 594 | focus_range: Some( | ||
| 595 | 45..53, | ||
| 596 | ), | ||
| 597 | container_name: None, | ||
| 598 | description: None, | ||
| 599 | docs: None, | ||
| 600 | }, | ||
| 400 | kind: TestMod { | 601 | kind: TestMod { |
| 401 | path: "foo::bar::test_mod", | 602 | path: "foo::bar::test_mod", |
| 402 | }, | 603 | }, |
| 604 | cfg_exprs: [], | ||
| 403 | }, | 605 | }, |
| 404 | Runnable { | 606 | Runnable { |
| 405 | range: 68..105, | 607 | nav: NavigationTarget { |
| 608 | file_id: FileId( | ||
| 609 | 1, | ||
| 610 | ), | ||
| 611 | full_range: 68..105, | ||
| 612 | name: "test_foo1", | ||
| 613 | kind: FN_DEF, | ||
| 614 | focus_range: Some( | ||
| 615 | 91..100, | ||
| 616 | ), | ||
| 617 | container_name: None, | ||
| 618 | description: None, | ||
| 619 | docs: None, | ||
| 620 | }, | ||
| 406 | kind: Test { | 621 | kind: Test { |
| 407 | test_id: Path( | 622 | test_id: Path( |
| 408 | "foo::bar::test_mod::test_foo1", | 623 | "foo::bar::test_mod::test_foo1", |
| @@ -411,6 +626,115 @@ mod tests { | |||
| 411 | ignore: false, | 626 | ignore: false, |
| 412 | }, | 627 | }, |
| 413 | }, | 628 | }, |
| 629 | cfg_exprs: [], | ||
| 630 | }, | ||
| 631 | ] | ||
| 632 | "### | ||
| 633 | ); | ||
| 634 | } | ||
| 635 | |||
| 636 | #[test] | ||
| 637 | fn test_runnables_with_feature() { | ||
| 638 | let (analysis, pos) = analysis_and_position( | ||
| 639 | r#" | ||
| 640 | //- /lib.rs crate:foo cfg:feature=foo | ||
| 641 | <|> //empty | ||
| 642 | #[test] | ||
| 643 | #[cfg(feature = "foo")] | ||
| 644 | fn test_foo1() {} | ||
| 645 | "#, | ||
| 646 | ); | ||
| 647 | let runnables = analysis.runnables(pos.file_id).unwrap(); | ||
| 648 | assert_debug_snapshot!(&runnables, | ||
| 649 | @r###" | ||
| 650 | [ | ||
| 651 | Runnable { | ||
| 652 | nav: NavigationTarget { | ||
| 653 | file_id: FileId( | ||
| 654 | 1, | ||
| 655 | ), | ||
| 656 | full_range: 1..58, | ||
| 657 | name: "test_foo1", | ||
| 658 | kind: FN_DEF, | ||
| 659 | focus_range: Some( | ||
| 660 | 44..53, | ||
| 661 | ), | ||
| 662 | container_name: None, | ||
| 663 | description: None, | ||
| 664 | docs: None, | ||
| 665 | }, | ||
| 666 | kind: Test { | ||
| 667 | test_id: Path( | ||
| 668 | "test_foo1", | ||
| 669 | ), | ||
| 670 | attr: TestAttr { | ||
| 671 | ignore: false, | ||
| 672 | }, | ||
| 673 | }, | ||
| 674 | cfg_exprs: [ | ||
| 675 | KeyValue { | ||
| 676 | key: "feature", | ||
| 677 | value: "foo", | ||
| 678 | }, | ||
| 679 | ], | ||
| 680 | }, | ||
| 681 | ] | ||
| 682 | "### | ||
| 683 | ); | ||
| 684 | } | ||
| 685 | |||
| 686 | #[test] | ||
| 687 | fn test_runnables_with_features() { | ||
| 688 | let (analysis, pos) = analysis_and_position( | ||
| 689 | r#" | ||
| 690 | //- /lib.rs crate:foo cfg:feature=foo,feature=bar | ||
| 691 | <|> //empty | ||
| 692 | #[test] | ||
| 693 | #[cfg(all(feature = "foo", feature = "bar"))] | ||
| 694 | fn test_foo1() {} | ||
| 695 | "#, | ||
| 696 | ); | ||
| 697 | let runnables = analysis.runnables(pos.file_id).unwrap(); | ||
| 698 | assert_debug_snapshot!(&runnables, | ||
| 699 | @r###" | ||
| 700 | [ | ||
| 701 | Runnable { | ||
| 702 | nav: NavigationTarget { | ||
| 703 | file_id: FileId( | ||
| 704 | 1, | ||
| 705 | ), | ||
| 706 | full_range: 1..80, | ||
| 707 | name: "test_foo1", | ||
| 708 | kind: FN_DEF, | ||
| 709 | focus_range: Some( | ||
| 710 | 66..75, | ||
| 711 | ), | ||
| 712 | container_name: None, | ||
| 713 | description: None, | ||
| 714 | docs: None, | ||
| 715 | }, | ||
| 716 | kind: Test { | ||
| 717 | test_id: Path( | ||
| 718 | "test_foo1", | ||
| 719 | ), | ||
| 720 | attr: TestAttr { | ||
| 721 | ignore: false, | ||
| 722 | }, | ||
| 723 | }, | ||
| 724 | cfg_exprs: [ | ||
| 725 | All( | ||
| 726 | [ | ||
| 727 | KeyValue { | ||
| 728 | key: "feature", | ||
| 729 | value: "foo", | ||
| 730 | }, | ||
| 731 | KeyValue { | ||
| 732 | key: "feature", | ||
| 733 | value: "bar", | ||
| 734 | }, | ||
| 735 | ], | ||
| 736 | ), | ||
| 737 | ], | ||
| 414 | }, | 738 | }, |
| 415 | ] | 739 | ] |
| 416 | "### | 740 | "### |
diff --git a/crates/ra_ide/src/snapshots/highlight_injection.html b/crates/ra_ide/src/snapshots/highlight_injection.html index ea026d7a0..fcdc98201 100644 --- a/crates/ra_ide/src/snapshots/highlight_injection.html +++ b/crates/ra_ide/src/snapshots/highlight_injection.html | |||
| @@ -10,6 +10,7 @@ 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 | .operator.unsafe { color: #E28C14; } | ||
| 13 | .parameter { color: #94BFF3; } | 14 | .parameter { color: #94BFF3; } |
| 14 | .text { color: #DCDCCC; } | 15 | .text { color: #DCDCCC; } |
| 15 | .type { color: #7CB8BB; } | 16 | .type { color: #7CB8BB; } |
| @@ -17,6 +18,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
| 17 | .type_param { color: #DFAF8F; } | 18 | .type_param { color: #DFAF8F; } |
| 18 | .attribute { color: #94BFF3; } | 19 | .attribute { color: #94BFF3; } |
| 19 | .numeric_literal { color: #BFEBBF; } | 20 | .numeric_literal { color: #BFEBBF; } |
| 21 | .bool_literal { color: #BFE6EB; } | ||
| 20 | .macro { color: #94BFF3; } | 22 | .macro { color: #94BFF3; } |
| 21 | .module { color: #AFD8AF; } | 23 | .module { color: #AFD8AF; } |
| 22 | .variable { color: #DCDCCC; } | 24 | .variable { color: #DCDCCC; } |
diff --git a/crates/ra_ide/src/snapshots/highlight_strings.html b/crates/ra_ide/src/snapshots/highlight_strings.html index 752b487e8..e97192b61 100644 --- a/crates/ra_ide/src/snapshots/highlight_strings.html +++ b/crates/ra_ide/src/snapshots/highlight_strings.html | |||
| @@ -10,6 +10,7 @@ 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 | .operator.unsafe { color: #E28C14; } | ||
| 13 | .parameter { color: #94BFF3; } | 14 | .parameter { color: #94BFF3; } |
| 14 | .text { color: #DCDCCC; } | 15 | .text { color: #DCDCCC; } |
| 15 | .type { color: #7CB8BB; } | 16 | .type { color: #7CB8BB; } |
| @@ -17,6 +18,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
| 17 | .type_param { color: #DFAF8F; } | 18 | .type_param { color: #DFAF8F; } |
| 18 | .attribute { color: #94BFF3; } | 19 | .attribute { color: #94BFF3; } |
| 19 | .numeric_literal { color: #BFEBBF; } | 20 | .numeric_literal { color: #BFEBBF; } |
| 21 | .bool_literal { color: #BFE6EB; } | ||
| 20 | .macro { color: #94BFF3; } | 22 | .macro { color: #94BFF3; } |
| 21 | .module { color: #AFD8AF; } | 23 | .module { color: #AFD8AF; } |
| 22 | .variable { color: #DCDCCC; } | 24 | .variable { color: #DCDCCC; } |
| @@ -51,6 +53,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
| 51 | <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">argument</span><span class="format_specifier">}</span><span class="string_literal">"</span>, argument = <span class="string_literal">"test"</span>); <span class="comment">// => "test"</span> |
| 52 | <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">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> |
| 53 | <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> | 55 | <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="format_specifier">}</span><span class="string_literal">}}"</span>, <span class="numeric_literal">2</span>); <span class="comment">// => "{2}"</span> | ||
| 54 | <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>); | 57 | <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>); |
| 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">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>); | 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">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>); |
| 56 | <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>); | 59 | <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>); |
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..17ffc727c --- /dev/null +++ b/crates/ra_ide/src/snapshots/highlight_unsafe.html | |||
| @@ -0,0 +1,48 @@ | |||
| 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 | .operator.unsafe { color: #E28C14; } | ||
| 14 | .parameter { color: #94BFF3; } | ||
| 15 | .text { color: #DCDCCC; } | ||
| 16 | .type { color: #7CB8BB; } | ||
| 17 | .builtin_type { color: #8CD0D3; } | ||
| 18 | .type_param { color: #DFAF8F; } | ||
| 19 | .attribute { color: #94BFF3; } | ||
| 20 | .numeric_literal { color: #BFEBBF; } | ||
| 21 | .bool_literal { color: #BFE6EB; } | ||
| 22 | .macro { color: #94BFF3; } | ||
| 23 | .module { color: #AFD8AF; } | ||
| 24 | .variable { color: #DCDCCC; } | ||
| 25 | .format_specifier { color: #CC696B; } | ||
| 26 | .mutable { text-decoration: underline; } | ||
| 27 | |||
| 28 | .keyword { color: #F0DFAF; font-weight: bold; } | ||
| 29 | .keyword.unsafe { color: #BC8383; font-weight: bold; } | ||
| 30 | .control { font-style: italic; } | ||
| 31 | </style> | ||
| 32 | <pre><code><span class="keyword unsafe">unsafe</span> <span class="keyword">fn</span> <span class="function declaration unsafe">unsafe_fn</span>() {} | ||
| 33 | |||
| 34 | <span class="keyword">struct</span> <span class="struct declaration">HasUnsafeFn</span>; | ||
| 35 | |||
| 36 | <span class="keyword">impl</span> <span class="struct">HasUnsafeFn</span> { | ||
| 37 | <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>) {} | ||
| 38 | } | ||
| 39 | |||
| 40 | <span class="keyword">fn</span> <span class="function declaration">main</span>() { | ||
| 41 | <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>; | ||
| 42 | <span class="keyword unsafe">unsafe</span> { | ||
| 43 | <span class="function unsafe">unsafe_fn</span>(); | ||
| 44 | <span class="struct">HasUnsafeFn</span>.<span class="function unsafe">unsafe_method</span>(); | ||
| 45 | <span class="keyword">let</span> <span class="variable declaration">y</span> = <span class="operator unsafe">*</span><span class="variable">x</span>; | ||
| 46 | <span class="keyword">let</span> <span class="variable declaration">z</span> = -<span class="variable">x</span>; | ||
| 47 | } | ||
| 48 | }</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 635fe5cf9..42c5f3e55 100644 --- a/crates/ra_ide/src/snapshots/highlighting.html +++ b/crates/ra_ide/src/snapshots/highlighting.html | |||
| @@ -10,6 +10,7 @@ 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 | .operator.unsafe { color: #E28C14; } | ||
| 13 | .parameter { color: #94BFF3; } | 14 | .parameter { color: #94BFF3; } |
| 14 | .text { color: #DCDCCC; } | 15 | .text { color: #DCDCCC; } |
| 15 | .type { color: #7CB8BB; } | 16 | .type { color: #7CB8BB; } |
| @@ -17,6 +18,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
| 17 | .type_param { color: #DFAF8F; } | 18 | .type_param { color: #DFAF8F; } |
| 18 | .attribute { color: #94BFF3; } | 19 | .attribute { color: #94BFF3; } |
| 19 | .numeric_literal { color: #BFEBBF; } | 20 | .numeric_literal { color: #BFEBBF; } |
| 21 | .bool_literal { color: #BFE6EB; } | ||
| 20 | .macro { color: #94BFF3; } | 22 | .macro { color: #94BFF3; } |
| 21 | .module { color: #AFD8AF; } | 23 | .module { color: #AFD8AF; } |
| 22 | .variable { color: #DCDCCC; } | 24 | .variable { color: #DCDCCC; } |
| @@ -27,19 +29,19 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
| 27 | .keyword.unsafe { color: #BC8383; font-weight: bold; } | 29 | .keyword.unsafe { color: #BC8383; font-weight: bold; } |
| 28 | .control { font-style: italic; } | 30 | .control { font-style: italic; } |
| 29 | </style> | 31 | </style> |
| 30 | <pre><code><span class="attribute">#[derive(Clone, Debug)]</span> | 32 | <pre><code><span class="attribute">#[</span><span class="function attribute">derive</span><span class="attribute">(Clone, Debug)]</span> |
| 31 | <span class="keyword">struct</span> <span class="struct declaration">Foo</span> { | 33 | <span class="keyword">struct</span> <span class="struct declaration">Foo</span> { |
| 32 | <span class="keyword">pub</span> <span class="field declaration">x</span>: <span class="builtin_type">i32</span>, | 34 | <span class="keyword">pub</span> <span class="field declaration">x</span>: <span class="builtin_type">i32</span>, |
| 33 | <span class="keyword">pub</span> <span class="field declaration">y</span>: <span class="builtin_type">i32</span>, | 35 | <span class="keyword">pub</span> <span class="field declaration">y</span>: <span class="builtin_type">i32</span>, |
| 34 | } | 36 | } |
| 35 | 37 | ||
| 36 | <span class="keyword">trait</span> <span class="trait declaration">Bar</span> { | 38 | <span class="keyword">trait</span> <span class="trait declaration">Bar</span> { |
| 37 | <span class="keyword">fn</span> <span class="function declaration">bar</span>(&<span class="keyword">self</span>) -> <span class="builtin_type">i32</span>; | 39 | <span class="keyword">fn</span> <span class="function declaration">bar</span>(&<span class="self_keyword">self</span>) -> <span class="builtin_type">i32</span>; |
| 38 | } | 40 | } |
| 39 | 41 | ||
| 40 | <span class="keyword">impl</span> <span class="trait">Bar</span> <span class="keyword">for</span> <span class="struct">Foo</span> { | 42 | <span class="keyword">impl</span> <span class="trait">Bar</span> <span class="keyword">for</span> <span class="struct">Foo</span> { |
| 41 | <span class="keyword">fn</span> <span class="function declaration">bar</span>(&<span class="keyword">self</span>) -> <span class="builtin_type">i32</span> { | 43 | <span class="keyword">fn</span> <span class="function declaration">bar</span>(&<span class="self_keyword">self</span>) -> <span class="builtin_type">i32</span> { |
| 42 | <span class="keyword">self</span>.<span class="field">x</span> | 44 | <span class="self_keyword">self</span>.<span class="field">x</span> |
| 43 | } | 45 | } |
| 44 | } | 46 | } |
| 45 | 47 | ||
| @@ -64,7 +66,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
| 64 | <span class="macro">println!</span>(<span class="string_literal">"Hello, {}!"</span>, <span class="numeric_literal">92</span>); | 66 | <span class="macro">println!</span>(<span class="string_literal">"Hello, {}!"</span>, <span class="numeric_literal">92</span>); |
| 65 | 67 | ||
| 66 | <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">vec</span> = <span class="unresolved_reference">Vec</span>::<span class="unresolved_reference">new</span>(); | 68 | <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">vec</span> = <span class="unresolved_reference">Vec</span>::<span class="unresolved_reference">new</span>(); |
| 67 | <span class="keyword control">if</span> <span class="keyword">true</span> { | 69 | <span class="keyword control">if</span> <span class="bool_literal">true</span> { |
| 68 | <span class="keyword">let</span> <span class="variable declaration">x</span> = <span class="numeric_literal">92</span>; | 70 | <span class="keyword">let</span> <span class="variable declaration">x</span> = <span class="numeric_literal">92</span>; |
| 69 | <span class="variable mutable">vec</span>.<span class="unresolved_reference">push</span>(<span class="struct">Foo</span> { <span class="field">x</span>, <span class="field">y</span>: <span class="numeric_literal">1</span> }); | 71 | <span class="variable mutable">vec</span>.<span class="unresolved_reference">push</span>(<span class="struct">Foo</span> { <span class="field">x</span>, <span class="field">y</span>: <span class="numeric_literal">1</span> }); |
| 70 | } | 72 | } |
| @@ -91,7 +93,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
| 91 | <span class="keyword">use</span> <span class="enum">Option</span>::*; | 93 | <span class="keyword">use</span> <span class="enum">Option</span>::*; |
| 92 | 94 | ||
| 93 | <span class="keyword">impl</span><<span class="type_param declaration">T</span>> <span class="enum">Option</span><<span class="type_param">T</span>> { | 95 | <span class="keyword">impl</span><<span class="type_param declaration">T</span>> <span class="enum">Option</span><<span class="type_param">T</span>> { |
| 94 | <span class="keyword">fn</span> <span class="function declaration">and</span><<span class="type_param declaration">U</span>>(<span class="keyword">self</span>, <span class="variable declaration">other</span>: <span class="enum">Option</span><<span class="type_param">U</span>>) -> <span class="enum">Option</span><(<span class="type_param">T</span>, <span class="type_param">U</span>)> { | 96 | <span class="keyword">fn</span> <span class="function declaration">and</span><<span class="type_param declaration">U</span>>(<span class="self_keyword">self</span>, <span class="variable declaration">other</span>: <span class="enum">Option</span><<span class="type_param">U</span>>) -> <span class="enum">Option</span><(<span class="type_param">T</span>, <span class="type_param">U</span>)> { |
| 95 | <span class="keyword control">match</span> <span class="variable">other</span> { | 97 | <span class="keyword control">match</span> <span class="variable">other</span> { |
| 96 | <span class="enum_variant">None</span> => <span class="macro">unimplemented!</span>(), | 98 | <span class="enum_variant">None</span> => <span class="macro">unimplemented!</span>(), |
| 97 | <span class="variable declaration">Nope</span> => <span class="variable">Nope</span>, | 99 | <span class="variable declaration">Nope</span> => <span class="variable">Nope</span>, |
diff --git a/crates/ra_ide/src/snapshots/rainbow_highlighting.html b/crates/ra_ide/src/snapshots/rainbow_highlighting.html index 11e1f3e44..2dd61d20d 100644 --- a/crates/ra_ide/src/snapshots/rainbow_highlighting.html +++ b/crates/ra_ide/src/snapshots/rainbow_highlighting.html | |||
| @@ -10,6 +10,7 @@ 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 | .operator.unsafe { color: #E28C14; } | ||
| 13 | .parameter { color: #94BFF3; } | 14 | .parameter { color: #94BFF3; } |
| 14 | .text { color: #DCDCCC; } | 15 | .text { color: #DCDCCC; } |
| 15 | .type { color: #7CB8BB; } | 16 | .type { color: #7CB8BB; } |
| @@ -17,6 +18,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
| 17 | .type_param { color: #DFAF8F; } | 18 | .type_param { color: #DFAF8F; } |
| 18 | .attribute { color: #94BFF3; } | 19 | .attribute { color: #94BFF3; } |
| 19 | .numeric_literal { color: #BFEBBF; } | 20 | .numeric_literal { color: #BFEBBF; } |
| 21 | .bool_literal { color: #BFE6EB; } | ||
| 20 | .macro { color: #94BFF3; } | 22 | .macro { color: #94BFF3; } |
| 21 | .module { color: #AFD8AF; } | 23 | .module { color: #AFD8AF; } |
| 22 | .variable { color: #DCDCCC; } | 24 | .variable { color: #DCDCCC; } |
diff --git a/crates/ra_ide/src/ssr.rs b/crates/ra_ide/src/ssr.rs index 1873d1d0d..93e9aee1d 100644 --- a/crates/ra_ide/src/ssr.rs +++ b/crates/ra_ide/src/ssr.rs | |||
| @@ -1,5 +1,3 @@ | |||
| 1 | //! structural search replace | ||
| 2 | |||
| 3 | use std::{collections::HashMap, iter::once, str::FromStr}; | 1 | use std::{collections::HashMap, iter::once, str::FromStr}; |
| 4 | 2 | ||
| 5 | use ra_db::{SourceDatabase, SourceDatabaseExt}; | 3 | use ra_db::{SourceDatabase, SourceDatabaseExt}; |
| @@ -25,6 +23,28 @@ impl std::fmt::Display for SsrError { | |||
| 25 | 23 | ||
| 26 | impl std::error::Error for SsrError {} | 24 | impl std::error::Error for SsrError {} |
| 27 | 25 | ||
| 26 | // Feature: Structural Seach and Replace | ||
| 27 | // | ||
| 28 | // Search and replace with named wildcards that will match any expression. | ||
| 29 | // The syntax for a structural search replace command is `<search_pattern> ==>> <replace_pattern>`. | ||
| 30 | // A `$<name>:expr` placeholder in the search pattern will match any expression and `$<name>` will reference it in the replacement. | ||
| 31 | // Available via the command `rust-analyzer.ssr`. | ||
| 32 | // | ||
| 33 | // ```rust | ||
| 34 | // // Using structural search replace command [foo($a:expr, $b:expr) ==>> ($a).foo($b)] | ||
| 35 | // | ||
| 36 | // // BEFORE | ||
| 37 | // String::from(foo(y + 5, z)) | ||
| 38 | // | ||
| 39 | // // AFTER | ||
| 40 | // String::from((y + 5).foo(z)) | ||
| 41 | // ``` | ||
| 42 | // | ||
| 43 | // |=== | ||
| 44 | // | Editor | Action Name | ||
| 45 | // | ||
| 46 | // | VS Code | **Rust Analyzer: Structural Search Replace** | ||
| 47 | // |=== | ||
| 28 | pub fn parse_search_replace( | 48 | pub fn parse_search_replace( |
| 29 | query: &str, | 49 | query: &str, |
| 30 | parse_only: bool, | 50 | parse_only: bool, |
| @@ -196,10 +216,10 @@ fn find(pattern: &SsrPattern, code: &SyntaxNode) -> SsrMatches { | |||
| 196 | ) -> Option<Match> { | 216 | ) -> Option<Match> { |
| 197 | let match_ = check_opt_nodes(pattern.path(), code.path(), placeholders, match_)?; | 217 | let match_ = check_opt_nodes(pattern.path(), code.path(), placeholders, match_)?; |
| 198 | 218 | ||
| 199 | let mut pattern_fields = | 219 | let mut pattern_fields: Vec<RecordField> = |
| 200 | pattern.record_field_list().map(|x| x.fields().collect()).unwrap_or(vec![]); | 220 | pattern.record_field_list().map(|x| x.fields().collect()).unwrap_or_default(); |
| 201 | let mut code_fields = | 221 | let mut code_fields: Vec<RecordField> = |
| 202 | code.record_field_list().map(|x| x.fields().collect()).unwrap_or(vec![]); | 222 | code.record_field_list().map(|x| x.fields().collect()).unwrap_or_default(); |
| 203 | 223 | ||
| 204 | if pattern_fields.len() != code_fields.len() { | 224 | if pattern_fields.len() != code_fields.len() { |
| 205 | return None; | 225 | return None; |
diff --git a/crates/ra_ide/src/status.rs b/crates/ra_ide/src/status.rs index 30eb5c995..5b7992920 100644 --- a/crates/ra_ide/src/status.rs +++ b/crates/ra_ide/src/status.rs | |||
| @@ -1,5 +1,3 @@ | |||
| 1 | //! FIXME: write short doc here | ||
| 2 | |||
| 3 | use std::{fmt, iter::FromIterator, sync::Arc}; | 1 | use std::{fmt, iter::FromIterator, sync::Arc}; |
| 4 | 2 | ||
| 5 | use hir::MacroFile; | 3 | use hir::MacroFile; |
| @@ -26,6 +24,15 @@ fn macro_syntax_tree_stats(db: &RootDatabase) -> SyntaxTreeStats { | |||
| 26 | db.query(hir::db::ParseMacroQuery).entries::<SyntaxTreeStats>() | 24 | db.query(hir::db::ParseMacroQuery).entries::<SyntaxTreeStats>() |
| 27 | } | 25 | } |
| 28 | 26 | ||
| 27 | // Feature: Status | ||
| 28 | // | ||
| 29 | // Shows internal statistic about memory usage of rust-analyzer. | ||
| 30 | // | ||
| 31 | // |=== | ||
| 32 | // | Editor | Action Name | ||
| 33 | // | ||
| 34 | // | VS Code | **Rust Analyzer: Status** | ||
| 35 | // |=== | ||
| 29 | pub(crate) fn status(db: &RootDatabase) -> String { | 36 | pub(crate) fn status(db: &RootDatabase) -> String { |
| 30 | let files_stats = db.query(FileTextQuery).entries::<FilesStats>(); | 37 | let files_stats = db.query(FileTextQuery).entries::<FilesStats>(); |
| 31 | let syntax_tree_stats = syntax_tree_stats(db); | 38 | let syntax_tree_stats = syntax_tree_stats(db); |
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs index be57eeb0a..19ecd54d6 100644 --- a/crates/ra_ide/src/syntax_highlighting.rs +++ b/crates/ra_ide/src/syntax_highlighting.rs | |||
| @@ -1,5 +1,3 @@ | |||
| 1 | //! Implements syntax highlighting. | ||
| 2 | |||
| 3 | mod tags; | 1 | mod tags; |
| 4 | mod html; | 2 | mod html; |
| 5 | #[cfg(test)] | 3 | #[cfg(test)] |
| @@ -32,81 +30,15 @@ pub struct HighlightedRange { | |||
| 32 | pub binding_hash: Option<u64>, | 30 | pub binding_hash: Option<u64>, |
| 33 | } | 31 | } |
| 34 | 32 | ||
| 35 | #[derive(Debug)] | 33 | // Feature: Semantic Syntax Highlighting |
| 36 | struct HighlightedRangeStack { | 34 | // |
| 37 | stack: Vec<Vec<HighlightedRange>>, | 35 | // rust-analyzer highlights the code semantically. |
| 38 | } | 36 | // For example, `bar` in `foo::Bar` might be colored differently depending on whether `Bar` is an enum or a trait. |
| 39 | 37 | // rust-analyzer does not specify colors directly, instead it assigns tag (like `struct`) and a set of modifiers (like `declaration`) to each token. | |
| 40 | /// We use a stack to implement the flattening logic for the highlighted | 38 | // It's up to the client to map those to specific colors. |
| 41 | /// syntax ranges. | 39 | // |
| 42 | impl HighlightedRangeStack { | 40 | // The general rule is that a reference to an entity gets colored the same way as the entity itself. |
| 43 | fn new() -> Self { | 41 | // We also give special modifier for `mut` and `&mut` local variables. |
| 44 | Self { stack: vec![Vec::new()] } | ||
| 45 | } | ||
| 46 | |||
| 47 | fn push(&mut self) { | ||
| 48 | self.stack.push(Vec::new()); | ||
| 49 | } | ||
| 50 | |||
| 51 | /// Flattens the highlighted ranges. | ||
| 52 | /// | ||
| 53 | /// For example `#[cfg(feature = "foo")]` contains the nested ranges: | ||
| 54 | /// 1) parent-range: Attribute [0, 23) | ||
| 55 | /// 2) child-range: String [16, 21) | ||
| 56 | /// | ||
| 57 | /// The following code implements the flattening, for our example this results to: | ||
| 58 | /// `[Attribute [0, 16), String [16, 21), Attribute [21, 23)]` | ||
| 59 | fn pop(&mut self) { | ||
| 60 | let children = self.stack.pop().unwrap(); | ||
| 61 | let prev = self.stack.last_mut().unwrap(); | ||
| 62 | let needs_flattening = !children.is_empty() | ||
| 63 | && !prev.is_empty() | ||
| 64 | && prev.last().unwrap().range.contains_range(children.first().unwrap().range); | ||
| 65 | if !needs_flattening { | ||
| 66 | prev.extend(children); | ||
| 67 | } else { | ||
| 68 | let mut parent = prev.pop().unwrap(); | ||
| 69 | for ele in children { | ||
| 70 | assert!(parent.range.contains_range(ele.range)); | ||
| 71 | let mut cloned = parent.clone(); | ||
| 72 | parent.range = TextRange::new(parent.range.start(), ele.range.start()); | ||
| 73 | cloned.range = TextRange::new(ele.range.end(), cloned.range.end()); | ||
| 74 | if !parent.range.is_empty() { | ||
| 75 | prev.push(parent); | ||
| 76 | } | ||
| 77 | prev.push(ele); | ||
| 78 | parent = cloned; | ||
| 79 | } | ||
| 80 | if !parent.range.is_empty() { | ||
| 81 | prev.push(parent); | ||
| 82 | } | ||
| 83 | } | ||
| 84 | } | ||
| 85 | |||
| 86 | fn add(&mut self, range: HighlightedRange) { | ||
| 87 | self.stack | ||
| 88 | .last_mut() | ||
| 89 | .expect("during DFS traversal, the stack must not be empty") | ||
| 90 | .push(range) | ||
| 91 | } | ||
| 92 | |||
| 93 | fn flattened(mut self) -> Vec<HighlightedRange> { | ||
| 94 | assert_eq!( | ||
| 95 | self.stack.len(), | ||
| 96 | 1, | ||
| 97 | "after DFS traversal, the stack should only contain a single element" | ||
| 98 | ); | ||
| 99 | let mut res = self.stack.pop().unwrap(); | ||
| 100 | res.sort_by_key(|range| range.range.start()); | ||
| 101 | // Check that ranges are sorted and disjoint | ||
| 102 | assert!(res | ||
| 103 | .iter() | ||
| 104 | .zip(res.iter().skip(1)) | ||
| 105 | .all(|(left, right)| left.range.end() <= right.range.start())); | ||
| 106 | res | ||
| 107 | } | ||
| 108 | } | ||
| 109 | |||
| 110 | pub(crate) fn highlight( | 42 | pub(crate) fn highlight( |
| 111 | db: &RootDatabase, | 43 | db: &RootDatabase, |
| 112 | file_id: FileId, | 44 | file_id: FileId, |
| @@ -291,6 +223,81 @@ pub(crate) fn highlight( | |||
| 291 | stack.flattened() | 223 | stack.flattened() |
| 292 | } | 224 | } |
| 293 | 225 | ||
| 226 | #[derive(Debug)] | ||
| 227 | struct HighlightedRangeStack { | ||
| 228 | stack: Vec<Vec<HighlightedRange>>, | ||
| 229 | } | ||
| 230 | |||
| 231 | /// We use a stack to implement the flattening logic for the highlighted | ||
| 232 | /// syntax ranges. | ||
| 233 | impl HighlightedRangeStack { | ||
| 234 | fn new() -> Self { | ||
| 235 | Self { stack: vec![Vec::new()] } | ||
| 236 | } | ||
| 237 | |||
| 238 | fn push(&mut self) { | ||
| 239 | self.stack.push(Vec::new()); | ||
| 240 | } | ||
| 241 | |||
| 242 | /// Flattens the highlighted ranges. | ||
| 243 | /// | ||
| 244 | /// For example `#[cfg(feature = "foo")]` contains the nested ranges: | ||
| 245 | /// 1) parent-range: Attribute [0, 23) | ||
| 246 | /// 2) child-range: String [16, 21) | ||
| 247 | /// | ||
| 248 | /// The following code implements the flattening, for our example this results to: | ||
| 249 | /// `[Attribute [0, 16), String [16, 21), Attribute [21, 23)]` | ||
| 250 | fn pop(&mut self) { | ||
| 251 | let children = self.stack.pop().unwrap(); | ||
| 252 | let prev = self.stack.last_mut().unwrap(); | ||
| 253 | let needs_flattening = !children.is_empty() | ||
| 254 | && !prev.is_empty() | ||
| 255 | && prev.last().unwrap().range.contains_range(children.first().unwrap().range); | ||
| 256 | if !needs_flattening { | ||
| 257 | prev.extend(children); | ||
| 258 | } else { | ||
| 259 | let mut parent = prev.pop().unwrap(); | ||
| 260 | for ele in children { | ||
| 261 | assert!(parent.range.contains_range(ele.range)); | ||
| 262 | let mut cloned = parent.clone(); | ||
| 263 | parent.range = TextRange::new(parent.range.start(), ele.range.start()); | ||
| 264 | cloned.range = TextRange::new(ele.range.end(), cloned.range.end()); | ||
| 265 | if !parent.range.is_empty() { | ||
| 266 | prev.push(parent); | ||
| 267 | } | ||
| 268 | prev.push(ele); | ||
| 269 | parent = cloned; | ||
| 270 | } | ||
| 271 | if !parent.range.is_empty() { | ||
| 272 | prev.push(parent); | ||
| 273 | } | ||
| 274 | } | ||
| 275 | } | ||
| 276 | |||
| 277 | fn add(&mut self, range: HighlightedRange) { | ||
| 278 | self.stack | ||
| 279 | .last_mut() | ||
| 280 | .expect("during DFS traversal, the stack must not be empty") | ||
| 281 | .push(range) | ||
| 282 | } | ||
| 283 | |||
| 284 | fn flattened(mut self) -> Vec<HighlightedRange> { | ||
| 285 | assert_eq!( | ||
| 286 | self.stack.len(), | ||
| 287 | 1, | ||
| 288 | "after DFS traversal, the stack should only contain a single element" | ||
| 289 | ); | ||
| 290 | let mut res = self.stack.pop().unwrap(); | ||
| 291 | res.sort_by_key(|range| range.range.start()); | ||
| 292 | // Check that ranges are sorted and disjoint | ||
| 293 | assert!(res | ||
| 294 | .iter() | ||
| 295 | .zip(res.iter().skip(1)) | ||
| 296 | .all(|(left, right)| left.range.end() <= right.range.start())); | ||
| 297 | res | ||
| 298 | } | ||
| 299 | } | ||
| 300 | |||
| 294 | fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HighlightTag> { | 301 | fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HighlightTag> { |
| 295 | Some(match kind { | 302 | Some(match kind { |
| 296 | FormatSpecifier::Open | 303 | FormatSpecifier::Open |
| @@ -361,7 +368,9 @@ fn highlight_element( | |||
| 361 | } | 368 | } |
| 362 | 369 | ||
| 363 | // Highlight references like the definitions they resolve to | 370 | // Highlight references like the definitions they resolve to |
| 364 | NAME_REF if element.ancestors().any(|it| it.kind() == ATTR) => return None, | 371 | NAME_REF if element.ancestors().any(|it| it.kind() == ATTR) => { |
| 372 | Highlight::from(HighlightTag::Function) | HighlightModifier::Attribute | ||
| 373 | } | ||
| 365 | NAME_REF => { | 374 | NAME_REF => { |
| 366 | let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap(); | 375 | let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap(); |
| 367 | match classify_name_ref(sema, &name_ref) { | 376 | match classify_name_ref(sema, &name_ref) { |
| @@ -389,6 +398,7 @@ fn highlight_element( | |||
| 389 | INT_NUMBER | FLOAT_NUMBER => HighlightTag::NumericLiteral.into(), | 398 | INT_NUMBER | FLOAT_NUMBER => HighlightTag::NumericLiteral.into(), |
| 390 | BYTE => HighlightTag::ByteLiteral.into(), | 399 | BYTE => HighlightTag::ByteLiteral.into(), |
| 391 | CHAR => HighlightTag::CharLiteral.into(), | 400 | CHAR => HighlightTag::CharLiteral.into(), |
| 401 | QUESTION => Highlight::new(HighlightTag::Operator) | HighlightModifier::ControlFlow, | ||
| 392 | LIFETIME => { | 402 | LIFETIME => { |
| 393 | let h = Highlight::new(HighlightTag::Lifetime); | 403 | let h = Highlight::new(HighlightTag::Lifetime); |
| 394 | match element.parent().map(|it| it.kind()) { | 404 | match element.parent().map(|it| it.kind()) { |
| @@ -396,6 +406,23 @@ fn highlight_element( | |||
| 396 | _ => h, | 406 | _ => h, |
| 397 | } | 407 | } |
| 398 | } | 408 | } |
| 409 | PREFIX_EXPR => { | ||
| 410 | let prefix_expr = element.into_node().and_then(ast::PrefixExpr::cast)?; | ||
| 411 | match prefix_expr.op_kind() { | ||
| 412 | Some(ast::PrefixOp::Deref) => {} | ||
| 413 | _ => return None, | ||
| 414 | } | ||
| 415 | |||
| 416 | let expr = prefix_expr.expr()?; | ||
| 417 | let ty = sema.type_of_expr(&expr)?; | ||
| 418 | if !ty.is_raw_ptr() { | ||
| 419 | return None; | ||
| 420 | } | ||
| 421 | |||
| 422 | let mut h = Highlight::new(HighlightTag::Operator); | ||
| 423 | h |= HighlightModifier::Unsafe; | ||
| 424 | h | ||
| 425 | } | ||
| 399 | 426 | ||
| 400 | k if k.is_keyword() => { | 427 | k if k.is_keyword() => { |
| 401 | let h = Highlight::new(HighlightTag::Keyword); | 428 | let h = Highlight::new(HighlightTag::Keyword); |
| @@ -411,6 +438,8 @@ fn highlight_element( | |||
| 411 | | T![in] => h | HighlightModifier::ControlFlow, | 438 | | T![in] => h | HighlightModifier::ControlFlow, |
| 412 | T![for] if !is_child_of_impl(element) => h | HighlightModifier::ControlFlow, | 439 | T![for] if !is_child_of_impl(element) => h | HighlightModifier::ControlFlow, |
| 413 | T![unsafe] => h | HighlightModifier::Unsafe, | 440 | T![unsafe] => h | HighlightModifier::Unsafe, |
| 441 | T![true] | T![false] => HighlightTag::BoolLiteral.into(), | ||
| 442 | T![self] => HighlightTag::SelfKeyword.into(), | ||
| 414 | _ => h, | 443 | _ => h, |
| 415 | } | 444 | } |
| 416 | } | 445 | } |
| @@ -446,7 +475,13 @@ fn highlight_name(db: &RootDatabase, def: Definition) -> Highlight { | |||
| 446 | Definition::Field(_) => HighlightTag::Field, | 475 | Definition::Field(_) => HighlightTag::Field, |
| 447 | Definition::ModuleDef(def) => match def { | 476 | Definition::ModuleDef(def) => match def { |
| 448 | hir::ModuleDef::Module(_) => HighlightTag::Module, | 477 | hir::ModuleDef::Module(_) => HighlightTag::Module, |
| 449 | hir::ModuleDef::Function(_) => HighlightTag::Function, | 478 | hir::ModuleDef::Function(func) => { |
| 479 | let mut h = HighlightTag::Function.into(); | ||
| 480 | if func.is_unsafe(db) { | ||
| 481 | h |= HighlightModifier::Unsafe; | ||
| 482 | } | ||
| 483 | return h; | ||
| 484 | } | ||
| 450 | hir::ModuleDef::Adt(hir::Adt::Struct(_)) => HighlightTag::Struct, | 485 | hir::ModuleDef::Adt(hir::Adt::Struct(_)) => HighlightTag::Struct, |
| 451 | hir::ModuleDef::Adt(hir::Adt::Enum(_)) => HighlightTag::Enum, | 486 | hir::ModuleDef::Adt(hir::Adt::Enum(_)) => HighlightTag::Enum, |
| 452 | hir::ModuleDef::Adt(hir::Adt::Union(_)) => HighlightTag::Union, | 487 | hir::ModuleDef::Adt(hir::Adt::Union(_)) => HighlightTag::Union, |
| @@ -478,23 +513,31 @@ fn highlight_name(db: &RootDatabase, def: Definition) -> Highlight { | |||
| 478 | } | 513 | } |
| 479 | 514 | ||
| 480 | fn highlight_name_by_syntax(name: ast::Name) -> Highlight { | 515 | fn highlight_name_by_syntax(name: ast::Name) -> Highlight { |
| 481 | let default = HighlightTag::Function.into(); | 516 | let default = HighlightTag::UnresolvedReference; |
| 482 | 517 | ||
| 483 | let parent = match name.syntax().parent() { | 518 | let parent = match name.syntax().parent() { |
| 484 | Some(it) => it, | 519 | Some(it) => it, |
| 485 | _ => return default, | 520 | _ => return default.into(), |
| 486 | }; | 521 | }; |
| 487 | 522 | ||
| 488 | match parent.kind() { | 523 | let tag = match parent.kind() { |
| 489 | STRUCT_DEF => HighlightTag::Struct.into(), | 524 | STRUCT_DEF => HighlightTag::Struct, |
| 490 | ENUM_DEF => HighlightTag::Enum.into(), | 525 | ENUM_DEF => HighlightTag::Enum, |
| 491 | UNION_DEF => HighlightTag::Union.into(), | 526 | UNION_DEF => HighlightTag::Union, |
| 492 | TRAIT_DEF => HighlightTag::Trait.into(), | 527 | TRAIT_DEF => HighlightTag::Trait, |
| 493 | TYPE_ALIAS_DEF => HighlightTag::TypeAlias.into(), | 528 | TYPE_ALIAS_DEF => HighlightTag::TypeAlias, |
| 494 | TYPE_PARAM => HighlightTag::TypeParam.into(), | 529 | TYPE_PARAM => HighlightTag::TypeParam, |
| 495 | RECORD_FIELD_DEF => HighlightTag::Field.into(), | 530 | RECORD_FIELD_DEF => HighlightTag::Field, |
| 531 | MODULE => HighlightTag::Module, | ||
| 532 | FN_DEF => HighlightTag::Function, | ||
| 533 | CONST_DEF => HighlightTag::Constant, | ||
| 534 | STATIC_DEF => HighlightTag::Static, | ||
| 535 | ENUM_VARIANT => HighlightTag::EnumVariant, | ||
| 536 | BIND_PAT => HighlightTag::Local, | ||
| 496 | _ => default, | 537 | _ => default, |
| 497 | } | 538 | }; |
| 539 | |||
| 540 | tag.into() | ||
| 498 | } | 541 | } |
| 499 | 542 | ||
| 500 | fn highlight_injection( | 543 | fn highlight_injection( |
diff --git a/crates/ra_ide/src/syntax_highlighting/html.rs b/crates/ra_ide/src/syntax_highlighting/html.rs index ff0eeeb52..7d946c98d 100644 --- a/crates/ra_ide/src/syntax_highlighting/html.rs +++ b/crates/ra_ide/src/syntax_highlighting/html.rs | |||
| @@ -69,6 +69,7 @@ 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 | .operator.unsafe { color: #E28C14; } | ||
| 72 | .parameter { color: #94BFF3; } | 73 | .parameter { color: #94BFF3; } |
| 73 | .text { color: #DCDCCC; } | 74 | .text { color: #DCDCCC; } |
| 74 | .type { color: #7CB8BB; } | 75 | .type { color: #7CB8BB; } |
| @@ -76,6 +77,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
| 76 | .type_param { color: #DFAF8F; } | 77 | .type_param { color: #DFAF8F; } |
| 77 | .attribute { color: #94BFF3; } | 78 | .attribute { color: #94BFF3; } |
| 78 | .numeric_literal { color: #BFEBBF; } | 79 | .numeric_literal { color: #BFEBBF; } |
| 80 | .bool_literal { color: #BFE6EB; } | ||
| 79 | .macro { color: #94BFF3; } | 81 | .macro { color: #94BFF3; } |
| 80 | .module { color: #AFD8AF; } | 82 | .module { color: #AFD8AF; } |
| 81 | .variable { color: #DCDCCC; } | 83 | .variable { color: #DCDCCC; } |
diff --git a/crates/ra_ide/src/syntax_highlighting/tags.rs b/crates/ra_ide/src/syntax_highlighting/tags.rs index be1a0f12b..94f466966 100644 --- a/crates/ra_ide/src/syntax_highlighting/tags.rs +++ b/crates/ra_ide/src/syntax_highlighting/tags.rs | |||
| @@ -15,6 +15,7 @@ pub struct HighlightModifiers(u32); | |||
| 15 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] | 15 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] |
| 16 | pub enum HighlightTag { | 16 | pub enum HighlightTag { |
| 17 | Attribute, | 17 | Attribute, |
| 18 | BoolLiteral, | ||
| 18 | BuiltinType, | 19 | BuiltinType, |
| 19 | ByteLiteral, | 20 | ByteLiteral, |
| 20 | CharLiteral, | 21 | CharLiteral, |
| @@ -23,12 +24,15 @@ pub enum HighlightTag { | |||
| 23 | Enum, | 24 | Enum, |
| 24 | EnumVariant, | 25 | EnumVariant, |
| 25 | Field, | 26 | Field, |
| 27 | FormatSpecifier, | ||
| 26 | Function, | 28 | Function, |
| 27 | Keyword, | 29 | Keyword, |
| 28 | Lifetime, | 30 | Lifetime, |
| 29 | Macro, | 31 | Macro, |
| 30 | Module, | 32 | Module, |
| 31 | NumericLiteral, | 33 | NumericLiteral, |
| 34 | Operator, | ||
| 35 | SelfKeyword, | ||
| 32 | SelfType, | 36 | SelfType, |
| 33 | Static, | 37 | Static, |
| 34 | StringLiteral, | 38 | StringLiteral, |
| @@ -39,14 +43,15 @@ pub enum HighlightTag { | |||
| 39 | Union, | 43 | Union, |
| 40 | Local, | 44 | Local, |
| 41 | UnresolvedReference, | 45 | UnresolvedReference, |
| 42 | FormatSpecifier, | ||
| 43 | } | 46 | } |
| 44 | 47 | ||
| 45 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] | 48 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] |
| 46 | #[repr(u8)] | 49 | #[repr(u8)] |
| 47 | pub enum HighlightModifier { | 50 | pub enum HighlightModifier { |
| 51 | /// Used to differentiate individual elements within attributes. | ||
| 52 | Attribute = 0, | ||
| 48 | /// Used with keywords like `if` and `break`. | 53 | /// Used with keywords like `if` and `break`. |
| 49 | ControlFlow = 0, | 54 | ControlFlow, |
| 50 | /// `foo` in `fn foo(x: i32)` is a definition, `foo` in `foo(90 + 2)` is | 55 | /// `foo` in `fn foo(x: i32)` is a definition, `foo` in `foo(90 + 2)` is |
| 51 | /// not. | 56 | /// not. |
| 52 | Definition, | 57 | Definition, |
| @@ -58,6 +63,7 @@ impl HighlightTag { | |||
| 58 | fn as_str(self) -> &'static str { | 63 | fn as_str(self) -> &'static str { |
| 59 | match self { | 64 | match self { |
| 60 | HighlightTag::Attribute => "attribute", | 65 | HighlightTag::Attribute => "attribute", |
| 66 | HighlightTag::BoolLiteral => "bool_literal", | ||
| 61 | HighlightTag::BuiltinType => "builtin_type", | 67 | HighlightTag::BuiltinType => "builtin_type", |
| 62 | HighlightTag::ByteLiteral => "byte_literal", | 68 | HighlightTag::ByteLiteral => "byte_literal", |
| 63 | HighlightTag::CharLiteral => "char_literal", | 69 | HighlightTag::CharLiteral => "char_literal", |
| @@ -66,12 +72,15 @@ impl HighlightTag { | |||
| 66 | HighlightTag::Enum => "enum", | 72 | HighlightTag::Enum => "enum", |
| 67 | HighlightTag::EnumVariant => "enum_variant", | 73 | HighlightTag::EnumVariant => "enum_variant", |
| 68 | HighlightTag::Field => "field", | 74 | HighlightTag::Field => "field", |
| 75 | HighlightTag::FormatSpecifier => "format_specifier", | ||
| 69 | HighlightTag::Function => "function", | 76 | HighlightTag::Function => "function", |
| 70 | HighlightTag::Keyword => "keyword", | 77 | HighlightTag::Keyword => "keyword", |
| 71 | HighlightTag::Lifetime => "lifetime", | 78 | HighlightTag::Lifetime => "lifetime", |
| 72 | HighlightTag::Macro => "macro", | 79 | HighlightTag::Macro => "macro", |
| 73 | HighlightTag::Module => "module", | 80 | HighlightTag::Module => "module", |
| 74 | HighlightTag::NumericLiteral => "numeric_literal", | 81 | HighlightTag::NumericLiteral => "numeric_literal", |
| 82 | HighlightTag::Operator => "operator", | ||
| 83 | HighlightTag::SelfKeyword => "self_keyword", | ||
| 75 | HighlightTag::SelfType => "self_type", | 84 | HighlightTag::SelfType => "self_type", |
| 76 | HighlightTag::Static => "static", | 85 | HighlightTag::Static => "static", |
| 77 | HighlightTag::StringLiteral => "string_literal", | 86 | HighlightTag::StringLiteral => "string_literal", |
| @@ -82,7 +91,6 @@ impl HighlightTag { | |||
| 82 | HighlightTag::Union => "union", | 91 | HighlightTag::Union => "union", |
| 83 | HighlightTag::Local => "variable", | 92 | HighlightTag::Local => "variable", |
| 84 | HighlightTag::UnresolvedReference => "unresolved_reference", | 93 | HighlightTag::UnresolvedReference => "unresolved_reference", |
| 85 | HighlightTag::FormatSpecifier => "format_specifier", | ||
| 86 | } | 94 | } |
| 87 | } | 95 | } |
| 88 | } | 96 | } |
| @@ -95,6 +103,7 @@ impl fmt::Display for HighlightTag { | |||
| 95 | 103 | ||
| 96 | impl HighlightModifier { | 104 | impl HighlightModifier { |
| 97 | const ALL: &'static [HighlightModifier] = &[ | 105 | const ALL: &'static [HighlightModifier] = &[ |
| 106 | HighlightModifier::Attribute, | ||
| 98 | HighlightModifier::ControlFlow, | 107 | HighlightModifier::ControlFlow, |
| 99 | HighlightModifier::Definition, | 108 | HighlightModifier::Definition, |
| 100 | HighlightModifier::Mutable, | 109 | HighlightModifier::Mutable, |
| @@ -103,6 +112,7 @@ impl HighlightModifier { | |||
| 103 | 112 | ||
| 104 | fn as_str(self) -> &'static str { | 113 | fn as_str(self) -> &'static str { |
| 105 | match self { | 114 | match self { |
| 115 | HighlightModifier::Attribute => "attribute", | ||
| 106 | HighlightModifier::ControlFlow => "control", | 116 | HighlightModifier::ControlFlow => "control", |
| 107 | HighlightModifier::Definition => "declaration", | 117 | HighlightModifier::Definition => "declaration", |
| 108 | HighlightModifier::Mutable => "mutable", | 118 | HighlightModifier::Mutable => "mutable", |
diff --git a/crates/ra_ide/src/syntax_highlighting/tests.rs b/crates/ra_ide/src/syntax_highlighting/tests.rs index eb43a23da..36a1aa419 100644 --- a/crates/ra_ide/src/syntax_highlighting/tests.rs +++ b/crates/ra_ide/src/syntax_highlighting/tests.rs | |||
| @@ -218,6 +218,7 @@ fn main() { | |||
| 218 | println!("{argument}", argument = "test"); // => "test" | 218 | println!("{argument}", argument = "test"); // => "test" |
| 219 | println!("{name} {}", 1, name = 2); // => "2 1" | 219 | println!("{name} {}", 1, name = 2); // => "2 1" |
| 220 | println!("{a} {c} {b}", a="a", b='b', c=3); // => "a 3 b" | 220 | println!("{a} {c} {b}", a="a", b='b', c=3); // => "a 3 b" |
| 221 | println!("{{{}}}", 2); // => "{2}" | ||
| 221 | println!("Hello {:5}!", "x"); | 222 | println!("Hello {:5}!", "x"); |
| 222 | println!("Hello {:1$}!", "x", 5); | 223 | println!("Hello {:1$}!", "x", 5); |
| 223 | println!("Hello {1:0$}!", 5, "x"); | 224 | println!("Hello {1:0$}!", 5, "x"); |
| @@ -257,3 +258,34 @@ fn main() { | |||
| 257 | fs::write(dst_file, &actual_html).unwrap(); | 258 | fs::write(dst_file, &actual_html).unwrap(); |
| 258 | assert_eq_text!(expected_html, actual_html); | 259 | assert_eq_text!(expected_html, actual_html); |
| 259 | } | 260 | } |
| 261 | |||
| 262 | #[test] | ||
| 263 | fn test_unsafe_highlighting() { | ||
| 264 | let (analysis, file_id) = single_file( | ||
| 265 | r#" | ||
| 266 | unsafe fn unsafe_fn() {} | ||
| 267 | |||
| 268 | struct HasUnsafeFn; | ||
| 269 | |||
| 270 | impl HasUnsafeFn { | ||
| 271 | unsafe fn unsafe_method(&self) {} | ||
| 272 | } | ||
| 273 | |||
| 274 | fn main() { | ||
| 275 | let x = &5 as *const usize; | ||
| 276 | unsafe { | ||
| 277 | unsafe_fn(); | ||
| 278 | HasUnsafeFn.unsafe_method(); | ||
| 279 | let y = *x; | ||
| 280 | let z = -x; | ||
| 281 | } | ||
| 282 | } | ||
| 283 | "# | ||
| 284 | .trim(), | ||
| 285 | ); | ||
| 286 | let dst_file = project_dir().join("crates/ra_ide/src/snapshots/highlight_unsafe.html"); | ||
| 287 | let actual_html = &analysis.highlight_as_html(file_id, false).unwrap(); | ||
| 288 | let expected_html = &read_text(&dst_file); | ||
| 289 | fs::write(dst_file, &actual_html).unwrap(); | ||
| 290 | assert_eq_text!(expected_html, actual_html); | ||
| 291 | } | ||
diff --git a/crates/ra_ide/src/syntax_tree.rs b/crates/ra_ide/src/syntax_tree.rs index 86c70ff83..a341684fd 100644 --- a/crates/ra_ide/src/syntax_tree.rs +++ b/crates/ra_ide/src/syntax_tree.rs | |||
| @@ -1,6 +1,4 @@ | |||
| 1 | //! FIXME: write short doc here | 1 | use ra_db::{FileId, SourceDatabase}; |
| 2 | |||
| 3 | use ra_db::SourceDatabase; | ||
| 4 | use ra_ide_db::RootDatabase; | 2 | use ra_ide_db::RootDatabase; |
| 5 | use ra_syntax::{ | 3 | use ra_syntax::{ |
| 6 | algo, AstNode, NodeOrToken, SourceFile, | 4 | algo, AstNode, NodeOrToken, SourceFile, |
| @@ -8,8 +6,16 @@ use ra_syntax::{ | |||
| 8 | SyntaxToken, TextRange, TextSize, | 6 | SyntaxToken, TextRange, TextSize, |
| 9 | }; | 7 | }; |
| 10 | 8 | ||
| 11 | pub use ra_db::FileId; | 9 | // Feature: Show Syntax Tree |
| 12 | 10 | // | |
| 11 | // Shows the parse tree of the current file. It exists mostly for debugging | ||
| 12 | // rust-analyzer itself. | ||
| 13 | // | ||
| 14 | // |=== | ||
| 15 | // | Editor | Action Name | ||
| 16 | // | ||
| 17 | // | VS Code | **Rust Analyzer: Show Syntax Tree** | ||
| 18 | // |=== | ||
| 13 | pub(crate) fn syntax_tree( | 19 | pub(crate) fn syntax_tree( |
| 14 | db: &RootDatabase, | 20 | db: &RootDatabase, |
| 15 | file_id: FileId, | 21 | file_id: FileId, |
diff --git a/crates/ra_ide/src/typing.rs b/crates/ra_ide/src/typing.rs index 39bb3b357..67e2c33a0 100644 --- a/crates/ra_ide/src/typing.rs +++ b/crates/ra_ide/src/typing.rs | |||
| @@ -32,6 +32,13 @@ pub(crate) use on_enter::on_enter; | |||
| 32 | 32 | ||
| 33 | pub(crate) const TRIGGER_CHARS: &str = ".=>"; | 33 | pub(crate) const TRIGGER_CHARS: &str = ".=>"; |
| 34 | 34 | ||
| 35 | // Feature: On Typing Assists | ||
| 36 | // | ||
| 37 | // Some features trigger on typing certain characters: | ||
| 38 | // | ||
| 39 | // - typing `let =` tries to smartly add `;` if `=` is followed by an existing expression | ||
| 40 | // - Enter inside comments automatically inserts `///` | ||
| 41 | // - typing `.` in a chain method call auto-indents | ||
| 35 | pub(crate) fn on_char_typed( | 42 | pub(crate) fn on_char_typed( |
| 36 | db: &RootDatabase, | 43 | db: &RootDatabase, |
| 37 | position: FilePosition, | 44 | position: FilePosition, |
diff --git a/crates/ra_ide/src/typing/on_enter.rs b/crates/ra_ide/src/typing/on_enter.rs index e7d64b4f6..a40d8af9c 100644 --- a/crates/ra_ide/src/typing/on_enter.rs +++ b/crates/ra_ide/src/typing/on_enter.rs | |||
| @@ -11,9 +11,7 @@ use ra_syntax::{ | |||
| 11 | }; | 11 | }; |
| 12 | use ra_text_edit::TextEdit; | 12 | use ra_text_edit::TextEdit; |
| 13 | 13 | ||
| 14 | use crate::{SourceChange, SourceFileEdit}; | 14 | pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<TextEdit> { |
| 15 | |||
| 16 | pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<SourceChange> { | ||
| 17 | let parse = db.parse(position.file_id); | 15 | let parse = db.parse(position.file_id); |
| 18 | let file = parse.tree(); | 16 | let file = parse.tree(); |
| 19 | let comment = file | 17 | let comment = file |
| @@ -41,9 +39,7 @@ pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<Sour | |||
| 41 | let inserted = format!("\n{}{} $0", indent, prefix); | 39 | let inserted = format!("\n{}{} $0", indent, prefix); |
| 42 | let edit = TextEdit::insert(position.offset, inserted); | 40 | let edit = TextEdit::insert(position.offset, inserted); |
| 43 | 41 | ||
| 44 | let mut res = SourceChange::from(SourceFileEdit { edit, file_id: position.file_id }); | 42 | Some(edit) |
| 45 | res.is_snippet = true; | ||
| 46 | Some(res) | ||
| 47 | } | 43 | } |
| 48 | 44 | ||
| 49 | fn followed_by_comment(comment: &ast::Comment) -> bool { | 45 | fn followed_by_comment(comment: &ast::Comment) -> bool { |
| @@ -90,9 +86,8 @@ mod tests { | |||
| 90 | let (analysis, file_id) = single_file(&before); | 86 | let (analysis, file_id) = single_file(&before); |
| 91 | let result = analysis.on_enter(FilePosition { offset, file_id }).unwrap()?; | 87 | let result = analysis.on_enter(FilePosition { offset, file_id }).unwrap()?; |
| 92 | 88 | ||
| 93 | assert_eq!(result.source_file_edits.len(), 1); | ||
| 94 | let mut actual = before.to_string(); | 89 | let mut actual = before.to_string(); |
| 95 | result.source_file_edits[0].edit.apply(&mut actual); | 90 | result.apply(&mut actual); |
| 96 | Some(actual) | 91 | Some(actual) |
| 97 | } | 92 | } |
| 98 | 93 | ||
