4664: Generate feature documentation from code r=matklad a=matklad



Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
This commit is contained in:
bors[bot] 2020-05-31 10:50:11 +00:00 committed by GitHub
commit 09df51dab8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 859 additions and 412 deletions

2
.gitignore vendored
View file

@ -7,4 +7,4 @@ crates/*/target
*.log *.log
*.iml *.iml
.vscode/settings.json .vscode/settings.json
cargo-timing*.html *.html

View file

@ -4,9 +4,9 @@ use test_utils::mark;
use crate::{utils::FamousDefs, AssistContext, AssistId, Assists}; 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) } // enum A { <|>One(u32) }

View file

@ -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] #[test]
fn doctest_add_function() { fn doctest_add_function() {
check_doc_test( check_doc_test(

View file

@ -1,5 +1,3 @@
//! FIXME: write short doc here
mod completion_config; mod completion_config;
mod completion_item; mod completion_item;
mod completion_context; mod completion_context;
@ -35,6 +33,51 @@ pub use crate::completion::{
completion_item::{CompletionItem, CompletionItemKind, CompletionScore, InsertTextFormat}, 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. /// 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. /// First, we look at the position and collect a so-called `CompletionContext.

View file

@ -1,12 +1,11 @@
//! FIXME: write short doc here //! FIXME: write short doc here
use ra_assists::utils::TryEnum;
use ra_syntax::{ use ra_syntax::{
ast::{self, AstNode}, ast::{self, AstNode},
TextRange, TextSize, TextRange, TextSize,
}; };
use ra_text_edit::TextEdit; use ra_text_edit::TextEdit;
use super::completion_config::SnippetCap;
use crate::{ use crate::{
completion::{ completion::{
completion_context::CompletionContext, completion_context::CompletionContext,
@ -14,7 +13,8 @@ use crate::{
}, },
CompletionItem, CompletionItem,
}; };
use ra_assists::utils::TryEnum;
use super::completion_config::SnippetCap;
pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
if !ctx.config.enable_postfix_completions { if !ctx.config.enable_postfix_completions {

View file

@ -1,10 +1,6 @@
//! FIXME: write short doc here
use crate::TextRange;
use ra_syntax::{ use ra_syntax::{
ast::{self, AttrsOwner, NameOwner, TypeAscriptionOwner, TypeParamsOwner}, ast::{self, AttrsOwner, NameOwner, TypeAscriptionOwner, TypeParamsOwner},
match_ast, AstNode, SourceFile, SyntaxKind, SyntaxNode, WalkEvent, match_ast, AstNode, SourceFile, SyntaxKind, SyntaxNode, TextRange, WalkEvent,
}; };
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -18,6 +14,19 @@ pub struct StructureNode {
pub deprecated: bool, 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> { pub fn file_structure(file: &SourceFile) -> Vec<StructureNode> {
let mut res = Vec::new(); let mut res = Vec::new();
let mut stack = Vec::new(); let mut stack = Vec::new();

View file

@ -1,5 +1,3 @@
//! This modules implements "expand macro" functionality in the IDE
use hir::Semantics; use hir::Semantics;
use ra_ide_db::RootDatabase; use ra_ide_db::RootDatabase;
use ra_syntax::{ use ra_syntax::{
@ -14,6 +12,15 @@ pub struct ExpandedMacro {
pub expansion: String, 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> { pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<ExpandedMacro> {
let sema = Semantics::new(db); let sema = Semantics::new(db);
let file = sema.parse(position.file_id); let file = sema.parse(position.file_id);

View file

@ -1,5 +1,3 @@
//! FIXME: write short doc here
use std::iter::successors; use std::iter::successors;
use hir::Semantics; use hir::Semantics;
@ -14,6 +12,16 @@ use ra_syntax::{
use crate::FileRange; 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 { pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange {
let sema = Semantics::new(db); let sema = Semantics::new(db);
let src = sema.parse(frange.file_id); let src = sema.parse(frange.file_id);

View file

@ -1,5 +1,3 @@
//! FIXME: write short doc here
use hir::Semantics; use hir::Semantics;
use ra_ide_db::{ use ra_ide_db::{
defs::{classify_name, classify_name_ref}, defs::{classify_name, classify_name_ref},
@ -17,6 +15,15 @@ use crate::{
FilePosition, NavigationTarget, RangeInfo, 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( pub(crate) fn goto_definition(
db: &RootDatabase, db: &RootDatabase,
position: FilePosition, position: FilePosition,

View file

@ -1,11 +1,18 @@
//! FIXME: write short doc here
use hir::{Crate, ImplDef, Semantics}; use hir::{Crate, ImplDef, Semantics};
use ra_ide_db::RootDatabase; use ra_ide_db::RootDatabase;
use ra_syntax::{algo::find_node_at_offset, ast, AstNode}; use ra_syntax::{algo::find_node_at_offset, ast, AstNode};
use crate::{display::ToNav, FilePosition, NavigationTarget, RangeInfo}; 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( pub(crate) fn goto_implementation(
db: &RootDatabase, db: &RootDatabase,
position: FilePosition, position: FilePosition,

View file

@ -1,10 +1,17 @@
//! FIXME: write short doc here
use ra_ide_db::RootDatabase; use ra_ide_db::RootDatabase;
use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset};
use crate::{display::ToNav, FilePosition, NavigationTarget, RangeInfo}; 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( pub(crate) fn goto_type_definition(
db: &RootDatabase, db: &RootDatabase,
position: FilePosition, position: FilePosition,

View file

@ -1,10 +1,10 @@
//! Logic for computing info that is displayed when the user hovers over any use std::iter::once;
//! source code items (e.g. function call, struct field, variable symbol...)
use hir::{ use hir::{
Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef, Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef,
ModuleSource, Semantics, ModuleSource, Semantics,
}; };
use itertools::Itertools;
use ra_db::SourceDatabase; use ra_db::SourceDatabase;
use ra_ide_db::{ use ra_ide_db::{
defs::{classify_name, classify_name_ref, Definition}, 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}, display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel},
FilePosition, RangeInfo, FilePosition, RangeInfo,
}; };
use itertools::Itertools;
use std::iter::once;
/// Contains the results when hovering over an item /// Contains the results when hovering over an item
#[derive(Debug, Default)] #[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( fn hover_text(
docs: Option<String>, docs: Option<String>,
desc: 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> { fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
return tokens.max_by_key(priority); return tokens.max_by_key(priority);
fn priority(n: &SyntaxToken) -> usize { fn priority(n: &SyntaxToken) -> usize {

View file

@ -1,5 +1,3 @@
//! This module defines multiple types of inlay hints and their visibility
use hir::{Adt, HirDisplay, Semantics, Type}; use hir::{Adt, HirDisplay, Semantics, Type};
use ra_ide_db::RootDatabase; use ra_ide_db::RootDatabase;
use ra_prof::profile; use ra_prof::profile;
@ -39,6 +37,26 @@ pub struct InlayHint {
pub label: SmolStr, 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( pub(crate) fn inlay_hints(
db: &RootDatabase, db: &RootDatabase,
file_id: FileId, file_id: FileId,

View file

@ -1,5 +1,3 @@
//! FIXME: write short doc here
use itertools::Itertools; use itertools::Itertools;
use ra_fmt::{compute_ws, extract_trivial_expression}; use ra_fmt::{compute_ws, extract_trivial_expression};
use ra_syntax::{ use ra_syntax::{
@ -11,6 +9,15 @@ use ra_syntax::{
}; };
use ra_text_edit::{TextEdit, TextEditBuilder}; 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 { pub fn join_lines(file: &SourceFile, range: TextRange) -> TextEdit {
let range = if range.is_empty() { let range = if range.is_empty() {
let syntax = file.syntax(); let syntax = file.syntax();

View file

@ -23,6 +23,7 @@ mod completion;
mod runnables; mod runnables;
mod goto_definition; mod goto_definition;
mod goto_type_definition; mod goto_type_definition;
mod goto_implementation;
mod extend_selection; mod extend_selection;
mod hover; mod hover;
mod call_hierarchy; mod call_hierarchy;
@ -30,7 +31,6 @@ mod call_info;
mod syntax_highlighting; mod syntax_highlighting;
mod parent_module; mod parent_module;
mod references; mod references;
mod impls;
mod diagnostics; mod diagnostics;
mod syntax_tree; mod syntax_tree;
mod folding_ranges; mod folding_ranges;
@ -373,7 +373,7 @@ impl Analysis {
&self, &self,
position: FilePosition, position: FilePosition,
) -> Cancelable<Option<RangeInfo<Vec<NavigationTarget>>>> { ) -> 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`. /// Returns the type definitions for the symbol at `position`.

View file

@ -1,7 +1,16 @@
//! FIXME: write short doc here
use ra_syntax::{ast::AstNode, SourceFile, SyntaxKind, TextSize, T}; 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> { pub fn matching_brace(file: &SourceFile, offset: TextSize) -> Option<TextSize> {
const BRACES: &[SyntaxKind] = const BRACES: &[SyntaxKind] =
&[T!['{'], T!['}'], T!['['], T![']'], T!['('], T![')'], T![<], T![>]]; &[T!['{'], T!['}'], T!['['], T![']'], T!['('], T![')'], T![<], T![>]];

View file

@ -1,5 +1,3 @@
//! FIXME: write short doc here
use hir::Semantics; use hir::Semantics;
use ra_db::{CrateId, FileId, FilePosition}; use ra_db::{CrateId, FileId, FilePosition};
use ra_ide_db::RootDatabase; use ra_ide_db::RootDatabase;
@ -11,6 +9,16 @@ use test_utils::mark;
use crate::NavigationTarget; 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 /// 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. /// 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> { pub(crate) fn parent_module(db: &RootDatabase, position: FilePosition) -> Vec<NavigationTarget> {

View file

@ -1,5 +1,3 @@
//! FIXME: write short doc here
use hir::{AsAssocItem, Attrs, HirFileId, InFile, Semantics}; use hir::{AsAssocItem, Attrs, HirFileId, InFile, Semantics};
use itertools::Itertools; use itertools::Itertools;
use ra_ide_db::RootDatabase; use ra_ide_db::RootDatabase;
@ -44,6 +42,17 @@ pub enum RunnableKind {
Bin, 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> { pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
let sema = Semantics::new(db); let sema = Semantics::new(db);
let source_file = sema.parse(file_id); let source_file = sema.parse(file_id);

View file

@ -1,5 +1,3 @@
//! structural search replace
use std::{collections::HashMap, iter::once, str::FromStr}; use std::{collections::HashMap, iter::once, str::FromStr};
use ra_db::{SourceDatabase, SourceDatabaseExt}; use ra_db::{SourceDatabase, SourceDatabaseExt};
@ -25,6 +23,28 @@ impl std::fmt::Display for SsrError {
impl std::error::Error 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( pub fn parse_search_replace(
query: &str, query: &str,
parse_only: bool, parse_only: bool,

View file

@ -1,5 +1,3 @@
//! FIXME: write short doc here
use std::{fmt, iter::FromIterator, sync::Arc}; use std::{fmt, iter::FromIterator, sync::Arc};
use hir::MacroFile; use hir::MacroFile;
@ -26,6 +24,15 @@ fn macro_syntax_tree_stats(db: &RootDatabase) -> SyntaxTreeStats {
db.query(hir::db::ParseMacroQuery).entries::<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 { pub(crate) fn status(db: &RootDatabase) -> String {
let files_stats = db.query(FileTextQuery).entries::<FilesStats>(); let files_stats = db.query(FileTextQuery).entries::<FilesStats>();
let syntax_tree_stats = syntax_tree_stats(db); let syntax_tree_stats = syntax_tree_stats(db);

View file

@ -1,5 +1,3 @@
//! Implements syntax highlighting.
mod tags; mod tags;
mod html; mod html;
#[cfg(test)] #[cfg(test)]
@ -32,81 +30,15 @@ pub struct HighlightedRange {
pub binding_hash: Option<u64>, pub binding_hash: Option<u64>,
} }
#[derive(Debug)] // Feature: Semantic Syntax Highlighting
struct HighlightedRangeStack { //
stack: Vec<Vec<HighlightedRange>>, // 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.
/// We use a stack to implement the flattening logic for the highlighted // It's up to the client to map those to specific colors.
/// syntax ranges. //
impl HighlightedRangeStack { // The general rule is that a reference to an entity gets colored the same way as the entity itself.
fn new() -> Self { // We also give special modifier for `mut` and `&mut` local variables.
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
}
}
pub(crate) fn highlight( pub(crate) fn highlight(
db: &RootDatabase, db: &RootDatabase,
file_id: FileId, file_id: FileId,
@ -291,6 +223,81 @@ pub(crate) fn highlight(
stack.flattened() 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> { fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HighlightTag> {
Some(match kind { Some(match kind {
FormatSpecifier::Open FormatSpecifier::Open

View file

@ -1,6 +1,4 @@
//! FIXME: write short doc here use ra_db::{FileId, SourceDatabase};
use ra_db::SourceDatabase;
use ra_ide_db::RootDatabase; use ra_ide_db::RootDatabase;
use ra_syntax::{ use ra_syntax::{
algo, AstNode, NodeOrToken, SourceFile, algo, AstNode, NodeOrToken, SourceFile,
@ -8,8 +6,16 @@ use ra_syntax::{
SyntaxToken, TextRange, TextSize, 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( pub(crate) fn syntax_tree(
db: &RootDatabase, db: &RootDatabase,
file_id: FileId, file_id: FileId,

View file

@ -32,6 +32,13 @@ pub(crate) use on_enter::on_enter;
pub(crate) const TRIGGER_CHARS: &str = ".=>"; 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( pub(crate) fn on_char_typed(
db: &RootDatabase, db: &RootDatabase,
position: FilePosition, position: FilePosition,

View file

@ -110,6 +110,27 @@ fn file_symbols(db: &impl SymbolsDatabase, file_id: FileId) -> Arc<SymbolIndex>
Arc::new(SymbolIndex::new(symbols)) 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> { pub fn world_symbols(db: &RootDatabase, query: Query) -> Vec<FileSymbol> {
/// Need to wrap Snapshot to provide `Clone` impl for `map_with` /// Need to wrap Snapshot to provide `Clone` impl for `map_with`
struct Snap(salsa::Snapshot<RootDatabase>); struct Snap(salsa::Snapshot<RootDatabase>);

View file

@ -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` ## `add_function`
Adds a stub function with a signature matching the function under the cursor. Adds a stub function with a signature matching the function under the cursor.

View file

@ -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).

View file

@ -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]
|===

View file

@ -8,6 +8,8 @@
:important-caption: :heavy_exclamation_mark: :important-caption: :heavy_exclamation_mark:
:caution-caption: :fire: :caution-caption: :fire:
:warning-caption: :warning: :warning-caption: :warning:
:source-highlighter: rouge
:experimental:
// Master copy of this document lives in the https://github.com/rust-analyzer/rust-analyzer repository // 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`). 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. 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.

View file

@ -8,14 +8,15 @@
mod gen_syntax; mod gen_syntax;
mod gen_parser_tests; mod gen_parser_tests;
mod gen_assists_docs; mod gen_assists_docs;
mod gen_feature_docs;
use std::{mem, path::Path}; use std::{mem, path::Path};
use crate::{not_bash::fs2, Result}; use crate::{not_bash::fs2, Result};
pub use self::{ pub use self::{
gen_assists_docs::generate_assists_docs, gen_parser_tests::generate_parser_tests, gen_assists_docs::generate_assists_docs, gen_feature_docs::generate_feature_docs,
gen_syntax::generate_syntax, gen_parser_tests::generate_parser_tests, gen_syntax::generate_syntax,
}; };
const GRAMMAR_DIR: &str = "crates/ra_parser/src/grammar"; const GRAMMAR_DIR: &str = "crates/ra_parser/src/grammar";
@ -40,7 +41,7 @@ pub enum Mode {
/// With verify = false, /// With verify = false,
fn update(path: &Path, contents: &str, mode: Mode) -> Result<()> { fn update(path: &Path, contents: &str, mode: Mode) -> Result<()> {
match fs2::read_to_string(path) { 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(()); return Ok(());
} }
_ => (), _ => (),
@ -61,8 +62,24 @@ fn extract_comment_blocks(text: &str) -> Vec<Vec<String>> {
do_extract_comment_blocks(text, false) do_extract_comment_blocks(text, false)
} }
fn extract_comment_blocks_with_empty_lines(text: &str) -> Vec<Vec<String>> { fn extract_comment_blocks_with_empty_lines(tag: &str, text: &str) -> Vec<CommentBlock> {
do_extract_comment_blocks(text, true) 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>> { fn do_extract_comment_blocks(text: &str, allow_blocks_with_empty_lines: bool) -> Vec<Vec<String>> {

View file

@ -33,22 +33,18 @@ impl Assist {
fn collect_file(acc: &mut Vec<Assist>, path: &Path) -> Result<()> { fn collect_file(acc: &mut Vec<Assist>, path: &Path) -> Result<()> {
let text = fs::read_to_string(path)?; 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 { for block in comment_blocks {
// FIXME: doesn't support blank lines yet, need to tweak // FIXME: doesn't support blank lines yet, need to tweak
// `extract_comment_blocks` for that. // `extract_comment_blocks` for that.
let mut lines = block.iter(); let id = block.id;
let first_line = lines.next().unwrap();
if !first_line.starts_with("Assist: ") {
continue;
}
let id = first_line["Assist: ".len()..].to_string();
assert!( assert!(
id.chars().all(|it| it.is_ascii_lowercase() || it == '_'), id.chars().all(|it| it.is_ascii_lowercase() || it == '_'),
"invalid assist id: {:?}", "invalid assist id: {:?}",
id id
); );
let mut lines = block.contents.iter();
let doc = take_until(lines.by_ref(), "```").trim().to_string(); let doc = take_until(lines.by_ref(), "```").trim().to_string();
assert!( assert!(

View file

@ -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(())
}
}

View file

@ -75,6 +75,7 @@ FLAGS:
codegen::generate_syntax(Mode::Overwrite)?; codegen::generate_syntax(Mode::Overwrite)?;
codegen::generate_parser_tests(Mode::Overwrite)?; codegen::generate_parser_tests(Mode::Overwrite)?;
codegen::generate_assists_docs(Mode::Overwrite)?; codegen::generate_assists_docs(Mode::Overwrite)?;
codegen::generate_feature_docs(Mode::Overwrite)?;
Ok(()) Ok(())
} }
"format" => { "format" => {

View file

@ -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] #[test]
fn check_code_formatting() { fn check_code_formatting() {
if let Err(error) = run_rustfmt(Mode::Verify) { if let Err(error) = run_rustfmt(Mode::Verify) {
@ -95,7 +102,7 @@ impl TidyDocs {
fn visit(&mut self, path: &Path, text: &str) { fn visit(&mut self, path: &Path, text: &str) {
// Test hopefully don't really need comments, and for assists we already // Test hopefully don't really need comments, and for assists we already
// have special comments which are source of doc tests and user docs. // 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; return;
} }
@ -110,9 +117,12 @@ impl TidyDocs {
if first_line.starts_with("//!") { if first_line.starts_with("//!") {
if first_line.contains("FIXME") { if first_line.contains("FIXME") {
self.contains_fixme.push(path.to_path_buf()) self.contains_fixme.push(path.to_path_buf());
} }
} else { } else {
if text.contains("// Feature:") || text.contains("// Assist:") {
return;
}
self.missing_docs.push(path.display().to_string()); self.missing_docs.push(path.display().to_string());
} }