aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-05-31 11:50:11 +0100
committerGitHub <[email protected]>2020-05-31 11:50:11 +0100
commit09df51dab89340bcf4b8ede95c02c32b0c8eb2bc (patch)
tree0240c629fe96243e1a1c91ccd679947bfb1ecb03
parent5f7225446e75509ae0d971a6f3e2b9d3e37d6f2a (diff)
parent13a996f3b68c175f6e6ad8d89081e45850dc5583 (diff)
Merge #4664
4664: Generate feature documentation from code r=matklad a=matklad Co-authored-by: Aleksey Kladov <[email protected]>
-rw-r--r--.gitignore2
-rw-r--r--crates/ra_assists/src/handlers/add_from_impl_for_enum.rs4
-rw-r--r--crates/ra_assists/src/tests/generated.rs19
-rw-r--r--crates/ra_ide/src/completion.rs47
-rw-r--r--crates/ra_ide/src/completion/complete_postfix.rs6
-rw-r--r--crates/ra_ide/src/display/structure.rs19
-rw-r--r--crates/ra_ide/src/expand_macro.rs11
-rw-r--r--crates/ra_ide/src/extend_selection.rs12
-rw-r--r--crates/ra_ide/src/goto_definition.rs11
-rw-r--r--crates/ra_ide/src/goto_implementation.rs (renamed from crates/ra_ide/src/impls.rs)11
-rw-r--r--crates/ra_ide/src/goto_type_definition.rs11
-rw-r--r--crates/ra_ide/src/hover.rs116
-rw-r--r--crates/ra_ide/src/inlay_hints.rs22
-rw-r--r--crates/ra_ide/src/join_lines.rs11
-rw-r--r--crates/ra_ide/src/lib.rs4
-rw-r--r--crates/ra_ide/src/matching_brace.rs13
-rw-r--r--crates/ra_ide/src/parent_module.rs12
-rw-r--r--crates/ra_ide/src/runnables.rs13
-rw-r--r--crates/ra_ide/src/ssr.rs24
-rw-r--r--crates/ra_ide/src/status.rs11
-rw-r--r--crates/ra_ide/src/syntax_highlighting.rs161
-rw-r--r--crates/ra_ide/src/syntax_tree.rs16
-rw-r--r--crates/ra_ide/src/typing.rs7
-rw-r--r--crates/ra_ide_db/src/symbol_index.rs21
-rw-r--r--docs/user/assists.md18
-rw-r--r--docs/user/features.md218
-rw-r--r--docs/user/generated_features.adoc298
-rw-r--r--docs/user/readme.adoc13
-rw-r--r--xtask/src/codegen.rs27
-rw-r--r--xtask/src/codegen/gen_assists_docs.rs10
-rw-r--r--xtask/src/codegen/gen_feature_docs.rs88
-rw-r--r--xtask/src/main.rs1
-rw-r--r--xtask/tests/tidy.rs14
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
10cargo-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
5use crate::{utils::FamousDefs, AssistContext, AssistId, Assists}; 5use 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]
62fn doctest_add_from_impl_for_enum() {
63 check_doc_test(
64 "add_from_impl_for_enum",
65 r#####"
66enum A { <|>One(u32) }
67"#####,
68 r#####"
69enum A { One(u32) }
70
71impl From<u32> for A {
72 fn from(v: u32) -> Self {
73 A::One(v)
74 }
75}
76"#####,
77 )
78}
79
80#[test]
62fn doctest_add_function() { 81fn 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
3mod completion_config; 1mod completion_config;
4mod completion_item; 2mod completion_item;
5mod completion_context; 3mod 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 2use ra_assists::utils::TryEnum;
3use ra_syntax::{ 3use ra_syntax::{
4 ast::{self, AstNode}, 4 ast::{self, AstNode},
5 TextRange, TextSize, 5 TextRange, TextSize,
6}; 6};
7use ra_text_edit::TextEdit; 7use ra_text_edit::TextEdit;
8 8
9use super::completion_config::SnippetCap;
10use crate::{ 9use 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};
17use ra_assists::utils::TryEnum; 16
17use super::completion_config::SnippetCap;
18 18
19pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { 19pub(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
3use crate::TextRange;
4
5use ra_syntax::{ 1use 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// |===
21pub fn file_structure(file: &SourceFile) -> Vec<StructureNode> { 30pub 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
3use hir::Semantics; 1use hir::Semantics;
4use ra_ide_db::RootDatabase; 2use ra_ide_db::RootDatabase;
5use ra_syntax::{ 3use 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// |===
17pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<ExpandedMacro> { 24pub(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
3use std::iter::successors; 1use std::iter::successors;
4 2
5use hir::Semantics; 3use hir::Semantics;
@@ -14,6 +12,16 @@ use ra_syntax::{
14 12
15use crate::FileRange; 13use 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// |===
17pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange { 25pub(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
3use hir::Semantics; 1use hir::Semantics;
4use ra_ide_db::{ 2use 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// |===
20pub(crate) fn goto_definition( 27pub(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
3use hir::{Crate, ImplDef, Semantics}; 1use hir::{Crate, ImplDef, Semantics};
4use ra_ide_db::RootDatabase; 2use ra_ide_db::RootDatabase;
5use ra_syntax::{algo::find_node_at_offset, ast, AstNode}; 3use ra_syntax::{algo::find_node_at_offset, ast, AstNode};
6 4
7use crate::{display::ToNav, FilePosition, NavigationTarget, RangeInfo}; 5use 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// |===
9pub(crate) fn goto_implementation( 16pub(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
3use ra_ide_db::RootDatabase; 1use ra_ide_db::RootDatabase;
4use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; 2use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset};
5 3
6use crate::{display::ToNav, FilePosition, NavigationTarget, RangeInfo}; 4use 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// |===
8pub(crate) fn goto_type_definition( 15pub(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 1use std::iter::once;
2//! source code items (e.g. function call, struct field, variable symbol...)
3 2
4use hir::{ 3use 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};
7use itertools::Itertools;
8use ra_db::SourceDatabase; 8use ra_db::SourceDatabase;
9use ra_ide_db::{ 9use 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};
24use itertools::Itertools;
25use 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.
67pub(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
65fn hover_text( 120fn 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
163pub(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
216fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { 218fn 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
3use hir::{Adt, HirDisplay, Semantics, Type}; 1use hir::{Adt, HirDisplay, Semantics, Type};
4use ra_ide_db::RootDatabase; 2use ra_ide_db::RootDatabase;
5use ra_prof::profile; 3use 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// |===
42pub(crate) fn inlay_hints( 60pub(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
3use itertools::Itertools; 1use itertools::Itertools;
4use ra_fmt::{compute_ws, extract_trivial_expression}; 2use ra_fmt::{compute_ws, extract_trivial_expression};
5use ra_syntax::{ 3use ra_syntax::{
@@ -11,6 +9,15 @@ use ra_syntax::{
11}; 9};
12use ra_text_edit::{TextEdit, TextEditBuilder}; 10use 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// |===
14pub fn join_lines(file: &SourceFile, range: TextRange) -> TextEdit { 21pub 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;
23mod runnables; 23mod runnables;
24mod goto_definition; 24mod goto_definition;
25mod goto_type_definition; 25mod goto_type_definition;
26mod goto_implementation;
26mod extend_selection; 27mod extend_selection;
27mod hover; 28mod hover;
28mod call_hierarchy; 29mod call_hierarchy;
@@ -30,7 +31,6 @@ mod call_info;
30mod syntax_highlighting; 31mod syntax_highlighting;
31mod parent_module; 32mod parent_module;
32mod references; 33mod references;
33mod impls;
34mod diagnostics; 34mod diagnostics;
35mod syntax_tree; 35mod syntax_tree;
36mod folding_ranges; 36mod 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
3use ra_syntax::{ast::AstNode, SourceFile, SyntaxKind, TextSize, T}; 1use 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// |===
5pub fn matching_brace(file: &SourceFile, offset: TextSize) -> Option<TextSize> { 14pub 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
3use hir::Semantics; 1use hir::Semantics;
4use ra_db::{CrateId, FileId, FilePosition}; 2use ra_db::{CrateId, FileId, FilePosition};
5use ra_ide_db::RootDatabase; 3use ra_ide_db::RootDatabase;
@@ -11,6 +9,16 @@ use test_utils::mark;
11 9
12use crate::NavigationTarget; 10use 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.
16pub(crate) fn parent_module(db: &RootDatabase, position: FilePosition) -> Vec<NavigationTarget> { 24pub(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
3use hir::{AsAssocItem, Attrs, HirFileId, InFile, Semantics}; 1use hir::{AsAssocItem, Attrs, HirFileId, InFile, Semantics};
4use itertools::Itertools; 2use itertools::Itertools;
5use ra_ide_db::RootDatabase; 3use 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// |===
47pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> { 56pub(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
3use std::{collections::HashMap, iter::once, str::FromStr}; 1use std::{collections::HashMap, iter::once, str::FromStr};
4 2
5use ra_db::{SourceDatabase, SourceDatabaseExt}; 3use ra_db::{SourceDatabase, SourceDatabaseExt};
@@ -25,6 +23,28 @@ impl std::fmt::Display for SsrError {
25 23
26impl std::error::Error for SsrError {} 24impl 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// |===
28pub fn parse_search_replace( 48pub 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
3use std::{fmt, iter::FromIterator, sync::Arc}; 1use std::{fmt, iter::FromIterator, sync::Arc};
4 2
5use hir::MacroFile; 3use 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// |===
29pub(crate) fn status(db: &RootDatabase) -> String { 36pub(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
3mod tags; 1mod tags;
4mod html; 2mod 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
36struct 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//
42impl 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
110pub(crate) fn highlight( 42pub(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)]
227struct HighlightedRangeStack {
228 stack: Vec<Vec<HighlightedRange>>,
229}
230
231/// We use a stack to implement the flattening logic for the highlighted
232/// syntax ranges.
233impl 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
294fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HighlightTag> { 301fn 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 1use ra_db::{FileId, SourceDatabase};
2
3use ra_db::SourceDatabase;
4use ra_ide_db::RootDatabase; 2use ra_ide_db::RootDatabase;
5use ra_syntax::{ 3use 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
11pub 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// |===
13pub(crate) fn syntax_tree( 19pub(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
33pub(crate) const TRIGGER_CHARS: &str = ".=>"; 33pub(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
35pub(crate) fn on_char_typed( 42pub(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// |===
113pub fn world_symbols(db: &RootDatabase, query: Query) -> Vec<FileSymbol> { 134pub 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
61Adds a From impl for an enum variant with one tuple field.
62
63```rust
64// BEFORE
65enum A { ┃One(u32) }
66
67// AFTER
68enum A { One(u32) }
69
70impl 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
61Adds a stub function with a signature matching the function under the cursor. 79Adds 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 @@
1This document is an index of features that the rust-analyzer language server
2provides. Shortcuts are for the default VS Code layout. If there's no shortcut,
3you can use <kbd>Ctrl+Shift+P</kbd> to search for the corresponding action.
4
5### Workspace Symbol <kbd>ctrl+t</kbd>
6
7Uses fuzzy-search to find types, modules and functions by name across your
8project and dependencies. This is **the** most useful feature, which improves code
9navigation tremendously. It mostly works on top of the built-in LSP
10functionality, however `#` and `*` symbols can be used to narrow down the
11search. 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
18That is, `#` switches from "types" to all symbols, `*` switches from the current
19workspace to dependencies.
20
21### Document Symbol <kbd>ctrl+shift+o</kbd>
22
23Provides 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
31Some 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
39Extends the current selection to the encompassing syntactic construct
40(expression, statement, item, module, etc). It works with multiple cursors. This
41is a relatively new feature of LSP:
42https://github.com/Microsoft/language-server-protocol/issues/613, check your
43editor's LSP library to see if this feature is supported.
44
45### Go to Definition
46
47Navigates to the definition of an identifier.
48
49### Go to Implementation
50
51Navigates to the impl block of structs, enums or traits. Also implemented as a code lens.
52
53### Go to Type Defintion
54
55Navigates to the type of an identifier.
56
57### Commands <kbd>ctrl+shift+p</kbd>
58
59#### Run
60
61Shows a popup suggesting to run a test/benchmark/binary **at the current cursor
62location**. Super useful for repeatedly running just a single test. Do bind this
63to a shortcut!
64
65#### Parent Module
66
67Navigates to the parent module of the current module.
68
69#### Matching Brace
70
71If the cursor is on any brace (`<>(){}[]`) which is a part of a brace-pair,
72moves cursor to the matching brace. It uses the actual parser to determine
73braces, so it won't confuse generics with comparisons.
74
75#### Join Lines
76
77Join selected lines into one, smartly fixing up whitespace and trailing commas.
78
79#### Show Syntax Tree
80
81Shows the parse tree of the current file. It exists mostly for debugging
82rust-analyzer itself.
83
84#### Expand Macro Recursively
85
86Shows the full macro expansion of the macro at current cursor.
87
88#### Status
89
90Shows internal statistic about memory usage of rust-analyzer.
91
92#### Show RA Version
93
94Show current rust-analyzer version.
95
96#### Toggle inlay hints
97
98Toggle inlay hints view for the current workspace.
99It is recommended to assign a shortcut for this command to quickly turn off
100inlay hints when they prevent you from reading/writing the code.
101
102#### Run Garbage Collection
103
104Manually triggers GC.
105
106#### Start Cargo Watch
107
108Start `cargo watch` for live error highlighting. Will prompt to install if it's not already installed.
109
110#### Stop Cargo Watch
111
112Stop `cargo watch`.
113
114#### Structural Seach and Replace
115
116Search and replace with named wildcards that will match any expression.
117The 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
123String::from(foo(y + 5, z))
124
125// AFTER
126String::from((y + 5).foo(z))
127```
128
129### Assists (Code Actions)
130
131Assists, or code actions, are small local refactorings, available in a particular context.
132They are usually triggered by a shortcut or by clicking a light bulb icon in the editor.
133
134See [assists.md](./assists.md) for the list of available assists.
135
136### Magic Completions
137
138In addition to usual reference completion, rust-analyzer provides some ✨magic✨
139completions as well:
140
141Keywords like `if`, `else` `while`, `loop` are completed with braces, and cursor
142is placed at the appropriate position. Even though `if` is easy to type, you
143still want to complete it, to get ` { }` for free! `return` is inserted with a
144space or `;` depending on the return type of the function.
145
146When completing a function call, `()` are automatically inserted. If a function
147takes arguments, the cursor is positioned inside the parenthesis.
148
149There 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
160There 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)]
173mod tests {
174 use super::*;
175
176 #[test]
177 fn test_fn() {}
178}
179```
180
181### Code Highlighting
182
183Experimental feature to let rust-analyzer highlight Rust code instead of using the
184default highlighter.
185
186#### Rainbow Highlighting
187
188Experimental feature that, given code highlighting using rust-analyzer is
189active, will pick unique colors for identifiers.
190
191### Code hints
192
193Rust-analyzer has two types of hints to show the information about the code:
194
195* hover hints, appearing on hover on any element.
196
197These contain extended information on the hovered language item.
198
199* inlay hints, shown near the element hinted directly in the editor.
200
201Two 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
209In 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.
217This 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
4Shows 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
16Extends 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
29Provides 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
45Navigates 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
57Navigates 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
69Navigates 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
81Shows additional information, like type of an expression or documentation for definition when "focusing" code.
82Focusing 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
88rust-analyzer shows additional information inline with the source code.
89Editors usually render this using read-only virtual text snippets interspersed with code.
90
91rust-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.
98This approach has limitations, the caret movement and bracket highlighting near the edges of the hint may be weird:
99https://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
111Join 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
123In addition to usual reference completion, rust-analyzer provides some ✨magic✨
124completions as well:
125
126Keywords like `if`, `else` `while`, `loop` are completed with braces, and cursor
127is placed at the appropriate position. Even though `if` is easy to type, you
128still want to complete it, to get ` { }` for free! `return` is inserted with a
129space or `;` depending on the return type of the function.
130
131When completing a function call, `()` are automatically inserted. If a function
132takes arguments, the cursor is positioned inside the parenthesis.
133
134There 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
145There 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)]
156mod 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
168If the cursor is on any brace (`<>(){}[]`) which is a part of a brace-pair,
169moves cursor to the matching brace. It uses the actual parser to determine
170braces, 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
182Some 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
192Navigates 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
204Shows a popup suggesting to run a test/benchmark/binary **at the current cursor
205location**. Super useful for repeatedly running just a single test. Do bind this
206to 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
218rust-analyzer highlights the code semantically.
219For example, `bar` in `foo::Bar` might be colored differently depending on whether `Bar` is an enum or a trait.
220rust-analyzer does not specify colors directly, instead it assigns tag (like `struct`) and a set of modifiers (like `declaration`) to each token.
221It's up to the client to map those to specific colors.
222
223The general rule is that a reference to an entity gets colored the same way as the entity itself.
224We 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
230Shows the parse tree of the current file. It exists mostly for debugging
231rust-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
243Shows 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
255Search and replace with named wildcards that will match any expression.
256The syntax for a structural search replace command is `<search_pattern> ==>> <replace_pattern>`.
257A `$<name>:expr` placeholder in the search pattern will match any expression and `$<name>` will reference it in the replacement.
258Available 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
264String::from(foo(y + 5, z))
265
266// AFTER
267String::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
280Uses fuzzy-search to find types, modules and functions by name across your
281project and dependencies. This is **the** most useful feature, which improves code
282navigation tremendously. It mostly works on top of the built-in LSP
283functionality, however `#` and `*` symbols can be used to narrow down the
284search. 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
291That is, `#` switches from "types" to all symbols, `*` switches from the current
292workspace 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
2681. Rename, symlink or copy the `rust-analyzer` binary to `rls` and place it somewhere Builder can find (in `PATH`, or under `~/.cargo/bin`). 2701. Rename, symlink or copy the `rust-analyzer` binary to `rls` and place it somewhere Builder can find (in `PATH`, or under `~/.cargo/bin`).
2692. Enable the Rust Builder plugin. 2712. Enable the Rust Builder plugin.
270 272
271== Usage 273== Features
272 274
273See https://github.com/rust-analyzer/rust-analyzer/blob/master/docs/user/features.md[features.md]. 275include::./generated_features.adoc[]
276
277== Assists (Code Actions)
278
279Assists, or code actions, are small local refactorings, available in a particular context.
280They are usually triggered by a shortcut or by clicking a light bulb icon in the editor.
281
282See [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 @@
8mod gen_syntax; 8mod gen_syntax;
9mod gen_parser_tests; 9mod gen_parser_tests;
10mod gen_assists_docs; 10mod gen_assists_docs;
11mod gen_feature_docs;
11 12
12use std::{mem, path::Path}; 13use std::{mem, path::Path};
13 14
14use crate::{not_bash::fs2, Result}; 15use crate::{not_bash::fs2, Result};
15 16
16pub use self::{ 17pub 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
21const GRAMMAR_DIR: &str = "crates/ra_parser/src/grammar"; 22const GRAMMAR_DIR: &str = "crates/ra_parser/src/grammar";
@@ -40,7 +41,7 @@ pub enum Mode {
40/// With verify = false, 41/// With verify = false,
41fn update(path: &Path, contents: &str, mode: Mode) -> Result<()> { 42fn 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
64fn extract_comment_blocks_with_empty_lines(text: &str) -> Vec<Vec<String>> { 65fn 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
80struct CommentBlock {
81 id: String,
82 contents: Vec<String>,
66} 83}
67 84
68fn do_extract_comment_blocks(text: &str, allow_blocks_with_empty_lines: bool) -> Vec<Vec<String>> { 85fn 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
3use std::{fmt, fs, path::PathBuf};
4
5use crate::{
6 codegen::{self, extract_comment_blocks_with_empty_lines, Mode},
7 project_root, rust_files, Result,
8};
9
10pub 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)]
20struct Feature {
21 id: String,
22 path: PathBuf,
23 doc: String,
24}
25
26impl 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
51fn 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
70impl 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]
34fn 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]
34fn check_code_formatting() { 41fn 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