diff options
33 files changed, 859 insertions, 412 deletions
diff --git a/.gitignore b/.gitignore index f835edef0..dab51647d 100644 --- a/.gitignore +++ b/.gitignore | |||
@@ -7,4 +7,4 @@ crates/*/target | |||
7 | *.log | 7 | *.log |
8 | *.iml | 8 | *.iml |
9 | .vscode/settings.json | 9 | .vscode/settings.json |
10 | cargo-timing*.html | 10 | *.html |
diff --git a/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs b/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs index 6a675e812..776bddf91 100644 --- a/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs +++ b/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs | |||
@@ -4,9 +4,9 @@ use test_utils::mark; | |||
4 | 4 | ||
5 | use crate::{utils::FamousDefs, AssistContext, AssistId, Assists}; | 5 | use crate::{utils::FamousDefs, AssistContext, AssistId, Assists}; |
6 | 6 | ||
7 | // Assist add_from_impl_for_enum | 7 | // Assist: add_from_impl_for_enum |
8 | // | 8 | // |
9 | // Adds a From impl for an enum variant with one tuple field | 9 | // Adds a From impl for an enum variant with one tuple field. |
10 | // | 10 | // |
11 | // ``` | 11 | // ``` |
12 | // enum A { <|>One(u32) } | 12 | // enum A { <|>One(u32) } |
diff --git a/crates/ra_assists/src/tests/generated.rs b/crates/ra_assists/src/tests/generated.rs index abffbf97c..73d43283d 100644 --- a/crates/ra_assists/src/tests/generated.rs +++ b/crates/ra_assists/src/tests/generated.rs | |||
@@ -59,6 +59,25 @@ fn main() { | |||
59 | } | 59 | } |
60 | 60 | ||
61 | #[test] | 61 | #[test] |
62 | fn doctest_add_from_impl_for_enum() { | ||
63 | check_doc_test( | ||
64 | "add_from_impl_for_enum", | ||
65 | r#####" | ||
66 | enum A { <|>One(u32) } | ||
67 | "#####, | ||
68 | r#####" | ||
69 | enum A { One(u32) } | ||
70 | |||
71 | impl From<u32> for A { | ||
72 | fn from(v: u32) -> Self { | ||
73 | A::One(v) | ||
74 | } | ||
75 | } | ||
76 | "#####, | ||
77 | ) | ||
78 | } | ||
79 | |||
80 | #[test] | ||
62 | fn doctest_add_function() { | 81 | fn doctest_add_function() { |
63 | check_doc_test( | 82 | check_doc_test( |
64 | "add_function", | 83 | "add_function", |
diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs index 191300704..d890b69d2 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. |
diff --git a/crates/ra_ide/src/completion/complete_postfix.rs b/crates/ra_ide/src/completion/complete_postfix.rs index 02e660ca8..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 { |
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 3e721dcca..d96cb5596 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs | |||
@@ -1,10 +1,10 @@ | |||
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, FieldSource, HasSource, HirDisplay, ModuleDef, |
6 | ModuleSource, Semantics, | 5 | 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}, |
@@ -21,8 +21,6 @@ use crate::{ | |||
21 | display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel}, | 21 | display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel}, |
22 | FilePosition, RangeInfo, | 22 | FilePosition, RangeInfo, |
23 | }; | 23 | }; |
24 | use itertools::Itertools; | ||
25 | use std::iter::once; | ||
26 | 24 | ||
27 | /// Contains the results when hovering over an item | 25 | /// Contains the results when hovering over an item |
28 | #[derive(Debug, Default)] | 26 | #[derive(Debug, Default)] |
@@ -62,6 +60,63 @@ impl HoverResult { | |||
62 | } | 60 | } |
63 | } | 61 | } |
64 | 62 | ||
63 | // Feature: Hover | ||
64 | // | ||
65 | // Shows additional information, like type of an expression or documentation for definition when "focusing" code. | ||
66 | // Focusing is usually hovering with a mouse, but can also be triggered with a shortcut. | ||
67 | pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<HoverResult>> { | ||
68 | let sema = Semantics::new(db); | ||
69 | let file = sema.parse(position.file_id).syntax().clone(); | ||
70 | let token = pick_best(file.token_at_offset(position.offset))?; | ||
71 | let token = sema.descend_into_macros(token); | ||
72 | |||
73 | let mut res = HoverResult::new(); | ||
74 | |||
75 | if let Some((node, name_kind)) = match_ast! { | ||
76 | match (token.parent()) { | ||
77 | ast::NameRef(name_ref) => { | ||
78 | classify_name_ref(&sema, &name_ref).map(|d| (name_ref.syntax().clone(), d.definition())) | ||
79 | }, | ||
80 | ast::Name(name) => { | ||
81 | classify_name(&sema, &name).map(|d| (name.syntax().clone(), d.definition())) | ||
82 | }, | ||
83 | _ => None, | ||
84 | } | ||
85 | } { | ||
86 | let range = sema.original_range(&node).range; | ||
87 | res.extend(hover_text_from_name_kind(db, name_kind)); | ||
88 | |||
89 | if !res.is_empty() { | ||
90 | return Some(RangeInfo::new(range, res)); | ||
91 | } | ||
92 | } | ||
93 | |||
94 | let node = token | ||
95 | .ancestors() | ||
96 | .find(|n| ast::Expr::cast(n.clone()).is_some() || ast::Pat::cast(n.clone()).is_some())?; | ||
97 | |||
98 | let ty = match_ast! { | ||
99 | match node { | ||
100 | ast::MacroCall(_it) => { | ||
101 | // If this node is a MACRO_CALL, it means that `descend_into_macros` failed to resolve. | ||
102 | // (e.g expanding a builtin macro). So we give up here. | ||
103 | return None; | ||
104 | }, | ||
105 | ast::Expr(it) => { | ||
106 | sema.type_of_expr(&it) | ||
107 | }, | ||
108 | ast::Pat(it) => { | ||
109 | sema.type_of_pat(&it) | ||
110 | }, | ||
111 | _ => None, | ||
112 | } | ||
113 | }?; | ||
114 | |||
115 | res.extend(Some(rust_code_markup(&ty.display(db)))); | ||
116 | let range = sema.original_range(&node).range; | ||
117 | Some(RangeInfo::new(range, res)) | ||
118 | } | ||
119 | |||
65 | fn hover_text( | 120 | fn hover_text( |
66 | docs: Option<String>, | 121 | docs: Option<String>, |
67 | desc: Option<String>, | 122 | desc: Option<String>, |
@@ -160,59 +215,6 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<Strin | |||
160 | } | 215 | } |
161 | } | 216 | } |
162 | 217 | ||
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> { | 218 | fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { |
217 | return tokens.max_by_key(priority); | 219 | return tokens.max_by_key(priority); |
218 | fn priority(n: &SyntaxToken) -> usize { | 220 | fn priority(n: &SyntaxToken) -> usize { |
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 d983cd910..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; |
@@ -373,7 +373,7 @@ impl Analysis { | |||
373 | &self, | 373 | &self, |
374 | position: FilePosition, | 374 | position: FilePosition, |
375 | ) -> Cancelable<Option<RangeInfo<Vec<NavigationTarget>>>> { | 375 | ) -> Cancelable<Option<RangeInfo<Vec<NavigationTarget>>>> { |
376 | self.with_db(|db| impls::goto_implementation(db, position)) | 376 | self.with_db(|db| goto_implementation::goto_implementation(db, position)) |
377 | } | 377 | } |
378 | 378 | ||
379 | /// 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/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/runnables.rs b/crates/ra_ide/src/runnables.rs index 6e7e47199..286d45eee 100644 --- a/crates/ra_ide/src/runnables.rs +++ b/crates/ra_ide/src/runnables.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use hir::{AsAssocItem, Attrs, HirFileId, InFile, Semantics}; | 1 | use hir::{AsAssocItem, Attrs, HirFileId, InFile, Semantics}; |
4 | use itertools::Itertools; | 2 | use itertools::Itertools; |
5 | use ra_ide_db::RootDatabase; | 3 | use ra_ide_db::RootDatabase; |
@@ -44,6 +42,17 @@ pub enum RunnableKind { | |||
44 | Bin, | 42 | Bin, |
45 | } | 43 | } |
46 | 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 | // |=== | ||
47 | pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> { | 56 | pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> { |
48 | let sema = Semantics::new(db); | 57 | let sema = Semantics::new(db); |
49 | let source_file = sema.parse(file_id); | 58 | let source_file = sema.parse(file_id); |
diff --git a/crates/ra_ide/src/ssr.rs b/crates/ra_ide/src/ssr.rs index 130d3b4c3..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, |
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 cd6464b40..0b53ebe69 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 |
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_db/src/symbol_index.rs b/crates/ra_ide_db/src/symbol_index.rs index 95be11134..acc31fe3b 100644 --- a/crates/ra_ide_db/src/symbol_index.rs +++ b/crates/ra_ide_db/src/symbol_index.rs | |||
@@ -110,6 +110,27 @@ fn file_symbols(db: &impl SymbolsDatabase, file_id: FileId) -> Arc<SymbolIndex> | |||
110 | Arc::new(SymbolIndex::new(symbols)) | 110 | Arc::new(SymbolIndex::new(symbols)) |
111 | } | 111 | } |
112 | 112 | ||
113 | // Feature: Workspace Symbol | ||
114 | // | ||
115 | // Uses fuzzy-search to find types, modules and functions by name across your | ||
116 | // project and dependencies. This is **the** most useful feature, which improves code | ||
117 | // navigation tremendously. It mostly works on top of the built-in LSP | ||
118 | // functionality, however `#` and `*` symbols can be used to narrow down the | ||
119 | // search. Specifically, | ||
120 | // | ||
121 | // - `Foo` searches for `Foo` type in the current workspace | ||
122 | // - `foo#` searches for `foo` function in the current workspace | ||
123 | // - `Foo*` searches for `Foo` type among dependencies, including `stdlib` | ||
124 | // - `foo#*` searches for `foo` function among dependencies | ||
125 | // | ||
126 | // That is, `#` switches from "types" to all symbols, `*` switches from the current | ||
127 | // workspace to dependencies. | ||
128 | // | ||
129 | // |=== | ||
130 | // | Editor | Shortcut | ||
131 | // | ||
132 | // | VS Code | kbd:[Ctrl+T] | ||
133 | // |=== | ||
113 | pub fn world_symbols(db: &RootDatabase, query: Query) -> Vec<FileSymbol> { | 134 | pub fn world_symbols(db: &RootDatabase, query: Query) -> Vec<FileSymbol> { |
114 | /// Need to wrap Snapshot to provide `Clone` impl for `map_with` | 135 | /// Need to wrap Snapshot to provide `Clone` impl for `map_with` |
115 | struct Snap(salsa::Snapshot<RootDatabase>); | 136 | struct Snap(salsa::Snapshot<RootDatabase>); |
diff --git a/docs/user/assists.md b/docs/user/assists.md index a1058ecde..04387e3b0 100644 --- a/docs/user/assists.md +++ b/docs/user/assists.md | |||
@@ -56,6 +56,24 @@ fn main() { | |||
56 | } | 56 | } |
57 | ``` | 57 | ``` |
58 | 58 | ||
59 | ## `add_from_impl_for_enum` | ||
60 | |||
61 | Adds a From impl for an enum variant with one tuple field. | ||
62 | |||
63 | ```rust | ||
64 | // BEFORE | ||
65 | enum A { ┃One(u32) } | ||
66 | |||
67 | // AFTER | ||
68 | enum A { One(u32) } | ||
69 | |||
70 | impl From<u32> for A { | ||
71 | fn from(v: u32) -> Self { | ||
72 | A::One(v) | ||
73 | } | ||
74 | } | ||
75 | ``` | ||
76 | |||
59 | ## `add_function` | 77 | ## `add_function` |
60 | 78 | ||
61 | Adds a stub function with a signature matching the function under the cursor. | 79 | Adds a stub function with a signature matching the function under the cursor. |
diff --git a/docs/user/features.md b/docs/user/features.md deleted file mode 100644 index 12ecdec13..000000000 --- a/docs/user/features.md +++ /dev/null | |||
@@ -1,218 +0,0 @@ | |||
1 | This document is an index of features that the rust-analyzer language server | ||
2 | provides. Shortcuts are for the default VS Code layout. If there's no shortcut, | ||
3 | you can use <kbd>Ctrl+Shift+P</kbd> to search for the corresponding action. | ||
4 | |||
5 | ### Workspace Symbol <kbd>ctrl+t</kbd> | ||
6 | |||
7 | Uses fuzzy-search to find types, modules and functions by name across your | ||
8 | project and dependencies. This is **the** most useful feature, which improves code | ||
9 | navigation tremendously. It mostly works on top of the built-in LSP | ||
10 | functionality, however `#` and `*` symbols can be used to narrow down the | ||
11 | search. Specifically, | ||
12 | |||
13 | - `Foo` searches for `Foo` type in the current workspace | ||
14 | - `foo#` searches for `foo` function in the current workspace | ||
15 | - `Foo*` searches for `Foo` type among dependencies, including `stdlib` | ||
16 | - `foo#*` searches for `foo` function among dependencies | ||
17 | |||
18 | That is, `#` switches from "types" to all symbols, `*` switches from the current | ||
19 | workspace to dependencies. | ||
20 | |||
21 | ### Document Symbol <kbd>ctrl+shift+o</kbd> | ||
22 | |||
23 | Provides a tree of the symbols defined in the file. Can be used to | ||
24 | |||
25 | * fuzzy search symbol in a file (super useful) | ||
26 | * draw breadcrumbs to describe the context around the cursor | ||
27 | * draw outline of the file | ||
28 | |||
29 | ### On Typing Assists | ||
30 | |||
31 | Some features trigger on typing certain characters: | ||
32 | |||
33 | - typing `let =` tries to smartly add `;` if `=` is followed by an existing expression | ||
34 | - Enter inside comments automatically inserts `///` | ||
35 | - typing `.` in a chain method call auto-indents | ||
36 | |||
37 | ### Extend Selection | ||
38 | |||
39 | Extends the current selection to the encompassing syntactic construct | ||
40 | (expression, statement, item, module, etc). It works with multiple cursors. This | ||
41 | is a relatively new feature of LSP: | ||
42 | https://github.com/Microsoft/language-server-protocol/issues/613, check your | ||
43 | editor's LSP library to see if this feature is supported. | ||
44 | |||
45 | ### Go to Definition | ||
46 | |||
47 | Navigates to the definition of an identifier. | ||
48 | |||
49 | ### Go to Implementation | ||
50 | |||
51 | Navigates to the impl block of structs, enums or traits. Also implemented as a code lens. | ||
52 | |||
53 | ### Go to Type Defintion | ||
54 | |||
55 | Navigates to the type of an identifier. | ||
56 | |||
57 | ### Commands <kbd>ctrl+shift+p</kbd> | ||
58 | |||
59 | #### Run | ||
60 | |||
61 | Shows a popup suggesting to run a test/benchmark/binary **at the current cursor | ||
62 | location**. Super useful for repeatedly running just a single test. Do bind this | ||
63 | to a shortcut! | ||
64 | |||
65 | #### Parent Module | ||
66 | |||
67 | Navigates to the parent module of the current module. | ||
68 | |||
69 | #### Matching Brace | ||
70 | |||
71 | If the cursor is on any brace (`<>(){}[]`) which is a part of a brace-pair, | ||
72 | moves cursor to the matching brace. It uses the actual parser to determine | ||
73 | braces, so it won't confuse generics with comparisons. | ||
74 | |||
75 | #### Join Lines | ||
76 | |||
77 | Join selected lines into one, smartly fixing up whitespace and trailing commas. | ||
78 | |||
79 | #### Show Syntax Tree | ||
80 | |||
81 | Shows the parse tree of the current file. It exists mostly for debugging | ||
82 | rust-analyzer itself. | ||
83 | |||
84 | #### Expand Macro Recursively | ||
85 | |||
86 | Shows the full macro expansion of the macro at current cursor. | ||
87 | |||
88 | #### Status | ||
89 | |||
90 | Shows internal statistic about memory usage of rust-analyzer. | ||
91 | |||
92 | #### Show RA Version | ||
93 | |||
94 | Show current rust-analyzer version. | ||
95 | |||
96 | #### Toggle inlay hints | ||
97 | |||
98 | Toggle inlay hints view for the current workspace. | ||
99 | It is recommended to assign a shortcut for this command to quickly turn off | ||
100 | inlay hints when they prevent you from reading/writing the code. | ||
101 | |||
102 | #### Run Garbage Collection | ||
103 | |||
104 | Manually triggers GC. | ||
105 | |||
106 | #### Start Cargo Watch | ||
107 | |||
108 | Start `cargo watch` for live error highlighting. Will prompt to install if it's not already installed. | ||
109 | |||
110 | #### Stop Cargo Watch | ||
111 | |||
112 | Stop `cargo watch`. | ||
113 | |||
114 | #### Structural Seach and Replace | ||
115 | |||
116 | Search and replace with named wildcards that will match any expression. | ||
117 | The syntax for a structural search replace command is `<search_pattern> ==>> <replace_pattern>`. A `$<name>:expr` placeholder in the search pattern will match any expression and `$<name>` will reference it in the replacement. Available via the command `rust-analyzer.ssr`. | ||
118 | |||
119 | ```rust | ||
120 | // Using structural search replace command [foo($a:expr, $b:expr) ==>> ($a).foo($b)] | ||
121 | |||
122 | // BEFORE | ||
123 | String::from(foo(y + 5, z)) | ||
124 | |||
125 | // AFTER | ||
126 | String::from((y + 5).foo(z)) | ||
127 | ``` | ||
128 | |||
129 | ### Assists (Code Actions) | ||
130 | |||
131 | Assists, or code actions, are small local refactorings, available in a particular context. | ||
132 | They are usually triggered by a shortcut or by clicking a light bulb icon in the editor. | ||
133 | |||
134 | See [assists.md](./assists.md) for the list of available assists. | ||
135 | |||
136 | ### Magic Completions | ||
137 | |||
138 | In addition to usual reference completion, rust-analyzer provides some ✨magic✨ | ||
139 | completions as well: | ||
140 | |||
141 | Keywords like `if`, `else` `while`, `loop` are completed with braces, and cursor | ||
142 | is placed at the appropriate position. Even though `if` is easy to type, you | ||
143 | still want to complete it, to get ` { }` for free! `return` is inserted with a | ||
144 | space or `;` depending on the return type of the function. | ||
145 | |||
146 | When completing a function call, `()` are automatically inserted. If a function | ||
147 | takes arguments, the cursor is positioned inside the parenthesis. | ||
148 | |||
149 | There are postfix completions, which can be triggered by typing something like | ||
150 | `foo().if`. The word after `.` determines postfix completion. Possible variants are: | ||
151 | |||
152 | - `expr.if` -> `if expr {}` or `if let ... {}` for `Option` or `Result` | ||
153 | - `expr.match` -> `match expr {}` | ||
154 | - `expr.while` -> `while expr {}` or `while let ... {}` for `Option` or `Result` | ||
155 | - `expr.ref` -> `&expr` | ||
156 | - `expr.refm` -> `&mut expr` | ||
157 | - `expr.not` -> `!expr` | ||
158 | - `expr.dbg` -> `dbg!(expr)` | ||
159 | |||
160 | There also snippet completions: | ||
161 | |||
162 | #### Inside Expressions | ||
163 | |||
164 | - `pd` -> `println!("{:?}")` | ||
165 | - `ppd` -> `println!("{:#?}")` | ||
166 | |||
167 | #### Inside Modules | ||
168 | |||
169 | - `tfn` -> `#[test] fn f(){}` | ||
170 | - `tmod` -> | ||
171 | ```rust | ||
172 | #[cfg(test)] | ||
173 | mod tests { | ||
174 | use super::*; | ||
175 | |||
176 | #[test] | ||
177 | fn test_fn() {} | ||
178 | } | ||
179 | ``` | ||
180 | |||
181 | ### Code Highlighting | ||
182 | |||
183 | Experimental feature to let rust-analyzer highlight Rust code instead of using the | ||
184 | default highlighter. | ||
185 | |||
186 | #### Rainbow Highlighting | ||
187 | |||
188 | Experimental feature that, given code highlighting using rust-analyzer is | ||
189 | active, will pick unique colors for identifiers. | ||
190 | |||
191 | ### Code hints | ||
192 | |||
193 | Rust-analyzer has two types of hints to show the information about the code: | ||
194 | |||
195 | * hover hints, appearing on hover on any element. | ||
196 | |||
197 | These contain extended information on the hovered language item. | ||
198 | |||
199 | * inlay hints, shown near the element hinted directly in the editor. | ||
200 | |||
201 | Two types of inlay hints are displayed currently: | ||
202 | |||
203 | * type hints, displaying the minimal information on the type of the expression (if the information is available) | ||
204 | * method chaining hints, type information for multi-line method chains | ||
205 | * parameter name hints, displaying the names of the parameters in the corresponding methods | ||
206 | |||
207 | #### VS Code | ||
208 | |||
209 | In VS Code, the following settings can be used to configure the inlay hints: | ||
210 | |||
211 | * `rust-analyzer.inlayHints.typeHints` - enable hints for inferred types. | ||
212 | * `rust-analyzer.inlayHints.chainingHints` - enable hints for inferred types on method chains. | ||
213 | * `rust-analyzer.inlayHints.parameterHints` - enable hints for function parameters. | ||
214 | * `rust-analyzer.inlayHints.maxLength` — shortens the hints if their length exceeds the value specified. If no value is specified (`null`), no shortening is applied. | ||
215 | |||
216 | **Note:** VS Code does not have native support for inlay hints [yet](https://github.com/microsoft/vscode/issues/16221) and the hints are implemented using decorations. | ||
217 | This approach has limitations, the caret movement and bracket highlighting near the edges of the hint may be weird: | ||
218 | [1](https://github.com/rust-analyzer/rust-analyzer/issues/1623), [2](https://github.com/rust-analyzer/rust-analyzer/issues/3453). | ||
diff --git a/docs/user/generated_features.adoc b/docs/user/generated_features.adoc new file mode 100644 index 000000000..803073d55 --- /dev/null +++ b/docs/user/generated_features.adoc | |||
@@ -0,0 +1,298 @@ | |||
1 | === Expand Macro Recursively | ||
2 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/expand_macro.rs[expand_macro.rs] | ||
3 | |||
4 | Shows the full macro expansion of the macro at current cursor. | ||
5 | |||
6 | |=== | ||
7 | | Editor | Action Name | ||
8 | |||
9 | | VS Code | **Rust Analyzer: Expand macro recursively** | ||
10 | |=== | ||
11 | |||
12 | |||
13 | === Extend Selection | ||
14 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/extend_selection.rs[extend_selection.rs] | ||
15 | |||
16 | Extends the current selection to the encompassing syntactic construct | ||
17 | (expression, statement, item, module, etc). It works with multiple cursors. | ||
18 | |||
19 | |=== | ||
20 | | Editor | Shortcut | ||
21 | |||
22 | | VS Code | kbd:[Ctrl+Shift+→] | ||
23 | |=== | ||
24 | |||
25 | |||
26 | === File Structure | ||
27 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/display/structure.rs[structure.rs] | ||
28 | |||
29 | Provides a tree of the symbols defined in the file. Can be used to | ||
30 | |||
31 | * fuzzy search symbol in a file (super useful) | ||
32 | * draw breadcrumbs to describe the context around the cursor | ||
33 | * draw outline of the file | ||
34 | |||
35 | |=== | ||
36 | | Editor | Shortcut | ||
37 | |||
38 | | VS Code | kbd:[Ctrl+Shift+O] | ||
39 | |=== | ||
40 | |||
41 | |||
42 | === Go to Definition | ||
43 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/goto_definition.rs[goto_definition.rs] | ||
44 | |||
45 | Navigates to the definition of an identifier. | ||
46 | |||
47 | |=== | ||
48 | | Editor | Shortcut | ||
49 | |||
50 | | VS Code | kbd:[F12] | ||
51 | |=== | ||
52 | |||
53 | |||
54 | === Go to Implementation | ||
55 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/goto_implementation.rs[goto_implementation.rs] | ||
56 | |||
57 | Navigates to the impl block of structs, enums or traits. Also implemented as a code lens. | ||
58 | |||
59 | |=== | ||
60 | | Editor | Shortcut | ||
61 | |||
62 | | VS Code | kbd:[Ctrl+F12] | ||
63 | |=== | ||
64 | |||
65 | |||
66 | === Go to Type Definition | ||
67 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/goto_type_definition.rs[goto_type_definition.rs] | ||
68 | |||
69 | Navigates to the type of an identifier. | ||
70 | |||
71 | |=== | ||
72 | | Editor | Action Name | ||
73 | |||
74 | | VS Code | **Go to Type Definition* | ||
75 | |=== | ||
76 | |||
77 | |||
78 | === Hover | ||
79 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/hover.rs[hover.rs] | ||
80 | |||
81 | Shows additional information, like type of an expression or documentation for definition when "focusing" code. | ||
82 | Focusing is usually hovering with a mouse, but can also be triggered with a shortcut. | ||
83 | |||
84 | |||
85 | === Inlay Hints | ||
86 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/inlay_hints.rs[inlay_hints.rs] | ||
87 | |||
88 | rust-analyzer shows additional information inline with the source code. | ||
89 | Editors usually render this using read-only virtual text snippets interspersed with code. | ||
90 | |||
91 | rust-analyzer shows hits for | ||
92 | |||
93 | * types of local variables | ||
94 | * names of function arguments | ||
95 | * types of chained expressions | ||
96 | |||
97 | **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. | ||
98 | This approach has limitations, the caret movement and bracket highlighting near the edges of the hint may be weird: | ||
99 | https://github.com/rust-analyzer/rust-analyzer/issues/1623[1], https://github.com/rust-analyzer/rust-analyzer/issues/3453[2]. | ||
100 | |||
101 | |=== | ||
102 | | Editor | Action Name | ||
103 | |||
104 | | VS Code | **Rust Analyzer: Toggle inlay hints* | ||
105 | |=== | ||
106 | |||
107 | |||
108 | === Join Lines | ||
109 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/join_lines.rs[join_lines.rs] | ||
110 | |||
111 | Join selected lines into one, smartly fixing up whitespace, trailing commas, and braces. | ||
112 | |||
113 | |=== | ||
114 | | Editor | Action Name | ||
115 | |||
116 | | VS Code | **Rust Analyzer: Join lines** | ||
117 | |=== | ||
118 | |||
119 | |||
120 | === Magic Completions | ||
121 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/completion.rs[completion.rs] | ||
122 | |||
123 | In addition to usual reference completion, rust-analyzer provides some ✨magic✨ | ||
124 | completions as well: | ||
125 | |||
126 | Keywords like `if`, `else` `while`, `loop` are completed with braces, and cursor | ||
127 | is placed at the appropriate position. Even though `if` is easy to type, you | ||
128 | still want to complete it, to get ` { }` for free! `return` is inserted with a | ||
129 | space or `;` depending on the return type of the function. | ||
130 | |||
131 | When completing a function call, `()` are automatically inserted. If a function | ||
132 | takes arguments, the cursor is positioned inside the parenthesis. | ||
133 | |||
134 | There are postfix completions, which can be triggered by typing something like | ||
135 | `foo().if`. The word after `.` determines postfix completion. Possible variants are: | ||
136 | |||
137 | - `expr.if` -> `if expr {}` or `if let ... {}` for `Option` or `Result` | ||
138 | - `expr.match` -> `match expr {}` | ||
139 | - `expr.while` -> `while expr {}` or `while let ... {}` for `Option` or `Result` | ||
140 | - `expr.ref` -> `&expr` | ||
141 | - `expr.refm` -> `&mut expr` | ||
142 | - `expr.not` -> `!expr` | ||
143 | - `expr.dbg` -> `dbg!(expr)` | ||
144 | |||
145 | There also snippet completions: | ||
146 | |||
147 | .Expressions | ||
148 | - `pd` -> `println!("{:?}")` | ||
149 | - `ppd` -> `println!("{:#?}")` | ||
150 | |||
151 | .Items | ||
152 | - `tfn` -> `#[test] fn f(){}` | ||
153 | - `tmod` -> | ||
154 | ```rust | ||
155 | #[cfg(test)] | ||
156 | mod tests { | ||
157 | use super::*; | ||
158 | |||
159 | #[test] | ||
160 | fn test_fn() {} | ||
161 | } | ||
162 | ``` | ||
163 | |||
164 | |||
165 | === Matching Brace | ||
166 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/matching_brace.rs[matching_brace.rs] | ||
167 | |||
168 | If the cursor is on any brace (`<>(){}[]`) which is a part of a brace-pair, | ||
169 | moves cursor to the matching brace. It uses the actual parser to determine | ||
170 | braces, so it won't confuse generics with comparisons. | ||
171 | |||
172 | |=== | ||
173 | | Editor | Action Name | ||
174 | |||
175 | | VS Code | **Rust Analyzer: Find matching brace** | ||
176 | |=== | ||
177 | |||
178 | |||
179 | === On Typing Assists | ||
180 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/typing.rs[typing.rs] | ||
181 | |||
182 | Some features trigger on typing certain characters: | ||
183 | |||
184 | - typing `let =` tries to smartly add `;` if `=` is followed by an existing expression | ||
185 | - Enter inside comments automatically inserts `///` | ||
186 | - typing `.` in a chain method call auto-indents | ||
187 | |||
188 | |||
189 | === Parent Module | ||
190 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/parent_module.rs[parent_module.rs] | ||
191 | |||
192 | Navigates to the parent module of the current module. | ||
193 | |||
194 | |=== | ||
195 | | Editor | Action Name | ||
196 | |||
197 | | VS Code | **Rust Analyzer: Locate parent module** | ||
198 | |=== | ||
199 | |||
200 | |||
201 | === Run | ||
202 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/runnables.rs[runnables.rs] | ||
203 | |||
204 | Shows a popup suggesting to run a test/benchmark/binary **at the current cursor | ||
205 | location**. Super useful for repeatedly running just a single test. Do bind this | ||
206 | to a shortcut! | ||
207 | |||
208 | |=== | ||
209 | | Editor | Action Name | ||
210 | |||
211 | | VS Code | **Rust Analyzer: Run** | ||
212 | |=== | ||
213 | |||
214 | |||
215 | === Semantic Syntax Highlighting | ||
216 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/syntax_highlighting.rs[syntax_highlighting.rs] | ||
217 | |||
218 | rust-analyzer highlights the code semantically. | ||
219 | For example, `bar` in `foo::Bar` might be colored differently depending on whether `Bar` is an enum or a trait. | ||
220 | rust-analyzer does not specify colors directly, instead it assigns tag (like `struct`) and a set of modifiers (like `declaration`) to each token. | ||
221 | It's up to the client to map those to specific colors. | ||
222 | |||
223 | The general rule is that a reference to an entity gets colored the same way as the entity itself. | ||
224 | We also give special modifier for `mut` and `&mut` local variables. | ||
225 | |||
226 | |||
227 | === Show Syntax Tree | ||
228 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/syntax_tree.rs[syntax_tree.rs] | ||
229 | |||
230 | Shows the parse tree of the current file. It exists mostly for debugging | ||
231 | rust-analyzer itself. | ||
232 | |||
233 | |=== | ||
234 | | Editor | Action Name | ||
235 | |||
236 | | VS Code | **Rust Analyzer: Show Syntax Tree** | ||
237 | |=== | ||
238 | |||
239 | |||
240 | === Status | ||
241 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/status.rs[status.rs] | ||
242 | |||
243 | Shows internal statistic about memory usage of rust-analyzer. | ||
244 | |||
245 | |=== | ||
246 | | Editor | Action Name | ||
247 | |||
248 | | VS Code | **Rust Analyzer: Status** | ||
249 | |=== | ||
250 | |||
251 | |||
252 | === Structural Seach and Replace | ||
253 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/ssr.rs[ssr.rs] | ||
254 | |||
255 | Search and replace with named wildcards that will match any expression. | ||
256 | The syntax for a structural search replace command is `<search_pattern> ==>> <replace_pattern>`. | ||
257 | A `$<name>:expr` placeholder in the search pattern will match any expression and `$<name>` will reference it in the replacement. | ||
258 | Available via the command `rust-analyzer.ssr`. | ||
259 | |||
260 | ```rust | ||
261 | // Using structural search replace command [foo($a:expr, $b:expr) ==>> ($a).foo($b)] | ||
262 | |||
263 | // BEFORE | ||
264 | String::from(foo(y + 5, z)) | ||
265 | |||
266 | // AFTER | ||
267 | String::from((y + 5).foo(z)) | ||
268 | ``` | ||
269 | |||
270 | |=== | ||
271 | | Editor | Action Name | ||
272 | |||
273 | | VS Code | **Rust Analyzer: Structural Search Replace** | ||
274 | |=== | ||
275 | |||
276 | |||
277 | === Workspace Symbol | ||
278 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide_db/src/symbol_index.rs[symbol_index.rs] | ||
279 | |||
280 | Uses fuzzy-search to find types, modules and functions by name across your | ||
281 | project and dependencies. This is **the** most useful feature, which improves code | ||
282 | navigation tremendously. It mostly works on top of the built-in LSP | ||
283 | functionality, however `#` and `*` symbols can be used to narrow down the | ||
284 | search. Specifically, | ||
285 | |||
286 | - `Foo` searches for `Foo` type in the current workspace | ||
287 | - `foo#` searches for `foo` function in the current workspace | ||
288 | - `Foo*` searches for `Foo` type among dependencies, including `stdlib` | ||
289 | - `foo#*` searches for `foo` function among dependencies | ||
290 | |||
291 | That is, `#` switches from "types" to all symbols, `*` switches from the current | ||
292 | workspace to dependencies. | ||
293 | |||
294 | |=== | ||
295 | | Editor | Shortcut | ||
296 | |||
297 | | VS Code | kbd:[Ctrl+T] | ||
298 | |=== | ||
diff --git a/docs/user/readme.adoc b/docs/user/readme.adoc index 64bd0feb1..12def7327 100644 --- a/docs/user/readme.adoc +++ b/docs/user/readme.adoc | |||
@@ -8,6 +8,8 @@ | |||
8 | :important-caption: :heavy_exclamation_mark: | 8 | :important-caption: :heavy_exclamation_mark: |
9 | :caution-caption: :fire: | 9 | :caution-caption: :fire: |
10 | :warning-caption: :warning: | 10 | :warning-caption: :warning: |
11 | :source-highlighter: rouge | ||
12 | :experimental: | ||
11 | 13 | ||
12 | // Master copy of this document lives in the https://github.com/rust-analyzer/rust-analyzer repository | 14 | // Master copy of this document lives in the https://github.com/rust-analyzer/rust-analyzer repository |
13 | 15 | ||
@@ -268,6 +270,13 @@ Gnome Builder currently has support for RLS, and there's no way to configure the | |||
268 | 1. Rename, symlink or copy the `rust-analyzer` binary to `rls` and place it somewhere Builder can find (in `PATH`, or under `~/.cargo/bin`). | 270 | 1. Rename, symlink or copy the `rust-analyzer` binary to `rls` and place it somewhere Builder can find (in `PATH`, or under `~/.cargo/bin`). |
269 | 2. Enable the Rust Builder plugin. | 271 | 2. Enable the Rust Builder plugin. |
270 | 272 | ||
271 | == Usage | 273 | == Features |
272 | 274 | ||
273 | See https://github.com/rust-analyzer/rust-analyzer/blob/master/docs/user/features.md[features.md]. | 275 | include::./generated_features.adoc[] |
276 | |||
277 | == Assists (Code Actions) | ||
278 | |||
279 | Assists, or code actions, are small local refactorings, available in a particular context. | ||
280 | They are usually triggered by a shortcut or by clicking a light bulb icon in the editor. | ||
281 | |||
282 | See [assists.md](./assists.md) for the list of available assists. | ||
diff --git a/xtask/src/codegen.rs b/xtask/src/codegen.rs index b4907f4b2..f47d54125 100644 --- a/xtask/src/codegen.rs +++ b/xtask/src/codegen.rs | |||
@@ -8,14 +8,15 @@ | |||
8 | mod gen_syntax; | 8 | mod gen_syntax; |
9 | mod gen_parser_tests; | 9 | mod gen_parser_tests; |
10 | mod gen_assists_docs; | 10 | mod gen_assists_docs; |
11 | mod gen_feature_docs; | ||
11 | 12 | ||
12 | use std::{mem, path::Path}; | 13 | use std::{mem, path::Path}; |
13 | 14 | ||
14 | use crate::{not_bash::fs2, Result}; | 15 | use crate::{not_bash::fs2, Result}; |
15 | 16 | ||
16 | pub use self::{ | 17 | pub use self::{ |
17 | gen_assists_docs::generate_assists_docs, gen_parser_tests::generate_parser_tests, | 18 | gen_assists_docs::generate_assists_docs, gen_feature_docs::generate_feature_docs, |
18 | gen_syntax::generate_syntax, | 19 | gen_parser_tests::generate_parser_tests, gen_syntax::generate_syntax, |
19 | }; | 20 | }; |
20 | 21 | ||
21 | const GRAMMAR_DIR: &str = "crates/ra_parser/src/grammar"; | 22 | const GRAMMAR_DIR: &str = "crates/ra_parser/src/grammar"; |
@@ -40,7 +41,7 @@ pub enum Mode { | |||
40 | /// With verify = false, | 41 | /// With verify = false, |
41 | fn update(path: &Path, contents: &str, mode: Mode) -> Result<()> { | 42 | fn update(path: &Path, contents: &str, mode: Mode) -> Result<()> { |
42 | match fs2::read_to_string(path) { | 43 | match fs2::read_to_string(path) { |
43 | Ok(ref old_contents) if normalize(old_contents) == normalize(contents) => { | 44 | Ok(old_contents) if normalize(&old_contents) == normalize(contents) => { |
44 | return Ok(()); | 45 | return Ok(()); |
45 | } | 46 | } |
46 | _ => (), | 47 | _ => (), |
@@ -61,8 +62,24 @@ fn extract_comment_blocks(text: &str) -> Vec<Vec<String>> { | |||
61 | do_extract_comment_blocks(text, false) | 62 | do_extract_comment_blocks(text, false) |
62 | } | 63 | } |
63 | 64 | ||
64 | fn extract_comment_blocks_with_empty_lines(text: &str) -> Vec<Vec<String>> { | 65 | fn extract_comment_blocks_with_empty_lines(tag: &str, text: &str) -> Vec<CommentBlock> { |
65 | do_extract_comment_blocks(text, true) | 66 | assert!(tag.starts_with(char::is_uppercase)); |
67 | let tag = format!("{}:", tag); | ||
68 | let mut res = Vec::new(); | ||
69 | for mut block in do_extract_comment_blocks(text, true) { | ||
70 | let first = block.remove(0); | ||
71 | if first.starts_with(&tag) { | ||
72 | let id = first[tag.len()..].trim().to_string(); | ||
73 | let block = CommentBlock { id, contents: block }; | ||
74 | res.push(block); | ||
75 | } | ||
76 | } | ||
77 | res | ||
78 | } | ||
79 | |||
80 | struct CommentBlock { | ||
81 | id: String, | ||
82 | contents: Vec<String>, | ||
66 | } | 83 | } |
67 | 84 | ||
68 | fn do_extract_comment_blocks(text: &str, allow_blocks_with_empty_lines: bool) -> Vec<Vec<String>> { | 85 | fn do_extract_comment_blocks(text: &str, allow_blocks_with_empty_lines: bool) -> Vec<Vec<String>> { |
diff --git a/xtask/src/codegen/gen_assists_docs.rs b/xtask/src/codegen/gen_assists_docs.rs index 20dcde820..6ebeb8aea 100644 --- a/xtask/src/codegen/gen_assists_docs.rs +++ b/xtask/src/codegen/gen_assists_docs.rs | |||
@@ -33,22 +33,18 @@ impl Assist { | |||
33 | 33 | ||
34 | fn collect_file(acc: &mut Vec<Assist>, path: &Path) -> Result<()> { | 34 | fn collect_file(acc: &mut Vec<Assist>, path: &Path) -> Result<()> { |
35 | let text = fs::read_to_string(path)?; | 35 | let text = fs::read_to_string(path)?; |
36 | let comment_blocks = extract_comment_blocks_with_empty_lines(&text); | 36 | let comment_blocks = extract_comment_blocks_with_empty_lines("Assist", &text); |
37 | 37 | ||
38 | for block in comment_blocks { | 38 | for block in comment_blocks { |
39 | // FIXME: doesn't support blank lines yet, need to tweak | 39 | // FIXME: doesn't support blank lines yet, need to tweak |
40 | // `extract_comment_blocks` for that. | 40 | // `extract_comment_blocks` for that. |
41 | let mut lines = block.iter(); | 41 | let id = block.id; |
42 | let first_line = lines.next().unwrap(); | ||
43 | if !first_line.starts_with("Assist: ") { | ||
44 | continue; | ||
45 | } | ||
46 | let id = first_line["Assist: ".len()..].to_string(); | ||
47 | assert!( | 42 | assert!( |
48 | id.chars().all(|it| it.is_ascii_lowercase() || it == '_'), | 43 | id.chars().all(|it| it.is_ascii_lowercase() || it == '_'), |
49 | "invalid assist id: {:?}", | 44 | "invalid assist id: {:?}", |
50 | id | 45 | id |
51 | ); | 46 | ); |
47 | let mut lines = block.contents.iter(); | ||
52 | 48 | ||
53 | let doc = take_until(lines.by_ref(), "```").trim().to_string(); | 49 | let doc = take_until(lines.by_ref(), "```").trim().to_string(); |
54 | assert!( | 50 | assert!( |
diff --git a/xtask/src/codegen/gen_feature_docs.rs b/xtask/src/codegen/gen_feature_docs.rs new file mode 100644 index 000000000..dbe583e8e --- /dev/null +++ b/xtask/src/codegen/gen_feature_docs.rs | |||
@@ -0,0 +1,88 @@ | |||
1 | //! Generates `assists.md` documentation. | ||
2 | |||
3 | use std::{fmt, fs, path::PathBuf}; | ||
4 | |||
5 | use crate::{ | ||
6 | codegen::{self, extract_comment_blocks_with_empty_lines, Mode}, | ||
7 | project_root, rust_files, Result, | ||
8 | }; | ||
9 | |||
10 | pub fn generate_feature_docs(mode: Mode) -> Result<()> { | ||
11 | let features = Feature::collect()?; | ||
12 | let contents = features.into_iter().map(|it| it.to_string()).collect::<Vec<_>>().join("\n\n"); | ||
13 | let contents = contents.trim().to_string() + "\n"; | ||
14 | let dst = project_root().join("docs/user/generated_features.adoc"); | ||
15 | codegen::update(&dst, &contents, mode)?; | ||
16 | Ok(()) | ||
17 | } | ||
18 | |||
19 | #[derive(Debug)] | ||
20 | struct Feature { | ||
21 | id: String, | ||
22 | path: PathBuf, | ||
23 | doc: String, | ||
24 | } | ||
25 | |||
26 | impl Feature { | ||
27 | fn collect() -> Result<Vec<Feature>> { | ||
28 | let mut res = Vec::new(); | ||
29 | for path in rust_files(&project_root()) { | ||
30 | collect_file(&mut res, path)?; | ||
31 | } | ||
32 | res.sort_by(|lhs, rhs| lhs.id.cmp(&rhs.id)); | ||
33 | return Ok(res); | ||
34 | |||
35 | fn collect_file(acc: &mut Vec<Feature>, path: PathBuf) -> Result<()> { | ||
36 | let text = fs::read_to_string(&path)?; | ||
37 | let comment_blocks = extract_comment_blocks_with_empty_lines("Feature", &text); | ||
38 | |||
39 | for block in comment_blocks { | ||
40 | let id = block.id; | ||
41 | assert!(is_valid_feature_name(&id), "invalid feature name: {:?}", id); | ||
42 | let doc = block.contents.join("\n"); | ||
43 | acc.push(Feature { id, path: path.clone(), doc }) | ||
44 | } | ||
45 | |||
46 | Ok(()) | ||
47 | } | ||
48 | } | ||
49 | } | ||
50 | |||
51 | fn is_valid_feature_name(feature: &str) -> bool { | ||
52 | 'word: for word in feature.split_whitespace() { | ||
53 | for &short in ["to", "and"].iter() { | ||
54 | if word == short { | ||
55 | continue 'word; | ||
56 | } | ||
57 | } | ||
58 | for &short in ["To", "And"].iter() { | ||
59 | if word == short { | ||
60 | return false; | ||
61 | } | ||
62 | } | ||
63 | if !word.starts_with(char::is_uppercase) { | ||
64 | return false; | ||
65 | } | ||
66 | } | ||
67 | true | ||
68 | } | ||
69 | |||
70 | impl fmt::Display for Feature { | ||
71 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
72 | writeln!(f, "=== {}", self.id)?; | ||
73 | let path = self.path.strip_prefix(&project_root()).unwrap().display().to_string(); | ||
74 | let path = path.replace('\\', "/"); | ||
75 | let name = self.path.file_name().unwrap(); | ||
76 | |||
77 | //FIXME: generate line number as well | ||
78 | writeln!( | ||
79 | f, | ||
80 | "**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/{}[{}]", | ||
81 | path, | ||
82 | name.to_str().unwrap(), | ||
83 | )?; | ||
84 | |||
85 | writeln!(f, "{}", self.doc)?; | ||
86 | Ok(()) | ||
87 | } | ||
88 | } | ||
diff --git a/xtask/src/main.rs b/xtask/src/main.rs index dff3ce4a1..9d7cdd114 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs | |||
@@ -75,6 +75,7 @@ FLAGS: | |||
75 | codegen::generate_syntax(Mode::Overwrite)?; | 75 | codegen::generate_syntax(Mode::Overwrite)?; |
76 | codegen::generate_parser_tests(Mode::Overwrite)?; | 76 | codegen::generate_parser_tests(Mode::Overwrite)?; |
77 | codegen::generate_assists_docs(Mode::Overwrite)?; | 77 | codegen::generate_assists_docs(Mode::Overwrite)?; |
78 | codegen::generate_feature_docs(Mode::Overwrite)?; | ||
78 | Ok(()) | 79 | Ok(()) |
79 | } | 80 | } |
80 | "format" => { | 81 | "format" => { |
diff --git a/xtask/tests/tidy.rs b/xtask/tests/tidy.rs index 2e9fcf07c..4ac5d929f 100644 --- a/xtask/tests/tidy.rs +++ b/xtask/tests/tidy.rs | |||
@@ -31,6 +31,13 @@ fn generated_assists_are_fresh() { | |||
31 | } | 31 | } |
32 | 32 | ||
33 | #[test] | 33 | #[test] |
34 | fn generated_features_are_fresh() { | ||
35 | if let Err(error) = codegen::generate_feature_docs(Mode::Verify) { | ||
36 | panic!("{}. Please update features by running `cargo xtask codegen`", error); | ||
37 | } | ||
38 | } | ||
39 | |||
40 | #[test] | ||
34 | fn check_code_formatting() { | 41 | fn check_code_formatting() { |
35 | if let Err(error) = run_rustfmt(Mode::Verify) { | 42 | if let Err(error) = run_rustfmt(Mode::Verify) { |
36 | panic!("{}. Please format the code by running `cargo format`", error); | 43 | panic!("{}. Please format the code by running `cargo format`", error); |
@@ -95,7 +102,7 @@ impl TidyDocs { | |||
95 | fn visit(&mut self, path: &Path, text: &str) { | 102 | fn visit(&mut self, path: &Path, text: &str) { |
96 | // Test hopefully don't really need comments, and for assists we already | 103 | // Test hopefully don't really need comments, and for assists we already |
97 | // have special comments which are source of doc tests and user docs. | 104 | // have special comments which are source of doc tests and user docs. |
98 | if is_exclude_dir(path, &["tests", "test_data", "handlers"]) { | 105 | if is_exclude_dir(path, &["tests", "test_data"]) { |
99 | return; | 106 | return; |
100 | } | 107 | } |
101 | 108 | ||
@@ -110,9 +117,12 @@ impl TidyDocs { | |||
110 | 117 | ||
111 | if first_line.starts_with("//!") { | 118 | if first_line.starts_with("//!") { |
112 | if first_line.contains("FIXME") { | 119 | if first_line.contains("FIXME") { |
113 | self.contains_fixme.push(path.to_path_buf()) | 120 | self.contains_fixme.push(path.to_path_buf()); |
114 | } | 121 | } |
115 | } else { | 122 | } else { |
123 | if text.contains("// Feature:") || text.contains("// Assist:") { | ||
124 | return; | ||
125 | } | ||
116 | self.missing_docs.push(path.display().to_string()); | 126 | self.missing_docs.push(path.display().to_string()); |
117 | } | 127 | } |
118 | 128 | ||