From c8f27a4a886413a15a2a6af4a87b39b901c873a8 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sun, 31 May 2020 01:54:54 +0200 Subject: Generate features docs from source --- crates/ra_ide/src/display/structure.rs | 13 ++ crates/ra_ide/src/extend_selection.rs | 10 ++ crates/ra_ide/src/goto_definition.rs | 9 ++ crates/ra_ide/src/goto_implementation.rs | 210 ++++++++++++++++++++++++++++++ crates/ra_ide/src/goto_type_definition.rs | 3 + crates/ra_ide/src/impls.rs | 201 ---------------------------- crates/ra_ide/src/lib.rs | 4 +- crates/ra_ide/src/typing.rs | 7 + 8 files changed, 254 insertions(+), 203 deletions(-) create mode 100644 crates/ra_ide/src/goto_implementation.rs delete mode 100644 crates/ra_ide/src/impls.rs (limited to 'crates/ra_ide') diff --git a/crates/ra_ide/src/display/structure.rs b/crates/ra_ide/src/display/structure.rs index 967eee5d2..3f59b89bb 100644 --- a/crates/ra_ide/src/display/structure.rs +++ b/crates/ra_ide/src/display/structure.rs @@ -18,6 +18,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 { let mut res = Vec::new(); let mut stack = Vec::new(); diff --git a/crates/ra_ide/src/extend_selection.rs b/crates/ra_ide/src/extend_selection.rs index 554594a43..cee0a19e1 100644 --- a/crates/ra_ide/src/extend_selection.rs +++ b/crates/ra_ide/src/extend_selection.rs @@ -14,6 +14,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 90e85d419..83ea5092c 100644 --- a/crates/ra_ide/src/goto_definition.rs +++ b/crates/ra_ide/src/goto_definition.rs @@ -17,6 +17,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/goto_implementation.rs b/crates/ra_ide/src/goto_implementation.rs new file mode 100644 index 000000000..a5a296d22 --- /dev/null +++ b/crates/ra_ide/src/goto_implementation.rs @@ -0,0 +1,210 @@ +//! 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, +) -> Option>> { + let sema = Semantics::new(db); + let source_file = sema.parse(position.file_id); + let syntax = source_file.syntax().clone(); + + let krate = sema.to_module_def(position.file_id)?.krate(); + + if let Some(nominal_def) = find_node_at_offset::(&syntax, position.offset) { + return Some(RangeInfo::new( + nominal_def.syntax().text_range(), + impls_for_def(&sema, &nominal_def, krate)?, + )); + } else if let Some(trait_def) = find_node_at_offset::(&syntax, position.offset) { + return Some(RangeInfo::new( + trait_def.syntax().text_range(), + impls_for_trait(&sema, &trait_def, krate)?, + )); + } + + None +} + +fn impls_for_def( + sema: &Semantics, + node: &ast::NominalDef, + krate: Crate, +) -> Option> { + let ty = match node { + ast::NominalDef::StructDef(def) => sema.to_def(def)?.ty(sema.db), + ast::NominalDef::EnumDef(def) => sema.to_def(def)?.ty(sema.db), + ast::NominalDef::UnionDef(def) => sema.to_def(def)?.ty(sema.db), + }; + + let impls = ImplDef::all_in_crate(sema.db, krate); + + Some( + impls + .into_iter() + .filter(|impl_def| ty.is_equal_for_find_impls(&impl_def.target_ty(sema.db))) + .map(|imp| imp.to_nav(sema.db)) + .collect(), + ) +} + +fn impls_for_trait( + sema: &Semantics, + node: &ast::TraitDef, + krate: Crate, +) -> Option> { + let tr = sema.to_def(node)?; + + let impls = ImplDef::for_trait(sema.db, krate, tr); + + Some(impls.into_iter().map(|imp| imp.to_nav(sema.db)).collect()) +} + +#[cfg(test)] +mod tests { + use crate::mock_analysis::analysis_and_position; + + fn check_goto(fixture: &str, expected: &[&str]) { + let (analysis, pos) = analysis_and_position(fixture); + + let mut navs = analysis.goto_implementation(pos).unwrap().unwrap().info; + assert_eq!(navs.len(), expected.len()); + navs.sort_by_key(|nav| (nav.file_id(), nav.full_range().start())); + navs.into_iter().enumerate().for_each(|(i, nav)| nav.assert_match(expected[i])); + } + + #[test] + fn goto_implementation_works() { + check_goto( + " + //- /lib.rs + struct Foo<|>; + impl Foo {} + ", + &["impl IMPL_DEF FileId(1) 12..23"], + ); + } + + #[test] + fn goto_implementation_works_multiple_blocks() { + check_goto( + " + //- /lib.rs + struct Foo<|>; + impl Foo {} + impl Foo {} + ", + &["impl IMPL_DEF FileId(1) 12..23", "impl IMPL_DEF FileId(1) 24..35"], + ); + } + + #[test] + fn goto_implementation_works_multiple_mods() { + check_goto( + " + //- /lib.rs + struct Foo<|>; + mod a { + impl super::Foo {} + } + mod b { + impl super::Foo {} + } + ", + &["impl IMPL_DEF FileId(1) 24..42", "impl IMPL_DEF FileId(1) 57..75"], + ); + } + + #[test] + fn goto_implementation_works_multiple_files() { + check_goto( + " + //- /lib.rs + struct Foo<|>; + mod a; + mod b; + //- /a.rs + impl crate::Foo {} + //- /b.rs + impl crate::Foo {} + ", + &["impl IMPL_DEF FileId(2) 0..18", "impl IMPL_DEF FileId(3) 0..18"], + ); + } + + #[test] + fn goto_implementation_for_trait() { + check_goto( + " + //- /lib.rs + trait T<|> {} + struct Foo; + impl T for Foo {} + ", + &["impl IMPL_DEF FileId(1) 23..40"], + ); + } + + #[test] + fn goto_implementation_for_trait_multiple_files() { + check_goto( + " + //- /lib.rs + trait T<|> {}; + struct Foo; + mod a; + mod b; + //- /a.rs + impl crate::T for crate::Foo {} + //- /b.rs + impl crate::T for crate::Foo {} + ", + &["impl IMPL_DEF FileId(2) 0..31", "impl IMPL_DEF FileId(3) 0..31"], + ); + } + + #[test] + fn goto_implementation_all_impls() { + check_goto( + " + //- /lib.rs + trait T {} + struct Foo<|>; + impl Foo {} + impl T for Foo {} + impl T for &Foo {} + ", + &[ + "impl IMPL_DEF FileId(1) 23..34", + "impl IMPL_DEF FileId(1) 35..52", + "impl IMPL_DEF FileId(1) 53..71", + ], + ); + } + + #[test] + fn goto_implementation_to_builtin_derive() { + check_goto( + " + //- /lib.rs + #[derive(Copy)] + struct Foo<|>; + ", + &["impl IMPL_DEF FileId(1) 0..15"], + ); + } +} diff --git a/crates/ra_ide/src/goto_type_definition.rs b/crates/ra_ide/src/goto_type_definition.rs index a84637489..eeadfa9ee 100644 --- a/crates/ra_ide/src/goto_type_definition.rs +++ b/crates/ra_ide/src/goto_type_definition.rs @@ -5,6 +5,9 @@ use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffs use crate::{display::ToNav, FilePosition, NavigationTarget, RangeInfo}; +// Feature: Go To Type Definition +// +// Navigates to the type of an identifier. pub(crate) fn goto_type_definition( db: &RootDatabase, position: FilePosition, diff --git a/crates/ra_ide/src/impls.rs b/crates/ra_ide/src/impls.rs deleted file mode 100644 index ea2225f70..000000000 --- a/crates/ra_ide/src/impls.rs +++ /dev/null @@ -1,201 +0,0 @@ -//! 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}; - -pub(crate) fn goto_implementation( - db: &RootDatabase, - position: FilePosition, -) -> Option>> { - let sema = Semantics::new(db); - let source_file = sema.parse(position.file_id); - let syntax = source_file.syntax().clone(); - - let krate = sema.to_module_def(position.file_id)?.krate(); - - if let Some(nominal_def) = find_node_at_offset::(&syntax, position.offset) { - return Some(RangeInfo::new( - nominal_def.syntax().text_range(), - impls_for_def(&sema, &nominal_def, krate)?, - )); - } else if let Some(trait_def) = find_node_at_offset::(&syntax, position.offset) { - return Some(RangeInfo::new( - trait_def.syntax().text_range(), - impls_for_trait(&sema, &trait_def, krate)?, - )); - } - - None -} - -fn impls_for_def( - sema: &Semantics, - node: &ast::NominalDef, - krate: Crate, -) -> Option> { - let ty = match node { - ast::NominalDef::StructDef(def) => sema.to_def(def)?.ty(sema.db), - ast::NominalDef::EnumDef(def) => sema.to_def(def)?.ty(sema.db), - ast::NominalDef::UnionDef(def) => sema.to_def(def)?.ty(sema.db), - }; - - let impls = ImplDef::all_in_crate(sema.db, krate); - - Some( - impls - .into_iter() - .filter(|impl_def| ty.is_equal_for_find_impls(&impl_def.target_ty(sema.db))) - .map(|imp| imp.to_nav(sema.db)) - .collect(), - ) -} - -fn impls_for_trait( - sema: &Semantics, - node: &ast::TraitDef, - krate: Crate, -) -> Option> { - let tr = sema.to_def(node)?; - - let impls = ImplDef::for_trait(sema.db, krate, tr); - - Some(impls.into_iter().map(|imp| imp.to_nav(sema.db)).collect()) -} - -#[cfg(test)] -mod tests { - use crate::mock_analysis::analysis_and_position; - - fn check_goto(fixture: &str, expected: &[&str]) { - let (analysis, pos) = analysis_and_position(fixture); - - let mut navs = analysis.goto_implementation(pos).unwrap().unwrap().info; - assert_eq!(navs.len(), expected.len()); - navs.sort_by_key(|nav| (nav.file_id(), nav.full_range().start())); - navs.into_iter().enumerate().for_each(|(i, nav)| nav.assert_match(expected[i])); - } - - #[test] - fn goto_implementation_works() { - check_goto( - " - //- /lib.rs - struct Foo<|>; - impl Foo {} - ", - &["impl IMPL_DEF FileId(1) 12..23"], - ); - } - - #[test] - fn goto_implementation_works_multiple_blocks() { - check_goto( - " - //- /lib.rs - struct Foo<|>; - impl Foo {} - impl Foo {} - ", - &["impl IMPL_DEF FileId(1) 12..23", "impl IMPL_DEF FileId(1) 24..35"], - ); - } - - #[test] - fn goto_implementation_works_multiple_mods() { - check_goto( - " - //- /lib.rs - struct Foo<|>; - mod a { - impl super::Foo {} - } - mod b { - impl super::Foo {} - } - ", - &["impl IMPL_DEF FileId(1) 24..42", "impl IMPL_DEF FileId(1) 57..75"], - ); - } - - #[test] - fn goto_implementation_works_multiple_files() { - check_goto( - " - //- /lib.rs - struct Foo<|>; - mod a; - mod b; - //- /a.rs - impl crate::Foo {} - //- /b.rs - impl crate::Foo {} - ", - &["impl IMPL_DEF FileId(2) 0..18", "impl IMPL_DEF FileId(3) 0..18"], - ); - } - - #[test] - fn goto_implementation_for_trait() { - check_goto( - " - //- /lib.rs - trait T<|> {} - struct Foo; - impl T for Foo {} - ", - &["impl IMPL_DEF FileId(1) 23..40"], - ); - } - - #[test] - fn goto_implementation_for_trait_multiple_files() { - check_goto( - " - //- /lib.rs - trait T<|> {}; - struct Foo; - mod a; - mod b; - //- /a.rs - impl crate::T for crate::Foo {} - //- /b.rs - impl crate::T for crate::Foo {} - ", - &["impl IMPL_DEF FileId(2) 0..31", "impl IMPL_DEF FileId(3) 0..31"], - ); - } - - #[test] - fn goto_implementation_all_impls() { - check_goto( - " - //- /lib.rs - trait T {} - struct Foo<|>; - impl Foo {} - impl T for Foo {} - impl T for &Foo {} - ", - &[ - "impl IMPL_DEF FileId(1) 23..34", - "impl IMPL_DEF FileId(1) 35..52", - "impl IMPL_DEF FileId(1) 53..71", - ], - ); - } - - #[test] - fn goto_implementation_to_builtin_derive() { - check_goto( - " - //- /lib.rs - #[derive(Copy)] - struct Foo<|>; - ", - &["impl IMPL_DEF FileId(1) 0..15"], - ); - } -} diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs index d983cd910..12d5716e8 100644 --- a/crates/ra_ide/src/lib.rs +++ b/crates/ra_ide/src/lib.rs @@ -23,6 +23,7 @@ mod completion; 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>>> { - 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/typing.rs b/crates/ra_ide/src/typing.rs index 39bb3b357..67e2c33a0 100644 --- a/crates/ra_ide/src/typing.rs +++ b/crates/ra_ide/src/typing.rs @@ -32,6 +32,13 @@ pub(crate) use on_enter::on_enter; 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, -- cgit v1.2.3 From f593393ebb9bfa515caf168a9f037324eeb6edfe Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sun, 31 May 2020 09:45:41 +0200 Subject: Specify actions --- crates/ra_ide/src/goto_definition.rs | 2 +- crates/ra_ide/src/goto_implementation.rs | 2 +- crates/ra_ide/src/goto_type_definition.rs | 8 +++++++- crates/ra_ide/src/runnables.rs | 11 +++++++++++ 4 files changed, 20 insertions(+), 3 deletions(-) (limited to 'crates/ra_ide') diff --git a/crates/ra_ide/src/goto_definition.rs b/crates/ra_ide/src/goto_definition.rs index 83ea5092c..daeeac76f 100644 --- a/crates/ra_ide/src/goto_definition.rs +++ b/crates/ra_ide/src/goto_definition.rs @@ -17,7 +17,7 @@ use crate::{ FilePosition, NavigationTarget, RangeInfo, }; -// Feature: Go To Definition +// Feature: Go to Definition // // Navigates to the definition of an identifier. // diff --git a/crates/ra_ide/src/goto_implementation.rs b/crates/ra_ide/src/goto_implementation.rs index a5a296d22..622a094e6 100644 --- a/crates/ra_ide/src/goto_implementation.rs +++ b/crates/ra_ide/src/goto_implementation.rs @@ -6,7 +6,7 @@ use ra_syntax::{algo::find_node_at_offset, ast, AstNode}; use crate::{display::ToNav, FilePosition, NavigationTarget, RangeInfo}; -// Feature: Go To Implementation +// Feature: Go to Implementation // // Navigates to the impl block of structs, enums or traits. Also implemented as a code lens. // diff --git a/crates/ra_ide/src/goto_type_definition.rs b/crates/ra_ide/src/goto_type_definition.rs index eeadfa9ee..e74a502ec 100644 --- a/crates/ra_ide/src/goto_type_definition.rs +++ b/crates/ra_ide/src/goto_type_definition.rs @@ -5,9 +5,15 @@ use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffs use crate::{display::ToNav, FilePosition, NavigationTarget, RangeInfo}; -// Feature: Go To Type Definition +// 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/runnables.rs b/crates/ra_ide/src/runnables.rs index 6e7e47199..4bf2678e1 100644 --- a/crates/ra_ide/src/runnables.rs +++ b/crates/ra_ide/src/runnables.rs @@ -44,6 +44,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 { let sema = Semantics::new(db); let source_file = sema.parse(file_id); -- cgit v1.2.3 From 8915183d7da07a4b295e5e93a889dea4c15024a0 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sun, 31 May 2020 09:59:38 +0200 Subject: Don't require module docs for Features and Assists --- crates/ra_ide/src/display/structure.rs | 6 +----- crates/ra_ide/src/extend_selection.rs | 2 -- crates/ra_ide/src/goto_definition.rs | 2 -- crates/ra_ide/src/goto_implementation.rs | 2 -- crates/ra_ide/src/goto_type_definition.rs | 2 -- crates/ra_ide/src/join_lines.rs | 11 +++++++++-- crates/ra_ide/src/matching_brace.rs | 13 +++++++++++-- crates/ra_ide/src/parent_module.rs | 12 ++++++++++-- crates/ra_ide/src/runnables.rs | 2 -- crates/ra_ide/src/syntax_tree.rs | 14 ++++++++++---- 10 files changed, 41 insertions(+), 25 deletions(-) (limited to 'crates/ra_ide') diff --git a/crates/ra_ide/src/display/structure.rs b/crates/ra_ide/src/display/structure.rs index 3f59b89bb..aad5a8e4d 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)] diff --git a/crates/ra_ide/src/extend_selection.rs b/crates/ra_ide/src/extend_selection.rs index cee0a19e1..a4bc93cdb 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; diff --git a/crates/ra_ide/src/goto_definition.rs b/crates/ra_ide/src/goto_definition.rs index daeeac76f..a6c86e99c 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}, diff --git a/crates/ra_ide/src/goto_implementation.rs b/crates/ra_ide/src/goto_implementation.rs index 622a094e6..0cec0657e 100644 --- a/crates/ra_ide/src/goto_implementation.rs +++ b/crates/ra_ide/src/goto_implementation.rs @@ -1,5 +1,3 @@ -//! 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}; diff --git a/crates/ra_ide/src/goto_type_definition.rs b/crates/ra_ide/src/goto_type_definition.rs index e74a502ec..91a3097fb 100644 --- a/crates/ra_ide/src/goto_type_definition.rs +++ b/crates/ra_ide/src/goto_type_definition.rs @@ -1,5 +1,3 @@ -//! FIXME: write short doc here - use ra_ide_db::RootDatabase; use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; diff --git a/crates/ra_ide/src/join_lines.rs b/crates/ra_ide/src/join_lines.rs index af1ade8a1..5036c1fb0 100644 --- a/crates/ra_ide/src/join_lines.rs +++ b/crates/ra_ide/src/join_lines.rs @@ -1,5 +1,3 @@ -//! 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/matching_brace.rs b/crates/ra_ide/src/matching_brace.rs index b85348706..407a9636d 100644 --- a/crates/ra_ide/src/matching_brace.rs +++ b/crates/ra_ide/src/matching_brace.rs @@ -1,7 +1,16 @@ -//! 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 { 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 a083fb1eb..fa1535da5 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 { diff --git a/crates/ra_ide/src/runnables.rs b/crates/ra_ide/src/runnables.rs index 4bf2678e1..286d45eee 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; diff --git a/crates/ra_ide/src/syntax_tree.rs b/crates/ra_ide/src/syntax_tree.rs index 86c70ff83..2192f5090 100644 --- a/crates/ra_ide/src/syntax_tree.rs +++ b/crates/ra_ide/src/syntax_tree.rs @@ -1,5 +1,3 @@ -//! FIXME: write short doc here - use ra_db::SourceDatabase; use ra_ide_db::RootDatabase; use ra_syntax::{ @@ -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, -- cgit v1.2.3 From b795a07320e13bcbedb6435bcfddb3ecd0ed2bde Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sun, 31 May 2020 10:14:36 +0200 Subject: Doc more features --- crates/ra_ide/src/expand_macro.rs | 11 +++++++++-- crates/ra_ide/src/ssr.rs | 24 ++++++++++++++++++++++-- crates/ra_ide/src/status.rs | 11 +++++++++-- 3 files changed, 40 insertions(+), 6 deletions(-) (limited to 'crates/ra_ide') diff --git a/crates/ra_ide/src/expand_macro.rs b/crates/ra_ide/src/expand_macro.rs index f536ba3e7..54a47aac0 100644 --- a/crates/ra_ide/src/expand_macro.rs +++ b/crates/ra_ide/src/expand_macro.rs @@ -1,5 +1,3 @@ -//! 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 { let sema = Semantics::new(db); let file = sema.parse(position.file_id); diff --git a/crates/ra_ide/src/ssr.rs b/crates/ra_ide/src/ssr.rs index 130d3b4c3..93e9aee1d 100644 --- a/crates/ra_ide/src/ssr.rs +++ b/crates/ra_ide/src/ssr.rs @@ -1,5 +1,3 @@ -//! 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 ` ==>> `. +// A `$:expr` placeholder in the search pattern will match any expression and `$` 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 30eb5c995..5b7992920 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::() } +// 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::(); let syntax_tree_stats = syntax_tree_stats(db); -- cgit v1.2.3 From 1c6a2eb14a84c3a66972d1a6da429cca1aa8b40a Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sun, 31 May 2020 11:29:19 +0200 Subject: Move the rest of the features to generated docs --- crates/ra_ide/src/completion.rs | 47 ++++++- crates/ra_ide/src/completion/complete_postfix.rs | 6 +- crates/ra_ide/src/hover.rs | 116 ++++++++-------- crates/ra_ide/src/inlay_hints.rs | 22 +++- crates/ra_ide/src/syntax_highlighting.rs | 161 ++++++++++++----------- crates/ra_ide/src/syntax_tree.rs | 2 +- 6 files changed, 212 insertions(+), 142 deletions(-) (limited to 'crates/ra_ide') diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs index 191300704..d890b69d2 100644 --- a/crates/ra_ide/src/completion.rs +++ b/crates/ra_ide/src/completion.rs @@ -1,5 +1,3 @@ -//! 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 02e660ca8..59b58bf98 100644 --- a/crates/ra_ide/src/completion/complete_postfix.rs +++ b/crates/ra_ide/src/completion/complete_postfix.rs @@ -1,12 +1,11 @@ //! 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/hover.rs b/crates/ra_ide/src/hover.rs index 3e721dcca..d96cb5596 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs @@ -1,10 +1,10 @@ -//! 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> { + 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, desc: Option, @@ -160,59 +215,6 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option Option> { - 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) -> Option { 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 b391f903a..75bd3c96b 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/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs index 8a995d779..3ab1f0a21 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, } -#[derive(Debug)] -struct HighlightedRangeStack { - stack: Vec>, -} - -/// 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 { - 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>, +} + +/// 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 { + 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 { Some(match kind { FormatSpecifier::Open diff --git a/crates/ra_ide/src/syntax_tree.rs b/crates/ra_ide/src/syntax_tree.rs index 2192f5090..a341684fd 100644 --- a/crates/ra_ide/src/syntax_tree.rs +++ b/crates/ra_ide/src/syntax_tree.rs @@ -1,4 +1,4 @@ -use ra_db::SourceDatabase; +use ra_db::{FileId, SourceDatabase}; use ra_ide_db::RootDatabase; use ra_syntax::{ algo, AstNode, NodeOrToken, SourceFile, -- cgit v1.2.3