mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-29 13:25:09 +00:00
Add recursive expand in vscode
This commit is contained in:
parent
d2782ab1c1
commit
3ccd05fedc
8 changed files with 210 additions and 5 deletions
112
crates/ra_ide_api/src/expand_macro.rs
Normal file
112
crates/ra_ide_api/src/expand_macro.rs
Normal file
|
@ -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<SyntaxNode> {
|
||||||
|
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::<ast::NameRef>(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(){}"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -42,6 +42,7 @@ mod display;
|
||||||
mod inlay_hints;
|
mod inlay_hints;
|
||||||
mod wasm_shims;
|
mod wasm_shims;
|
||||||
mod expand;
|
mod expand;
|
||||||
|
mod expand_macro;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod marks;
|
mod marks;
|
||||||
|
@ -296,6 +297,10 @@ impl Analysis {
|
||||||
self.with_db(|db| syntax_tree::syntax_tree(&db, file_id, text_range))
|
self.with_db(|db| syntax_tree::syntax_tree(&db, file_id, text_range))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn expand_macro(&self, position: FilePosition) -> Cancelable<Option<(String, String)>> {
|
||||||
|
self.with_db(|db| expand_macro::expand_macro(db, position))
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns an edit to remove all newlines in the range, cleaning up minor
|
/// Returns an edit to remove all newlines in the range, cleaning up minor
|
||||||
/// stuff like trailing commas.
|
/// stuff like trailing commas.
|
||||||
pub fn join_lines(&self, frange: FileRange) -> Cancelable<SourceChange> {
|
pub fn join_lines(&self, frange: FileRange) -> Cancelable<SourceChange> {
|
||||||
|
|
|
@ -436,6 +436,7 @@ fn on_request(
|
||||||
})?
|
})?
|
||||||
.on::<req::AnalyzerStatus>(handlers::handle_analyzer_status)?
|
.on::<req::AnalyzerStatus>(handlers::handle_analyzer_status)?
|
||||||
.on::<req::SyntaxTree>(handlers::handle_syntax_tree)?
|
.on::<req::SyntaxTree>(handlers::handle_syntax_tree)?
|
||||||
|
.on::<req::ExpandMacro>(handlers::handle_expand_macro)?
|
||||||
.on::<req::OnTypeFormatting>(handlers::handle_on_type_formatting)?
|
.on::<req::OnTypeFormatting>(handlers::handle_on_type_formatting)?
|
||||||
.on::<req::DocumentSymbolRequest>(handlers::handle_document_symbol)?
|
.on::<req::DocumentSymbolRequest>(handlers::handle_document_symbol)?
|
||||||
.on::<req::WorkspaceSymbol>(handlers::handle_workspace_symbol)?
|
.on::<req::WorkspaceSymbol>(handlers::handle_workspace_symbol)?
|
||||||
|
|
|
@ -47,6 +47,21 @@ pub fn handle_syntax_tree(world: WorldSnapshot, params: req::SyntaxTreeParams) -
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn handle_expand_macro(
|
||||||
|
world: WorldSnapshot,
|
||||||
|
params: req::ExpandMacroParams,
|
||||||
|
) -> Result<Option<(String, String)>> {
|
||||||
|
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(
|
pub fn handle_selection_range(
|
||||||
world: WorldSnapshot,
|
world: WorldSnapshot,
|
||||||
params: req::SelectionRangeParams,
|
params: req::SelectionRangeParams,
|
||||||
|
|
|
@ -45,6 +45,21 @@ pub struct SyntaxTreeParams {
|
||||||
pub range: Option<Range>,
|
pub range: Option<Range>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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<Position>,
|
||||||
|
}
|
||||||
|
|
||||||
pub enum SelectionRangeRequest {}
|
pub enum SelectionRangeRequest {}
|
||||||
|
|
||||||
impl Request for SelectionRangeRequest {
|
impl Request for SelectionRangeRequest {
|
||||||
|
|
45
editors/code/src/commands/expand_macro.ts
Normal file
45
editors/code/src/commands/expand_macro.ts
Normal file
|
@ -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<vscode.Hover | null> | null {
|
||||||
|
async function handle() {
|
||||||
|
const request: MacroExpandParams = {
|
||||||
|
textDocument: { uri: document.uri.toString() },
|
||||||
|
position,
|
||||||
|
};
|
||||||
|
const result = await Server.client.sendRequest<ExpandMacroResult>(
|
||||||
|
'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;
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import * as analyzerStatus from './analyzer_status';
|
import * as analyzerStatus from './analyzer_status';
|
||||||
import * as applySourceChange from './apply_source_change';
|
import * as applySourceChange from './apply_source_change';
|
||||||
|
import * as expandMacro from './expand_macro';
|
||||||
import * as inlayHints from './inlay_hints';
|
import * as inlayHints from './inlay_hints';
|
||||||
import * as joinLines from './join_lines';
|
import * as joinLines from './join_lines';
|
||||||
import * as matchingBrace from './matching_brace';
|
import * as matchingBrace from './matching_brace';
|
||||||
|
@ -11,6 +12,7 @@ import * as syntaxTree from './syntaxTree';
|
||||||
export {
|
export {
|
||||||
analyzerStatus,
|
analyzerStatus,
|
||||||
applySourceChange,
|
applySourceChange,
|
||||||
|
expandMacro,
|
||||||
joinLines,
|
joinLines,
|
||||||
matchingBrace,
|
matchingBrace,
|
||||||
parentModule,
|
parentModule,
|
||||||
|
|
|
@ -3,6 +3,7 @@ import * as lc from 'vscode-languageclient';
|
||||||
|
|
||||||
import * as commands from './commands';
|
import * as commands from './commands';
|
||||||
import { CargoWatchProvider } from './commands/cargo_watch';
|
import { CargoWatchProvider } from './commands/cargo_watch';
|
||||||
|
import { ExpandMacroHoverProvider } from './commands/expand_macro'
|
||||||
import { HintsUpdater } from './commands/inlay_hints';
|
import { HintsUpdater } from './commands/inlay_hints';
|
||||||
import {
|
import {
|
||||||
interactivelyStartCargoWatch,
|
interactivelyStartCargoWatch,
|
||||||
|
@ -91,11 +92,11 @@ export function activate(context: vscode.ExtensionContext) {
|
||||||
const allNotifications: Iterable<
|
const allNotifications: Iterable<
|
||||||
[string, lc.GenericNotificationHandler]
|
[string, lc.GenericNotificationHandler]
|
||||||
> = [
|
> = [
|
||||||
[
|
[
|
||||||
'rust-analyzer/publishDecorations',
|
'rust-analyzer/publishDecorations',
|
||||||
notifications.publishDecorations.handle
|
notifications.publishDecorations.handle
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
const syntaxTreeContentProvider = new SyntaxTreeContentProvider();
|
const syntaxTreeContentProvider = new SyntaxTreeContentProvider();
|
||||||
|
|
||||||
// The events below are plain old javascript events, triggered and handled by vscode
|
// 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
|
context.subscriptions
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const expandMacroContentProvider = new ExpandMacroHoverProvider();
|
||||||
|
|
||||||
|
disposeOnDeactivation(
|
||||||
|
vscode.languages.registerHoverProvider(
|
||||||
|
'rust',
|
||||||
|
expandMacroContentProvider
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
const startServer = () => Server.start(allNotifications);
|
const startServer = () => Server.start(allNotifications);
|
||||||
const reloadCommand = () => reloadServer(startServer);
|
const reloadCommand = () => reloadServer(startServer);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue