Repository: rust-analyzer/rust-analyzer
Pull request #4664 (2020-05-30 23:56:07)

Generate feature documentation from code


Patch:
diff --git a/.gitignore b/.gitignore
index f835edef00..dab51647db 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,4 +7,4 @@ crates/*/target
 *.log
 *.iml
 .vscode/settings.json
-cargo-timing*.html
+*.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 6a675e8126..776bddf918 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;
 
 use crate::{utils::FamousDefs, AssistContext, AssistId, Assists};
 
-// Assist add_from_impl_for_enum
+// Assist: add_from_impl_for_enum
 //
-// Adds a From impl for an enum variant with one tuple field
+// Adds a From impl for an enum variant with one tuple field.
 //
 // ```
 // enum A { <|>One(u32) }
diff --git a/crates/ra_assists/src/handlers/change_lifetime_anon_to_named.rs b/crates/ra_assists/src/handlers/change_lifetime_anon_to_named.rs
deleted file mode 100644
index 999aec421b..0000000000
--- a/crates/ra_assists/src/handlers/change_lifetime_anon_to_named.rs
+++ /dev/null
@@ -1,303 +0,0 @@
-use crate::{assist_context::AssistBuilder, AssistContext, AssistId, Assists};
-use ast::{NameOwner, ParamList, TypeAscriptionOwner, TypeParamList, TypeRef};
-use ra_syntax::{ast, ast::TypeParamsOwner, AstNode, SyntaxKind, TextRange, TextSize};
-use rustc_hash::FxHashSet;
-
-static ASSIST_NAME: &str = "change_lifetime_anon_to_named";
-static ASSIST_LABEL: &str = "Give anonymous lifetime a name";
-
-// Assist: change_lifetime_anon_to_named
-//
-// Change an anonymous lifetime to a named lifetime.
-//
-// ```
-// impl Cursor<'_<|>> {
-//     fn node(self) -> &SyntaxNode {
-//         match self {
-//             Cursor::Replace(node) | Cursor::Before(node) => node,
-//         }
-//     }
-// }
-// ```
-// ->
-// ```
-// impl<'a> Cursor<'a> {
-//     fn node(self) -> &SyntaxNode {
-//         match self {
-//             Cursor::Replace(node) | Cursor::Before(node) => node,
-//         }
-//     }
-// }
-// ```
-// FIXME: How can we handle renaming any one of multiple anonymous lifetimes?
-// FIXME: should also add support for the case fun(f: &Foo) -> &<|>Foo
-pub(crate) fn change_lifetime_anon_to_named(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
-    let lifetime_token = ctx
-        .find_token_at_offset(SyntaxKind::LIFETIME)
-        .filter(|lifetime| lifetime.text() == "'_")?;
-    if let Some(fn_def) = lifetime_token.ancestors().find_map(ast::FnDef::cast) {
-        generate_fn_def_assist(acc, &fn_def, lifetime_token.text_range())
-    } else if let Some(impl_def) = lifetime_token.ancestors().find_map(ast::ImplDef::cast) {
-        // only allow naming the last anonymous lifetime
-        lifetime_token.next_token().filter(|tok| tok.kind() == SyntaxKind::R_ANGLE)?;
-        generate_impl_def_assist(acc, &impl_def, lifetime_token.text_range())
-    } else {
-        None
-    }
-}
-
-/// Generate the assist for the fn def case
-fn generate_fn_def_assist(
-    acc: &mut Assists,
-    fn_def: &ast::FnDef,
-    lifetime_loc: TextRange,
-) -> Option<()> {
-    let param_list: ParamList = fn_def.param_list()?;
-    let new_lifetime_param = generate_unique_lifetime_param_name(&fn_def.type_param_list())?;
-    let end_of_fn_ident = fn_def.name()?.ident_token()?.text_range().end();
-    let self_param =
-        // use the self if it's a reference and has no explicit lifetime
-        param_list.self_param().filter(|p| p.lifetime_token().is_none() && p.amp_token().is_some());
-    // compute the location which implicitly has the same lifetime as the anonymous lifetime
-    let loc_needing_lifetime = if let Some(self_param) = self_param {
-        // if we have a self reference, use that
-        Some(self_param.self_token()?.text_range().start())
-    } else {
-        // otherwise, if there's a single reference parameter without a named liftime, use that
-        let fn_params_without_lifetime: Vec<_> = param_list
-            .params()
-            .filter_map(|param| match param.ascribed_type() {
-                Some(TypeRef::ReferenceType(ascribed_type))
-                    if ascribed_type.lifetime_token() == None =>
-                {
-                    Some(ascribed_type.amp_token()?.text_range().end())
-                }
-                _ => None,
-            })
-            .collect();
-        match fn_params_without_lifetime.len() {
-            1 => Some(fn_params_without_lifetime.into_iter().nth(0)?),
-            0 => None,
-            // multiple unnnamed is invalid. assist is not applicable
-            _ => return None,
-        }
-    };
-    acc.add(AssistId(ASSIST_NAME), ASSIST_LABEL, lifetime_loc, |builder| {
-        add_lifetime_param(fn_def, builder, end_of_fn_ident, new_lifetime_param);
-        builder.replace(lifetime_loc, format!("'{}", new_lifetime_param));
-        loc_needing_lifetime.map(|loc| builder.insert(loc, format!("'{} ", new_lifetime_param)));
-    })
-}
-
-/// Generate the assist for the impl def case
-fn generate_impl_def_assist(
-    acc: &mut Assists,
-    impl_def: &ast::ImplDef,
-    lifetime_loc: TextRange,
-) -> Option<()> {
-    let new_lifetime_param = generate_unique_lifetime_param_name(&impl_def.type_param_list())?;
-    let end_of_impl_kw = impl_def.impl_token()?.text_range().end();
-    acc.add(AssistId(ASSIST_NAME), ASSIST_LABEL, lifetime_loc, |builder| {
-        add_lifetime_param(impl_def, builder, end_of_impl_kw, new_lifetime_param);
-        builder.replace(lifetime_loc, format!("'{}", new_lifetime_param));
-    })
-}
-
-/// Given a type parameter list, generate a unique lifetime parameter name
-/// which is not in the list
-fn generate_unique_lifetime_param_name(
-    existing_type_param_list: &Option<TypeParamList>,
-) -> Option<char> {
-    match existing_type_param_list {
-        Some(type_params) => {
-            let used_lifetime_params: FxHashSet<_> = type_params
-                .lifetime_params()
-                .map(|p| p.syntax().text().to_string()[1..].to_owned())
-                .collect();
-            (b'a'..=b'z').map(char::from).find(|c| !used_lifetime_params.contains(&c.to_string()))
-        }
-        None => Some('a'),
-    }
-}
-
-/// Add the lifetime param to `builder`. If there are type parameters in `type_params_owner`, add it to the end. Otherwise
-/// add new type params brackets with the lifetime parameter at `new_type_params_loc`.
-fn add_lifetime_param<TypeParamsOwner: ast::TypeParamsOwner>(
-    type_params_owner: &TypeParamsOwner,
-    builder: &mut AssistBuilder,
-    new_type_params_loc: TextSize,
-    new_lifetime_param: char,
-) {
-    match type_params_owner.type_param_list() {
-        // add the new lifetime parameter to an existing type param list
-        Some(type_params) => {
-            builder.insert(
-                (u32::from(type_params.syntax().text_range().end()) - 1).into(),
-                format!(", '{}", new_lifetime_param),
-            );
-        }
-        // create a new type param list containing only the new lifetime parameter
-        None => {
-            builder.insert(new_type_params_loc, format!("<'{}>", new_lifetime_param));
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::tests::{check_assist, check_assist_not_applicable};
-
-    #[test]
-    fn test_example_case() {
-        check_assist(
-            change_lifetime_anon_to_named,
-            r#"impl Cursor<'_<|>> {
-                fn node(self) -> &SyntaxNode {
-                    match self {
-                        Cursor::Replace(node) | Cursor::Before(node) => node,
-                    }
-                }
-            }"#,
-            r#"impl<'a> Cursor<'a> {
-                fn node(self) -> &SyntaxNode {
-                    match self {
-                        Cursor::Replace(node) | Cursor::Before(node) => node,
-                    }
-                }
-            }"#,
-        );
-    }
-
-    #[test]
-    fn test_example_case_simplified() {
-        check_assist(
-            change_lifetime_anon_to_named,
-            r#"impl Cursor<'_<|>> {"#,
-            r#"impl<'a> Cursor<'a> {"#,
-        );
-    }
-
-    #[test]
-    fn test_example_case_cursor_after_tick() {
-        check_assist(
-            change_lifetime_anon_to_named,
-            r#"impl Cursor<'<|>_> {"#,
-            r#"impl<'a> Cursor<'a> {"#,
-        );
-    }
-
-    #[test]
-    fn test_example_case_cursor_before_tick() {
-        check_assist(
-            change_lifetime_anon_to_named,
-            r#"impl Cursor<<|>'_> {"#,
-            r#"impl<'a> Cursor<'a> {"#,
-        );
-    }
-
-    #[test]
-    fn test_not_applicable_cursor_position() {
-        check_assist_not_applicable(change_lifetime_anon_to_named, r#"impl Cursor<'_><|> {"#);
-        check_assist_not_applicable(change_lifetime_anon_to_named, r#"impl Cursor<|><'_> {"#);
-    }
-
-    #[test]
-    fn test_not_applicable_lifetime_already_name() {
-        check_assist_not_applicable(change_lifetime_anon_to_named, r#"impl Cursor<'a<|>> {"#);
-        check_assist_not_applicable(
-            change_lifetime_anon_to_named,
-            r#"fn my_fun<'a>() -> X<'a<|>>"#,
-        );
-    }
-
-    #[test]
-    fn test_with_type_parameter() {
-        check_assist(
-            change_lifetime_anon_to_named,
-            r#"impl<T> Cursor<T, '_<|>>"#,
-            r#"impl<T, 'a> Cursor<T, 'a>"#,
-        );
-    }
-
-    #[test]
-    fn test_with_existing_lifetime_name_conflict() {
-        check_assist(
-            change_lifetime_anon_to_named,
-            r#"impl<'a, 'b> Cursor<'a, 'b, '_<|>>"#,
-            r#"impl<'a, 'b, 'c> Cursor<'a, 'b, 'c>"#,
-        );
-    }
-
-    #[test]
-    fn test_function_return_value_anon_lifetime_param() {
-        check_assist(
-            change_lifetime_anon_to_named,
-            r#"fn my_fun() -> X<'_<|>>"#,
-            r#"fn my_fun<'a>() -> X<'a>"#,
-        );
-    }
-
-    #[test]
-    fn test_function_return_value_anon_reference_lifetime() {
-        check_assist(
-            change_lifetime_anon_to_named,
-            r#"fn my_fun() -> &'_<|> X"#,
-            r#"fn my_fun<'a>() -> &'a X"#,
-        );
-    }
-
-    #[test]
-    fn test_function_param_anon_lifetime() {
-        check_assist(
-            change_lifetime_anon_to_named,
-            r#"fn my_fun(x: X<'_<|>>)"#,
-            r#"fn my_fun<'a>(x: X<'a>)"#,
-        );
-    }
-
-    #[test]
-    fn test_function_add_lifetime_to_params() {
-        check_assist(
-            change_lifetime_anon_to_named,
-            r#"fn my_fun(f: &Foo) -> X<'_<|>>"#,
-            r#"fn my_fun<'a>(f: &'a Foo) -> X<'a>"#,
-        );
-    }
-
-    #[test]
-    fn test_function_add_lifetime_to_params_in_presence_of_other_lifetime() {
-        check_assist(
-            change_lifetime_anon_to_named,
-            r#"fn my_fun<'other>(f: &Foo, b: &'other Bar) -> X<'_<|>>"#,
-            r#"fn my_fun<'other, 'a>(f: &'a Foo, b: &'other Bar) -> X<'a>"#,
-        );
-    }
-
-    #[test]
-    fn test_function_not_applicable_without_self_and_multiple_unnamed_param_lifetimes() {
-        // this is not permitted under lifetime elision rules
-        check_assist_not_applicable(
-            change_lifetime_anon_to_named,
-            r#"fn my_fun(f: &Foo, b: &Bar) -> X<'_<|>>"#,
-        );
-    }
-
-    #[test]
-    fn test_function_add_lifetime_to_self_ref_param() {
-        check_assist(
-            change_lifetime_anon_to_named,
-            r#"fn my_fun<'other>(&self, f: &Foo, b: &'other Bar) -> X<'_<|>>"#,
-            r#"fn my_fun<'other, 'a>(&'a self, f: &Foo, b: &'other Bar) -> X<'a>"#,
-        );
-    }
-
-    #[test]
-    fn test_function_add_lifetime_to_param_with_non_ref_self() {
-        check_assist(
-            change_lifetime_anon_to_named,
-            r#"fn my_fun<'other>(self, f: &Foo, b: &'other Bar) -> X<'_<|>>"#,
-            r#"fn my_fun<'other, 'a>(self, f: &'a Foo, b: &'other Bar) -> X<'a>"#,
-        );
-    }
-}
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs
index 3f8f7ffbfc..464bc03dde 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -112,7 +112,6 @@ mod handlers {
     mod add_turbo_fish;
     mod apply_demorgan;
     mod auto_import;
-    mod change_lifetime_anon_to_named;
     mod change_return_type_to_result;
     mod change_visibility;
     mod early_return;
@@ -152,7 +151,6 @@ mod handlers {
             add_turbo_fish::add_turbo_fish,
             apply_demorgan::apply_demorgan,
             auto_import::auto_import,
-            change_lifetime_anon_to_named::change_lifetime_anon_to_named,
             change_return_type_to_result::change_return_type_to_result,
             change_visibility::change_visibility,
             early_return::convert_to_guarded_return,
diff --git a/crates/ra_hir_ty/src/infer/expr.rs b/crates/ra_hir_ty/src/infer/expr.rs
index 78084cb573..54bab3476c 100644
--- a/crates/ra_hir_ty/src/infer/expr.rs
+++ b/crates/ra_hir_ty/src/infer/expr.rs
@@ -140,13 +140,13 @@ impl<'a> InferenceContext<'a> {
 
                 let mut sig_tys = Vec::new();
 
-                // collect explicitly written argument types
-                for arg_type in arg_types.iter() {
-                    let arg_ty = if let Some(type_ref) = arg_type {
+                for (arg_pat, arg_type) in args.iter().zip(arg_types.iter()) {
+                    let expected = if let Some(type_ref) = arg_type {
                         self.make_ty(type_ref)
                     } else {
-                        self.table.new_type_var()
+                        Ty::Unknown
                     };
+                    let arg_ty = self.infer_pat(*arg_pat, &expected, BindingMode::default());
                     sig_tys.push(arg_ty);
                 }
 
@@ -158,7 +158,7 @@ impl<'a> InferenceContext<'a> {
                 sig_tys.push(ret_ty.clone());
                 let sig_ty = Ty::apply(
                     TypeCtor::FnPtr { num_args: sig_tys.len() as u16 - 1 },
-                    Substs(sig_tys.clone().into()),
+                    Substs(sig_tys.into()),
                 );
                 let closure_ty =
                     Ty::apply_one(TypeCtor::Closure { def: self.owner, expr: tgt_expr }, sig_ty);
@@ -168,12 +168,6 @@ impl<'a> InferenceContext<'a> {
                 // infer the body.
                 self.coerce(&closure_ty, &expected.ty);
 
-                // Now go through the argument patterns
-                for (arg_pat, arg_ty) in args.iter().zip(sig_tys) {
-                    let resolved = self.resolve_ty_as_possible(arg_ty);
-                    self.infer_pat(*arg_pat, &resolved, BindingMode::default());
-                }
-
                 let prev_diverges = mem::replace(&mut self.diverges, Diverges::Maybe);
                 let prev_ret_ty = mem::replace(&mut self.return_ty, ret_ty.clone());
 
diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs
index 191300704b..d890b69d26 100644
--- a/crates/ra_ide/src/completion.rs
+++ b/crates/ra_ide/src/completion.rs
@@ -1,5 +1,3 @@
-//! FIXME: write short doc here
-
 mod completion_config;
 mod completion_item;
 mod completion_context;
@@ -35,6 +33,51 @@ pub use crate::completion::{
     completion_item::{CompletionItem, CompletionItemKind, CompletionScore, InsertTextFormat},
 };
 
+//FIXME: split the following feature into fine-grained features.
+
+// Feature: Magic Completions
+//
+// In addition to usual reference completion, rust-analyzer provides some ✨magic✨
+// completions as well:
+//
+// Keywords like `if`, `else` `while`, `loop` are completed with braces, and cursor
+// is placed at the appropriate position. Even though `if` is easy to type, you
+// still want to complete it, to get ` { }` for free! `return` is inserted with a
+// space or `;` depending on the return type of the function.
+//
+// When completing a function call, `()` are automatically inserted. If a function
+// takes arguments, the cursor is positioned inside the parenthesis.
+//
+// There are postfix completions, which can be triggered by typing something like
+// `foo().if`. The word after `.` determines postfix completion. Possible variants are:
+//
+// - `expr.if` -> `if expr {}` or `if let ... {}` for `Option` or `Result`
+// - `expr.match` -> `match expr {}`
+// - `expr.while` -> `while expr {}` or `while let ... {}` for `Option` or `Result`
+// - `expr.ref` -> `&expr`
+// - `expr.refm` -> `&mut expr`
+// - `expr.not` -> `!expr`
+// - `expr.dbg` -> `dbg!(expr)`
+//
+// There also snippet completions:
+//
+// .Expressions
+// - `pd` -> `println!("{:?}")`
+// - `ppd` -> `println!("{:#?}")`
+//
+// .Items
+// - `tfn` -> `#[test] fn f(){}`
+// - `tmod` ->
+// ```rust
+// #[cfg(test)]
+// mod tests {
+//     use super::*;
+//
+//     #[test]
+//     fn test_fn() {}
+// }
+// ```
+
 /// Main entry point for completion. We run completion as a two-phase process.
 ///
 /// 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 02e660ca8e..59b58bf98b 100644
--- a/crates/ra_ide/src/completion/complete_postfix.rs
+++ b/crates/ra_ide/src/completion/complete_postfix.rs
@@ -1,12 +1,11 @@
 //! FIXME: write short doc here
-
+use ra_assists::utils::TryEnum;
 use ra_syntax::{
     ast::{self, AstNode},
     TextRange, TextSize,
 };
 use ra_text_edit::TextEdit;
 
-use super::completion_config::SnippetCap;
 use crate::{
     completion::{
         completion_context::CompletionContext,
@@ -14,7 +13,8 @@ use crate::{
     },
     CompletionItem,
 };
-use ra_assists::utils::TryEnum;
+
+use super::completion_config::SnippetCap;
 
 pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
     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 967eee5d23..aad5a8e4db 100644
--- a/crates/ra_ide/src/display/structure.rs
+++ b/crates/ra_ide/src/display/structure.rs
@@ -1,10 +1,6 @@
-//! FIXME: write short doc here
-
-use crate::TextRange;
-
 use ra_syntax::{
     ast::{self, AttrsOwner, NameOwner, TypeAscriptionOwner, TypeParamsOwner},
-    match_ast, AstNode, SourceFile, SyntaxKind, SyntaxNode, WalkEvent,
+    match_ast, AstNode, SourceFile, SyntaxKind, SyntaxNode, TextRange, WalkEvent,
 };
 
 #[derive(Debug, Clone)]
@@ -18,6 +14,19 @@ pub struct StructureNode {
     pub deprecated: bool,
 }
 
+// Feature: File Structure
+//
+// Provides a tree of the symbols defined in the file. Can be used to
+//
+// * fuzzy search symbol in a file (super useful)
+// * draw breadcrumbs to describe the context around the cursor
+// * draw outline of the file
+//
+// |===
+// | Editor  | Shortcut
+//
+// | VS Code | kbd:[Ctrl+Shift+O]
+// |===
 pub fn file_structure(file: &SourceFile) -> Vec<StructureNode> {
     let mut res = Vec::new();
     let mut stack = Vec::new();
diff --git a/crates/ra_ide/src/expand_macro.rs b/crates/ra_ide/src/expand_macro.rs
index f536ba3e78..54a47aac06 100644
--- a/crates/ra_ide/src/expand_macro.rs
+++ b/crates/ra_ide/src/expand_macro.rs
@@ -1,5 +1,3 @@
-//! This modules implements "expand macro" functionality in the IDE
-
 use hir::Semantics;
 use ra_ide_db::RootDatabase;
 use ra_syntax::{
@@ -14,6 +12,15 @@ pub struct ExpandedMacro {
     pub expansion: String,
 }
 
+// Feature: Expand Macro Recursively
+//
+// Shows the full macro expansion of the macro at current cursor.
+//
+// |===
+// | Editor  | Action Name
+//
+// | VS Code | **Rust Analyzer: Expand macro recursively**
+// |===
 pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<ExpandedMacro> {
     let sema = Semantics::new(db);
     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 554594a436..a4bc93cdba 100644
--- a/crates/ra_ide/src/extend_selection.rs
+++ b/crates/ra_ide/src/extend_selection.rs
@@ -1,5 +1,3 @@
-//! FIXME: write short doc here
-
 use std::iter::successors;
 
 use hir::Semantics;
@@ -14,6 +12,16 @@ use ra_syntax::{
 
 use crate::FileRange;
 
+// Feature: Extend Selection
+//
+// Extends the current selection to the encompassing syntactic construct
+// (expression, statement, item, module, etc). It works with multiple cursors.
+//
+// |===
+// | Editor  | Shortcut
+//
+// | VS Code | kbd:[Ctrl+Shift+→]
+// |===
 pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange {
     let sema = Semantics::new(db);
     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 90e85d4197..a6c86e99c9 100644
--- a/crates/ra_ide/src/goto_definition.rs
+++ b/crates/ra_ide/src/goto_definition.rs
@@ -1,5 +1,3 @@
-//! FIXME: write short doc here
-
 use hir::Semantics;
 use ra_ide_db::{
     defs::{classify_name, classify_name_ref},
@@ -17,6 +15,15 @@ use crate::{
     FilePosition, NavigationTarget, RangeInfo,
 };
 
+// Feature: Go to Definition
+//
+// Navigates to the definition of an identifier.
+//
+// |===
+// | Editor  | Shortcut
+//
+// | VS Code | kbd:[F12]
+// |===
 pub(crate) fn goto_definition(
     db: &RootDatabase,
     position: FilePosition,
diff --git a/crates/ra_ide/src/impls.rs b/crates/ra_ide/src/goto_implementation.rs
similarity index 96%
rename from crates/ra_ide/src/impls.rs
rename to crates/ra_ide/src/goto_implementation.rs
index ea2225f707..0cec0657e4 100644
--- a/crates/ra_ide/src/impls.rs
+++ b/crates/ra_ide/src/goto_implementation.rs
@@ -1,11 +1,18 @@
-//! FIXME: write short doc here
-
 use hir::{Crate, ImplDef, Semantics};
 use ra_ide_db::RootDatabase;
 use ra_syntax::{algo::find_node_at_offset, ast, AstNode};
 
 use crate::{display::ToNav, FilePosition, NavigationTarget, RangeInfo};
 
+// Feature: Go to Implementation
+//
+// Navigates to the impl block of structs, enums or traits. Also implemented as a code lens.
+//
+// |===
+// | Editor  | Shortcut
+//
+// | VS Code | kbd:[Ctrl+F12]
+// |===
 pub(crate) fn goto_implementation(
     db: &RootDatabase,
     position: FilePosition,
diff --git a/crates/ra_ide/src/goto_type_definition.rs b/crates/ra_ide/src/goto_type_definition.rs
index a84637489e..91a3097fbb 100644
--- a/crates/ra_ide/src/goto_type_definition.rs
+++ b/crates/ra_ide/src/goto_type_definition.rs
@@ -1,10 +1,17 @@
-//! FIXME: write short doc here
-
 use ra_ide_db::RootDatabase;
 use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset};
 
 use crate::{display::ToNav, FilePosition, NavigationTarget, RangeInfo};
 
+// Feature: Go to Type Definition
+//
+// Navigates to the type of an identifier.
+//
+// |===
+// | Editor  | Action Name
+//
+// | VS Code | **Go to Type Definition*
+// |===
 pub(crate) fn goto_type_definition(
     db: &RootDatabase,
     position: FilePosition,
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs
index 3e721dcca3..d96cb55969 100644
--- a/crates/ra_ide/src/hover.rs
+++ b/crates/ra_ide/src/hover.rs
@@ -1,10 +1,10 @@
-//! Logic for computing info that is displayed when the user hovers over any
-//! source code items (e.g. function call, struct field, variable symbol...)
+use std::iter::once;
 
 use hir::{
     Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef,
     ModuleSource, Semantics,
 };
+use itertools::Itertools;
 use ra_db::SourceDatabase;
 use ra_ide_db::{
     defs::{classify_name, classify_name_ref, Definition},
@@ -21,8 +21,6 @@ use crate::{
     display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel},
     FilePosition, RangeInfo,
 };
-use itertools::Itertools;
-use std::iter::once;
 
 /// Contains the results when hovering over an item
 #[derive(Debug, Default)]
@@ -62,6 +60,63 @@ impl HoverResult {
     }
 }
 
+// Feature: Hover
+//
+// Shows additional information, like type of an expression or documentation for definition when "focusing" code.
+// Focusing is usually hovering with a mouse, but can also be triggered with a shortcut.
+pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<HoverResult>> {
+    let sema = Semantics::new(db);
+    let file = sema.parse(position.file_id).syntax().clone();
+    let token = pick_best(file.token_at_offset(position.offset))?;
+    let token = sema.descend_into_macros(token);
+
+    let mut res = HoverResult::new();
+
+    if let Some((node, name_kind)) = match_ast! {
+        match (token.parent()) {
+            ast::NameRef(name_ref) => {
+                classify_name_ref(&sema, &name_ref).map(|d| (name_ref.syntax().clone(), d.definition()))
+            },
+            ast::Name(name) => {
+                classify_name(&sema, &name).map(|d| (name.syntax().clone(), d.definition()))
+            },
+            _ => None,
+        }
+    } {
+        let range = sema.original_range(&node).range;
+        res.extend(hover_text_from_name_kind(db, name_kind));
+
+        if !res.is_empty() {
+            return Some(RangeInfo::new(range, res));
+        }
+    }
+
+    let node = token
+        .ancestors()
+        .find(|n| ast::Expr::cast(n.clone()).is_some() || ast::Pat::cast(n.clone()).is_some())?;
+
+    let ty = match_ast! {
+        match node {
+            ast::MacroCall(_it) => {
+                // If this node is a MACRO_CALL, it means that `descend_into_macros` failed to resolve.
+                // (e.g expanding a builtin macro). So we give up here.
+                return None;
+            },
+            ast::Expr(it) => {
+                sema.type_of_expr(&it)
+            },
+            ast::Pat(it) => {
+                sema.type_of_pat(&it)
+            },
+            _ => None,
+        }
+    }?;
+
+    res.extend(Some(rust_code_markup(&ty.display(db))));
+    let range = sema.original_range(&node).range;
+    Some(RangeInfo::new(range, res))
+}
+
 fn hover_text(
     docs: Option<String>,
     desc: Option<String>,
@@ -160,59 +215,6 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<Strin
     }
 }
 
-pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<HoverResult>> {
-    let sema = Semantics::new(db);
-    let file = sema.parse(position.file_id).syntax().clone();
-    let token = pick_best(file.token_at_offset(position.offset))?;
-    let token = sema.descend_into_macros(token);
-
-    let mut res = HoverResult::new();
-
-    if let Some((node, name_kind)) = match_ast! {
-        match (token.parent()) {
-            ast::NameRef(name_ref) => {
-                classify_name_ref(&sema, &name_ref).map(|d| (name_ref.syntax().clone(), d.definition()))
-            },
-            ast::Name(name) => {
-                classify_name(&sema, &name).map(|d| (name.syntax().clone(), d.definition()))
-            },
-            _ => None,
-        }
-    } {
-        let range = sema.original_range(&node).range;
-        res.extend(hover_text_from_name_kind(db, name_kind));
-
-        if !res.is_empty() {
-            return Some(RangeInfo::new(range, res));
-        }
-    }
-
-    let node = token
-        .ancestors()
-        .find(|n| ast::Expr::cast(n.clone()).is_some() || ast::Pat::cast(n.clone()).is_some())?;
-
-    let ty = match_ast! {
-        match node {
-            ast::MacroCall(_it) => {
-                // If this node is a MACRO_CALL, it means that `descend_into_macros` failed to resolve.
-                // (e.g expanding a builtin macro). So we give up here.
-                return None;
-            },
-            ast::Expr(it) => {
-                sema.type_of_expr(&it)
-            },
-            ast::Pat(it) => {
-                sema.type_of_pat(&it)
-            },
-            _ => None,
-        }
-    }?;
-
-    res.extend(Some(rust_code_markup(&ty.display(db))));
-    let range = sema.original_range(&node).range;
-    Some(RangeInfo::new(range, res))
-}
-
 fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
     return tokens.max_by_key(priority);
     fn priority(n: &SyntaxToken) -> usize {
diff --git a/crates/ra_ide/src/inlay_hints.rs b/crates/ra_ide/src/inlay_hints.rs
index b391f903a8..75bd3c96bb 100644
--- a/crates/ra_ide/src/inlay_hints.rs
+++ b/crates/ra_ide/src/inlay_hints.rs
@@ -1,5 +1,3 @@
-//! This module defines multiple types of inlay hints and their visibility
-
 use hir::{Adt, HirDisplay, Semantics, Type};
 use ra_ide_db::RootDatabase;
 use ra_prof::profile;
@@ -39,6 +37,26 @@ pub struct InlayHint {
     pub label: SmolStr,
 }
 
+// Feature: Inlay Hints
+//
+// rust-analyzer shows additional information inline with the source code.
+// Editors usually render this using read-only virtual text snippets interspersed with code.
+//
+// rust-analyzer shows hits for
+//
+// * types of local variables
+// * names of function arguments
+// * types of chained expressions
+//
+// **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.
+// This approach has limitations, the caret movement and bracket highlighting near the edges of the hint may be weird:
+// https://github.com/rust-analyzer/rust-analyzer/issues/1623[1], https://github.com/rust-analyzer/rust-analyzer/issues/3453[2].
+//
+// |===
+// | Editor  | Action Name
+//
+// | VS Code | **Rust Analyzer: Toggle inlay hints*
+// |===
 pub(crate) fn inlay_hints(
     db: &RootDatabase,
     file_id: FileId,
diff --git a/crates/ra_ide/src/join_lines.rs b/crates/ra_ide/src/join_lines.rs
index af1ade8a1e..5036c1fb0c 100644
--- a/crates/ra_ide/src/join_lines.rs
+++ b/crates/ra_ide/src/join_lines.rs
@@ -1,5 +1,3 @@
-//! FIXME: write short doc here
-
 use itertools::Itertools;
 use ra_fmt::{compute_ws, extract_trivial_expression};
 use ra_syntax::{
@@ -11,6 +9,15 @@ use ra_syntax::{
 };
 use ra_text_edit::{TextEdit, TextEditBuilder};
 
+// Feature: Join Lines
+//
+// Join selected lines into one, smartly fixing up whitespace, trailing commas, and braces.
+//
+// |===
+// | Editor  | Action Name
+//
+// | VS Code | **Rust Analyzer: Join lines**
+// |===
 pub fn join_lines(file: &SourceFile, range: TextRange) -> TextEdit {
     let range = if range.is_empty() {
         let syntax = file.syntax();
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs
index d983cd9100..12d5716e83 100644
--- a/crates/ra_ide/src/lib.rs
+++ b/crates/ra_ide/src/lib.rs
@@ -23,6 +23,7 @@ mod completion;
 mod runnables;
 mod goto_definition;
 mod goto_type_definition;
+mod goto_implementation;
 mod extend_selection;
 mod hover;
 mod call_hierarchy;
@@ -30,7 +31,6 @@ mod call_info;
 mod syntax_highlighting;
 mod parent_module;
 mod references;
-mod impls;
 mod diagnostics;
 mod syntax_tree;
 mod folding_ranges;
@@ -373,7 +373,7 @@ impl Analysis {
         &self,
         position: FilePosition,
     ) -> Cancelable<Option<RangeInfo<Vec<NavigationTarget>>>> {
-        self.with_db(|db| impls::goto_implementation(db, position))
+        self.with_db(|db| goto_implementation::goto_implementation(db, position))
     }
 
     /// 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 b853487060..407a9636d1 100644
--- a/crates/ra_ide/src/matching_brace.rs
+++ b/crates/ra_ide/src/matching_brace.rs
@@ -1,7 +1,16 @@
-//! FIXME: write short doc here
-
 use ra_syntax::{ast::AstNode, SourceFile, SyntaxKind, TextSize, T};
 
+// Feature: Matching Brace
+//
+// If the cursor is on any brace (`<>(){}[]`) which is a part of a brace-pair,
+// moves cursor to the matching brace. It uses the actual parser to determine
+// braces, so it won't confuse generics with comparisons.
+//
+// |===
+// | Editor  | Action Name
+//
+// | VS Code | **Rust Analyzer: Find matching brace**
+// |===
 pub fn matching_brace(file: &SourceFile, offset: TextSize) -> Option<TextSize> {
     const BRACES: &[SyntaxKind] =
         &[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 a083fb1eb3..fa1535da5b 100644
--- a/crates/ra_ide/src/parent_module.rs
+++ b/crates/ra_ide/src/parent_module.rs
@@ -1,5 +1,3 @@
-//! FIXME: write short doc here
-
 use hir::Semantics;
 use ra_db::{CrateId, FileId, FilePosition};
 use ra_ide_db::RootDatabase;
@@ -11,6 +9,16 @@ use test_utils::mark;
 
 use crate::NavigationTarget;
 
+// Feature: Parent Module
+//
+// Navigates to the parent module of the current module.
+//
+// |===
+// | Editor  | Action Name
+//
+// | VS Code | **Rust Analyzer: Locate parent module**
+// |===
+
 /// This returns `Vec` because a module may be included from several places. We
 /// don't handle this case yet though, so the Vec has length at most one.
 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 6e7e47199c..286d45eee9 100644
--- a/crates/ra_ide/src/runnables.rs
+++ b/crates/ra_ide/src/runnables.rs
@@ -1,5 +1,3 @@
-//! FIXME: write short doc here
-
 use hir::{AsAssocItem, Attrs, HirFileId, InFile, Semantics};
 use itertools::Itertools;
 use ra_ide_db::RootDatabase;
@@ -44,6 +42,17 @@ pub enum RunnableKind {
     Bin,
 }
 
+// Feature: Run
+//
+// Shows a popup suggesting to run a test/benchmark/binary **at the current cursor
+// location**. Super useful for repeatedly running just a single test. Do bind this
+// to a shortcut!
+//
+// |===
+// | Editor  | Action Name
+//
+// | VS Code | **Rust Analyzer: Run**
+// |===
 pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
     let sema = Semantics::new(db);
     let source_file = sema.parse(file_id);
diff --git a/crates/ra_ide/src/ssr.rs b/crates/ra_ide/src/ssr.rs
index 130d3b4c3b..93e9aee1d0 100644
--- a/crates/ra_ide/src/ssr.rs
+++ b/crates/ra_ide/src/ssr.rs
@@ -1,5 +1,3 @@
-//!  structural search replace
-
 use std::{collections::HashMap, iter::once, str::FromStr};
 
 use ra_db::{SourceDatabase, SourceDatabaseExt};
@@ -25,6 +23,28 @@ impl std::fmt::Display for SsrError {
 
 impl std::error::Error for SsrError {}
 
+// Feature: Structural Seach and Replace
+//
+// Search and replace with named wildcards that will match any expression.
+// 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`.
+//
+// ```rust
+// // Using structural search replace command [foo($a:expr, $b:expr) ==>> ($a).foo($b)]
+//
+// // BEFORE
+// String::from(foo(y + 5, z))
+//
+// // AFTER
+// String::from((y + 5).foo(z))
+// ```
+//
+// |===
+// | Editor  | Action Name
+//
+// | VS Code | **Rust Analyzer: Structural Search Replace**
+// |===
 pub fn parse_search_replace(
     query: &str,
     parse_only: bool,
diff --git a/crates/ra_ide/src/status.rs b/crates/ra_ide/src/status.rs
index 30eb5c995e..5b79929200 100644
--- a/crates/ra_ide/src/status.rs
+++ b/crates/ra_ide/src/status.rs
@@ -1,5 +1,3 @@
-//! FIXME: write short doc here
-
 use std::{fmt, iter::FromIterator, sync::Arc};
 
 use hir::MacroFile;
@@ -26,6 +24,15 @@ fn macro_syntax_tree_stats(db: &RootDatabase) -> SyntaxTreeStats {
     db.query(hir::db::ParseMacroQuery).entries::<SyntaxTreeStats>()
 }
 
+// Feature: Status
+//
+// Shows internal statistic about memory usage of rust-analyzer.
+//
+// |===
+// | Editor  | Action Name
+//
+// | VS Code | **Rust Analyzer: Status**
+// |===
 pub(crate) fn status(db: &RootDatabase) -> String {
     let files_stats = db.query(FileTextQuery).entries::<FilesStats>();
     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 cd6464b405..3ab1f0a215 100644
--- a/crates/ra_ide/src/syntax_highlighting.rs
+++ b/crates/ra_ide/src/syntax_highlighting.rs
@@ -1,5 +1,3 @@
-//! Implements syntax highlighting.
-
 mod tags;
 mod html;
 #[cfg(test)]
@@ -32,81 +30,15 @@ pub struct HighlightedRange {
     pub binding_hash: Option<u64>,
 }
 
-#[derive(Debug)]
-struct HighlightedRangeStack {
-    stack: Vec<Vec<HighlightedRange>>,
-}
-
-/// We use a stack to implement the flattening logic for the highlighted
-/// syntax ranges.
-impl HighlightedRangeStack {
-    fn new() -> Self {
-        Self { stack: vec![Vec::new()] }
-    }
-
-    fn push(&mut self) {
-        self.stack.push(Vec::new());
-    }
-
-    /// Flattens the highlighted ranges.
-    ///
-    /// For example `#[cfg(feature = "foo")]` contains the nested ranges:
-    /// 1) parent-range: Attribute [0, 23)
-    /// 2) child-range: String [16, 21)
-    ///
-    /// The following code implements the flattening, for our example this results to:
-    /// `[Attribute [0, 16), String [16, 21), Attribute [21, 23)]`
-    fn pop(&mut self) {
-        let children = self.stack.pop().unwrap();
-        let prev = self.stack.last_mut().unwrap();
-        let needs_flattening = !children.is_empty()
-            && !prev.is_empty()
-            && prev.last().unwrap().range.contains_range(children.first().unwrap().range);
-        if !needs_flattening {
-            prev.extend(children);
-        } else {
-            let mut parent = prev.pop().unwrap();
-            for ele in children {
-                assert!(parent.range.contains_range(ele.range));
-                let mut cloned = parent.clone();
-                parent.range = TextRange::new(parent.range.start(), ele.range.start());
-                cloned.range = TextRange::new(ele.range.end(), cloned.range.end());
-                if !parent.range.is_empty() {
-                    prev.push(parent);
-                }
-                prev.push(ele);
-                parent = cloned;
-            }
-            if !parent.range.is_empty() {
-                prev.push(parent);
-            }
-        }
-    }
-
-    fn add(&mut self, range: HighlightedRange) {
-        self.stack
-            .last_mut()
-            .expect("during DFS traversal, the stack must not be empty")
-            .push(range)
-    }
-
-    fn flattened(mut self) -> Vec<HighlightedRange> {
-        assert_eq!(
-            self.stack.len(),
-            1,
-            "after DFS traversal, the stack should only contain a single element"
-        );
-        let mut res = self.stack.pop().unwrap();
-        res.sort_by_key(|range| range.range.start());
-        // Check that ranges are sorted and disjoint
-        assert!(res
-            .iter()
-            .zip(res.iter().skip(1))
-            .all(|(left, right)| left.range.end() <= right.range.start()));
-        res
-    }
-}
-
+// Feature: Semantic Syntax Highlighting
+//
+// rust-analyzer highlights the code semantically.
+// For example, `bar` in `foo::Bar` might be colored differently depending on whether `Bar` is an enum or a trait.
+// rust-analyzer does not specify colors directly, instead it assigns tag (like `struct`) and a set of modifiers (like `declaration`) to each token.
+// It's up to the client to map those to specific colors.
+//
+// The general rule is that a reference to an entity gets colored the same way as the entity itself.
+// We also give special modifier for `mut` and `&mut` local variables.
 pub(crate) fn highlight(
     db: &RootDatabase,
     file_id: FileId,
@@ -291,6 +223,81 @@ pub(crate) fn highlight(
     stack.flattened()
 }
 
+#[derive(Debug)]
+struct HighlightedRangeStack {
+    stack: Vec<Vec<HighlightedRange>>,
+}
+
+/// We use a stack to implement the flattening logic for the highlighted
+/// syntax ranges.
+impl HighlightedRangeStack {
+    fn new() -> Self {
+        Self { stack: vec![Vec::new()] }
+    }
+
+    fn push(&mut self) {
+        self.stack.push(Vec::new());
+    }
+
+    /// Flattens the highlighted ranges.
+    ///
+    /// For example `#[cfg(feature = "foo")]` contains the nested ranges:
+    /// 1) parent-range: Attribute [0, 23)
+    /// 2) child-range: String [16, 21)
+    ///
+    /// The following code implements the flattening, for our example this results to:
+    /// `[Attribute [0, 16), String [16, 21), Attribute [21, 23)]`
+    fn pop(&mut self) {
+        let children = self.stack.pop().unwrap();
+        let prev = self.stack.last_mut().unwrap();
+        let needs_flattening = !children.is_empty()
+            && !prev.is_empty()
+            && prev.last().unwrap().range.contains_range(children.first().unwrap().range);
+        if !needs_flattening {
+            prev.extend(children);
+        } else {
+            let mut parent = prev.pop().unwrap();
+            for ele in children {
+                assert!(parent.range.contains_range(ele.range));
+                let mut cloned = parent.clone();
+                parent.range = TextRange::new(parent.range.start(), ele.range.start());
+                cloned.range = TextRange::new(ele.range.end(), cloned.range.end());
+                if !parent.range.is_empty() {
+                    prev.push(parent);
+                }
+                prev.push(ele);
+                parent = cloned;
+            }
+            if !parent.range.is_empty() {
+                prev.push(parent);
+            }
+        }
+    }
+
+    fn add(&mut self, range: HighlightedRange) {
+        self.stack
+            .last_mut()
+            .expect("during DFS traversal, the stack must not be empty")
+            .push(range)
+    }
+
+    fn flattened(mut self) -> Vec<HighlightedRange> {
+        assert_eq!(
+            self.stack.len(),
+            1,
+            "after DFS traversal, the stack should only contain a single element"
+        );
+        let mut res = self.stack.pop().unwrap();
+        res.sort_by_key(|range| range.range.start());
+        // Check that ranges are sorted and disjoint
+        assert!(res
+            .iter()
+            .zip(res.iter().skip(1))
+            .all(|(left, right)| left.range.end() <= right.range.start()));
+        res
+    }
+}
+
 fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HighlightTag> {
     Some(match kind {
         FormatSpecifier::Open
@@ -391,7 +398,6 @@ fn highlight_element(
         INT_NUMBER | FLOAT_NUMBER => HighlightTag::NumericLiteral.into(),
         BYTE => HighlightTag::ByteLiteral.into(),
         CHAR => HighlightTag::CharLiteral.into(),
-        QUESTION => Highlight::new(HighlightTag::Operator) | HighlightModifier::ControlFlow,
         LIFETIME => {
             let h = Highlight::new(HighlightTag::Lifetime);
             match element.parent().map(|it| it.kind()) {
diff --git a/crates/ra_ide/src/syntax_highlighting/tags.rs b/crates/ra_ide/src/syntax_highlighting/tags.rs
index 1514531de2..46c718c91e 100644
--- a/crates/ra_ide/src/syntax_highlighting/tags.rs
+++ b/crates/ra_ide/src/syntax_highlighting/tags.rs
@@ -42,7 +42,6 @@ pub enum HighlightTag {
     Local,
     UnresolvedReference,
     FormatSpecifier,
-    Operator,
 }
 
 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
@@ -90,7 +89,6 @@ impl HighlightTag {
             HighlightTag::Local => "variable",
             HighlightTag::UnresolvedReference => "unresolved_reference",
             HighlightTag::FormatSpecifier => "format_specifier",
-            HighlightTag::Operator => "operator",
         }
     }
 }
diff --git a/crates/ra_ide/src/syntax_tree.rs b/crates/ra_ide/src/syntax_tree.rs
index 86c70ff830..a341684fda 100644
--- a/crates/ra_ide/src/syntax_tree.rs
+++ b/crates/ra_ide/src/syntax_tree.rs
@@ -1,6 +1,4 @@
-//! FIXME: write short doc here
-
-use ra_db::SourceDatabase;
+use ra_db::{FileId, SourceDatabase};
 use ra_ide_db::RootDatabase;
 use ra_syntax::{
     algo, AstNode, NodeOrToken, SourceFile,
@@ -8,8 +6,16 @@ use ra_syntax::{
     SyntaxToken, TextRange, TextSize,
 };
 
-pub use ra_db::FileId;
-
+// Feature: Show Syntax Tree
+//
+// Shows the parse tree of the current file. It exists mostly for debugging
+// rust-analyzer itself.
+//
+// |===
+// | Editor  | Action Name
+//
+// | VS Code | **Rust Analyzer: Show Syntax Tree**
+// |===
 pub(crate) fn syntax_tree(
     db: &RootDatabase,
     file_id: FileId,
diff --git a/crates/ra_ide/src/typing.rs b/crates/ra_ide/src/typing.rs
index 39bb3b3579..67e2c33a00 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;
 
 pub(crate) const TRIGGER_CHARS: &str = ".=>";
 
+// Feature: On Typing Assists
+//
+// Some features trigger on typing certain characters:
+//
+// - typing `let =` tries to smartly add `;` if `=` is followed by an existing expression
+// - Enter inside comments automatically inserts `///`
+// - typing `.` in a chain method call auto-indents
 pub(crate) fn on_char_typed(
     db: &RootDatabase,
     position: FilePosition,
diff --git a/crates/ra_ide_db/src/symbol_index.rs b/crates/ra_ide_db/src/symbol_index.rs
index 95be111345..acc31fe3b5 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>
     Arc::new(SymbolIndex::new(symbols))
 }
 
+// Feature: Workspace Symbol
+//
+// Uses fuzzy-search to find types, modules and functions by name across your
+// project and dependencies. This is **the** most useful feature, which improves code
+// navigation tremendously. It mostly works on top of the built-in LSP
+// functionality, however `#` and `*` symbols can be used to narrow down the
+// search. Specifically,
+//
+// - `Foo` searches for `Foo` type in the current workspace
+// - `foo#` searches for `foo` function in the current workspace
+// - `Foo*` searches for `Foo` type among dependencies, including `stdlib`
+// - `foo#*` searches for `foo` function among dependencies
+//
+// That is, `#` switches from "types" to all symbols, `*` switches from the current
+// workspace to dependencies.
+//
+// |===
+// | Editor  | Shortcut
+//
+// | VS Code | kbd:[Ctrl+T]
+// |===
 pub fn world_symbols(db: &RootDatabase, query: Query) -> Vec<FileSymbol> {
     /// Need to wrap Snapshot to provide `Clone` impl for `map_with`
     struct Snap(salsa::Snapshot<RootDatabase>);
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index 2fbbb4e632..8e8e7033db 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -317,7 +317,6 @@ fn semantic_token_type_and_modifiers(
         HighlightTag::Keyword => lsp_types::SemanticTokenType::KEYWORD,
         HighlightTag::UnresolvedReference => semantic_tokens::UNRESOLVED_REFERENCE,
         HighlightTag::FormatSpecifier => semantic_tokens::FORMAT_SPECIFIER,
-        HighlightTag::Operator => lsp_types::SemanticTokenType::OPERATOR,
     };
 
     for modifier in highlight.modifiers.iter() {
diff --git a/docs/user/assists.md b/docs/user/assists.md
index a1058ecde9..b5d813b546 100644
--- a/docs/user/assists.md
+++ b/docs/user/assists.md
@@ -56,6 +56,24 @@ fn main() {
 }
 ```
 
+## `add_from_impl_for_enum`
+
+Adds a From impl for an enum variant with one tuple field.
+
+```rust
+// BEFORE
+enum A { ┃One(u32) }
+
+// AFTER
+enum A { One(u32) }
+
+impl From<u32> for A {
+    fn from(v: u32) -> Self {
+        A::One(v)
+    }
+}
+```
+
 ## `add_function`
 
 Adds a stub function with a signature matching the function under the cursor.
@@ -259,30 +277,6 @@ fn main() {
 }
 ```
 
-## `change_lifetime_anon_to_named`
-
-Change an anonymous lifetime to a named lifetime.
-
-```rust
-// BEFORE
-impl Cursor<'_┃> {
-    fn node(self) -> &SyntaxNode {
-        match self {
-            Cursor::Replace(node) | Cursor::Before(node) => node,
-        }
-    }
-}
-
-// AFTER
-impl<'a> Cursor<'a> {
-    fn node(self) -> &SyntaxNode {
-        match self {
-            Cursor::Replace(node) | Cursor::Before(node) => node,
-        }
-    }
-}
-```
-
 ## `change_return_type_to_result`
 
 Change the function's return type to Result.
diff --git a/docs/user/features.md b/docs/user/features.md
deleted file mode 100644
index 12ecdec136..0000000000
--- a/docs/user/features.md
+++ /dev/null
@@ -1,218 +0,0 @@
-This document is an index of features that the rust-analyzer language server
-provides. Shortcuts are for the default VS Code layout. If there's no shortcut,
-you can use <kbd>Ctrl+Shift+P</kbd> to search for the corresponding action.
-
-### Workspace Symbol <kbd>ctrl+t</kbd>
-
-Uses fuzzy-search to find types, modules and functions by name across your
-project and dependencies. This is **the** most useful feature, which improves code
-navigation tremendously. It mostly works on top of the built-in LSP
-functionality, however `#` and `*` symbols can be used to narrow down the
-search. Specifically,
-
-- `Foo` searches for `Foo` type in the current workspace
-- `foo#` searches for `foo` function in the current workspace
-- `Foo*` searches for `Foo` type among dependencies, including `stdlib`
-- `foo#*` searches for `foo` function among dependencies
-
-That is, `#` switches from "types" to all symbols, `*` switches from the current
-workspace to dependencies.
-
-### Document Symbol <kbd>ctrl+shift+o</kbd>
-
-Provides a tree of the symbols defined in the file. Can be used to
-
-* fuzzy search symbol in a file (super useful)
-* draw breadcrumbs to describe the context around the cursor
-* draw outline of the file
-
-### On Typing Assists
-
-Some features trigger on typing certain characters:
-
-- typing `let =` tries to smartly add `;` if `=` is followed by an existing expression
-- Enter inside comments automatically inserts `///`
-- typing `.` in a chain method call auto-indents
-
-### Extend Selection
-
-Extends the current selection to the encompassing syntactic construct
-(expression, statement, item, module, etc). It works with multiple cursors. This
-is a relatively new feature of LSP:
-https://github.com/Microsoft/language-server-protocol/issues/613, check your
-editor's LSP library to see if this feature is supported.
-
-### Go to Definition
-
-Navigates to the definition of an identifier.
-
-### Go to Implementation
-
-Navigates to the impl block of structs, enums or traits. Also implemented as a code lens.
-
-### Go to Type Defintion
-
-Navigates to the type of an identifier.
-
-### Commands <kbd>ctrl+shift+p</kbd>
-
-#### Run
-
-Shows a popup suggesting to run a test/benchmark/binary **at the current cursor
-location**. Super useful for repeatedly running just a single test. Do bind this
-to a shortcut!
-
-#### Parent Module
-
-Navigates to the parent module of the current module.
-
-#### Matching Brace
-
-If the cursor is on any brace (`<>(){}[]`) which is a part of a brace-pair,
-moves cursor to the matching brace. It uses the actual parser to determine
-braces, so it won't confuse generics with comparisons.
-
-#### Join Lines
-
-Join selected lines into one, smartly fixing up whitespace and trailing commas.
-
-#### Show Syntax Tree
-
-Shows the parse tree of the current file. It exists mostly for debugging
-rust-analyzer itself.
-
-#### Expand Macro Recursively
-
-Shows the full macro expansion of the macro at current cursor.
-
-#### Status
-
-Shows internal statistic about memory usage of rust-analyzer.
-
-#### Show RA Version
-
-Show current rust-analyzer version.
-
-#### Toggle inlay hints
-
-Toggle inlay hints view for the current workspace.
-It is recommended to assign a shortcut for this command to quickly turn off
-inlay hints when they prevent you from reading/writing the code.
-
-#### Run Garbage Collection
-
-Manually triggers GC.
-
-#### Start Cargo Watch
-
-Start `cargo watch` for live error highlighting. Will prompt to install if it's not already installed.
-
-#### Stop Cargo Watch
-
-Stop `cargo watch`.
-
-#### Structural Seach and Replace
-
-Search and replace with named wildcards that will match any expression.
-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`.
-
-```rust
-// Using structural search replace command [foo($a:expr, $b:expr) ==>> ($a).foo($b)]
-
-// BEFORE
-String::from(foo(y + 5, z))
-
-// AFTER
-String::from((y + 5).foo(z))
-```
-
-### Assists (Code Actions)
-
-Assists, or code actions, are small local refactorings, available in a particular context.
-They are usually triggered by a shortcut or by clicking a light bulb icon in the editor.
-
-See [assists.md](./assists.md) for the list of available assists.
-
-### Magic Completions
-
-In addition to usual reference completion, rust-analyzer provides some ✨magic✨
-completions as well:
-
-Keywords like `if`, `else` `while`, `loop` are completed with braces, and cursor
-is placed at the appropriate position. Even though `if` is easy to type, you
-still want to complete it, to get ` { }` for free! `return` is inserted with a
-space or `;` depending on the return type of the function.
-
-When completing a function call, `()` are automatically inserted. If a function
-takes arguments, the cursor is positioned inside the parenthesis.
-
-There are postfix completions, which can be triggered by typing something like
-`foo().if`. The word after `.` determines postfix completion. Possible variants are:
-
-- `expr.if` -> `if expr {}` or `if let ... {}` for `Option` or `Result`
-- `expr.match` -> `match expr {}`
-- `expr.while` -> `while expr {}` or `while let ... {}` for `Option` or `Result`
-- `expr.ref` -> `&expr`
-- `expr.refm` -> `&mut expr`
-- `expr.not` -> `!expr`
-- `expr.dbg` -> `dbg!(expr)`
-
-There also snippet completions:
-
-#### Inside Expressions
-
-- `pd` -> `println!("{:?}")`
-- `ppd` -> `println!("{:#?}")`
-
-#### Inside Modules
-
-- `tfn` -> `#[test] fn f(){}`
-- `tmod` ->
-```rust
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn test_fn() {}
-}
-```
-
-### Code Highlighting
-
-Experimental feature to let rust-analyzer highlight Rust code instead of using the
-default highlighter.
-
-#### Rainbow Highlighting
-
-Experimental feature that, given code highlighting using rust-analyzer is
-active, will pick unique colors for identifiers.
-
-### Code hints
-
-Rust-analyzer has two types of hints to show the information about the code:
-
-* hover hints, appearing on hover on any element.
-
-These contain extended information on the hovered language item.
-
-* inlay hints, shown near the element hinted directly in the editor.
-
-Two types of inlay hints are displayed currently:
-
-* type hints, displaying the minimal information on the type of the expression (if the information is available)
-* method chaining hints, type information for multi-line method chains
-* parameter name hints, displaying the names of the parameters in the corresponding methods
-
-#### VS Code
-
-In VS Code, the following settings can be used to configure the inlay hints:
-
-* `rust-analyzer.inlayHints.typeHints` - enable hints for inferred types.
-* `rust-analyzer.inlayHints.chainingHints` - enable hints for inferred types on method chains.
-* `rust-analyzer.inlayHints.parameterHints` - enable hints for function parameters.
-* `rust-analyzer.inlayHints.maxLength` — shortens the hints if their length exceeds the value specified. If no value is specified (`null`), no shortening is applied.
-
-**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.
-This approach has limitations, the caret movement and bracket highlighting near the edges of the hint may be weird:
-[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 0000000000..803073d550
--- /dev/null
+++ b/docs/user/generated_features.adoc
@@ -0,0 +1,298 @@
+=== Expand Macro Recursively
+**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/expand_macro.rs[expand_macro.rs]
+
+Shows the full macro expansion of the macro at current cursor.
+
+|===
+| Editor  | Action Name
+
+| VS Code | **Rust Analyzer: Expand macro recursively**
+|===
+
+
+=== Extend Selection
+**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/extend_selection.rs[extend_selection.rs]
+
+Extends the current selection to the encompassing syntactic construct
+(expression, statement, item, module, etc). It works with multiple cursors.
+
+|===
+| Editor  | Shortcut
+
+| VS Code | kbd:[Ctrl+Shift+→]
+|===
+
+
+=== File Structure
+**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/display/structure.rs[structure.rs]
+
+Provides a tree of the symbols defined in the file. Can be used to
+
+* fuzzy search symbol in a file (super useful)
+* draw breadcrumbs to describe the context around the cursor
+* draw outline of the file
+
+|===
+| Editor  | Shortcut
+
+| VS Code | kbd:[Ctrl+Shift+O]
+|===
+
+
+=== Go to Definition
+**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/goto_definition.rs[goto_definition.rs]
+
+Navigates to the definition of an identifier.
+
+|===
+| Editor  | Shortcut
+
+| VS Code | kbd:[F12]
+|===
+
+
+=== Go to Implementation
+**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/goto_implementation.rs[goto_implementation.rs]
+
+Navigates to the impl block of structs, enums or traits. Also implemented as a code lens.
+
+|===
+| Editor  | Shortcut
+
+| VS Code | kbd:[Ctrl+F12]
+|===
+
+
+=== Go to Type Definition
+**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/goto_type_definition.rs[goto_type_definition.rs]
+
+Navigates to the type of an identifier.
+
+|===
+| Editor  | Action Name
+
+| VS Code | **Go to Type Definition*
+|===
+
+
+=== Hover
+**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/hover.rs[hover.rs]
+
+Shows additional information, like type of an expression or documentation for definition when "focusing" code.
+Focusing is usually hovering with a mouse, but can also be triggered with a shortcut.
+
+
+=== Inlay Hints
+**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/inlay_hints.rs[inlay_hints.rs]
+
+rust-analyzer shows additional information inline with the source code.
+Editors usually render this using read-only virtual text snippets interspersed with code.
+
+rust-analyzer shows hits for
+
+* types of local variables
+* names of function arguments
+* types of chained expressions
+
+**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.
+This approach has limitations, the caret movement and bracket highlighting near the edges of the hint may be weird:
+https://github.com/rust-analyzer/rust-analyzer/issues/1623[1], https://github.com/rust-analyzer/rust-analyzer/issues/3453[2].
+
+|===
+| Editor  | Action Name
+
+| VS Code | **Rust Analyzer: Toggle inlay hints*
+|===
+
+
+=== Join Lines
+**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/join_lines.rs[join_lines.rs]
+
+Join selected lines into one, smartly fixing up whitespace, trailing commas, and braces.
+
+|===
+| Editor  | Action Name
+
+| VS Code | **Rust Analyzer: Join lines**
+|===
+
+
+=== Magic Completions
+**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/completion.rs[completion.rs]
+
+In addition to usual reference completion, rust-analyzer provides some ✨magic✨
+completions as well:
+
+Keywords like `if`, `else` `while`, `loop` are completed with braces, and cursor
+is placed at the appropriate position. Even though `if` is easy to type, you
+still want to complete it, to get ` { }` for free! `return` is inserted with a
+space or `;` depending on the return type of the function.
+
+When completing a function call, `()` are automatically inserted. If a function
+takes arguments, the cursor is positioned inside the parenthesis.
+
+There are postfix completions, which can be triggered by typing something like
+`foo().if`. The word after `.` determines postfix completion. Possible variants are:
+
+- `expr.if` -> `if expr {}` or `if let ... {}` for `Option` or `Result`
+- `expr.match` -> `match expr {}`
+- `expr.while` -> `while expr {}` or `while let ... {}` for `Option` or `Result`
+- `expr.ref` -> `&expr`
+- `expr.refm` -> `&mut expr`
+- `expr.not` -> `!expr`
+- `expr.dbg` -> `dbg!(expr)`
+
+There also snippet completions:
+
+.Expressions
+- `pd` -> `println!("{:?}")`
+- `ppd` -> `println!("{:#?}")`
+
+.Items
+- `tfn` -> `#[test] fn f(){}`
+- `tmod` ->
+```rust
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_fn() {}
+}
+```
+
+
+=== Matching Brace
+**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/matching_brace.rs[matching_brace.rs]
+
+If the cursor is on any brace (`<>(){}[]`) which is a part of a brace-pair,
+moves cursor to the matching brace. It uses the actual parser to determine
+braces, so it won't confuse generics with comparisons.
+
+|===
+| Editor  | Action Name
+
+| VS Code | **Rust Analyzer: Find matching brace**
+|===
+
+
+=== On Typing Assists
+**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/typing.rs[typing.rs]
+
+Some features trigger on typing certain characters:
+
+- typing `let =` tries to smartly add `;` if `=` is followed by an existing expression
+- Enter inside comments automatically inserts `///`
+- typing `.` in a chain method call auto-indents
+
+
+=== Parent Module
+**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/parent_module.rs[parent_module.rs]
+
+Navigates to the parent module of the current module.
+
+|===
+| Editor  | Action Name
+
+| VS Code | **Rust Analyzer: Locate parent module**
+|===
+
+
+=== Run
+**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/runnables.rs[runnables.rs]
+
+Shows a popup suggesting to run a test/benchmark/binary **at the current cursor
+location**. Super useful for repeatedly running just a single test. Do bind this
+to a shortcut!
+
+|===
+| Editor  | Action Name
+
+| VS Code | **Rust Analyzer: Run**
+|===
+
+
+=== Semantic Syntax Highlighting
+**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/syntax_highlighting.rs[syntax_highlighting.rs]
+
+rust-analyzer highlights the code semantically.
+For example, `bar` in `foo::Bar` might be colored differently depending on whether `Bar` is an enum or a trait.
+rust-analyzer does not specify colors directly, instead it assigns tag (like `struct`) and a set of modifiers (like `declaration`) to each token.
+It's up to the client to map those to specific colors.
+
+The general rule is that a reference to an entity gets colored the same way as the entity itself.
+We also give special modifier for `mut` and `&mut` local variables.
+
+
+=== Show Syntax Tree
+**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/syntax_tree.rs[syntax_tree.rs]
+
+Shows the parse tree of the current file. It exists mostly for debugging
+rust-analyzer itself.
+
+|===
+| Editor  | Action Name
+
+| VS Code | **Rust Analyzer: Show Syntax Tree**
+|===
+
+
+=== Status
+**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/status.rs[status.rs]
+
+Shows internal statistic about memory usage of rust-analyzer.
+
+|===
+| Editor  | Action Name
+
+| VS Code | **Rust Analyzer: Status**
+|===
+
+
+=== Structural Seach and Replace
+**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/ssr.rs[ssr.rs]
+
+Search and replace with named wildcards that will match any expression.
+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`.
+
+```rust
+// Using structural search replace command [foo($a:expr, $b:expr) ==>> ($a).foo($b)]
+
+// BEFORE
+String::from(foo(y + 5, z))
+
+// AFTER
+String::from((y + 5).foo(z))
+```
+
+|===
+| Editor  | Action Name
+
+| VS Code | **Rust Analyzer: Structural Search Replace**
+|===
+
+
+=== Workspace Symbol
+**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide_db/src/symbol_index.rs[symbol_index.rs]
+
+Uses fuzzy-search to find types, modules and functions by name across your
+project and dependencies. This is **the** most useful feature, which improves code
+navigation tremendously. It mostly works on top of the built-in LSP
+functionality, however `#` and `*` symbols can be used to narrow down the
+search. Specifically,
+
+- `Foo` searches for `Foo` type in the current workspace
+- `foo#` searches for `foo` function in the current workspace
+- `Foo*` searches for `Foo` type among dependencies, including `stdlib`
+- `foo#*` searches for `foo` function among dependencies
+
+That is, `#` switches from "types" to all symbols, `*` switches from the current
+workspace to dependencies.
+
+|===
+| Editor  | Shortcut
+
+| VS Code | kbd:[Ctrl+T]
+|===
diff --git a/docs/user/readme.adoc b/docs/user/readme.adoc
index 64bd0feb13..12def73271 100644
--- a/docs/user/readme.adoc
+++ b/docs/user/readme.adoc
@@ -8,6 +8,8 @@
 :important-caption: :heavy_exclamation_mark:
 :caution-caption: :fire:
 :warning-caption: :warning:
+:source-highlighter: rouge
+:experimental:
 
 // Master copy of this document lives in the https://github.com/rust-analyzer/rust-analyzer repository
 
@@ -268,6 +270,13 @@ Gnome Builder currently has support for RLS, and there's no way to configure the
 1. Rename, symlink or copy the `rust-analyzer` binary to `rls` and place it somewhere Builder can find (in `PATH`, or under `~/.cargo/bin`).
 2. Enable the Rust Builder plugin.
 
-== Usage
+== Features
 
-See https://github.com/rust-analyzer/rust-analyzer/blob/master/docs/user/features.md[features.md].
+include::./generated_features.adoc[]
+
+== Assists (Code Actions)
+
+Assists, or code actions, are small local refactorings, available in a particular context.
+They are usually triggered by a shortcut or by clicking a light bulb icon in the editor.
+
+See [assists.md](./assists.md) for the list of available assists.
diff --git a/xtask/src/codegen.rs b/xtask/src/codegen.rs
index b4907f4b2b..f47d54125f 100644
--- a/xtask/src/codegen.rs
+++ b/xtask/src/codegen.rs
@@ -8,14 +8,15 @@
 mod gen_syntax;
 mod gen_parser_tests;
 mod gen_assists_docs;
+mod gen_feature_docs;
 
 use std::{mem, path::Path};
 
 use crate::{not_bash::fs2, Result};
 
 pub use self::{
-    gen_assists_docs::generate_assists_docs, gen_parser_tests::generate_parser_tests,
-    gen_syntax::generate_syntax,
+    gen_assists_docs::generate_assists_docs, gen_feature_docs::generate_feature_docs,
+    gen_parser_tests::generate_parser_tests, gen_syntax::generate_syntax,
 };
 
 const GRAMMAR_DIR: &str = "crates/ra_parser/src/grammar";
@@ -40,7 +41,7 @@ pub enum Mode {
 /// With verify = false,
 fn update(path: &Path, contents: &str, mode: Mode) -> Result<()> {
     match fs2::read_to_string(path) {
-        Ok(ref old_contents) if normalize(old_contents) == normalize(contents) => {
+        Ok(old_contents) if normalize(&old_contents) == normalize(contents) => {
             return Ok(());
         }
         _ => (),
@@ -61,8 +62,24 @@ fn extract_comment_blocks(text: &str) -> Vec<Vec<String>> {
     do_extract_comment_blocks(text, false)
 }
 
-fn extract_comment_blocks_with_empty_lines(text: &str) -> Vec<Vec<String>> {
-    do_extract_comment_blocks(text, true)
+fn extract_comment_blocks_with_empty_lines(tag: &str, text: &str) -> Vec<CommentBlock> {
+    assert!(tag.starts_with(char::is_uppercase));
+    let tag = format!("{}:", tag);
+    let mut res = Vec::new();
+    for mut block in do_extract_comment_blocks(text, true) {
+        let first = block.remove(0);
+        if first.starts_with(&tag) {
+            let id = first[tag.len()..].trim().to_string();
+            let block = CommentBlock { id, contents: block };
+            res.push(block);
+        }
+    }
+    res
+}
+
+struct CommentBlock {
+    id: String,
+    contents: Vec<String>,
 }
 
 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 20dcde8206..6ebeb8aea4 100644
--- a/xtask/src/codegen/gen_assists_docs.rs
+++ b/xtask/src/codegen/gen_assists_docs.rs
@@ -33,22 +33,18 @@ impl Assist {
 
         fn collect_file(acc: &mut Vec<Assist>, path: &Path) -> Result<()> {
             let text = fs::read_to_string(path)?;
-            let comment_blocks = extract_comment_blocks_with_empty_lines(&text);
+            let comment_blocks = extract_comment_blocks_with_empty_lines("Assist", &text);
 
             for block in comment_blocks {
                 // FIXME: doesn't support blank lines yet, need to tweak
                 // `extract_comment_blocks` for that.
-                let mut lines = block.iter();
-                let first_line = lines.next().unwrap();
-                if !first_line.starts_with("Assist: ") {
-                    continue;
-                }
-                let id = first_line["Assist: ".len()..].to_string();
+                let id = block.id;
                 assert!(
                     id.chars().all(|it| it.is_ascii_lowercase() || it == '_'),
                     "invalid assist id: {:?}",
                     id
                 );
+                let mut lines = block.contents.iter();
 
                 let doc = take_until(lines.by_ref(), "```").trim().to_string();
                 assert!(
diff --git a/xtask/src/codegen/gen_feature_docs.rs b/xtask/src/codegen/gen_feature_docs.rs
new file mode 100644
index 0000000000..dbe583e8e4
--- /dev/null
+++ b/xtask/src/codegen/gen_feature_docs.rs
@@ -0,0 +1,88 @@
+//! Generates `assists.md` documentation.
+
+use std::{fmt, fs, path::PathBuf};
+
+use crate::{
+    codegen::{self, extract_comment_blocks_with_empty_lines, Mode},
+    project_root, rust_files, Result,
+};
+
+pub fn generate_feature_docs(mode: Mode) -> Result<()> {
+    let features = Feature::collect()?;
+    let contents = features.into_iter().map(|it| it.to_string()).collect::<Vec<_>>().join("\n\n");
+    let contents = contents.trim().to_string() + "\n";
+    let dst = project_root().join("docs/user/generated_features.adoc");
+    codegen::update(&dst, &contents, mode)?;
+    Ok(())
+}
+
+#[derive(Debug)]
+struct Feature {
+    id: String,
+    path: PathBuf,
+    doc: String,
+}
+
+impl Feature {
+    fn collect() -> Result<Vec<Feature>> {
+        let mut res = Vec::new();
+        for path in rust_files(&project_root()) {
+            collect_file(&mut res, path)?;
+        }
+        res.sort_by(|lhs, rhs| lhs.id.cmp(&rhs.id));
+        return Ok(res);
+
+        fn collect_file(acc: &mut Vec<Feature>, path: PathBuf) -> Result<()> {
+            let text = fs::read_to_string(&path)?;
+            let comment_blocks = extract_comment_blocks_with_empty_lines("Feature", &text);
+
+            for block in comment_blocks {
+                let id = block.id;
+                assert!(is_valid_feature_name(&id), "invalid feature name: {:?}", id);
+                let doc = block.contents.join("\n");
+                acc.push(Feature { id, path: path.clone(), doc })
+            }
+
+            Ok(())
+        }
+    }
+}
+
+fn is_valid_feature_name(feature: &str) -> bool {
+    'word: for word in feature.split_whitespace() {
+        for &short in ["to", "and"].iter() {
+            if word == short {
+                continue 'word;
+            }
+        }
+        for &short in ["To", "And"].iter() {
+            if word == short {
+                return false;
+            }
+        }
+        if !word.starts_with(char::is_uppercase) {
+            return false;
+        }
+    }
+    true
+}
+
+impl fmt::Display for Feature {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        writeln!(f, "=== {}", self.id)?;
+        let path = self.path.strip_prefix(&project_root()).unwrap().display().to_string();
+        let path = path.replace('\\', "/");
+        let name = self.path.file_name().unwrap();
+
+        //FIXME: generate line number as well
+        writeln!(
+            f,
+            "**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/{}[{}]",
+            path,
+            name.to_str().unwrap(),
+        )?;
+
+        writeln!(f, "{}", self.doc)?;
+        Ok(())
+    }
+}
diff --git a/xtask/src/main.rs b/xtask/src/main.rs
index dff3ce4a1d..9d7cdd1145 100644
--- a/xtask/src/main.rs
+++ b/xtask/src/main.rs
@@ -75,6 +75,7 @@ FLAGS:
             codegen::generate_syntax(Mode::Overwrite)?;
             codegen::generate_parser_tests(Mode::Overwrite)?;
             codegen::generate_assists_docs(Mode::Overwrite)?;
+            codegen::generate_feature_docs(Mode::Overwrite)?;
             Ok(())
         }
         "format" => {


Test patch:
diff --git a/crates/ra_assists/src/tests/generated.rs b/crates/ra_assists/src/tests/generated.rs
index abffbf97cf..4e0536828c 100644
--- a/crates/ra_assists/src/tests/generated.rs
+++ b/crates/ra_assists/src/tests/generated.rs
@@ -58,6 +58,25 @@ fn main() {
     )
 }
 
+#[test]
+fn doctest_add_from_impl_for_enum() {
+    check_doc_test(
+        "add_from_impl_for_enum",
+        r#####"
+enum A { <|>One(u32) }
+"#####,
+        r#####"
+enum A { One(u32) }
+
+impl From<u32> for A {
+    fn from(v: u32) -> Self {
+        A::One(v)
+    }
+}
+"#####,
+    )
+}
+
 #[test]
 fn doctest_add_function() {
     check_doc_test(
@@ -268,31 +287,6 @@ pub mod std { pub mod collections { pub struct HashMap { } } }
     )
 }
 
-#[test]
-fn doctest_change_lifetime_anon_to_named() {
-    check_doc_test(
-        "change_lifetime_anon_to_named",
-        r#####"
-impl Cursor<'_<|>> {
-    fn node(self) -> &SyntaxNode {
-        match self {
-            Cursor::Replace(node) | Cursor::Before(node) => node,
-        }
-    }
-}
-"#####,
-        r#####"
-impl<'a> Cursor<'a> {
-    fn node(self) -> &SyntaxNode {
-        match self {
-            Cursor::Replace(node) | Cursor::Before(node) => node,
-        }
-    }
-}
-"#####,
-    )
-}
-
 #[test]
 fn doctest_change_return_type_to_result() {
     check_doc_test(
diff --git a/crates/ra_hir_ty/src/tests/patterns.rs b/crates/ra_hir_ty/src/tests/patterns.rs
index fe62587c0d..0c5f972a2c 100644
--- a/crates/ra_hir_ty/src/tests/patterns.rs
+++ b/crates/ra_hir_ty/src/tests/patterns.rs
@@ -520,53 +520,3 @@ fn main() {
         105..107 '()': ()
     ")
 }
-
-#[test]
-fn match_ergonomics_in_closure_params() {
-    assert_snapshot!(
-        infer(r#"
-#[lang = "fn_once"]
-trait FnOnce<Args> {
-    type Output;
-}
-
-fn foo<T, U, F: FnOnce(T) -> U>(t: T, f: F) -> U { loop {} }
-
-fn test() {
-    foo(&(1, "a"), |&(x, y)| x); // normal, no match ergonomics
-    foo(&(1, "a"), |(x, y)| x);
-}
-"#),
-        @r###"
-    94..95 't': T
-    100..101 'f': F
-    111..122 '{ loop {} }': U
-    113..120 'loop {}': !
-    118..120 '{}': ()
-    134..233 '{     ... x); }': ()
-    140..143 'foo': fn foo<&(i32, &str), i32, |&(i32, &str)| -> i32>(&(i32, &str), |&(i32, &str)| -> i32) -> i32
-    140..167 'foo(&(...y)| x)': i32
-    144..153 '&(1, "a")': &(i32, &str)
-    145..153 '(1, "a")': (i32, &str)
-    146..147 '1': i32
-    149..152 '"a"': &str
-    155..166 '|&(x, y)| x': |&(i32, &str)| -> i32
-    156..163 '&(x, y)': &(i32, &str)
-    157..163 '(x, y)': (i32, &str)
-    158..159 'x': i32
-    161..162 'y': &str
-    165..166 'x': i32
-    204..207 'foo': fn foo<&(i32, &str), &i32, |&(i32, &str)| -> &i32>(&(i32, &str), |&(i32, &str)| -> &i32) -> &i32
-    204..230 'foo(&(...y)| x)': &i32
-    208..217 '&(1, "a")': &(i32, &str)
-    209..217 '(1, "a")': (i32, &str)
-    210..211 '1': i32
-    213..216 '"a"': &str
-    219..229 '|(x, y)| x': |&(i32, &str)| -> &i32
-    220..226 '(x, y)': (i32, &str)
-    221..222 'x': &i32
-    224..225 'y': &&str
-    228..229 'x': &i32
-    "###
-    );
-}
diff --git a/xtask/tests/tidy.rs b/xtask/tests/tidy.rs
index 2e9fcf07c5..4ac5d929fc 100644
--- a/xtask/tests/tidy.rs
+++ b/xtask/tests/tidy.rs
@@ -30,6 +30,13 @@ fn generated_assists_are_fresh() {
     }
 }
 
+#[test]
+fn generated_features_are_fresh() {
+    if let Err(error) = codegen::generate_feature_docs(Mode::Verify) {
+        panic!("{}. Please update features by running `cargo xtask codegen`", error);
+    }
+}
+
 #[test]
 fn check_code_formatting() {
     if let Err(error) = run_rustfmt(Mode::Verify) {
@@ -95,7 +102,7 @@ impl TidyDocs {
     fn visit(&mut self, path: &Path, text: &str) {
         // Test hopefully don't really need comments, and for assists we already
         // have special comments which are source of doc tests and user docs.
-        if is_exclude_dir(path, &["tests", "test_data", "handlers"]) {
+        if is_exclude_dir(path, &["tests", "test_data"]) {
             return;
         }
 
@@ -110,9 +117,12 @@ impl TidyDocs {
 
         if first_line.starts_with("//!") {
             if first_line.contains("FIXME") {
-                self.contains_fixme.push(path.to_path_buf())
+                self.contains_fixme.push(path.to_path_buf());
             }
         } else {
+            if text.contains("// Feature:") || text.contains("// Assist:") {
+                return;
+            }
             self.missing_docs.push(path.display().to_string());
         }