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/src') 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