From 6058b8b0f6a24ad5b905d99d780a31b9e3d578d7 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 25 May 2020 12:02:30 +0200 Subject: Flatten commands.ts --- editors/code/src/ast_inspector.ts | 185 ++++++++++++++++ editors/code/src/commands.ts | 370 +++++++++++++++++++++++++++++++ editors/code/src/commands/index.ts | 283 ----------------------- editors/code/src/commands/runnables.ts | 218 ------------------ editors/code/src/commands/syntax_tree.ts | 263 ---------------------- editors/code/src/main.ts | 1 - editors/code/src/run.ts | 204 +++++++++++++++++ 7 files changed, 759 insertions(+), 765 deletions(-) create mode 100644 editors/code/src/ast_inspector.ts create mode 100644 editors/code/src/commands.ts delete mode 100644 editors/code/src/commands/index.ts delete mode 100644 editors/code/src/commands/runnables.ts delete mode 100644 editors/code/src/commands/syntax_tree.ts create mode 100644 editors/code/src/run.ts (limited to 'editors/code/src') diff --git a/editors/code/src/ast_inspector.ts b/editors/code/src/ast_inspector.ts new file mode 100644 index 000000000..4fdd167bd --- /dev/null +++ b/editors/code/src/ast_inspector.ts @@ -0,0 +1,185 @@ +import * as vscode from 'vscode'; + +import { Ctx, Disposable } from './ctx'; +import { RustEditor, isRustEditor } from './util'; + +// FIXME: consider implementing this via the Tree View API? +// https://code.visualstudio.com/api/extension-guides/tree-view +export class AstInspector implements vscode.HoverProvider, vscode.DefinitionProvider, Disposable { + private readonly astDecorationType = vscode.window.createTextEditorDecorationType({ + borderColor: new vscode.ThemeColor('rust_analyzer.syntaxTreeBorder'), + borderStyle: "solid", + borderWidth: "2px", + }); + private rustEditor: undefined | RustEditor; + + // Lazy rust token range -> syntax tree file range. + private readonly rust2Ast = new Lazy(() => { + const astEditor = this.findAstTextEditor(); + if (!this.rustEditor || !astEditor) return undefined; + + const buf: [vscode.Range, vscode.Range][] = []; + for (let i = 0; i < astEditor.document.lineCount; ++i) { + const astLine = astEditor.document.lineAt(i); + + // Heuristically look for nodes with quoted text (which are token nodes) + const isTokenNode = astLine.text.lastIndexOf('"') >= 0; + if (!isTokenNode) continue; + + const rustRange = this.parseRustTextRange(this.rustEditor.document, astLine.text); + if (!rustRange) continue; + + buf.push([rustRange, this.findAstNodeRange(astLine)]); + } + return buf; + }); + + constructor(ctx: Ctx) { + ctx.pushCleanup(vscode.languages.registerHoverProvider({ scheme: 'rust-analyzer' }, this)); + ctx.pushCleanup(vscode.languages.registerDefinitionProvider({ language: "rust" }, this)); + vscode.workspace.onDidCloseTextDocument(this.onDidCloseTextDocument, this, ctx.subscriptions); + vscode.workspace.onDidChangeTextDocument(this.onDidChangeTextDocument, this, ctx.subscriptions); + vscode.window.onDidChangeVisibleTextEditors(this.onDidChangeVisibleTextEditors, this, ctx.subscriptions); + + ctx.pushCleanup(this); + } + dispose() { + this.setRustEditor(undefined); + } + + private onDidChangeTextDocument(event: vscode.TextDocumentChangeEvent) { + if (this.rustEditor && event.document.uri.toString() === this.rustEditor.document.uri.toString()) { + this.rust2Ast.reset(); + } + } + + private onDidCloseTextDocument(doc: vscode.TextDocument) { + if (this.rustEditor && doc.uri.toString() === this.rustEditor.document.uri.toString()) { + this.setRustEditor(undefined); + } + } + + private onDidChangeVisibleTextEditors(editors: vscode.TextEditor[]) { + if (!this.findAstTextEditor()) { + this.setRustEditor(undefined); + return; + } + this.setRustEditor(editors.find(isRustEditor)); + } + + private findAstTextEditor(): undefined | vscode.TextEditor { + return vscode.window.visibleTextEditors.find(it => it.document.uri.scheme === 'rust-analyzer'); + } + + private setRustEditor(newRustEditor: undefined | RustEditor) { + if (this.rustEditor && this.rustEditor !== newRustEditor) { + this.rustEditor.setDecorations(this.astDecorationType, []); + this.rust2Ast.reset(); + } + this.rustEditor = newRustEditor; + } + + // additional positional params are omitted + provideDefinition(doc: vscode.TextDocument, pos: vscode.Position): vscode.ProviderResult { + if (!this.rustEditor || doc.uri.toString() !== this.rustEditor.document.uri.toString()) return; + + const astEditor = this.findAstTextEditor(); + if (!astEditor) return; + + const rust2AstRanges = this.rust2Ast.get()?.find(([rustRange, _]) => rustRange.contains(pos)); + if (!rust2AstRanges) return; + + const [rustFileRange, astFileRange] = rust2AstRanges; + + astEditor.revealRange(astFileRange); + astEditor.selection = new vscode.Selection(astFileRange.start, astFileRange.end); + + return [{ + targetRange: astFileRange, + targetUri: astEditor.document.uri, + originSelectionRange: rustFileRange, + targetSelectionRange: astFileRange, + }]; + } + + // additional positional params are omitted + provideHover(doc: vscode.TextDocument, hoverPosition: vscode.Position): vscode.ProviderResult { + if (!this.rustEditor) return; + + const astFileLine = doc.lineAt(hoverPosition.line); + + const rustFileRange = this.parseRustTextRange(this.rustEditor.document, astFileLine.text); + if (!rustFileRange) return; + + this.rustEditor.setDecorations(this.astDecorationType, [rustFileRange]); + this.rustEditor.revealRange(rustFileRange); + + const rustSourceCode = this.rustEditor.document.getText(rustFileRange); + const astFileRange = this.findAstNodeRange(astFileLine); + + return new vscode.Hover(["```rust\n" + rustSourceCode + "\n```"], astFileRange); + } + + private findAstNodeRange(astLine: vscode.TextLine): vscode.Range { + const lineOffset = astLine.range.start; + const begin = lineOffset.translate(undefined, astLine.firstNonWhitespaceCharacterIndex); + const end = lineOffset.translate(undefined, astLine.text.trimEnd().length); + return new vscode.Range(begin, end); + } + + private parseRustTextRange(doc: vscode.TextDocument, astLine: string): undefined | vscode.Range { + const parsedRange = /(\d+)\.\.(\d+)/.exec(astLine); + if (!parsedRange) return; + + const [begin, end] = parsedRange + .slice(1) + .map(off => this.positionAt(doc, +off)); + + return new vscode.Range(begin, end); + } + + // Memoize the last value, otherwise the CPU is at 100% single core + // with quadratic lookups when we build rust2Ast cache + cache?: { doc: vscode.TextDocument; offset: number; line: number }; + + positionAt(doc: vscode.TextDocument, targetOffset: number): vscode.Position { + if (doc.eol === vscode.EndOfLine.LF) { + return doc.positionAt(targetOffset); + } + + // Dirty workaround for crlf line endings + // We are still in this prehistoric era of carriage returns here... + + let line = 0; + let offset = 0; + + const cache = this.cache; + if (cache?.doc === doc && cache.offset <= targetOffset) { + ({ line, offset } = cache); + } + + while (true) { + const lineLenWithLf = doc.lineAt(line).text.length + 1; + if (offset + lineLenWithLf > targetOffset) { + this.cache = { doc, offset, line }; + return doc.positionAt(targetOffset + line); + } + offset += lineLenWithLf; + line += 1; + } + } +} + +class Lazy { + val: undefined | T; + + constructor(private readonly compute: () => undefined | T) { } + + get() { + return this.val ?? (this.val = this.compute()); + } + + reset() { + this.val = undefined; + } +} diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts new file mode 100644 index 000000000..573af5aa5 --- /dev/null +++ b/editors/code/src/commands.ts @@ -0,0 +1,370 @@ +import * as vscode from 'vscode'; +import * as lc from 'vscode-languageclient'; +import * as ra from './rust-analyzer-api'; + +import { Ctx, Cmd } from './ctx'; +import { applySnippetWorkspaceEdit } from './snippets'; +import { spawnSync } from 'child_process'; +import { RunnableQuickPick, selectRunnable, createTask } from './run'; +import { AstInspector } from './ast_inspector'; +import { isRustDocument, sleep, isRustEditor } from './util'; + +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(); + + provideTextDocumentContent(_uri: vscode.Uri): vscode.ProviderResult { + if (!vscode.window.activeTextEditor) return ''; + + return ctx.client.sendRequest(ra.analyzerStatus, null); + } + + get onDidChange(): vscode.Event { + 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 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 change = 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 (!change) return false; + + const workspaceEdit = client.protocol2CodeConverter.asWorkspaceEdit(change); + await applySnippetWorkspaceEdit(workspaceEdit); + 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 == null) return; + + const uri = client.protocol2CodeConverter.asUri(loc.uri); + const range = client.protocol2CodeConverter.asRange(loc.range); + + 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 client = ctx.client; + if (!client) return; + + const options: vscode.InputBoxOptions = { + value: "() ==>> ()", + prompt: "Enter request, for example 'Foo($a:expr) ==> Foo::new($a)' ", + validateInput: async (x: string) => { + try { + await client.sendRequest(ra.ssr, { query: x, parseOnly: true }); + } catch (e) { + return e.toString(); + } + return null; + } + }; + const request = await vscode.window.showInputBox(options); + if (!request) return; + + const edit = await client.sendRequest(ra.ssr, { query: request, parseOnly: false }); + + 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); + }; +} + +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 = createTask(item.runnable); + return await vscode.tasks.executeTask(task); + }; +} + +// 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(); + 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 { + 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 { + 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(); + async provideTextDocumentContent(_uri: vscode.Uri): Promise { + 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 { + 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 collectGarbage(ctx: Ctx): Cmd { + return async () => ctx.client.sendRequest(ra.collectGarbage, 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; edit: vscode.WorkspaceEdit }[]) => { + const selectedAction = await vscode.window.showQuickPick(actions); + if (!selectedAction) return; + await applySnippetWorkspaceEdit(selectedAction.edit); + }; +} + +export function applySnippetWorkspaceEditCommand(_ctx: Ctx): Cmd { + return async (edit: vscode.WorkspaceEdit) => { + await applySnippetWorkspaceEdit(edit); + }; +} diff --git a/editors/code/src/commands/index.ts b/editors/code/src/commands/index.ts deleted file mode 100644 index 1585912a2..000000000 --- a/editors/code/src/commands/index.ts +++ /dev/null @@ -1,283 +0,0 @@ -import * as vscode from 'vscode'; -import * as lc from 'vscode-languageclient'; -import * as ra from '../rust-analyzer-api'; - -import { Ctx, Cmd } from '../ctx'; -import { applySnippetWorkspaceEdit } from '../snippets'; -import { spawnSync } from 'child_process'; - -export * from './syntax_tree'; -export * from './runnables'; - -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(); - - provideTextDocumentContent(_uri: vscode.Uri): vscode.ProviderResult { - if (!vscode.window.activeTextEditor) return ''; - - return ctx.client.sendRequest(ra.analyzerStatus, null); - } - - get onDidChange(): vscode.Event { - 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 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 change = 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 (!change) return false; - - const workspaceEdit = client.protocol2CodeConverter.asWorkspaceEdit(change); - await applySnippetWorkspaceEdit(workspaceEdit); - 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 == null) return; - - const uri = client.protocol2CodeConverter.asUri(loc.uri); - const range = client.protocol2CodeConverter.asRange(loc.range); - - 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 client = ctx.client; - if (!client) return; - - const options: vscode.InputBoxOptions = { - value: "() ==>> ()", - prompt: "Enter request, for example 'Foo($a:expr) ==> Foo::new($a)' ", - validateInput: async (x: string) => { - try { - await client.sendRequest(ra.ssr, { query: x, parseOnly: true }); - } catch (e) { - return e.toString(); - } - return null; - } - }; - const request = await vscode.window.showInputBox(options); - if (!request) return; - - const edit = await client.sendRequest(ra.ssr, { query: request, parseOnly: false }); - - 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 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(); - async provideTextDocumentContent(_uri: vscode.Uri): Promise { - 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 { - 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 collectGarbage(ctx: Ctx): Cmd { - return async () => ctx.client.sendRequest(ra.collectGarbage, 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; edit: vscode.WorkspaceEdit }[]) => { - const selectedAction = await vscode.window.showQuickPick(actions); - if (!selectedAction) return; - await applySnippetWorkspaceEdit(selectedAction.edit); - }; -} - -export function applySnippetWorkspaceEditCommand(_ctx: Ctx): Cmd { - return async (edit: vscode.WorkspaceEdit) => { - await applySnippetWorkspaceEdit(edit); - }; -} diff --git a/editors/code/src/commands/runnables.ts b/editors/code/src/commands/runnables.ts deleted file mode 100644 index 0bd30fb07..000000000 --- a/editors/code/src/commands/runnables.ts +++ /dev/null @@ -1,218 +0,0 @@ -import * as vscode from 'vscode'; -import * as lc from 'vscode-languageclient'; -import * as ra from '../rust-analyzer-api'; - -import { Ctx, Cmd } from '../ctx'; -import { startDebugSession, getDebugConfiguration } from '../debug'; - -const quickPickButtons = [{ iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configurtation." }]; - -async function selectRunnable(ctx: Ctx, prevRunnable?: RunnableQuickPick, debuggeeOnly = false, showButtons: boolean = true): Promise { - const editor = ctx.activeRustEditor; - const client = ctx.client; - if (!editor || !client) return; - - const textDocument: lc.TextDocumentIdentifier = { - uri: editor.document.uri.toString(), - }; - - const runnables = await client.sendRequest(ra.runnables, { - textDocument, - position: client.code2ProtocolConverter.asPosition( - editor.selection.active, - ), - }); - const items: RunnableQuickPick[] = []; - if (prevRunnable) { - items.push(prevRunnable); - } - for (const r of runnables) { - if ( - prevRunnable && - JSON.stringify(prevRunnable.runnable) === JSON.stringify(r) - ) { - continue; - } - - if (debuggeeOnly && (r.label.startsWith('doctest') || r.label.startsWith('cargo'))) { - continue; - } - items.push(new RunnableQuickPick(r)); - } - - if (items.length === 0) { - // it is the debug case, run always has at least 'cargo check ...' - // see crates\rust-analyzer\src\main_loop\handlers.rs, handle_runnables - vscode.window.showErrorMessage("There's no debug target!"); - return; - } - - return await new Promise((resolve) => { - const disposables: vscode.Disposable[] = []; - const close = (result?: RunnableQuickPick) => { - resolve(result); - disposables.forEach(d => d.dispose()); - }; - - const quickPick = vscode.window.createQuickPick(); - quickPick.items = items; - quickPick.title = "Select Runnable"; - if (showButtons) { - quickPick.buttons = quickPickButtons; - } - disposables.push( - quickPick.onDidHide(() => close()), - quickPick.onDidAccept(() => close(quickPick.selectedItems[0])), - quickPick.onDidTriggerButton((_button) => { - (async () => await makeDebugConfig(ctx, quickPick.activeItems[0]))(); - close(); - }), - quickPick.onDidChangeActive((active) => { - if (showButtons && active.length > 0) { - if (active[0].label.startsWith('cargo')) { - // save button makes no sense for `cargo test` or `cargo check` - quickPick.buttons = []; - } else if (quickPick.buttons.length === 0) { - quickPick.buttons = quickPickButtons; - } - } - }), - quickPick - ); - quickPick.show(); - }); -} - -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 = createTask(item.runnable); - 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 = createTask(runnable); - 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); - }; -} - -async function makeDebugConfig(ctx: Ctx, item: RunnableQuickPick): Promise { - const scope = ctx.activeRustEditor?.document.uri; - if (!scope) return; - - const debugConfig = await getDebugConfiguration(ctx, item.runnable); - if (!debugConfig) return; - - const wsLaunchSection = vscode.workspace.getConfiguration("launch", scope); - const configurations = wsLaunchSection.get("configurations") || []; - - const index = configurations.findIndex(c => c.name === debugConfig.name); - if (index !== -1) { - const answer = await vscode.window.showErrorMessage(`Launch configuration '${debugConfig.name}' already exists!`, 'Cancel', 'Update'); - if (answer === "Cancel") return; - - configurations[index] = debugConfig; - } else { - configurations.push(debugConfig); - } - - await wsLaunchSection.update("configurations", configurations); -} - -export function newDebugConfig(ctx: Ctx): Cmd { - return async () => { - const item = await selectRunnable(ctx, undefined, true, false); - if (!item) return; - - await makeDebugConfig(ctx, item); - }; -} - -class RunnableQuickPick implements vscode.QuickPickItem { - public label: string; - public description?: string | undefined; - public detail?: string | undefined; - public picked?: boolean | undefined; - - constructor(public runnable: ra.Runnable) { - this.label = runnable.label; - } -} - -interface CargoTaskDefinition extends vscode.TaskDefinition { - type: 'cargo'; - label: string; - command: string; - args: string[]; - env?: { [key: string]: string }; -} - -function createTask(spec: ra.Runnable): vscode.Task { - const TASK_SOURCE = 'Rust'; - const definition: CargoTaskDefinition = { - type: 'cargo', - label: spec.label, - command: spec.bin, - args: spec.extraArgs ? [...spec.args, '--', ...spec.extraArgs] : spec.args, - env: spec.env, - }; - - const execOption: vscode.ShellExecutionOptions = { - cwd: spec.cwd || '.', - env: definition.env, - }; - const exec = new vscode.ShellExecution( - definition.command, - definition.args, - execOption, - ); - - const f = vscode.workspace.workspaceFolders![0]; - const t = new vscode.Task( - definition, - f, - definition.label, - TASK_SOURCE, - exec, - ['$rustc'], - ); - t.presentationOptions.clear = true; - return t; -} diff --git a/editors/code/src/commands/syntax_tree.ts b/editors/code/src/commands/syntax_tree.ts deleted file mode 100644 index a5446c327..000000000 --- a/editors/code/src/commands/syntax_tree.ts +++ /dev/null @@ -1,263 +0,0 @@ -import * as vscode from 'vscode'; -import * as ra from '../rust-analyzer-api'; - -import { Ctx, Cmd, Disposable } from '../ctx'; -import { isRustDocument, RustEditor, isRustEditor, sleep } from '../util'; - -const AST_FILE_SCHEME = "rust-analyzer"; - -// 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 TextDocumentContentProvider(ctx); - - void new AstInspector(ctx); - - ctx.pushCleanup(vscode.workspace.registerTextDocumentContentProvider(AST_FILE_SCHEME, 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 - }); - }; -} - -class TextDocumentContentProvider implements vscode.TextDocumentContentProvider { - readonly uri = vscode.Uri.parse('rust-analyzer://syntaxtree/tree.rast'); - readonly eventEmitter = new vscode.EventEmitter(); - - - constructor(private readonly ctx: Ctx) { - 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 { - const rustEditor = this.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 - ? this.ctx.client.code2ProtocolConverter.asRange(rustEditor.selection) - : null; - - const params = { textDocument: { uri: rustEditor.document.uri.toString() }, range, }; - return this.ctx.client.sendRequest(ra.syntaxTree, params, ct); - } - - get onDidChange(): vscode.Event { - return this.eventEmitter.event; - } -} - - -// FIXME: consider implementing this via the Tree View API? -// https://code.visualstudio.com/api/extension-guides/tree-view -class AstInspector implements vscode.HoverProvider, vscode.DefinitionProvider, Disposable { - private readonly astDecorationType = vscode.window.createTextEditorDecorationType({ - borderColor: new vscode.ThemeColor('rust_analyzer.syntaxTreeBorder'), - borderStyle: "solid", - borderWidth: "2px", - - }); - private rustEditor: undefined | RustEditor; - - // Lazy rust token range -> syntax tree file range. - private readonly rust2Ast = new Lazy(() => { - const astEditor = this.findAstTextEditor(); - if (!this.rustEditor || !astEditor) return undefined; - - const buf: [vscode.Range, vscode.Range][] = []; - for (let i = 0; i < astEditor.document.lineCount; ++i) { - const astLine = astEditor.document.lineAt(i); - - // Heuristically look for nodes with quoted text (which are token nodes) - const isTokenNode = astLine.text.lastIndexOf('"') >= 0; - if (!isTokenNode) continue; - - const rustRange = this.parseRustTextRange(this.rustEditor.document, astLine.text); - if (!rustRange) continue; - - buf.push([rustRange, this.findAstNodeRange(astLine)]); - } - return buf; - }); - - constructor(ctx: Ctx) { - ctx.pushCleanup(vscode.languages.registerHoverProvider({ scheme: AST_FILE_SCHEME }, this)); - ctx.pushCleanup(vscode.languages.registerDefinitionProvider({ language: "rust" }, this)); - vscode.workspace.onDidCloseTextDocument(this.onDidCloseTextDocument, this, ctx.subscriptions); - vscode.workspace.onDidChangeTextDocument(this.onDidChangeTextDocument, this, ctx.subscriptions); - vscode.window.onDidChangeVisibleTextEditors(this.onDidChangeVisibleTextEditors, this, ctx.subscriptions); - - ctx.pushCleanup(this); - } - dispose() { - this.setRustEditor(undefined); - } - - private onDidChangeTextDocument(event: vscode.TextDocumentChangeEvent) { - if (this.rustEditor && event.document.uri.toString() === this.rustEditor.document.uri.toString()) { - this.rust2Ast.reset(); - } - } - - private onDidCloseTextDocument(doc: vscode.TextDocument) { - if (this.rustEditor && doc.uri.toString() === this.rustEditor.document.uri.toString()) { - this.setRustEditor(undefined); - } - } - - private onDidChangeVisibleTextEditors(editors: vscode.TextEditor[]) { - if (!this.findAstTextEditor()) { - this.setRustEditor(undefined); - return; - } - this.setRustEditor(editors.find(isRustEditor)); - } - - private findAstTextEditor(): undefined | vscode.TextEditor { - return vscode.window.visibleTextEditors.find(it => it.document.uri.scheme === AST_FILE_SCHEME); - } - - private setRustEditor(newRustEditor: undefined | RustEditor) { - if (this.rustEditor && this.rustEditor !== newRustEditor) { - this.rustEditor.setDecorations(this.astDecorationType, []); - this.rust2Ast.reset(); - } - this.rustEditor = newRustEditor; - } - - // additional positional params are omitted - provideDefinition(doc: vscode.TextDocument, pos: vscode.Position): vscode.ProviderResult { - if (!this.rustEditor || doc.uri.toString() !== this.rustEditor.document.uri.toString()) return; - - const astEditor = this.findAstTextEditor(); - if (!astEditor) return; - - const rust2AstRanges = this.rust2Ast.get()?.find(([rustRange, _]) => rustRange.contains(pos)); - if (!rust2AstRanges) return; - - const [rustFileRange, astFileRange] = rust2AstRanges; - - astEditor.revealRange(astFileRange); - astEditor.selection = new vscode.Selection(astFileRange.start, astFileRange.end); - - return [{ - targetRange: astFileRange, - targetUri: astEditor.document.uri, - originSelectionRange: rustFileRange, - targetSelectionRange: astFileRange, - }]; - } - - // additional positional params are omitted - provideHover(doc: vscode.TextDocument, hoverPosition: vscode.Position): vscode.ProviderResult { - if (!this.rustEditor) return; - - const astFileLine = doc.lineAt(hoverPosition.line); - - const rustFileRange = this.parseRustTextRange(this.rustEditor.document, astFileLine.text); - if (!rustFileRange) return; - - this.rustEditor.setDecorations(this.astDecorationType, [rustFileRange]); - this.rustEditor.revealRange(rustFileRange); - - const rustSourceCode = this.rustEditor.document.getText(rustFileRange); - const astFileRange = this.findAstNodeRange(astFileLine); - - return new vscode.Hover(["```rust\n" + rustSourceCode + "\n```"], astFileRange); - } - - private findAstNodeRange(astLine: vscode.TextLine): vscode.Range { - const lineOffset = astLine.range.start; - const begin = lineOffset.translate(undefined, astLine.firstNonWhitespaceCharacterIndex); - const end = lineOffset.translate(undefined, astLine.text.trimEnd().length); - return new vscode.Range(begin, end); - } - - private parseRustTextRange(doc: vscode.TextDocument, astLine: string): undefined | vscode.Range { - const parsedRange = /(\d+)\.\.(\d+)/.exec(astLine); - if (!parsedRange) return; - - const [begin, end] = parsedRange - .slice(1) - .map(off => this.positionAt(doc, +off)); - - return new vscode.Range(begin, end); - } - - // Memoize the last value, otherwise the CPU is at 100% single core - // with quadratic lookups when we build rust2Ast cache - cache?: { doc: vscode.TextDocument; offset: number; line: number }; - - positionAt(doc: vscode.TextDocument, targetOffset: number): vscode.Position { - if (doc.eol === vscode.EndOfLine.LF) { - return doc.positionAt(targetOffset); - } - - // Dirty workaround for crlf line endings - // We are still in this prehistoric era of carriage returns here... - - let line = 0; - let offset = 0; - - const cache = this.cache; - if (cache?.doc === doc && cache.offset <= targetOffset) { - ({ line, offset } = cache); - } - - while (true) { - const lineLenWithLf = doc.lineAt(line).text.length + 1; - if (offset + lineLenWithLf > targetOffset) { - this.cache = { doc, offset, line }; - return doc.positionAt(targetOffset + line); - } - offset += lineLenWithLf; - line += 1; - } - } -} - -class Lazy { - val: undefined | T; - - constructor(private readonly compute: () => undefined | T) { } - - get() { - return this.val ?? (this.val = this.compute()); - } - - reset() { - this.val = undefined; - } -} diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index 0e5a20641..31ac81ee8 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts @@ -92,7 +92,6 @@ export async function activate(context: vscode.ExtensionContext) { ctx.registerCommand('runSingle', commands.runSingle); ctx.registerCommand('debugSingle', commands.debugSingle); ctx.registerCommand('showReferences', commands.showReferences); - ctx.registerCommand('applySourceChange', commands.applySourceChange); ctx.registerCommand('applySnippetWorkspaceEdit', commands.applySnippetWorkspaceEditCommand); ctx.registerCommand('applyActionGroup', commands.applyActionGroup); diff --git a/editors/code/src/run.ts b/editors/code/src/run.ts new file mode 100644 index 000000000..8f0487d74 --- /dev/null +++ b/editors/code/src/run.ts @@ -0,0 +1,204 @@ +import * as vscode from 'vscode'; +import * as lc from 'vscode-languageclient'; +import * as ra from './rust-analyzer-api'; + +import { Ctx, Cmd } from './ctx'; +import { startDebugSession, getDebugConfiguration } from './debug'; + +const quickPickButtons = [{ iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configurtation." }]; + +export async function selectRunnable(ctx: Ctx, prevRunnable?: RunnableQuickPick, debuggeeOnly = false, showButtons: boolean = true): Promise { + const editor = ctx.activeRustEditor; + const client = ctx.client; + if (!editor || !client) return; + + const textDocument: lc.TextDocumentIdentifier = { + uri: editor.document.uri.toString(), + }; + + const runnables = await client.sendRequest(ra.runnables, { + textDocument, + position: client.code2ProtocolConverter.asPosition( + editor.selection.active, + ), + }); + const items: RunnableQuickPick[] = []; + if (prevRunnable) { + items.push(prevRunnable); + } + for (const r of runnables) { + if ( + prevRunnable && + JSON.stringify(prevRunnable.runnable) === JSON.stringify(r) + ) { + continue; + } + + if (debuggeeOnly && (r.label.startsWith('doctest') || r.label.startsWith('cargo'))) { + continue; + } + items.push(new RunnableQuickPick(r)); + } + + if (items.length === 0) { + // it is the debug case, run always has at least 'cargo check ...' + // see crates\rust-analyzer\src\main_loop\handlers.rs, handle_runnables + vscode.window.showErrorMessage("There's no debug target!"); + return; + } + + return await new Promise((resolve) => { + const disposables: vscode.Disposable[] = []; + const close = (result?: RunnableQuickPick) => { + resolve(result); + disposables.forEach(d => d.dispose()); + }; + + const quickPick = vscode.window.createQuickPick(); + quickPick.items = items; + quickPick.title = "Select Runnable"; + if (showButtons) { + quickPick.buttons = quickPickButtons; + } + disposables.push( + quickPick.onDidHide(() => close()), + quickPick.onDidAccept(() => close(quickPick.selectedItems[0])), + quickPick.onDidTriggerButton((_button) => { + (async () => await makeDebugConfig(ctx, quickPick.activeItems[0]))(); + close(); + }), + quickPick.onDidChangeActive((active) => { + if (showButtons && active.length > 0) { + if (active[0].label.startsWith('cargo')) { + // save button makes no sense for `cargo test` or `cargo check` + quickPick.buttons = []; + } else if (quickPick.buttons.length === 0) { + quickPick.buttons = quickPickButtons; + } + } + }), + quickPick + ); + quickPick.show(); + }); +} + +export function runSingle(ctx: Ctx): Cmd { + return async (runnable: ra.Runnable) => { + const editor = ctx.activeRustEditor; + if (!editor) return; + + const task = createTask(runnable); + 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); + }; +} + +async function makeDebugConfig(ctx: Ctx, item: RunnableQuickPick): Promise { + const scope = ctx.activeRustEditor?.document.uri; + if (!scope) return; + + const debugConfig = await getDebugConfiguration(ctx, item.runnable); + if (!debugConfig) return; + + const wsLaunchSection = vscode.workspace.getConfiguration("launch", scope); + const configurations = wsLaunchSection.get("configurations") || []; + + const index = configurations.findIndex(c => c.name === debugConfig.name); + if (index !== -1) { + const answer = await vscode.window.showErrorMessage(`Launch configuration '${debugConfig.name}' already exists!`, 'Cancel', 'Update'); + if (answer === "Cancel") return; + + configurations[index] = debugConfig; + } else { + configurations.push(debugConfig); + } + + await wsLaunchSection.update("configurations", configurations); +} + +export function newDebugConfig(ctx: Ctx): Cmd { + return async () => { + const item = await selectRunnable(ctx, undefined, true, false); + if (!item) return; + + await makeDebugConfig(ctx, item); + }; +} + +export class RunnableQuickPick implements vscode.QuickPickItem { + public label: string; + public description?: string | undefined; + public detail?: string | undefined; + public picked?: boolean | undefined; + + constructor(public runnable: ra.Runnable) { + this.label = runnable.label; + } +} + +interface CargoTaskDefinition extends vscode.TaskDefinition { + type: 'cargo'; + label: string; + command: string; + args: string[]; + env?: { [key: string]: string }; +} + +export function createTask(spec: ra.Runnable): vscode.Task { + const TASK_SOURCE = 'Rust'; + const definition: CargoTaskDefinition = { + type: 'cargo', + label: spec.label, + command: spec.bin, + args: spec.extraArgs ? [...spec.args, '--', ...spec.extraArgs] : spec.args, + env: spec.env, + }; + + const execOption: vscode.ShellExecutionOptions = { + cwd: spec.cwd || '.', + env: definition.env, + }; + const exec = new vscode.ShellExecution( + definition.command, + definition.args, + execOption, + ); + + const f = vscode.workspace.workspaceFolders![0]; + const t = new vscode.Task( + definition, + f, + definition.label, + TASK_SOURCE, + exec, + ['$rustc'], + ); + t.presentationOptions.clear = true; + return t; +} -- cgit v1.2.3