cf55806257
The selection is also used to avoid unnecessary work, but only to the file level. Further restricting unnecessary work is left for later.
493 lines
17 KiB
TypeScript
493 lines
17 KiB
TypeScript
import * as vscode from 'vscode';
|
|
import * as lc from 'vscode-languageclient';
|
|
import * as ra from './lsp_ext';
|
|
|
|
import { Ctx, Cmd } from './ctx';
|
|
import { applySnippetWorkspaceEdit, applySnippetTextEdits } from './snippets';
|
|
import { spawnSync } from 'child_process';
|
|
import { RunnableQuickPick, selectRunnable, createTask } from './run';
|
|
import { AstInspector } from './ast_inspector';
|
|
import { isRustDocument, sleep, isRustEditor } from './util';
|
|
import { startDebugSession, makeDebugConfig } from './debug';
|
|
|
|
export * from './ast_inspector';
|
|
export * from './run';
|
|
|
|
export function analyzerStatus(ctx: Ctx): Cmd {
|
|
const tdcp = new class implements vscode.TextDocumentContentProvider {
|
|
readonly uri = vscode.Uri.parse('rust-analyzer-status://status');
|
|
readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>();
|
|
|
|
provideTextDocumentContent(_uri: vscode.Uri): vscode.ProviderResult<string> {
|
|
if (!vscode.window.activeTextEditor) return '';
|
|
|
|
return ctx.client.sendRequest(ra.analyzerStatus, null);
|
|
}
|
|
|
|
get onDidChange(): vscode.Event<vscode.Uri> {
|
|
return this.eventEmitter.event;
|
|
}
|
|
}();
|
|
|
|
let poller: NodeJS.Timer | undefined = undefined;
|
|
|
|
ctx.pushCleanup(
|
|
vscode.workspace.registerTextDocumentContentProvider(
|
|
'rust-analyzer-status',
|
|
tdcp,
|
|
),
|
|
);
|
|
|
|
ctx.pushCleanup({
|
|
dispose() {
|
|
if (poller !== undefined) {
|
|
clearInterval(poller);
|
|
}
|
|
},
|
|
});
|
|
|
|
return async () => {
|
|
if (poller === undefined) {
|
|
poller = setInterval(() => tdcp.eventEmitter.fire(tdcp.uri), 1000);
|
|
}
|
|
const document = await vscode.workspace.openTextDocument(tdcp.uri);
|
|
return vscode.window.showTextDocument(document, vscode.ViewColumn.Two, true);
|
|
};
|
|
}
|
|
|
|
export function memoryUsage(ctx: Ctx): Cmd {
|
|
const tdcp = new class implements vscode.TextDocumentContentProvider {
|
|
readonly uri = vscode.Uri.parse('rust-analyzer-memory://memory');
|
|
readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>();
|
|
|
|
provideTextDocumentContent(_uri: vscode.Uri): vscode.ProviderResult<string> {
|
|
if (!vscode.window.activeTextEditor) return '';
|
|
|
|
return ctx.client.sendRequest(ra.memoryUsage, null).then((mem) => {
|
|
return 'Per-query memory usage:\n' + mem + '\n(note: database has been cleared)';
|
|
});
|
|
}
|
|
|
|
get onDidChange(): vscode.Event<vscode.Uri> {
|
|
return this.eventEmitter.event;
|
|
}
|
|
}();
|
|
|
|
ctx.pushCleanup(
|
|
vscode.workspace.registerTextDocumentContentProvider(
|
|
'rust-analyzer-memory',
|
|
tdcp,
|
|
),
|
|
);
|
|
|
|
return async () => {
|
|
tdcp.eventEmitter.fire(tdcp.uri);
|
|
const document = await vscode.workspace.openTextDocument(tdcp.uri);
|
|
return vscode.window.showTextDocument(document, vscode.ViewColumn.Two, true);
|
|
};
|
|
}
|
|
|
|
export function matchingBrace(ctx: Ctx): Cmd {
|
|
return async () => {
|
|
const editor = ctx.activeRustEditor;
|
|
const client = ctx.client;
|
|
if (!editor || !client) return;
|
|
|
|
const response = await client.sendRequest(ra.matchingBrace, {
|
|
textDocument: { uri: editor.document.uri.toString() },
|
|
positions: editor.selections.map(s =>
|
|
client.code2ProtocolConverter.asPosition(s.active),
|
|
),
|
|
});
|
|
editor.selections = editor.selections.map((sel, idx) => {
|
|
const active = client.protocol2CodeConverter.asPosition(
|
|
response[idx],
|
|
);
|
|
const anchor = sel.isEmpty ? active : sel.anchor;
|
|
return new vscode.Selection(anchor, active);
|
|
});
|
|
editor.revealRange(editor.selection);
|
|
};
|
|
}
|
|
|
|
export function joinLines(ctx: Ctx): Cmd {
|
|
return async () => {
|
|
const editor = ctx.activeRustEditor;
|
|
const client = ctx.client;
|
|
if (!editor || !client) return;
|
|
|
|
const items: lc.TextEdit[] = await client.sendRequest(ra.joinLines, {
|
|
ranges: editor.selections.map((it) => client.code2ProtocolConverter.asRange(it)),
|
|
textDocument: { uri: editor.document.uri.toString() },
|
|
});
|
|
editor.edit((builder) => {
|
|
client.protocol2CodeConverter.asTextEdits(items).forEach((edit) => {
|
|
builder.replace(edit.range, edit.newText);
|
|
});
|
|
});
|
|
};
|
|
}
|
|
|
|
export function onEnter(ctx: Ctx): Cmd {
|
|
async function handleKeypress() {
|
|
const editor = ctx.activeRustEditor;
|
|
const client = ctx.client;
|
|
|
|
if (!editor || !client) return false;
|
|
|
|
const lcEdits = await client.sendRequest(ra.onEnter, {
|
|
textDocument: { uri: editor.document.uri.toString() },
|
|
position: client.code2ProtocolConverter.asPosition(
|
|
editor.selection.active,
|
|
),
|
|
}).catch(_error => {
|
|
// client.logFailedRequest(OnEnterRequest.type, error);
|
|
return null;
|
|
});
|
|
if (!lcEdits) return false;
|
|
|
|
const edits = client.protocol2CodeConverter.asTextEdits(lcEdits);
|
|
await applySnippetTextEdits(editor, edits);
|
|
return true;
|
|
}
|
|
|
|
return async () => {
|
|
if (await handleKeypress()) return;
|
|
|
|
await vscode.commands.executeCommand('default:type', { text: '\n' });
|
|
};
|
|
}
|
|
|
|
export function parentModule(ctx: Ctx): Cmd {
|
|
return async () => {
|
|
const editor = ctx.activeRustEditor;
|
|
const client = ctx.client;
|
|
if (!editor || !client) return;
|
|
|
|
const response = await client.sendRequest(ra.parentModule, {
|
|
textDocument: { uri: editor.document.uri.toString() },
|
|
position: client.code2ProtocolConverter.asPosition(
|
|
editor.selection.active,
|
|
),
|
|
});
|
|
const loc = response[0];
|
|
if (!loc) return;
|
|
|
|
const uri = client.protocol2CodeConverter.asUri(loc.targetUri);
|
|
const range = client.protocol2CodeConverter.asRange(loc.targetRange);
|
|
|
|
const doc = await vscode.workspace.openTextDocument(uri);
|
|
const e = await vscode.window.showTextDocument(doc);
|
|
e.selection = new vscode.Selection(range.start, range.start);
|
|
e.revealRange(range, vscode.TextEditorRevealType.InCenter);
|
|
};
|
|
}
|
|
|
|
export function ssr(ctx: Ctx): Cmd {
|
|
return async () => {
|
|
const editor = vscode.window.activeTextEditor;
|
|
const client = ctx.client;
|
|
if (!editor || !client) return;
|
|
|
|
const position = editor.selection.active;
|
|
const selections = editor.selections;
|
|
const textDocument = { uri: editor.document.uri.toString() };
|
|
|
|
const options: vscode.InputBoxOptions = {
|
|
value: "() ==>> ()",
|
|
prompt: "Enter request, for example 'Foo($a) ==> Foo::new($a)' ",
|
|
validateInput: async (x: string) => {
|
|
try {
|
|
await client.sendRequest(ra.ssr, {
|
|
query: x, parseOnly: true, textDocument, position, selections,
|
|
});
|
|
} catch (e) {
|
|
return e.toString();
|
|
}
|
|
return null;
|
|
}
|
|
};
|
|
const request = await vscode.window.showInputBox(options);
|
|
if (!request) return;
|
|
|
|
vscode.window.withProgress({
|
|
location: vscode.ProgressLocation.Notification,
|
|
title: "Structured search replace in progress...",
|
|
cancellable: false,
|
|
}, async (_progress, _token) => {
|
|
const edit = await client.sendRequest(ra.ssr, {
|
|
query: request, parseOnly: false, textDocument, position, selections,
|
|
});
|
|
|
|
await vscode.workspace.applyEdit(client.protocol2CodeConverter.asWorkspaceEdit(edit));
|
|
});
|
|
};
|
|
}
|
|
|
|
export function serverVersion(ctx: Ctx): Cmd {
|
|
return async () => {
|
|
const { stdout } = spawnSync(ctx.serverPath, ["--version"], { encoding: "utf8" });
|
|
const commitHash = stdout.slice(`rust-analyzer `.length).trim();
|
|
const { releaseTag } = ctx.config.package;
|
|
|
|
void vscode.window.showInformationMessage(
|
|
`rust-analyzer version: ${releaseTag ?? "unreleased"} (${commitHash})`
|
|
);
|
|
};
|
|
}
|
|
|
|
export function toggleInlayHints(ctx: Ctx): Cmd {
|
|
return async () => {
|
|
await vscode
|
|
.workspace
|
|
.getConfiguration(`${ctx.config.rootSection}.inlayHints`)
|
|
.update('enable', !ctx.config.inlayHints.enable, vscode.ConfigurationTarget.Workspace);
|
|
};
|
|
}
|
|
|
|
// Opens the virtual file that will show the syntax tree
|
|
//
|
|
// The contents of the file come from the `TextDocumentContentProvider`
|
|
export function syntaxTree(ctx: Ctx): Cmd {
|
|
const tdcp = new class implements vscode.TextDocumentContentProvider {
|
|
readonly uri = vscode.Uri.parse('rust-analyzer://syntaxtree/tree.rast');
|
|
readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>();
|
|
constructor() {
|
|
vscode.workspace.onDidChangeTextDocument(this.onDidChangeTextDocument, this, ctx.subscriptions);
|
|
vscode.window.onDidChangeActiveTextEditor(this.onDidChangeActiveTextEditor, this, ctx.subscriptions);
|
|
}
|
|
|
|
private onDidChangeTextDocument(event: vscode.TextDocumentChangeEvent) {
|
|
if (isRustDocument(event.document)) {
|
|
// We need to order this after language server updates, but there's no API for that.
|
|
// Hence, good old sleep().
|
|
void sleep(10).then(() => this.eventEmitter.fire(this.uri));
|
|
}
|
|
}
|
|
private onDidChangeActiveTextEditor(editor: vscode.TextEditor | undefined) {
|
|
if (editor && isRustEditor(editor)) {
|
|
this.eventEmitter.fire(this.uri);
|
|
}
|
|
}
|
|
|
|
provideTextDocumentContent(uri: vscode.Uri, ct: vscode.CancellationToken): vscode.ProviderResult<string> {
|
|
const rustEditor = ctx.activeRustEditor;
|
|
if (!rustEditor) return '';
|
|
|
|
// When the range based query is enabled we take the range of the selection
|
|
const range = uri.query === 'range=true' && !rustEditor.selection.isEmpty
|
|
? ctx.client.code2ProtocolConverter.asRange(rustEditor.selection)
|
|
: null;
|
|
|
|
const params = { textDocument: { uri: rustEditor.document.uri.toString() }, range, };
|
|
return ctx.client.sendRequest(ra.syntaxTree, params, ct);
|
|
}
|
|
|
|
get onDidChange(): vscode.Event<vscode.Uri> {
|
|
return this.eventEmitter.event;
|
|
}
|
|
};
|
|
|
|
void new AstInspector(ctx);
|
|
|
|
ctx.pushCleanup(vscode.workspace.registerTextDocumentContentProvider('rust-analyzer', tdcp));
|
|
ctx.pushCleanup(vscode.languages.setLanguageConfiguration("ra_syntax_tree", {
|
|
brackets: [["[", ")"]],
|
|
}));
|
|
|
|
return async () => {
|
|
const editor = vscode.window.activeTextEditor;
|
|
const rangeEnabled = !!editor && !editor.selection.isEmpty;
|
|
|
|
const uri = rangeEnabled
|
|
? vscode.Uri.parse(`${tdcp.uri.toString()}?range=true`)
|
|
: tdcp.uri;
|
|
|
|
const document = await vscode.workspace.openTextDocument(uri);
|
|
|
|
tdcp.eventEmitter.fire(uri);
|
|
|
|
void await vscode.window.showTextDocument(document, {
|
|
viewColumn: vscode.ViewColumn.Two,
|
|
preserveFocus: true
|
|
});
|
|
};
|
|
}
|
|
|
|
|
|
// Opens the virtual file that will show the syntax tree
|
|
//
|
|
// The contents of the file come from the `TextDocumentContentProvider`
|
|
export function expandMacro(ctx: Ctx): Cmd {
|
|
function codeFormat(expanded: ra.ExpandedMacro): string {
|
|
let result = `// Recursive expansion of ${expanded.name}! macro\n`;
|
|
result += '// ' + '='.repeat(result.length - 3);
|
|
result += '\n\n';
|
|
result += expanded.expansion;
|
|
|
|
return result;
|
|
}
|
|
|
|
const tdcp = new class implements vscode.TextDocumentContentProvider {
|
|
uri = vscode.Uri.parse('rust-analyzer://expandMacro/[EXPANSION].rs');
|
|
eventEmitter = new vscode.EventEmitter<vscode.Uri>();
|
|
async provideTextDocumentContent(_uri: vscode.Uri): Promise<string> {
|
|
const editor = vscode.window.activeTextEditor;
|
|
const client = ctx.client;
|
|
if (!editor || !client) return '';
|
|
|
|
const position = editor.selection.active;
|
|
|
|
const expanded = await client.sendRequest(ra.expandMacro, {
|
|
textDocument: { uri: editor.document.uri.toString() },
|
|
position,
|
|
});
|
|
|
|
if (expanded == null) return 'Not available';
|
|
|
|
return codeFormat(expanded);
|
|
}
|
|
|
|
get onDidChange(): vscode.Event<vscode.Uri> {
|
|
return this.eventEmitter.event;
|
|
}
|
|
}();
|
|
|
|
ctx.pushCleanup(
|
|
vscode.workspace.registerTextDocumentContentProvider(
|
|
'rust-analyzer',
|
|
tdcp,
|
|
),
|
|
);
|
|
|
|
return async () => {
|
|
const document = await vscode.workspace.openTextDocument(tdcp.uri);
|
|
tdcp.eventEmitter.fire(tdcp.uri);
|
|
return vscode.window.showTextDocument(
|
|
document,
|
|
vscode.ViewColumn.Two,
|
|
true,
|
|
);
|
|
};
|
|
}
|
|
|
|
export function reloadWorkspace(ctx: Ctx): Cmd {
|
|
return async () => ctx.client.sendRequest(ra.reloadWorkspace, null);
|
|
}
|
|
|
|
export function showReferences(ctx: Ctx): Cmd {
|
|
return (uri: string, position: lc.Position, locations: lc.Location[]) => {
|
|
const client = ctx.client;
|
|
if (client) {
|
|
vscode.commands.executeCommand(
|
|
'editor.action.showReferences',
|
|
vscode.Uri.parse(uri),
|
|
client.protocol2CodeConverter.asPosition(position),
|
|
locations.map(client.protocol2CodeConverter.asLocation),
|
|
);
|
|
}
|
|
};
|
|
}
|
|
|
|
export function applyActionGroup(_ctx: Ctx): Cmd {
|
|
return async (actions: { label: string; arguments: ra.ResolveCodeActionParams }[]) => {
|
|
const selectedAction = await vscode.window.showQuickPick(actions);
|
|
if (!selectedAction) return;
|
|
vscode.commands.executeCommand(
|
|
'rust-analyzer.resolveCodeAction',
|
|
selectedAction.arguments,
|
|
);
|
|
};
|
|
}
|
|
|
|
export function gotoLocation(ctx: Ctx): Cmd {
|
|
return async (locationLink: lc.LocationLink) => {
|
|
const client = ctx.client;
|
|
if (client) {
|
|
const uri = client.protocol2CodeConverter.asUri(locationLink.targetUri);
|
|
let range = client.protocol2CodeConverter.asRange(locationLink.targetSelectionRange);
|
|
// collapse the range to a cursor position
|
|
range = range.with({ end: range.start });
|
|
|
|
await vscode.window.showTextDocument(uri, { selection: range });
|
|
}
|
|
};
|
|
}
|
|
|
|
export function resolveCodeAction(ctx: Ctx): Cmd {
|
|
const client = ctx.client;
|
|
return async (params: ra.ResolveCodeActionParams) => {
|
|
const item: lc.WorkspaceEdit = await client.sendRequest(ra.resolveCodeAction, params);
|
|
if (!item) {
|
|
return;
|
|
}
|
|
const edit = client.protocol2CodeConverter.asWorkspaceEdit(item);
|
|
await applySnippetWorkspaceEdit(edit);
|
|
};
|
|
}
|
|
|
|
export function applySnippetWorkspaceEditCommand(_ctx: Ctx): Cmd {
|
|
return async (edit: vscode.WorkspaceEdit) => {
|
|
await applySnippetWorkspaceEdit(edit);
|
|
};
|
|
}
|
|
|
|
export function run(ctx: Ctx): Cmd {
|
|
let prevRunnable: RunnableQuickPick | undefined;
|
|
|
|
return async () => {
|
|
const item = await selectRunnable(ctx, prevRunnable);
|
|
if (!item) return;
|
|
|
|
item.detail = 'rerun';
|
|
prevRunnable = item;
|
|
const task = await createTask(item.runnable, ctx.config);
|
|
return await vscode.tasks.executeTask(task);
|
|
};
|
|
}
|
|
|
|
export function runSingle(ctx: Ctx): Cmd {
|
|
return async (runnable: ra.Runnable) => {
|
|
const editor = ctx.activeRustEditor;
|
|
if (!editor) return;
|
|
|
|
const task = await createTask(runnable, ctx.config);
|
|
task.group = vscode.TaskGroup.Build;
|
|
task.presentationOptions = {
|
|
reveal: vscode.TaskRevealKind.Always,
|
|
panel: vscode.TaskPanelKind.Dedicated,
|
|
clear: true,
|
|
};
|
|
|
|
return vscode.tasks.executeTask(task);
|
|
};
|
|
}
|
|
|
|
export function debug(ctx: Ctx): Cmd {
|
|
let prevDebuggee: RunnableQuickPick | undefined;
|
|
|
|
return async () => {
|
|
const item = await selectRunnable(ctx, prevDebuggee, true);
|
|
if (!item) return;
|
|
|
|
item.detail = 'restart';
|
|
prevDebuggee = item;
|
|
return await startDebugSession(ctx, item.runnable);
|
|
};
|
|
}
|
|
|
|
export function debugSingle(ctx: Ctx): Cmd {
|
|
return async (config: ra.Runnable) => {
|
|
await startDebugSession(ctx, config);
|
|
};
|
|
}
|
|
|
|
export function newDebugConfig(ctx: Ctx): Cmd {
|
|
return async () => {
|
|
const item = await selectRunnable(ctx, undefined, true, false);
|
|
if (!item) return;
|
|
|
|
await makeDebugConfig(ctx, item.runnable);
|
|
};
|
|
}
|