diff options
Diffstat (limited to 'editors/code/src')
| -rw-r--r-- | editors/code/src/ast_inspector.ts | 26 | ||||
| -rw-r--r-- | editors/code/src/bootstrap.ts | 22 | ||||
| -rw-r--r-- | editors/code/src/client.ts | 76 | ||||
| -rw-r--r-- | editors/code/src/commands.ts | 386 | ||||
| -rw-r--r-- | editors/code/src/config.ts | 81 | ||||
| -rw-r--r-- | editors/code/src/ctx.ts | 48 | ||||
| -rw-r--r-- | editors/code/src/debug.ts | 37 | ||||
| -rw-r--r-- | editors/code/src/dependencies_provider.ts | 28 | ||||
| -rw-r--r-- | editors/code/src/diagnostics.ts | 20 | ||||
| -rw-r--r-- | editors/code/src/lsp_ext.ts | 55 | ||||
| -rw-r--r-- | editors/code/src/main.ts | 23 | ||||
| -rw-r--r-- | editors/code/src/nullable.ts | 19 | ||||
| -rw-r--r-- | editors/code/src/persistent_state.ts | 2 | ||||
| -rw-r--r-- | editors/code/src/run.ts | 28 | ||||
| -rw-r--r-- | editors/code/src/snippets.ts | 16 | ||||
| -rw-r--r-- | editors/code/src/tasks.ts | 17 | ||||
| -rw-r--r-- | editors/code/src/toolchain.ts | 19 | ||||
| -rw-r--r-- | editors/code/src/undefinable.ts | 19 | ||||
| -rw-r--r-- | editors/code/src/util.ts | 36 |
19 files changed, 659 insertions, 299 deletions
diff --git a/editors/code/src/ast_inspector.ts b/editors/code/src/ast_inspector.ts index 176040120f4..688c53a9b1f 100644 --- a/editors/code/src/ast_inspector.ts +++ b/editors/code/src/ast_inspector.ts @@ -1,7 +1,8 @@ import * as vscode from "vscode"; -import { Ctx, Disposable } from "./ctx"; -import { RustEditor, isRustEditor } from "./util"; +import type { Ctx, Disposable } from "./ctx"; +import { type RustEditor, isRustEditor } from "./util"; +import { unwrapUndefinable } from "./undefinable"; // FIXME: consider implementing this via the Tree View API? // https://code.visualstudio.com/api/extension-guides/tree-view @@ -36,23 +37,23 @@ export class AstInspector implements vscode.HoverProvider, vscode.DefinitionProv constructor(ctx: Ctx) { ctx.pushExtCleanup( - vscode.languages.registerHoverProvider({ scheme: "rust-analyzer" }, this) + vscode.languages.registerHoverProvider({ scheme: "rust-analyzer" }, this), ); ctx.pushExtCleanup(vscode.languages.registerDefinitionProvider({ language: "rust" }, this)); vscode.workspace.onDidCloseTextDocument( this.onDidCloseTextDocument, this, - ctx.subscriptions + ctx.subscriptions, ); vscode.workspace.onDidChangeTextDocument( this.onDidChangeTextDocument, this, - ctx.subscriptions + ctx.subscriptions, ); vscode.window.onDidChangeVisibleTextEditors( this.onDidChangeVisibleTextEditors, this, - ctx.subscriptions + ctx.subscriptions, ); } dispose() { @@ -84,7 +85,7 @@ export class AstInspector implements vscode.HoverProvider, vscode.DefinitionProv private findAstTextEditor(): undefined | vscode.TextEditor { return vscode.window.visibleTextEditors.find( - (it) => it.document.uri.scheme === "rust-analyzer" + (it) => it.document.uri.scheme === "rust-analyzer", ); } @@ -99,7 +100,7 @@ export class AstInspector implements vscode.HoverProvider, vscode.DefinitionProv // additional positional params are omitted provideDefinition( doc: vscode.TextDocument, - pos: vscode.Position + pos: vscode.Position, ): vscode.ProviderResult<vscode.DefinitionLink[]> { if (!this.rustEditor || doc.uri.toString() !== this.rustEditor.document.uri.toString()) { return; @@ -131,7 +132,7 @@ export class AstInspector implements vscode.HoverProvider, vscode.DefinitionProv // additional positional params are omitted provideHover( doc: vscode.TextDocument, - hoverPosition: vscode.Position + hoverPosition: vscode.Position, ): vscode.ProviderResult<vscode.Hover> { if (!this.rustEditor) return; @@ -158,14 +159,15 @@ export class AstInspector implements vscode.HoverProvider, vscode.DefinitionProv private parseRustTextRange( doc: vscode.TextDocument, - astLine: string + 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); + const actualBegin = unwrapUndefinable(begin); + const actualEnd = unwrapUndefinable(end); + return new vscode.Range(actualBegin, actualEnd); } // Memoize the last value, otherwise the CPU is at 100% single core diff --git a/editors/code/src/bootstrap.ts b/editors/code/src/bootstrap.ts index b38fa06a85c..ef4dff095cf 100644 --- a/editors/code/src/bootstrap.ts +++ b/editors/code/src/bootstrap.ts @@ -1,20 +1,20 @@ import * as vscode from "vscode"; import * as os from "os"; -import { Config } from "./config"; +import type { Config } from "./config"; import { log, isValidExecutable } from "./util"; -import { PersistentState } from "./persistent_state"; +import type { PersistentState } from "./persistent_state"; import { exec } from "child_process"; export async function bootstrap( context: vscode.ExtensionContext, config: Config, - state: PersistentState + state: PersistentState, ): Promise<string> { const path = await getServer(context, config, state); if (!path) { throw new Error( "Rust Analyzer Language Server is not available. " + - "Please, ensure its [proper installation](https://rust-analyzer.github.io/manual.html#installation)." + "Please, ensure its [proper installation](https://rust-analyzer.github.io/manual.html#installation).", ); } @@ -34,9 +34,9 @@ export async function bootstrap( async function getServer( context: vscode.ExtensionContext, config: Config, - state: PersistentState + state: PersistentState, ): Promise<string | undefined> { - const explicitPath = process.env.__RA_LSP_SERVER_DEBUG ?? config.serverPath; + const explicitPath = process.env["__RA_LSP_SERVER_DEBUG"] ?? config.serverPath; if (explicitPath) { if (explicitPath.startsWith("~/")) { return os.homedir() + explicitPath.slice("~".length); @@ -49,7 +49,7 @@ async function getServer( const bundled = vscode.Uri.joinPath(context.extensionUri, "server", `rust-analyzer${ext}`); const bundledExists = await vscode.workspace.fs.stat(bundled).then( () => true, - () => false + () => false, ); if (bundledExists) { let server = bundled; @@ -58,7 +58,7 @@ async function getServer( const dest = vscode.Uri.joinPath(config.globalStorageUri, `rust-analyzer${ext}`); let exists = await vscode.workspace.fs.stat(dest).then( () => true, - () => false + () => false, ); if (exists && config.package.version !== state.serverVersion) { await vscode.workspace.fs.delete(dest); @@ -81,7 +81,7 @@ async function getServer( "run `cargo xtask install --server` to build the language server from sources. " + "If you feel that your platform should be supported, please create an issue " + "about that [here](https://github.com/rust-lang/rust-analyzer/issues) and we " + - "will consider it." + "will consider it.", ); return undefined; } @@ -131,7 +131,7 @@ async function patchelf(dest: vscode.Uri): Promise<void> { } else { resolve(stdout); } - } + }, ); handle.stdin?.write(expression); handle.stdin?.end(); @@ -139,6 +139,6 @@ async function patchelf(dest: vscode.Uri): Promise<void> { } finally { await vscode.workspace.fs.delete(origFile); } - } + }, ); } diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts index f721fcce766..ba8546763ec 100644 --- a/editors/code/src/client.ts +++ b/editors/code/src/client.ts @@ -6,9 +6,10 @@ import * as Is from "vscode-languageclient/lib/common/utils/is"; import { assert } from "./util"; import * as diagnostics from "./diagnostics"; import { WorkspaceEdit } from "vscode"; -import { Config, prepareVSCodeConfig } from "./config"; +import { type Config, prepareVSCodeConfig } from "./config"; import { randomUUID } from "crypto"; import { sep as pathSeparator } from "path"; +import { unwrapUndefinable } from "./undefinable"; export interface Env { [name: string]: string; @@ -33,21 +34,24 @@ export const LINKED_COMMANDS = new Map<string, ra.CommandLink>(); // add code to remove a target command from the map after the link is // clicked, but assuming most links in hover sheets won't be clicked anyway // this code won't change the overall memory use much. -setInterval(function cleanupOlderCommandLinks() { - // keys are returned in insertion order, we'll keep a few - // of recent keys available, and clean the rest - const keys = [...LINKED_COMMANDS.keys()]; - const keysToRemove = keys.slice(0, keys.length - 10); - for (const key of keysToRemove) { - LINKED_COMMANDS.delete(key); - } -}, 10 * 60 * 1000); +setInterval( + function cleanupOlderCommandLinks() { + // keys are returned in insertion order, we'll keep a few + // of recent keys available, and clean the rest + const keys = [...LINKED_COMMANDS.keys()]; + const keysToRemove = keys.slice(0, keys.length - 10); + for (const key of keysToRemove) { + LINKED_COMMANDS.delete(key); + } + }, + 10 * 60 * 1000, +); function renderCommand(cmd: ra.CommandLink): string { const commandId = randomUUID(); LINKED_COMMANDS.set(commandId, cmd); return `[${cmd.title}](command:rust-analyzer.linkToCommand?${encodeURIComponent( - JSON.stringify([commandId]) + JSON.stringify([commandId]), )} '${cmd.tooltip}')`; } @@ -56,7 +60,7 @@ function renderHoverActions(actions: ra.CommandLinkGroup[]): vscode.MarkdownStri .map( (group) => (group.title ? group.title + " " : "") + - group.commands.map(renderCommand).join(" | ") + group.commands.map(renderCommand).join(" | "), ) .join("___"); @@ -71,7 +75,7 @@ export async function createClient( initializationOptions: vscode.WorkspaceConfiguration, serverOptions: lc.ServerOptions, config: Config, - unlinkedFiles: vscode.Uri[] + unlinkedFiles: vscode.Uri[], ): Promise<lc.LanguageClient> { const clientOptions: lc.LanguageClientOptions = { documentSelector: [{ scheme: "file", language: "rust" }], @@ -92,7 +96,7 @@ export async function createClient( async configuration( params: lc.ConfigurationParams, token: vscode.CancellationToken, - next: lc.ConfigurationRequest.HandlerSignature + next: lc.ConfigurationRequest.HandlerSignature, ) { const resp = await next(params, token); if (resp && Array.isArray(resp)) { @@ -116,7 +120,7 @@ export async function createClient( async handleDiagnostics( uri: vscode.Uri, diagnosticList: vscode.Diagnostic[], - next: lc.HandleDiagnosticsSignature + next: lc.HandleDiagnosticsSignature, ) { const preview = config.previewRustcOutput; const errorCode = config.useRustcErrorCode; @@ -136,20 +140,20 @@ export async function createClient( const folder = vscode.workspace.getWorkspaceFolder(uri)?.uri.fsPath; if (folder) { const parentBackslash = uri.fsPath.lastIndexOf( - pathSeparator + "src" + pathSeparator + "src", ); const parent = uri.fsPath.substring(0, parentBackslash); if (parent.startsWith(folder)) { const path = vscode.Uri.file( - parent + pathSeparator + "Cargo.toml" + parent + pathSeparator + "Cargo.toml", ); void vscode.workspace.fs.stat(path).then(async () => { const choice = await vscode.window.showInformationMessage( `This rust file does not belong to a loaded cargo project. It looks like it might belong to the workspace at ${path.path}, do you want to add it to the linked Projects?`, "Yes", "No", - "Don't show this again" + "Don't show this again", ); switch (choice) { case undefined: @@ -167,14 +171,14 @@ export async function createClient( config .get<any[]>("linkedProjects") ?.concat(pathToInsert), - false + false, ); break; case "Don't show this again": await config.update( "showUnlinkedFileNotification", false, - false + false, ); break; } @@ -221,7 +225,7 @@ export async function createClient( document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken, - _next: lc.ProvideHoverSignature + _next: lc.ProvideHoverSignature, ) { const editor = vscode.window.activeTextEditor; const positionOrRange = editor?.selection?.contains(position) @@ -235,7 +239,7 @@ export async function createClient( client.code2ProtocolConverter.asTextDocumentIdentifier(document), position: positionOrRange, }, - token + token, ) .then( (result) => { @@ -249,7 +253,7 @@ export async function createClient( (error) => { client.handleFailedRequest(lc.HoverRequest.type, token, error, null); return Promise.resolve(null); - } + }, ); }, // Using custom handling of CodeActions to support action groups and snippet edits. @@ -259,14 +263,14 @@ export async function createClient( range: vscode.Range, context: vscode.CodeActionContext, token: vscode.CancellationToken, - _next: lc.ProvideCodeActionsSignature + _next: lc.ProvideCodeActionsSignature, ) { const params: lc.CodeActionParams = { textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document), range: client.code2ProtocolConverter.asRange(range), context: await client.code2ProtocolConverter.asCodeActionContext( context, - token + token, ), }; return client.sendRequest(lc.CodeActionRequest.type, params, token).then( @@ -282,21 +286,21 @@ export async function createClient( if (lc.CodeAction.is(item)) { assert( !item.command, - "We don't expect to receive commands in CodeActions" + "We don't expect to receive commands in CodeActions", ); const action = await client.protocol2CodeConverter.asCodeAction( item, - token + token, ); result.push(action); continue; } assert( isCodeActionWithoutEditsAndCommands(item), - "We don't expect edits or commands here" + "We don't expect edits or commands here", ); const kind = client.protocol2CodeConverter.asCodeActionKind( - (item as any).kind + (item as any).kind, ); const action = new vscode.CodeAction(item.title, kind); const group = (item as any).group; @@ -323,10 +327,12 @@ export async function createClient( } for (const [group, { index, items }] of groups) { if (items.length === 1) { - result[index] = items[0]; + const item = unwrapUndefinable(items[0]); + result[index] = item; } else { const action = new vscode.CodeAction(group); - action.kind = items[0].kind; + const item = unwrapUndefinable(items[0]); + action.kind = item.kind; action.command = { command: "rust-analyzer.applyActionGroup", title: "", @@ -348,7 +354,7 @@ export async function createClient( } return result; }, - (_error) => undefined + (_error) => undefined, ); }, }, @@ -361,7 +367,7 @@ export async function createClient( "rust-analyzer", "Rust Analyzer Language Server", serverOptions, - clientOptions + clientOptions, ); // To turn on all proposed features use: client.registerProposedFeatures(); @@ -397,7 +403,7 @@ class ExperimentalFeatures implements lc.StaticFeature { } initialize( _capabilities: lc.ServerCapabilities, - _documentSelector: lc.DocumentSelector | undefined + _documentSelector: lc.DocumentSelector | undefined, ): void {} dispose(): void {} } @@ -416,7 +422,7 @@ class OverrideFeatures implements lc.StaticFeature { } initialize( _capabilities: lc.ServerCapabilities, - _documentSelector: lc.DocumentSelector | undefined + _documentSelector: lc.DocumentSelector | undefined, ): void {} dispose(): void {} } diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts index 98ccd50dc04..e21f536f26a 100644 --- a/editors/code/src/commands.ts +++ b/editors/code/src/commands.ts @@ -3,23 +3,24 @@ import * as lc from "vscode-languageclient"; import * as ra from "./lsp_ext"; import * as path from "path"; -import { Ctx, Cmd, CtxInit, discoverWorkspace } from "./ctx"; +import { type Ctx, type Cmd, type CtxInit, discoverWorkspace } from "./ctx"; import { applySnippetWorkspaceEdit, applySnippetTextEdits } from "./snippets"; import { spawnSync } from "child_process"; -import { RunnableQuickPick, selectRunnable, createTask, createArgs } from "./run"; +import { type RunnableQuickPick, selectRunnable, createTask, createArgs } from "./run"; import { AstInspector } from "./ast_inspector"; import { isRustDocument, isCargoTomlDocument, sleep, isRustEditor, - RustEditor, - RustDocument, + type RustEditor, + type RustDocument, } from "./util"; import { startDebugSession, makeDebugConfig } from "./debug"; -import { LanguageClient } from "vscode-languageclient/node"; +import type { LanguageClient } from "vscode-languageclient/node"; import { LINKED_COMMANDS } from "./client"; -import { DependencyId } from "./dependencies_provider"; +import type { DependencyId } from "./dependencies_provider"; +import { unwrapUndefinable } from "./undefinable"; export * from "./ast_inspector"; export * from "./run"; @@ -47,7 +48,7 @@ export function analyzerStatus(ctx: CtxInit): Cmd { })(); ctx.pushExtCleanup( - vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-status", tdcp) + vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-status", tdcp), ); return async () => { @@ -79,7 +80,7 @@ export function memoryUsage(ctx: CtxInit): Cmd { })(); ctx.pushExtCleanup( - vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-memory", tdcp) + vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-memory", tdcp), ); return async () => { @@ -125,11 +126,12 @@ export function matchingBrace(ctx: CtxInit): Cmd { const response = await client.sendRequest(ra.matchingBrace, { textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document), positions: editor.selections.map((s) => - client.code2ProtocolConverter.asPosition(s.active) + client.code2ProtocolConverter.asPosition(s.active), ), }); editor.selections = editor.selections.map((sel, idx) => { - const active = client.protocol2CodeConverter.asPosition(response[idx]); + const position = unwrapUndefinable(response[idx]); + const active = client.protocol2CodeConverter.asPosition(position); const anchor = sel.isEmpty ? active : sel.anchor; return new vscode.Selection(anchor, active); }); @@ -194,7 +196,7 @@ export function onEnter(ctx: CtxInit): Cmd { const lcEdits = await client .sendRequest(ra.onEnter, { textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier( - editor.document + editor.document, ), position: client.code2ProtocolConverter.asPosition(editor.selection.active), }) @@ -231,7 +233,7 @@ export function parentModule(ctx: CtxInit): Cmd { if (!locations) return; if (locations.length === 1) { - const loc = locations[0]; + const loc = unwrapUndefinable(locations[0]); const uri = client.protocol2CodeConverter.asUri(loc.targetUri); const range = client.protocol2CodeConverter.asRange(loc.targetRange); @@ -247,7 +249,7 @@ export function parentModule(ctx: CtxInit): Cmd { client, uri, position, - locations.map((loc) => lc.Location.create(loc.targetUri, loc.targetRange)) + locations.map((loc) => lc.Location.create(loc.targetUri, loc.targetRange)), ); } }; @@ -331,7 +333,13 @@ async function revealParentChain(document: RustDocument, ctx: CtxInit) { } while (!ctx.dependencies?.contains(documentPath)); parentChain.reverse(); for (const idx in parentChain) { - await ctx.treeView?.reveal(parentChain[idx], { select: true, expand: true }); + const treeView = ctx.treeView; + if (!treeView) { + continue; + } + + const dependency = unwrapUndefinable(parentChain[idx]); + await treeView.reveal(dependency, { select: true, expand: true }); } } @@ -349,7 +357,7 @@ export function ssr(ctx: CtxInit): Cmd { const position = editor.selection.active; const selections = editor.selections; const textDocument = client.code2ProtocolConverter.asTextDocumentIdentifier( - editor.document + editor.document, ); const options: vscode.InputBoxOptions = { @@ -365,7 +373,7 @@ export function ssr(ctx: CtxInit): Cmd { selections, }); } catch (e) { - return e.toString(); + return String(e); } return null; }, @@ -389,9 +397,9 @@ export function ssr(ctx: CtxInit): Cmd { }); await vscode.workspace.applyEdit( - await client.protocol2CodeConverter.asWorkspaceEdit(edit, token) + await client.protocol2CodeConverter.asWorkspaceEdit(edit, token), ); - } + }, ); }; } @@ -420,12 +428,12 @@ export function syntaxTree(ctx: CtxInit): Cmd { vscode.workspace.onDidChangeTextDocument( this.onDidChangeTextDocument, this, - ctx.subscriptions + ctx.subscriptions, ); vscode.window.onDidChangeActiveTextEditor( this.onDidChangeActiveTextEditor, this, - ctx.subscriptions + ctx.subscriptions, ); } @@ -444,7 +452,7 @@ export function syntaxTree(ctx: CtxInit): Cmd { async provideTextDocumentContent( uri: vscode.Uri, - ct: vscode.CancellationToken + ct: vscode.CancellationToken, ): Promise<string> { const rustEditor = ctx.activeRustEditor; if (!rustEditor) return ""; @@ -467,12 +475,12 @@ export function syntaxTree(ctx: CtxInit): Cmd { ctx.pushExtCleanup(new AstInspector(ctx)); ctx.pushExtCleanup( - vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-syntax-tree", tdcp) + vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-syntax-tree", tdcp), ); ctx.pushExtCleanup( vscode.languages.setLanguageConfiguration("ra_syntax_tree", { brackets: [["[", ")"]], - }) + }), ); return async () => { @@ -505,7 +513,7 @@ function viewFileUsingTextDocumentContentProvider( requestType: lc.RequestType<lc.TextDocumentPositionParams, string, void>, uri: string, scheme: string, - shouldUpdate: boolean + shouldUpdate: boolean, ): Cmd { const tdcp = new (class implements vscode.TextDocumentContentProvider { readonly uri = vscode.Uri.parse(uri); @@ -514,12 +522,12 @@ function viewFileUsingTextDocumentContentProvider( vscode.workspace.onDidChangeTextDocument( this.onDidChangeTextDocument, this, - ctx.subscriptions + ctx.subscriptions, ); vscode.window.onDidChangeActiveTextEditor( this.onDidChangeActiveTextEditor, this, - ctx.subscriptions + ctx.subscriptions, ); } @@ -538,7 +546,7 @@ function viewFileUsingTextDocumentContentProvider( async provideTextDocumentContent( _uri: vscode.Uri, - ct: vscode.CancellationToken + ct: vscode.CancellationToken, ): Promise<string> { const rustEditor = ctx.activeRustEditor; if (!rustEditor) return ""; @@ -546,7 +554,7 @@ function viewFileUsingTextDocumentContentProvider( const client = ctx.client; const params = { textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier( - rustEditor.document + rustEditor.document, ), position: client.code2ProtocolConverter.asPosition(rustEditor.selection.active), }; @@ -594,7 +602,7 @@ export function interpretFunction(ctx: CtxInit): Cmd { ra.interpretFunction, uri, `rust-analyzer-interpret-function`, - false + false, ); } @@ -606,12 +614,12 @@ export function viewFileText(ctx: CtxInit): Cmd { vscode.workspace.onDidChangeTextDocument( this.onDidChangeTextDocument, this, - ctx.subscriptions + ctx.subscriptions, ); vscode.window.onDidChangeActiveTextEditor( this.onDidChangeActiveTextEditor, this, - ctx.subscriptions + ctx.subscriptions, ); } @@ -630,14 +638,14 @@ export function viewFileText(ctx: CtxInit): Cmd { async provideTextDocumentContent( _uri: vscode.Uri, - ct: vscode.CancellationToken + ct: vscode.CancellationToken, ): Promise<string> { const rustEditor = ctx.activeRustEditor; if (!rustEditor) return ""; const client = ctx.client; const params = client.code2ProtocolConverter.asTextDocumentIdentifier( - rustEditor.document + rustEditor.document, ); return client.sendRequest(ra.viewFileText, params, ct); } @@ -648,7 +656,7 @@ export function viewFileText(ctx: CtxInit): Cmd { })(); ctx.pushExtCleanup( - vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-file-text", tdcp) + vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-file-text", tdcp), ); return async () => { @@ -669,12 +677,12 @@ export function viewItemTree(ctx: CtxInit): Cmd { vscode.workspace.onDidChangeTextDocument( this.onDidChangeTextDocument, this, - ctx.subscriptions + ctx.subscriptions, ); vscode.window.onDidChangeActiveTextEditor( this.onDidChangeActiveTextEditor, this, - ctx.subscriptions + ctx.subscriptions, ); } @@ -693,7 +701,7 @@ export function viewItemTree(ctx: CtxInit): Cmd { async provideTextDocumentContent( _uri: vscode.Uri, - ct: vscode.CancellationToken + ct: vscode.CancellationToken, ): Promise<string> { const rustEditor = ctx.activeRustEditor; if (!rustEditor) return ""; @@ -701,7 +709,7 @@ export function viewItemTree(ctx: CtxInit): Cmd { const params = { textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier( - rustEditor.document + rustEditor.document, ), }; return client.sendRequest(ra.viewItemTree, params, ct); @@ -713,7 +721,7 @@ export function viewItemTree(ctx: CtxInit): Cmd { })(); ctx.pushExtCleanup( - vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-item-tree", tdcp) + vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-item-tree", tdcp), ); return async () => { @@ -738,7 +746,7 @@ function crateGraph(ctx: CtxInit, full: boolean): Cmd { enableScripts: true, retainContextWhenHidden: true, localResourceRoots: [nodeModulesPath], - } + }, ); const params = { full: full, @@ -827,7 +835,7 @@ export function expandMacro(ctx: CtxInit): Cmd { const expanded = await client.sendRequest(ra.expandMacro, { textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier( - editor.document + editor.document, ), position, }); @@ -843,7 +851,7 @@ export function expandMacro(ctx: CtxInit): Cmd { })(); ctx.pushExtCleanup( - vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-expand-macro", tdcp) + vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-expand-macro", tdcp), ); return async () => { @@ -875,7 +883,7 @@ export function addProject(ctx: CtxInit): Cmd { return discoverWorkspace([file], discoverProjectCommand, { cwd: path.dirname(file.uri.fsPath), }); - }) + }), ); ctx.addToDiscoveredWorkspaces(workspaces); @@ -893,14 +901,14 @@ async function showReferencesImpl( client: LanguageClient | undefined, uri: string, position: lc.Position, - locations: lc.Location[] + locations: lc.Location[], ) { if (client) { await vscode.commands.executeCommand( "editor.action.showReferences", vscode.Uri.parse(uri), client.protocol2CodeConverter.asPosition(position), - locations.map(client.protocol2CodeConverter.asLocation) + locations.map(client.protocol2CodeConverter.asLocation), ); } } @@ -917,7 +925,7 @@ export function applyActionGroup(_ctx: CtxInit): Cmd { if (!selectedAction) return; await vscode.commands.executeCommand( "rust-analyzer.resolveCodeAction", - selectedAction.arguments + selectedAction.arguments, ); }; } @@ -992,7 +1000,7 @@ export function resolveCodeAction(ctx: CtxInit): Cmd { documentChanges: itemEdit.documentChanges?.filter((change) => "kind" in change), }; const fileSystemEdit = await client.protocol2CodeConverter.asWorkspaceEdit( - lcFileSystemEdit + lcFileSystemEdit, ); await vscode.workspace.applyEdit(fileSystemEdit); await applySnippetWorkspaceEdit(edit); @@ -1045,12 +1053,12 @@ export function peekTests(ctx: CtxInit): Cmd { const locations: lc.Location[] = tests.map((it) => lc.Location.create( it.runnable.location!.targetUri, - it.runnable.location!.targetSelectionRange - ) + it.runnable.location!.targetSelectionRange, + ), ); await showReferencesImpl(client, uri, position, locations); - } + }, ); }; } @@ -1121,3 +1129,281 @@ export function linkToCommand(_: Ctx): Cmd { } }; } + +export function viewMemoryLayout(ctx: CtxInit): Cmd { + return async () => { + const editor = vscode.window.activeTextEditor; + if (!editor) return; + const client = ctx.client; + + const position = editor.selection.active; + const expanded = await client.sendRequest(ra.viewRecursiveMemoryLayout, { + textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document), + position, + }); + + const document = vscode.window.createWebviewPanel( + "memory_layout", + "[Memory Layout]", + vscode.ViewColumn.Two, + { enableScripts: true }, + ); + + document.webview.html = `<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Document</title> + <style> + * { + box-sizing: border-box; + } + + body { + margin: 0; + overflow: hidden; + min-height: 100%; + height: 100vh; + padding: 32px; + position: relative; + display: block; + + background-color: var(--vscode-editor-background); + font-family: var(--vscode-editor-font-family); + font-size: var(--vscode-editor-font-size); + color: var(--vscode-editor-foreground); + } + + .container { + position: relative; + } + + .trans { + transition: all 0.2s ease-in-out; + } + + .grid { + height: 100%; + position: relative; + color: var(--vscode-commandCenter-activeBorder); + pointer-events: none; + } + + .grid-line { + position: absolute; + width: 100%; + height: 1px; + background-color: var(--vscode-commandCenter-activeBorder); + } + + #tooltip { + position: fixed; + display: none; + z-index: 1; + pointer-events: none; + padding: 4px 8px; + z-index: 2; + + color: var(--vscode-editorHoverWidget-foreground); + background-color: var(--vscode-editorHoverWidget-background); + border: 1px solid var(--vscode-editorHoverWidget-border); + } + + #tooltip b { + color: var(--vscode-editorInlayHint-typeForeground); + } + + #tooltip ul { + margin-left: 0; + padding-left: 20px; + } + + table { + position: absolute; + transform: rotateZ(90deg) rotateX(180deg); + transform-origin: top left; + border-collapse: collapse; + table-layout: fixed; + left: 48px; + top: 0; + max-height: calc(100vw - 64px - 48px); + z-index: 1; + } + + td { + border: 1px solid var(--vscode-focusBorder); + writing-mode: vertical-rl; + text-orientation: sideways-right; + + height: 80px; + } + + td p { + height: calc(100% - 16px); + width: calc(100% - 8px); + margin: 8px 4px; + display: inline-block; + transform: rotateY(180deg); + pointer-events: none; + overflow: hidden; + } + + td p * { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + display: inline-block; + height: 100%; + } + + td p b { + color: var(--vscode-editorInlayHint-typeForeground); + } + + td:hover { + background-color: var(--vscode-editor-hoverHighlightBackground); + } + + td:empty { + visibility: hidden; + border: 0; + } + </style> +</head> +<body> + <div id="tooltip"></div> +</body> +<script>(function() { + +const data = ${JSON.stringify(expanded)} + +if (!(data && data.nodes.length)) { + document.body.innerText = "Not Available" + return +} + +data.nodes.map(n => { + n.typename = n.typename.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', ' & quot; ').replaceAll("'", ''') + return n +}) + +let height = window.innerHeight - 64 + +addEventListener("resize", e => { + const new_height = window.innerHeight - 64 + height = new_height + container.classList.remove("trans") + table.classList.remove("trans") + locate() + setTimeout(() => { // give delay to redraw, annoying but needed + container.classList.add("trans") + table.classList.add("trans") + }, 0) +}) + +const container = document.createElement("div") +container.classList.add("container") +container.classList.add("trans") +document.body.appendChild(container) + +const tooltip = document.getElementById("tooltip") + +let y = 0 +let zoom = 1.0 + +const table = document.createElement("table") +table.classList.add("trans") +container.appendChild(table) +const rows = [] + +function node_t(idx, depth, offset) { + if (!rows[depth]) { + rows[depth] = { el: document.createElement("tr"), offset: 0 } + } + + if (rows[depth].offset < offset) { + const pad = document.createElement("td") + pad.colSpan = offset - rows[depth].offset + rows[depth].el.appendChild(pad) + rows[depth].offset += offset - rows[depth].offset + } + + const td = document.createElement("td") + td.innerHTML = '<p><span>' + data.nodes[idx].itemName + ':</span> <b>' + data.nodes[idx].typename + '</b></p>' + + td.colSpan = data.nodes[idx].size + + td.addEventListener("mouseover", e => { + const node = data.nodes[idx] + tooltip.innerHTML = node.itemName + ": <b>" + node.typename + "</b><br/>" + + "<ul>" + + "<li>size = " + node.size + "</li>" + + "<li>align = " + node.alignment + "</li>" + + "<li>field offset = " + node.offset + "</li>" + + "</ul>" + + "<i>double click to focus</i>" + + tooltip.style.display = "block" + }) + td.addEventListener("mouseleave", _ => tooltip.style.display = "none") + const total_offset = rows[depth].offset + td.addEventListener("dblclick", e => { + const node = data.nodes[idx] + zoom = data.nodes[0].size / node.size + y = -(total_offset) / data.nodes[0].size * zoom + x = 0 + locate() + }) + + rows[depth].el.appendChild(td) + rows[depth].offset += data.nodes[idx].size + + + if (data.nodes[idx].childrenStart != -1) { + for (let i = 0; i < data.nodes[idx].childrenLen; i++) { + if (data.nodes[data.nodes[idx].childrenStart + i].size) { + node_t(data.nodes[idx].childrenStart + i, depth + 1, offset + data.nodes[data.nodes[idx].childrenStart + i].offset) + } + } + } +} + +node_t(0, 0, 0) + +for (const row of rows) table.appendChild(row.el) + +const grid = document.createElement("div") +grid.classList.add("grid") +container.appendChild(grid) + +for (let i = 0; i < data.nodes[0].size / 8 + 1; i++) { + const el = document.createElement("div") + el.classList.add("grid-line") + el.style.top = (i / (data.nodes[0].size / 8) * 100) + "%" + el.innerText = i * 8 + grid.appendChild(el) +} + +addEventListener("mousemove", e => { + tooltip.style.top = e.clientY + 10 + "px" + tooltip.style.left = e.clientX + 10 + "px" +}) + +function locate() { + container.style.top = height * y + "px" + container.style.height = (height * zoom) + "px" + + table.style.width = container.style.height +} + +locate() + +})() +</script> +</html>`; + + ctx.pushExtCleanup(document); + }; +} diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index c6d2bcc2b2a..a047f9659a9 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -2,8 +2,9 @@ import * as Is from "vscode-languageclient/lib/common/utils/is"; import * as os from "os"; import * as path from "path"; import * as vscode from "vscode"; -import { Env } from "./client"; +import type { Env } from "./client"; import { log } from "./util"; +import { expectNotUndefined, unwrapUndefinable } from "./undefinable"; export type RunnableEnvCfg = | undefined @@ -37,7 +38,7 @@ export class Config { vscode.workspace.onDidChangeConfiguration( this.onDidChangeConfiguration, this, - ctx.subscriptions + ctx.subscriptions, ); this.refreshLogging(); this.configureLanguage(); @@ -63,7 +64,7 @@ export class Config { this.configureLanguage(); const requiresReloadOpt = this.requiresReloadOpts.find((opt) => - event.affectsConfiguration(opt) + event.affectsConfiguration(opt), ); if (!requiresReloadOpt) return; @@ -98,7 +99,8 @@ export class Config { let onEnterRules: vscode.OnEnterRule[] = [ { // Carry indentation from the previous line - beforeText: /^\s*$/, + // if it's only whitespace + beforeText: /^\s+$/, action: { indentAction: vscode.IndentAction.None }, }, { @@ -208,8 +210,8 @@ export class Config { Object.entries(extraEnv).map(([k, v]) => [ k, typeof v !== "string" ? v.toString() : v, - ]) - ) + ]), + ), ); } get traceExtension() { @@ -220,12 +222,16 @@ export class Config { return this.get<string[] | undefined>("discoverProjectCommand"); } + get problemMatcher(): string[] { + return this.get<string[]>("runnables.problemMatcher") || []; + } + get cargoRunner() { return this.get<string | undefined>("cargoRunner"); } - get runnableEnv() { - const item = this.get<any>("runnableEnv"); + get runnablesExtraEnv() { + const item = this.get<any>("runnables.extraEnv") ?? this.get<any>("runnableEnv"); if (!item) return item; const fixRecord = (r: Record<string, any>) => { for (const key in r) { @@ -300,7 +306,7 @@ export class Config { // to interact with. export function prepareVSCodeConfig<T>( resp: T, - cb?: (key: Extract<keyof T, string>, res: { [key: string]: any }) => void + cb?: (key: Extract<keyof T, string>, res: { [key: string]: any }) => void, ): T { if (Is.string(resp)) { return substituteVSCodeVariableInString(resp) as T; @@ -334,7 +340,7 @@ export function substituteVariablesInEnv(env: Env): Env { const depRe = new RegExp(/\${(?<depName>.+?)}/g); let match = undefined; while ((match = depRe.exec(value))) { - const depName = match.groups!.depName; + const depName = unwrapUndefinable(match.groups?.["depName"]); deps.add(depName); // `depName` at this point can have a form of `expression` or // `prefix:expression` @@ -343,7 +349,7 @@ export function substituteVariablesInEnv(env: Env): Env { } } return [`env:${key}`, { deps: [...deps], value }]; - }) + }), ); const resolved = new Set<string>(); @@ -352,7 +358,7 @@ export function substituteVariablesInEnv(env: Env): Env { if (match) { const { prefix, body } = match.groups!; if (prefix === "env") { - const envName = body; + const envName = unwrapUndefinable(body); envWithDeps[dep] = { value: process.env[envName] ?? "", deps: [], @@ -380,13 +386,12 @@ export function substituteVariablesInEnv(env: Env): Env { do { leftToResolveSize = toResolve.size; for (const key of toResolve) { - if (envWithDeps[key].deps.every((dep) => resolved.has(dep))) { - envWithDeps[key].value = envWithDeps[key].value.replace( - /\${(?<depName>.+?)}/g, - (_wholeMatch, depName) => { - return envWithDeps[depName].value; - } - ); + const item = unwrapUndefinable(envWithDeps[key]); + if (item.deps.every((dep) => resolved.has(dep))) { + item.value = item.value.replace(/\${(?<depName>.+?)}/g, (_wholeMatch, depName) => { + const item = unwrapUndefinable(envWithDeps[depName]); + return item.value; + }); resolved.add(key); toResolve.delete(key); } @@ -395,7 +400,8 @@ export function substituteVariablesInEnv(env: Env): Env { const resolvedEnv: Env = {}; for (const key of Object.keys(env)) { - resolvedEnv[key] = envWithDeps[`env:${key}`].value; + const item = unwrapUndefinable(envWithDeps[`env:${key}`]); + resolvedEnv[key] = item.value; } return resolvedEnv; } @@ -414,20 +420,19 @@ function substituteVSCodeVariableInString(val: string): string { function computeVscodeVar(varName: string): string | null { const workspaceFolder = () => { const folders = vscode.workspace.workspaceFolders ?? []; - if (folders.length === 1) { - // TODO: support for remote workspaces? - return folders[0].uri.fsPath; - } else if (folders.length > 1) { - // could use currently opened document to detect the correct - // workspace. However, that would be determined by the document - // user has opened on Editor startup. Could lead to - // unpredictable workspace selection in practice. - // It's better to pick the first one - return folders[0].uri.fsPath; - } else { - // no workspace opened - return ""; - } + const folder = folders[0]; + // TODO: support for remote workspaces? + const fsPath: string = + folder === undefined + ? // no workspace opened + "" + : // could use currently opened document to detect the correct + // workspace. However, that would be determined by the document + // user has opened on Editor startup. Could lead to + // unpredictable workspace selection in practice. + // It's better to pick the first one + folder.uri.fsPath; + return fsPath; }; // https://code.visualstudio.com/docs/editor/variables-reference const supportedVariables: { [k: string]: () => string } = { @@ -444,13 +449,17 @@ function computeVscodeVar(varName: string): string | null { // https://github.com/microsoft/vscode/blob/08ac1bb67ca2459496b272d8f4a908757f24f56f/src/vs/workbench/api/common/extHostVariableResolverService.ts#L81 // or // https://github.com/microsoft/vscode/blob/29eb316bb9f154b7870eb5204ec7f2e7cf649bec/src/vs/server/node/remoteTerminalChannel.ts#L56 - execPath: () => process.env.VSCODE_EXEC_PATH ?? process.execPath, + execPath: () => process.env["VSCODE_EXEC_PATH"] ?? process.execPath, pathSeparator: () => path.sep, }; if (varName in supportedVariables) { - return supportedVariables[varName](); + const fn = expectNotUndefined( + supportedVariables[varName], + `${varName} should not be undefined here`, + ); + return fn(); } else { // return "${" + varName + "}"; return null; diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index a72b5391ff1..ac144cbebb2 100644 --- a/editors/code/src/ctx.ts +++ b/editors/code/src/ctx.ts @@ -1,5 +1,5 @@ import * as vscode from "vscode"; -import * as lc from "vscode-languageclient/node"; +import type * as lc from "vscode-languageclient/node"; import * as ra from "./lsp_ext"; import * as path from "path"; @@ -12,19 +12,19 @@ import { isRustEditor, LazyOutputChannel, log, - RustEditor, + type RustEditor, } from "./util"; -import { ServerStatusParams } from "./lsp_ext"; +import type { ServerStatusParams } from "./lsp_ext"; import { - Dependency, - DependencyFile, + type Dependency, + type DependencyFile, RustDependenciesProvider, - DependencyId, + type DependencyId, } from "./dependencies_provider"; import { execRevealDependency } from "./commands"; import { PersistentState } from "./persistent_state"; import { bootstrap } from "./bootstrap"; -import { ExecOptions } from "child_process"; +import type { ExecOptions } from "child_process"; // We only support local folders, not eg. Live Share (`vlsl:` scheme), so don't activate if // only those are in use. We use "Empty" to represent these scenarios @@ -42,10 +42,10 @@ export type Workspace = export function fetchWorkspace(): Workspace { const folders = (vscode.workspace.workspaceFolders || []).filter( - (folder) => folder.uri.scheme === "file" + (folder) => folder.uri.scheme === "file", ); const rustDocuments = vscode.workspace.textDocuments.filter((document) => - isRustDocument(document) + isRustDocument(document), ); return folders.length === 0 @@ -61,7 +61,7 @@ export function fetchWorkspace(): Workspace { export async function discoverWorkspace( files: readonly vscode.TextDocument[], command: string[], - options: ExecOptions + options: ExecOptions, ): Promise<JsonProject> { const paths = files.map((f) => `"${f.uri.fsPath}"`).join(" "); const joinedCommand = command.join(" "); @@ -110,7 +110,7 @@ export class Ctx { constructor( readonly extCtx: vscode.ExtensionContext, commandFactories: Record<string, CommandFactory>, - workspace: Workspace + workspace: Workspace, ) { extCtx.subscriptions.push(this); this.statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left); @@ -186,7 +186,7 @@ export class Ctx { log.error("Bootstrap error", err); throw new Error(message); - } + }, ); const newEnv = Object.assign({}, process.env, this.config.serverExtraEnv); const run: lc.Executable = { @@ -216,7 +216,7 @@ export class Ctx { return discoverWorkspace([file], discoverProjectCommand, { cwd: path.dirname(file.uri.fsPath), }); - }) + }), ); this.addToDiscoveredWorkspaces(workspaces); @@ -230,7 +230,7 @@ export class Ctx { if (key === "linkedProjects" && this.config.discoveredWorkspaces.length > 0) { obj["linkedProjects"] = this.config.discoveredWorkspaces; } - } + }, ); this._client = await createClient( @@ -239,17 +239,17 @@ export class Ctx { initializationOptions, serverOptions, this.config, - this.unlinkedFiles + this.unlinkedFiles, ); this.pushClientCleanup( this._client.onNotification(ra.serverStatus, (params) => - this.setServerStatus(params) - ) + this.setServerStatus(params), + ), ); this.pushClientCleanup( this._client.onNotification(ra.openServerLogs, () => { this.outputChannel!.show(); - }) + }), ); } return this._client; @@ -395,7 +395,7 @@ export class Ctx { } else { callback = () => vscode.window.showErrorMessage( - `command ${fullName} failed: rust-analyzer server is not running` + `command ${fullName} failed: rust-analyzer server is not running`, ); } @@ -423,7 +423,7 @@ export class Ctx { } statusBar.color = new vscode.ThemeColor("statusBarItem.warningForeground"); statusBar.backgroundColor = new vscode.ThemeColor( - "statusBarItem.warningBackground" + "statusBarItem.warningBackground", ); statusBar.command = "rust-analyzer.openLogs"; icon = "$(warning) "; @@ -440,7 +440,7 @@ export class Ctx { case "stopped": statusBar.tooltip.appendText("Server is stopped"); statusBar.tooltip.appendMarkdown( - "\n\n[Start server](command:rust-analyzer.startServer)" + "\n\n[Start server](command:rust-analyzer.startServer)", ); statusBar.color = undefined; statusBar.backgroundColor = undefined; @@ -453,13 +453,13 @@ export class Ctx { } statusBar.tooltip.appendMarkdown("\n\n[Open logs](command:rust-analyzer.openLogs)"); statusBar.tooltip.appendMarkdown( - "\n\n[Reload Workspace](command:rust-analyzer.reloadWorkspace)" + "\n\n[Reload Workspace](command:rust-analyzer.reloadWorkspace)", ); statusBar.tooltip.appendMarkdown( - "\n\n[Rebuild Proc Macros](command:rust-analyzer.rebuildProcMacros)" + "\n\n[Rebuild Proc Macros](command:rust-analyzer.rebuildProcMacros)", ); statusBar.tooltip.appendMarkdown( - "\n\n[Restart server](command:rust-analyzer.restartServer)" + "\n\n[Restart server](command:rust-analyzer.restartServer)", ); statusBar.tooltip.appendMarkdown("\n\n[Stop server](command:rust-analyzer.stopServer)"); if (!status.quiescent) icon = "$(sync~spin) "; diff --git a/editors/code/src/debug.ts b/editors/code/src/debug.ts index cffee1de6af..f3d6238d51b 100644 --- a/editors/code/src/debug.ts +++ b/editors/code/src/debug.ts @@ -1,18 +1,19 @@ import * as os from "os"; import * as vscode from "vscode"; import * as path from "path"; -import * as ra from "./lsp_ext"; +import type * as ra from "./lsp_ext"; import { Cargo, getRustcId, getSysroot } from "./toolchain"; -import { Ctx } from "./ctx"; +import type { Ctx } from "./ctx"; import { prepareEnv } from "./run"; +import { unwrapUndefinable } from "./undefinable"; const debugOutput = vscode.window.createOutputChannel("Debug"); type DebugConfigProvider = ( config: ra.Runnable, executable: string, env: Record<string, string>, - sourceFileMap?: Record<string, string> + sourceFileMap?: Record<string, string>, ) => vscode.DebugConfiguration; export async function makeDebugConfig(ctx: Ctx, runnable: ra.Runnable): Promise<void> { @@ -30,7 +31,7 @@ export async function makeDebugConfig(ctx: Ctx, runnable: ra.Runnable): Promise< const answer = await vscode.window.showErrorMessage( `Launch configuration '${debugConfig.name}' already exists!`, "Cancel", - "Update" + "Update", ); if (answer === "Cancel") return; @@ -67,7 +68,7 @@ export async function startDebugSession(ctx: Ctx, runnable: ra.Runnable): Promis async function getDebugConfiguration( ctx: Ctx, - runnable: ra.Runnable + runnable: ra.Runnable, ): Promise<vscode.DebugConfiguration | undefined> { const editor = ctx.activeRustEditor; if (!editor) return; @@ -91,7 +92,7 @@ async function getDebugConfiguration( if (!debugEngine) { await vscode.window.showErrorMessage( `Install [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb)` + - ` or [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools) extension for debugging.` + ` or [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools) extension for debugging.`, ); return; } @@ -105,12 +106,13 @@ async function getDebugConfiguration( const workspaceFolders = vscode.workspace.workspaceFolders!; const isMultiFolderWorkspace = workspaceFolders.length > 1; const firstWorkspace = workspaceFolders[0]; - const workspace = + const maybeWorkspace = !isMultiFolderWorkspace || !runnable.args.workspaceRoot ? firstWorkspace : workspaceFolders.find((w) => runnable.args.workspaceRoot?.includes(w.uri.fsPath)) || firstWorkspace; + const workspace = unwrapUndefinable(maybeWorkspace); const wsFolder = path.normalize(workspace.uri.fsPath); const workspaceQualifier = isMultiFolderWorkspace ? `:${workspace.name}` : ""; function simplifyPath(p: string): string { @@ -118,7 +120,7 @@ async function getDebugConfiguration( return path.normalize(p).replace(wsFolder, "${workspaceFolder" + workspaceQualifier + "}"); } - const env = prepareEnv(runnable, ctx.config.runnableEnv); + const env = prepareEnv(runnable, ctx.config.runnablesExtraEnv); const executable = await getDebugExecutable(runnable, env); let sourceFileMap = debugOptions.sourceFileMap; if (sourceFileMap === "auto") { @@ -130,12 +132,8 @@ async function getDebugConfiguration( sourceFileMap[`/rustc/${commitHash}/`] = rustlib; } - const debugConfig = knownEngines[debugEngine.id]( - runnable, - simplifyPath(executable), - env, - sourceFileMap - ); + const provider = unwrapUndefinable(knownEngines[debugEngine.id]); + const debugConfig = provider(runnable, simplifyPath(executable), env, sourceFileMap); if (debugConfig.type in debugOptions.engineSettings) { const settingsMap = (debugOptions.engineSettings as any)[debugConfig.type]; for (var key in settingsMap) { @@ -149,8 +147,9 @@ async function getDebugConfiguration( debugConfig.name = `run ${path.basename(executable)}`; } - if (debugConfig.cwd) { - debugConfig.cwd = simplifyPath(debugConfig.cwd); + const cwd = debugConfig["cwd"]; + if (cwd) { + debugConfig["cwd"] = simplifyPath(cwd); } return debugConfig; @@ -158,7 +157,7 @@ async function getDebugConfiguration( async function getDebugExecutable( runnable: ra.Runnable, - env: Record<string, string> + env: Record<string, string>, ): Promise<string> { const cargo = new Cargo(runnable.args.workspaceRoot || ".", debugOutput, env); const executable = await cargo.executableFromArgs(runnable.args.cargoArgs); @@ -171,7 +170,7 @@ function getLldbDebugConfig( runnable: ra.Runnable, executable: string, env: Record<string, string>, - sourceFileMap?: Record<string, string> + sourceFileMap?: Record<string, string>, ): vscode.DebugConfiguration { return { type: "lldb", @@ -190,7 +189,7 @@ function getCppvsDebugConfig( runnable: ra.Runnable, executable: string, env: Record<string, string>, - sourceFileMap?: Record<string, string> + sourceFileMap?: Record<string, string>, ): vscode.DebugConfiguration { return { type: os.platform() === "win32" ? "cppvsdbg" : "cppdbg", diff --git a/editors/code/src/dependencies_provider.ts b/editors/code/src/dependencies_provider.ts index 8900aa9a5f3..863ace07801 100644 --- a/editors/code/src/dependencies_provider.ts +++ b/editors/code/src/dependencies_provider.ts @@ -1,9 +1,10 @@ import * as vscode from "vscode"; import * as fspath from "path"; import * as fs from "fs"; -import { CtxInit } from "./ctx"; +import type { CtxInit } from "./ctx"; import * as ra from "./lsp_ext"; -import { FetchDependencyListResult } from "./lsp_ext"; +import type { FetchDependencyListResult } from "./lsp_ext"; +import { unwrapUndefinable } from "./undefinable"; export class RustDependenciesProvider implements vscode.TreeDataProvider<Dependency | DependencyFile> @@ -42,19 +43,24 @@ export class RustDependenciesProvider } getParent?( - element: Dependency | DependencyFile + element: Dependency | DependencyFile, ): vscode.ProviderResult<Dependency | DependencyFile> { if (element instanceof Dependency) return undefined; return element.parent; } getTreeItem(element: Dependency | DependencyFile): vscode.TreeItem | Thenable<vscode.TreeItem> { - if (element.id! in this.dependenciesMap) return this.dependenciesMap[element.id!]; + const dependenciesMap = this.dependenciesMap; + const elementId = element.id!; + if (elementId in dependenciesMap) { + const dependency = unwrapUndefinable(dependenciesMap[elementId]); + return dependency; + } return element; } getChildren( - element?: Dependency | DependencyFile + element?: Dependency | DependencyFile, ): vscode.ProviderResult<Dependency[] | DependencyFile[]> { return new Promise((resolve, _reject) => { if (!vscode.workspace.workspaceFolders) { @@ -81,7 +87,7 @@ export class RustDependenciesProvider private async getRootDependencies(): Promise<Dependency[]> { const dependenciesResult: FetchDependencyListResult = await this.ctx.client.sendRequest( ra.fetchDependencyList, - {} + {}, ); const crates = dependenciesResult.crates; @@ -101,17 +107,17 @@ export class RustDependenciesProvider moduleName, version, vscode.Uri.parse(path).fsPath, - vscode.TreeItemCollapsibleState.Collapsed + vscode.TreeItemCollapsibleState.Collapsed, ); } } export class Dependency extends vscode.TreeItem { constructor( - public readonly label: string, + public override readonly label: string, private version: string, readonly dependencyPath: string, - public readonly collapsibleState: vscode.TreeItemCollapsibleState + public override readonly collapsibleState: vscode.TreeItemCollapsibleState, ) { super(label, collapsibleState); this.resourceUri = vscode.Uri.file(dependencyPath); @@ -127,10 +133,10 @@ export class Dependency extends vscode.TreeItem { export class DependencyFile extends vscode.TreeItem { constructor( - readonly label: string, + override readonly label: string, readonly dependencyPath: string, readonly parent: Dependency | DependencyFile, - public readonly collapsibleState: vscode.TreeItemCollapsibleState + public override readonly collapsibleState: vscode.TreeItemCollapsibleState, ) { super(vscode.Uri.file(dependencyPath), collapsibleState); this.id = this.resourceUri!.fsPath.toLowerCase(); diff --git a/editors/code/src/diagnostics.ts b/editors/code/src/diagnostics.ts index 9695d8bf26d..e31a1cdcef9 100644 --- a/editors/code/src/diagnostics.ts +++ b/editors/code/src/diagnostics.ts @@ -1,7 +1,14 @@ import * as anser from "anser"; import * as vscode from "vscode"; -import { ProviderResult, Range, TextEditorDecorationType, ThemeColor, window } from "vscode"; -import { Ctx } from "./ctx"; +import { + type ProviderResult, + Range, + type TextEditorDecorationType, + ThemeColor, + window, +} from "vscode"; +import type { Ctx } from "./ctx"; +import { unwrapUndefinable } from "./undefinable"; export const URI_SCHEME = "rust-analyzer-diagnostics-view"; @@ -82,7 +89,7 @@ export class AnsiDecorationProvider implements vscode.Disposable { } private _getDecorations( - uri: vscode.Uri + uri: vscode.Uri, ): ProviderResult<[TextEditorDecorationType, Range[]][]> { const stringContents = getRenderedDiagnostic(this.ctx, uri); const lines = stringContents.split("\n"); @@ -110,7 +117,7 @@ export class AnsiDecorationProvider implements vscode.Disposable { lineNumber, offset - totalEscapeLength, lineNumber, - offset + content.length - totalEscapeLength + offset + content.length - totalEscapeLength, ); offset += content.length; @@ -176,7 +183,7 @@ export class AnsiDecorationProvider implements vscode.Disposable { private static _convertColor( color?: string, - truecolor?: string + truecolor?: string, ): ThemeColor | string | undefined { if (!color) { return undefined; @@ -195,7 +202,8 @@ export class AnsiDecorationProvider implements vscode.Disposable { // anser won't return both the RGB and the color name at the same time, // so just fake a single foreground control char with the palette number: const spans = anser.ansiToJson(`\x1b[38;5;${paletteColor}m`); - const rgb = spans[1].fg; + const span = unwrapUndefinable(spans[1]); + const rgb = span.fg; if (rgb) { return `rgb(${rgb})`; diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts index b72804e510c..bb7896973f1 100644 --- a/editors/code/src/lsp_ext.ts +++ b/editors/code/src/lsp_ext.ts @@ -27,17 +27,17 @@ export type CommandLinkGroup = { // rust-analyzer extensions export const analyzerStatus = new lc.RequestType<AnalyzerStatusParams, string, void>( - "rust-analyzer/analyzerStatus" + "rust-analyzer/analyzerStatus", ); export const cancelFlycheck = new lc.NotificationType0("rust-analyzer/cancelFlycheck"); export const clearFlycheck = new lc.NotificationType0("rust-analyzer/clearFlycheck"); export const expandMacro = new lc.RequestType<ExpandMacroParams, ExpandedMacro | null, void>( - "rust-analyzer/expandMacro" + "rust-analyzer/expandMacro", ); export const memoryUsage = new lc.RequestType0<string, void>("rust-analyzer/memoryUsage"); export const openServerLogs = new lc.NotificationType0("rust-analyzer/openServerLogs"); export const relatedTests = new lc.RequestType<lc.TextDocumentPositionParams, TestInfo[], void>( - "rust-analyzer/relatedTests" + "rust-analyzer/relatedTests", ); export const reloadWorkspace = new lc.RequestType0<null, void>("rust-analyzer/reloadWorkspace"); export const rebuildProcMacros = new lc.RequestType0<null, void>("rust-analyzer/rebuildProcMacros"); @@ -47,25 +47,25 @@ export const runFlycheck = new lc.NotificationType<{ }>("rust-analyzer/runFlycheck"); export const shuffleCrateGraph = new lc.RequestType0<null, void>("rust-analyzer/shuffleCrateGraph"); export const syntaxTree = new lc.RequestType<SyntaxTreeParams, string, void>( - "rust-analyzer/syntaxTree" + "rust-analyzer/syntaxTree", ); export const viewCrateGraph = new lc.RequestType<ViewCrateGraphParams, string, void>( - "rust-analyzer/viewCrateGraph" + "rust-analyzer/viewCrateGraph", ); export const viewFileText = new lc.RequestType<lc.TextDocumentIdentifier, string, void>( - "rust-analyzer/viewFileText" + "rust-analyzer/viewFileText", ); export const viewHir = new lc.RequestType<lc.TextDocumentPositionParams, string, void>( - "rust-analyzer/viewHir" + "rust-analyzer/viewHir", ); export const viewMir = new lc.RequestType<lc.TextDocumentPositionParams, string, void>( - "rust-analyzer/viewMir" + "rust-analyzer/viewMir", ); export const interpretFunction = new lc.RequestType<lc.TextDocumentPositionParams, string, void>( - "rust-analyzer/interpretFunction" + "rust-analyzer/interpretFunction", ); export const viewItemTree = new lc.RequestType<ViewItemTreeParams, string, void>( - "rust-analyzer/viewItemTree" + "rust-analyzer/viewItemTree", ); export type AnalyzerStatusParams = { textDocument?: lc.TextDocumentIdentifier }; @@ -121,22 +121,22 @@ export type ViewItemTreeParams = { textDocument: lc.TextDocumentIdentifier }; // experimental extensions export const joinLines = new lc.RequestType<JoinLinesParams, lc.TextEdit[], void>( - "experimental/joinLines" + "experimental/joinLines", ); export const matchingBrace = new lc.RequestType<MatchingBraceParams, lc.Position[], void>( - "experimental/matchingBrace" + "experimental/matchingBrace", ); export const moveItem = new lc.RequestType<MoveItemParams, lc.TextEdit[], void>( - "experimental/moveItem" + "experimental/moveItem", ); export const onEnter = new lc.RequestType<lc.TextDocumentPositionParams, lc.TextEdit[], void>( - "experimental/onEnter" + "experimental/onEnter", ); export const openCargoToml = new lc.RequestType<OpenCargoTomlParams, lc.Location, void>( - "experimental/openCargoToml" + "experimental/openCargoToml", ); export const openDocs = new lc.RequestType<lc.TextDocumentPositionParams, string | void, void>( - "experimental/externalDocs" + "experimental/externalDocs", ); export const parentModule = new lc.RequestType< lc.TextDocumentPositionParams, @@ -144,12 +144,17 @@ export const parentModule = new lc.RequestType< void >("experimental/parentModule"); export const runnables = new lc.RequestType<RunnablesParams, Runnable[], void>( - "experimental/runnables" + "experimental/runnables", ); export const serverStatus = new lc.NotificationType<ServerStatusParams>( - "experimental/serverStatus" + "experimental/serverStatus", ); export const ssr = new lc.RequestType<SsrParams, lc.WorkspaceEdit, void>("experimental/ssr"); +export const viewRecursiveMemoryLayout = new lc.RequestType< + lc.TextDocumentPositionParams, + RecursiveMemoryLayout | null, + void +>("rust-analyzer/viewRecursiveMemoryLayout"); export type JoinLinesParams = { textDocument: lc.TextDocumentIdentifier; @@ -197,3 +202,17 @@ export type SsrParams = { position: lc.Position; selections: readonly lc.Range[]; }; + +export type RecursiveMemoryLayoutNode = { + item_name: string; + typename: string; + size: number; + alignment: number; + offset: number; + parent_idx: number; + children_start: number; + children_len: number; +}; +export type RecursiveMemoryLayout = { + nodes: RecursiveMemoryLayoutNode[]; +}; diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index be9bc9d363c..448150bac06 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts @@ -2,7 +2,7 @@ import * as vscode from "vscode"; import * as lc from "vscode-languageclient/node"; import * as commands from "./commands"; -import { CommandFactory, Ctx, fetchWorkspace } from "./ctx"; +import { type CommandFactory, Ctx, fetchWorkspace } from "./ctx"; import * as diagnostics from "./diagnostics"; import { activateTaskProvider } from "./tasks"; import { setContextValue } from "./util"; @@ -18,7 +18,7 @@ export async function deactivate() { } export async function activate( - context: vscode.ExtensionContext + context: vscode.ExtensionContext, ): Promise<RustAnalyzerExtensionApi> { if (vscode.extensions.getExtension("rust-lang.rust")) { vscode.window @@ -26,7 +26,7 @@ export async function activate( `You have both the rust-analyzer (rust-lang.rust-analyzer) and Rust (rust-lang.rust) ` + "plugins enabled. These are known to conflict and cause various functions of " + "both plugins to not work correctly. You should disable one of them.", - "Got it" + "Got it", ) .then(() => {}, console.error); } @@ -36,7 +36,7 @@ export async function activate( // so we do it ourselves. const api = await activateServer(ctx).catch((err) => { void vscode.window.showErrorMessage( - `Cannot activate rust-analyzer extension: ${err.message}` + `Cannot activate rust-analyzer extension: ${err.message}`, ); throw err; }); @@ -53,8 +53,8 @@ async function activateServer(ctx: Ctx): Promise<RustAnalyzerExtensionApi> { ctx.pushExtCleanup( vscode.workspace.registerTextDocumentContentProvider( diagnostics.URI_SCHEME, - diagnosticProvider - ) + diagnosticProvider, + ), ); const decorationProvider = new diagnostics.AnsiDecorationProvider(ctx); @@ -71,7 +71,7 @@ async function activateServer(ctx: Ctx): Promise<RustAnalyzerExtensionApi> { vscode.workspace.onDidChangeTextDocument( async (event) => await decorateVisibleEditors(event.document), null, - ctx.subscriptions + ctx.subscriptions, ); vscode.workspace.onDidOpenTextDocument(decorateVisibleEditors, null, ctx.subscriptions); vscode.window.onDidChangeActiveTextEditor( @@ -82,7 +82,7 @@ async function activateServer(ctx: Ctx): Promise<RustAnalyzerExtensionApi> { } }, null, - ctx.subscriptions + ctx.subscriptions, ); vscode.window.onDidChangeVisibleTextEditors( async (visibleEditors) => { @@ -92,13 +92,13 @@ async function activateServer(ctx: Ctx): Promise<RustAnalyzerExtensionApi> { } }, null, - ctx.subscriptions + ctx.subscriptions, ); vscode.workspace.onDidChangeWorkspaceFolders( async (_) => ctx.onWorkspaceFolderChanges(), null, - ctx.subscriptions + ctx.subscriptions, ); vscode.workspace.onDidChangeConfiguration( async (_) => { @@ -107,7 +107,7 @@ async function activateServer(ctx: Ctx): Promise<RustAnalyzerExtensionApi> { }); }, null, - ctx.subscriptions + ctx.subscriptions, ); await ctx.start(); @@ -179,6 +179,7 @@ function createCommands(): Record<string, CommandFactory> { runFlycheck: { enabled: commands.runFlycheck }, ssr: { enabled: commands.ssr }, serverVersion: { enabled: commands.serverVersion }, + viewMemoryLayout: { enabled: commands.viewMemoryLayout }, // Internal commands which are invoked by the server. applyActionGroup: { enabled: commands.applyActionGroup }, applySnippetWorkspaceEdit: { enabled: commands.applySnippetWorkspaceEditCommand }, diff --git a/editors/code/src/nullable.ts b/editors/code/src/nullable.ts new file mode 100644 index 00000000000..e973e162907 --- /dev/null +++ b/editors/code/src/nullable.ts @@ -0,0 +1,19 @@ +export type NotNull<T> = T extends null ? never : T; + +export type Nullable<T> = T | null; + +function isNotNull<T>(input: Nullable<T>): input is NotNull<T> { + return input !== null; +} + +function expectNotNull<T>(input: Nullable<T>, msg: string): NotNull<T> { + if (isNotNull(input)) { + return input; + } + + throw new TypeError(msg); +} + +export function unwrapNullable<T>(input: Nullable<T>): NotNull<T> { + return expectNotNull(input, `unwrapping \`null\``); +} diff --git a/editors/code/src/persistent_state.ts b/editors/code/src/persistent_state.ts index 8964a78dc32..cebd16a3c90 100644 --- a/editors/code/src/persistent_state.ts +++ b/editors/code/src/persistent_state.ts @@ -1,4 +1,4 @@ -import * as vscode from "vscode"; +import type * as vscode from "vscode"; import { log } from "./util"; export class PersistentState { diff --git a/editors/code/src/run.ts b/editors/code/src/run.ts index 623fb102953..c893d390554 100644 --- a/editors/code/src/run.ts +++ b/editors/code/src/run.ts @@ -1,11 +1,12 @@ import * as vscode from "vscode"; -import * as lc from "vscode-languageclient"; +import type * as lc from "vscode-languageclient"; import * as ra from "./lsp_ext"; import * as tasks from "./tasks"; -import { CtxInit } from "./ctx"; +import type { CtxInit } from "./ctx"; import { makeDebugConfig } from "./debug"; -import { Config, RunnableEnvCfg } from "./config"; +import type { Config, RunnableEnvCfg } from "./config"; +import { unwrapUndefinable } from "./undefinable"; const quickPickButtons = [ { iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configuration." }, @@ -15,7 +16,7 @@ export async function selectRunnable( ctx: CtxInit, prevRunnable?: RunnableQuickPick, debuggeeOnly = false, - showButtons: boolean = true + showButtons: boolean = true, ): Promise<RunnableQuickPick | undefined> { const editor = ctx.activeRustEditor; if (!editor) return; @@ -68,12 +69,14 @@ export async function selectRunnable( quickPick.onDidHide(() => close()), quickPick.onDidAccept(() => close(quickPick.selectedItems[0])), quickPick.onDidTriggerButton(async (_button) => { - await makeDebugConfig(ctx, quickPick.activeItems[0].runnable); + const runnable = unwrapUndefinable(quickPick.activeItems[0]).runnable; + await makeDebugConfig(ctx, runnable); close(); }), - quickPick.onDidChangeActive((active) => { - if (showButtons && active.length > 0) { - if (active[0].label.startsWith("cargo")) { + quickPick.onDidChangeActive((activeList) => { + if (showButtons && activeList.length > 0) { + const active = unwrapUndefinable(activeList[0]); + if (active.label.startsWith("cargo")) { // save button makes no sense for `cargo test` or `cargo check` quickPick.buttons = []; } else if (quickPick.buttons.length === 0) { @@ -81,7 +84,7 @@ export async function selectRunnable( } } }), - quickPick + quickPick, ); quickPick.show(); }); @@ -100,7 +103,7 @@ export class RunnableQuickPick implements vscode.QuickPickItem { export function prepareEnv( runnable: ra.Runnable, - runnableEnvCfg: RunnableEnvCfg + runnableEnvCfg: RunnableEnvCfg, ): Record<string, string> { const env: Record<string, string> = { RUST_BACKTRACE: "short" }; @@ -140,7 +143,7 @@ export async function createTask(runnable: ra.Runnable, config: Config): Promise command: args[0], // run, test, etc... args: args.slice(1), cwd: runnable.args.workspaceRoot || ".", - env: prepareEnv(runnable, config.runnableEnv), + env: prepareEnv(runnable, config.runnablesExtraEnv), overrideCargo: runnable.args.overrideCargo, }; @@ -151,8 +154,9 @@ export async function createTask(runnable: ra.Runnable, config: Config): Promise definition, runnable.label, args, + config.problemMatcher, config.cargoRunner, - true + true, ); cargoTask.presentationOptions.clear = true; diff --git a/editors/code/src/snippets.ts b/editors/code/src/snippets.ts index 299d29c27ee..d81765649ff 100644 --- a/editors/code/src/snippets.ts +++ b/editors/code/src/snippets.ts @@ -1,10 +1,11 @@ import * as vscode from "vscode"; import { assert } from "./util"; +import { unwrapUndefinable } from "./undefinable"; export async function applySnippetWorkspaceEdit(edit: vscode.WorkspaceEdit) { if (edit.entries().length === 1) { - const [uri, edits] = edit.entries()[0]; + const [uri, edits] = unwrapUndefinable(edit.entries()[0]); const editor = await editorFromUri(uri); if (editor) await applySnippetTextEdits(editor, edits); return; @@ -16,7 +17,9 @@ export async function applySnippetWorkspaceEdit(edit: vscode.WorkspaceEdit) { for (const indel of edits) { assert( !parseSnippet(indel.newText), - `bad ws edit: snippet received with multiple edits: ${JSON.stringify(edit)}` + `bad ws edit: snippet received with multiple edits: ${JSON.stringify( + edit, + )}`, ); builder.replace(indel.range, indel.newText); } @@ -31,7 +34,7 @@ async function editorFromUri(uri: vscode.Uri): Promise<vscode.TextEditor | undef await vscode.window.showTextDocument(uri, {}); } return vscode.window.visibleTextEditors.find( - (it) => it.document.uri.toString() === uri.toString() + (it) => it.document.uri.toString() === uri.toString(), ); } @@ -55,8 +58,8 @@ export async function applySnippetTextEdits(editor: vscode.TextEditor, edits: vs selections.push( new vscode.Selection( new vscode.Position(startLine, startColumn), - new vscode.Position(startLine, endColumn) - ) + new vscode.Position(startLine, endColumn), + ), ); builder.replace(indel.range, newText); } else { @@ -68,7 +71,8 @@ export async function applySnippetTextEdits(editor: vscode.TextEditor, edits: vs }); if (selections.length > 0) editor.selections = selections; if (selections.length === 1) { - editor.revealRange(selections[0], vscode.TextEditorRevealType.InCenterIfOutsideViewport); + const selection = unwrapUndefinable(selections[0]); + editor.revealRange(selection, vscode.TextEditorRevealType.InCenterIfOutsideViewport); } } diff --git a/editors/code/src/tasks.ts b/editors/code/src/tasks.ts index d6509d9aa6e..1d5ab82aa04 100644 --- a/editors/code/src/tasks.ts +++ b/editors/code/src/tasks.ts @@ -1,7 +1,8 @@ import * as vscode from "vscode"; import * as toolchain from "./toolchain"; -import { Config } from "./config"; +import type { Config } from "./config"; import { log } from "./util"; +import { unwrapUndefinable } from "./undefinable"; // This ends up as the `type` key in tasks.json. RLS also uses `cargo` and // our configuration should be compatible with it so use the same key. @@ -46,7 +47,8 @@ class CargoTaskProvider implements vscode.TaskProvider { { type: TASK_TYPE, command: def.command }, `cargo ${def.command}`, [def.command], - this.config.cargoRunner + this.config.problemMatcher, + this.config.cargoRunner, ); vscodeTask.group = def.group; tasks.push(vscodeTask); @@ -70,7 +72,8 @@ class CargoTaskProvider implements vscode.TaskProvider { definition, task.name, args, - this.config.cargoRunner + this.config.problemMatcher, + this.config.cargoRunner, ); } @@ -83,8 +86,9 @@ export async function buildCargoTask( definition: CargoTaskDefinition, name: string, args: string[], + problemMatcher: string[], customRunner?: string, - throwOnError: boolean = false + throwOnError: boolean = false, ): Promise<vscode.Task> { let exec: vscode.ProcessExecution | vscode.ShellExecution | undefined = undefined; @@ -117,7 +121,8 @@ export async function buildCargoTask( const fullCommand = [...cargoCommand, ...args]; - exec = new vscode.ProcessExecution(fullCommand[0], fullCommand.slice(1), definition); + const processName = unwrapUndefinable(fullCommand[0]); + exec = new vscode.ProcessExecution(processName, fullCommand.slice(1), definition); } return new vscode.Task( @@ -128,7 +133,7 @@ export async function buildCargoTask( name, TASK_SOURCE, exec, - ["$rustc", "$rust-panic"] + problemMatcher, ); } diff --git a/editors/code/src/toolchain.ts b/editors/code/src/toolchain.ts index 917a1d6b099..58e5fc747a1 100644 --- a/editors/code/src/toolchain.ts +++ b/editors/code/src/toolchain.ts @@ -4,6 +4,8 @@ import * as path from "path"; import * as readline from "readline"; import * as vscode from "vscode"; import { execute, log, memoizeAsync } from "./util"; +import { unwrapNullable } from "./nullable"; +import { unwrapUndefinable } from "./undefinable"; interface CompilationArtifact { fileName: string; @@ -21,7 +23,7 @@ export class Cargo { constructor( readonly rootFolder: string, readonly output: vscode.OutputChannel, - readonly env: Record<string, string> + readonly env: Record<string, string>, ) {} // Made public for testing purposes @@ -74,7 +76,7 @@ export class Cargo { this.output.append(message.message.rendered); } }, - (stderr) => this.output.append(stderr) + (stderr) => this.output.append(stderr), ); } catch (err) { this.output.show(true); @@ -93,13 +95,14 @@ export class Cargo { throw new Error("Multiple compilation artifacts are not supported."); } - return artifacts[0].fileName; + const artifact = unwrapUndefinable(artifacts[0]); + return artifact.fileName; } private async runCargo( cargoArgs: string[], onStdoutJson: (obj: any) => void, - onStderrString: (data: string) => void + onStderrString: (data: string) => void, ): Promise<number> { const path = await cargoPath(); return await new Promise((resolve, reject) => { @@ -142,7 +145,9 @@ export async function getRustcId(dir: string): Promise<string> { const data = await execute(`${rustcPath} -V -v`, { cwd: dir }); const rx = /commit-hash:\s(.*)$/m; - return rx.exec(data)![1]; + const result = unwrapNullable(rx.exec(data)); + const first = unwrapUndefinable(result[1]); + return first; } /** Mirrors `toolchain::cargo()` implementation */ @@ -167,11 +172,11 @@ export const getPathForExecutable = memoizeAsync( if (await isFileAtUri(standardPath)) return standardPath.fsPath; } return executableName; - } + }, ); async function lookupInPath(exec: string): Promise<boolean> { - const paths = process.env.PATH ?? ""; + const paths = process.env["PATH"] ?? ""; const candidates = paths.split(path.delimiter).flatMap((dirInPath) => { const candidate = path.join(dirInPath, exec); diff --git a/editors/code/src/undefinable.ts b/editors/code/src/undefinable.ts new file mode 100644 index 00000000000..813bac5a123 --- /dev/null +++ b/editors/code/src/undefinable.ts @@ -0,0 +1,19 @@ +export type NotUndefined<T> = T extends undefined ? never : T; + +export type Undefinable<T> = T | undefined; + +function isNotUndefined<T>(input: Undefinable<T>): input is NotUndefined<T> { + return input !== undefined; +} + +export function expectNotUndefined<T>(input: Undefinable<T>, msg: string): NotUndefined<T> { + if (isNotUndefined(input)) { + return input; + } + + throw new TypeError(msg); +} + +export function unwrapUndefinable<T>(input: Undefinable<T>): NotUndefined<T> { + return expectNotUndefined(input, `unwrapping \`undefined\``); +} diff --git a/editors/code/src/util.ts b/editors/code/src/util.ts index b6b779e2660..38ce6761578 100644 --- a/editors/code/src/util.ts +++ b/editors/code/src/util.ts @@ -1,7 +1,6 @@ -import * as lc from "vscode-languageclient/node"; import * as vscode from "vscode"; import { strict as nativeAssert } from "assert"; -import { exec, ExecOptions, spawnSync } from "child_process"; +import { exec, type ExecOptions, spawnSync } from "child_process"; import { inspect } from "util"; export function assert(condition: boolean, explanation: string): asserts condition { @@ -57,37 +56,6 @@ export const log = new (class { } })(); -export async function sendRequestWithRetry<TParam, TRet>( - client: lc.LanguageClient, - reqType: lc.RequestType<TParam, TRet, unknown>, - param: TParam, - token?: vscode.CancellationToken -): Promise<TRet> { - // The sequence is `10 * (2 ** (2 * n))` where n is 1, 2, 3... - for (const delay of [40, 160, 640, 2560, 10240, null]) { - try { - return await (token - ? client.sendRequest(reqType, param, token) - : client.sendRequest(reqType, param)); - } catch (error) { - if (delay === null) { - log.warn("LSP request timed out", { method: reqType.method, param, error }); - throw error; - } - if (error.code === lc.LSPErrorCodes.RequestCancelled) { - throw error; - } - - if (error.code !== lc.LSPErrorCodes.ContentModified) { - log.warn("LSP request failed", { method: reqType.method, param, error }); - throw error; - } - await sleep(delay); - } - } - throw "unreachable"; -} - export function sleep(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)); } @@ -146,7 +114,7 @@ export function setContextValue(key: string, value: any): Thenable<void> { * underlying async function. */ export function memoizeAsync<Ret, TThis, Param extends string>( - func: (this: TThis, arg: Param) => Promise<Ret> + func: (this: TThis, arg: Param) => Promise<Ret>, ) { const cache = new Map<string, Ret>(); |
