From 3ccd05fedc46796f793295901a8619492256468e Mon Sep 17 00:00:00 2001 From: Edwin Cheng Date: Mon, 18 Nov 2019 02:47:50 +0800 Subject: Add recursive expand in vscode --- crates/ra_ide_api/src/expand_macro.rs | 112 +++++++++++++++++++++++++ crates/ra_ide_api/src/lib.rs | 5 ++ crates/ra_lsp_server/src/main_loop.rs | 1 + crates/ra_lsp_server/src/main_loop/handlers.rs | 15 ++++ crates/ra_lsp_server/src/req.rs | 15 ++++ editors/code/src/commands/expand_macro.ts | 45 ++++++++++ editors/code/src/commands/index.ts | 2 + editors/code/src/extension.ts | 20 +++-- 8 files changed, 210 insertions(+), 5 deletions(-) create mode 100644 crates/ra_ide_api/src/expand_macro.rs create mode 100644 editors/code/src/commands/expand_macro.ts diff --git a/crates/ra_ide_api/src/expand_macro.rs b/crates/ra_ide_api/src/expand_macro.rs new file mode 100644 index 000000000..48dc90932 --- /dev/null +++ b/crates/ra_ide_api/src/expand_macro.rs @@ -0,0 +1,112 @@ +//! FIXME: write short doc here + +use crate::{db::RootDatabase, FilePosition}; +use ra_db::SourceDatabase; +use rustc_hash::FxHashMap; + +use hir::db::AstDatabase; +use ra_syntax::{ + algo::{find_node_at_offset, replace_descendants}, + ast::{self}, + AstNode, NodeOrToken, SyntaxKind, SyntaxNode, WalkEvent, +}; + +fn insert_whitespaces(syn: SyntaxNode) -> String { + let mut res = String::new(); + + let mut token_iter = syn + .preorder_with_tokens() + .filter_map(|event| { + if let WalkEvent::Enter(NodeOrToken::Token(token)) = event { + Some(token) + } else { + None + } + }) + .peekable(); + + while let Some(token) = token_iter.next() { + res += &token.text().to_string(); + if token.kind().is_keyword() + || token.kind().is_literal() + || token.kind() == SyntaxKind::IDENT + { + if !token_iter.peek().map(|it| it.kind().is_punct()).unwrap_or(false) { + res += " "; + } + } + } + + res +} + +fn expand_macro_recur( + db: &RootDatabase, + source: hir::Source<&SyntaxNode>, + macro_call: &ast::MacroCall, +) -> Option { + let analyzer = hir::SourceAnalyzer::new(db, source, None); + let expansion = analyzer.expand(db, ¯o_call)?; + let expanded: SyntaxNode = db.parse_or_expand(expansion.file_id())?; + + let children = expanded.descendants().filter_map(ast::MacroCall::cast); + let mut replaces = FxHashMap::default(); + + for child in children.into_iter() { + let source = hir::Source::new(expansion.file_id(), source.ast); + let new_node = expand_macro_recur(db, source, &child)?; + + replaces.insert(child.syntax().clone().into(), new_node.into()); + } + + Some(replace_descendants(&expanded, &replaces)) +} + +pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<(String, String)> { + let parse = db.parse(position.file_id); + let file = parse.tree(); + let name_ref = find_node_at_offset::(file.syntax(), position.offset)?; + let mac = name_ref.syntax().ancestors().find_map(ast::MacroCall::cast)?; + + let source = hir::Source::new(position.file_id.into(), mac.syntax()); + + let expanded = expand_macro_recur(db, source, &mac)?; + + // FIXME: + // macro expansion may lose all white space information + // But we hope someday we can use ra_fmt for that + let res = insert_whitespaces(expanded); + Some((name_ref.text().to_string(), res)) +} + +#[cfg(test)] +mod tests { + use crate::mock_analysis::analysis_and_position; + + fn check_expand_macro(fixture: &str, expected: (&str, &str)) { + let (analysis, pos) = analysis_and_position(fixture); + + let result = analysis.expand_macro(pos).unwrap().unwrap(); + assert_eq!(result, (expected.0.to_string(), expected.1.to_string())); + } + + #[test] + fn macro_expand_recursive_expansion() { + check_expand_macro( + r#" + //- /lib.rs + macro_rules! bar { + () => { fn b() {} } + } + macro_rules! foo { + () => { bar!(); } + } + macro_rules! baz { + () => { foo!(); } + } + f<|>oo!(); + "#, + ("foo", "fn b(){}"), + ); + } +} diff --git a/crates/ra_ide_api/src/lib.rs b/crates/ra_ide_api/src/lib.rs index 110ddcd62..d1b73ef6f 100644 --- a/crates/ra_ide_api/src/lib.rs +++ b/crates/ra_ide_api/src/lib.rs @@ -42,6 +42,7 @@ mod display; mod inlay_hints; mod wasm_shims; mod expand; +mod expand_macro; #[cfg(test)] mod marks; @@ -296,6 +297,10 @@ impl Analysis { self.with_db(|db| syntax_tree::syntax_tree(&db, file_id, text_range)) } + pub fn expand_macro(&self, position: FilePosition) -> Cancelable> { + self.with_db(|db| expand_macro::expand_macro(db, position)) + } + /// Returns an edit to remove all newlines in the range, cleaning up minor /// stuff like trailing commas. pub fn join_lines(&self, frange: FileRange) -> Cancelable { diff --git a/crates/ra_lsp_server/src/main_loop.rs b/crates/ra_lsp_server/src/main_loop.rs index 379dab438..f828efdee 100644 --- a/crates/ra_lsp_server/src/main_loop.rs +++ b/crates/ra_lsp_server/src/main_loop.rs @@ -436,6 +436,7 @@ fn on_request( })? .on::(handlers::handle_analyzer_status)? .on::(handlers::handle_syntax_tree)? + .on::(handlers::handle_expand_macro)? .on::(handlers::handle_on_type_formatting)? .on::(handlers::handle_document_symbol)? .on::(handlers::handle_workspace_symbol)? diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs index 20f9aee13..783b0a827 100644 --- a/crates/ra_lsp_server/src/main_loop/handlers.rs +++ b/crates/ra_lsp_server/src/main_loop/handlers.rs @@ -47,6 +47,21 @@ pub fn handle_syntax_tree(world: WorldSnapshot, params: req::SyntaxTreeParams) - Ok(res) } +pub fn handle_expand_macro( + world: WorldSnapshot, + params: req::ExpandMacroParams, +) -> Result> { + let _p = profile("handle_expand_macro"); + let file_id = params.text_document.try_conv_with(&world)?; + let line_index = world.analysis().file_line_index(file_id)?; + let offset = params.position.map(|p| p.conv_with(&line_index)); + + match offset { + None => Ok(None), + Some(offset) => Ok(world.analysis().expand_macro(FilePosition { file_id, offset })?), + } +} + pub fn handle_selection_range( world: WorldSnapshot, params: req::SelectionRangeParams, diff --git a/crates/ra_lsp_server/src/req.rs b/crates/ra_lsp_server/src/req.rs index d25fc5726..dbc0e9624 100644 --- a/crates/ra_lsp_server/src/req.rs +++ b/crates/ra_lsp_server/src/req.rs @@ -45,6 +45,21 @@ pub struct SyntaxTreeParams { pub range: Option, } +pub enum ExpandMacro {} + +impl Request for ExpandMacro { + type Params = ExpandMacroParams; + type Result = Option<(String, String)>; + const METHOD: &'static str = "rust-analyzer/expandMacro"; +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct ExpandMacroParams { + pub text_document: TextDocumentIdentifier, + pub position: Option, +} + pub enum SelectionRangeRequest {} impl Request for SelectionRangeRequest { diff --git a/editors/code/src/commands/expand_macro.ts b/editors/code/src/commands/expand_macro.ts new file mode 100644 index 000000000..bf1923190 --- /dev/null +++ b/editors/code/src/commands/expand_macro.ts @@ -0,0 +1,45 @@ +import * as vscode from 'vscode'; +import { Position, TextDocumentIdentifier } from 'vscode-languageclient'; +import { Server } from '../server'; + +type ExpandMacroResult = [string, string] + +function code_format([name, text]: [string, string]): vscode.MarkdownString { + const markdown = new vscode.MarkdownString(`#### Recursive expansion of ${name}! macro`); + markdown.appendCodeblock(text, 'rust'); + return markdown; +} + +export class ExpandMacroHoverProvider implements vscode.HoverProvider { + public provideHover( + document: vscode.TextDocument, + position: vscode.Position, + token: vscode.CancellationToken, + ): Thenable | null { + async function handle() { + const request: MacroExpandParams = { + textDocument: { uri: document.uri.toString() }, + position, + }; + const result = await Server.client.sendRequest( + 'rust-analyzer/expandMacro', + request + ); + if (result != null) { + const formated = code_format(result); + return new vscode.Hover(formated); + } + + return null; + }; + + return handle(); + } +} + + +interface MacroExpandParams { + textDocument: TextDocumentIdentifier; + position: Position; +} + diff --git a/editors/code/src/commands/index.ts b/editors/code/src/commands/index.ts index c194bd2ea..2ade6d331 100644 --- a/editors/code/src/commands/index.ts +++ b/editors/code/src/commands/index.ts @@ -1,5 +1,6 @@ import * as analyzerStatus from './analyzer_status'; import * as applySourceChange from './apply_source_change'; +import * as expandMacro from './expand_macro'; import * as inlayHints from './inlay_hints'; import * as joinLines from './join_lines'; import * as matchingBrace from './matching_brace'; @@ -11,6 +12,7 @@ import * as syntaxTree from './syntaxTree'; export { analyzerStatus, applySourceChange, + expandMacro, joinLines, matchingBrace, parentModule, diff --git a/editors/code/src/extension.ts b/editors/code/src/extension.ts index c06928d12..1dfa6046f 100644 --- a/editors/code/src/extension.ts +++ b/editors/code/src/extension.ts @@ -3,6 +3,7 @@ import * as lc from 'vscode-languageclient'; import * as commands from './commands'; import { CargoWatchProvider } from './commands/cargo_watch'; +import { ExpandMacroHoverProvider } from './commands/expand_macro' import { HintsUpdater } from './commands/inlay_hints'; import { interactivelyStartCargoWatch, @@ -91,11 +92,11 @@ export function activate(context: vscode.ExtensionContext) { const allNotifications: Iterable< [string, lc.GenericNotificationHandler] > = [ - [ - 'rust-analyzer/publishDecorations', - notifications.publishDecorations.handle - ] - ]; + [ + 'rust-analyzer/publishDecorations', + notifications.publishDecorations.handle + ] + ]; const syntaxTreeContentProvider = new SyntaxTreeContentProvider(); // The events below are plain old javascript events, triggered and handled by vscode @@ -121,6 +122,15 @@ export function activate(context: vscode.ExtensionContext) { context.subscriptions ); + const expandMacroContentProvider = new ExpandMacroHoverProvider(); + + disposeOnDeactivation( + vscode.languages.registerHoverProvider( + 'rust', + expandMacroContentProvider + ) + ); + const startServer = () => Server.start(allNotifications); const reloadCommand = () => reloadServer(startServer); -- cgit v1.2.3