diff options
Diffstat (limited to 'src/tools/rust-analyzer/editors/code')
15 files changed, 422 insertions, 152 deletions
diff --git a/src/tools/rust-analyzer/editors/code/package.json b/src/tools/rust-analyzer/editors/code/package.json index db2a989106f..7e77c7e52fa 100644 --- a/src/tools/rust-analyzer/editors/code/package.json +++ b/src/tools/rust-analyzer/editors/code/package.json @@ -305,6 +305,16 @@ "command": "rust-analyzer.toggleLSPLogs", "title": "Toggle LSP Logs", "category": "rust-analyzer" + }, + { + "command": "rust-analyzer.openWalkthrough", + "title": "Open Walkthrough", + "category": "rust-analyzer" + }, + { + "command": "rust-analyzer.openFAQ", + "title": "Open FAQ", + "category": "rust-analyzer" } ], "keybindings": [ @@ -323,14 +333,6 @@ { "title": "general", "properties": { - "rust-analyzer.cargoRunner": { - "type": [ - "null", - "string" - ], - "default": null, - "description": "Custom cargo runner extension ID." - }, "rust-analyzer.restartServerOnConfigChange": { "markdownDescription": "Whether to restart the server automatically when certain settings that require a restart are changed.", "default": false, @@ -591,9 +593,19 @@ { "title": "assist", "properties": { + "rust-analyzer.assist.termSearch.borrowcheck": { + "markdownDescription": "Enable borrow checking for term search code assists. If set to false, also there will be more suggestions, but some of them may not borrow-check.", + "default": true, + "type": "boolean" + } + } + }, + { + "title": "assist", + "properties": { "rust-analyzer.assist.termSearch.fuel": { - "markdownDescription": "Term search fuel in \"units of work\" for assists (Defaults to 400).", - "default": 400, + "markdownDescription": "Term search fuel in \"units of work\" for assists (Defaults to 1800).", + "default": 1800, "type": "integer", "minimum": 0 } @@ -1187,12 +1199,6 @@ "description": "Put the expression into a pinned `Box`", "scope": "expr" }, - "Ok": { - "postfix": "ok", - "body": "Ok(${receiver})", - "description": "Wrap the expression in a `Result::Ok`", - "scope": "expr" - }, "Err": { "postfix": "err", "body": "Err(${receiver})", @@ -1204,6 +1210,12 @@ "body": "Some(${receiver})", "description": "Wrap the expression in an `Option::Some`", "scope": "expr" + }, + "Ok": { + "postfix": "ok", + "body": "Ok(${receiver})", + "description": "Wrap the expression in a `Result::Ok`", + "scope": "expr" } }, "type": "object" @@ -1224,8 +1236,8 @@ "title": "completion", "properties": { "rust-analyzer.completion.termSearch.fuel": { - "markdownDescription": "Term search fuel in \"units of work\" for autocompletion (Defaults to 200).", - "default": 200, + "markdownDescription": "Term search fuel in \"units of work\" for autocompletion (Defaults to 1000).", + "default": 1000, "type": "integer", "minimum": 0 } @@ -1723,6 +1735,16 @@ } }, { + "title": "imports", + "properties": { + "rust-analyzer.imports.prefixExternPrelude": { + "markdownDescription": "Whether to prefix external (including std, core) crate imports with `::`. e.g. \"use ::std::io::Read;\".", + "default": false, + "type": "boolean" + } + } + }, + { "title": "inlayHints", "properties": { "rust-analyzer.inlayHints.bindingModeHints.enable": { @@ -1890,6 +1912,36 @@ { "title": "inlayHints", "properties": { + "rust-analyzer.inlayHints.genericParameterHints.const.enable": { + "markdownDescription": "Whether to show const generic parameter name inlay hints.", + "default": false, + "type": "boolean" + } + } + }, + { + "title": "inlayHints", + "properties": { + "rust-analyzer.inlayHints.genericParameterHints.lifetime.enable": { + "markdownDescription": "Whether to show generic lifetime parameter name inlay hints.", + "default": true, + "type": "boolean" + } + } + }, + { + "title": "inlayHints", + "properties": { + "rust-analyzer.inlayHints.genericParameterHints.type.enable": { + "markdownDescription": "Whether to show generic type parameter name inlay hints.", + "default": false, + "type": "boolean" + } + } + }, + { + "title": "inlayHints", + "properties": { "rust-analyzer.inlayHints.implicitDrops.enable": { "markdownDescription": "Whether to show implicit drop hints.", "default": false, @@ -3132,6 +3184,12 @@ { "command": "rust-analyzer.toggleLSPLogs", "when": "inRustProject" + }, + { + "command": "rust-analyzer.openWalkthrough" + }, + { + "command": "rust-analyzer.openFAQ" } ], "editor/context": [ @@ -3161,6 +3219,57 @@ "fileMatch": "rust-project.json", "url": "https://json.schemastore.org/rust-project.json" } + ], + "walkthroughs": [ + { + "id": "landing", + "title": "Learn about rust-analyzer", + "description": "A brief introduction to get started with rust-analyzer. Learn about key features and resources to help you get the most out of the extension.", + "steps": [ + { + "id": "setup", + "title": "Useful Setup Tips", + "description": "There are a couple of things you might want to configure upfront to your tastes. We'll name a few here but be sure to check out the docs linked below!\n\n**Marking library sources as readonly**\n\nAdding the following to your settings.json will mark all Rust library sources as readonly:\n```json\n\"files.readonlyInclude\": {\n \"**/.cargo/registry/src/**/*.rs\": true,\n \"**/lib/rustlib/src/rust/library/**/*.rs\": true,\n},\n```\n\n**Check on Save**\n\nBy default, rust-analyzer will run `cargo check` on your codebase when you save a file, rendering diagnostics emitted by `cargo check` within your code. This can potentially collide with other `cargo` commands running concurrently, blocking them from running for a certain amount of time. In these cases it is recommended to disable the `rust-analyzer.checkOnSave` configuration and running the `rust-analyzer: Run flycheck` command on-demand instead.", + "media": { + "image": "./icon.png", + "altText": "rust-analyzer logo" + } + }, + { + "id": "docs", + "title": "Visit the docs!", + "description": "Confused about configurations? Want to learn more about rust-analyzer? Visit the [User Manual](https://rust-analyzer.github.io/manual.html)!", + "media": { + "image": "./icon.png", + "altText": "rust-analyzer logo" + }, + "completionEvents": [ + "onLink:https://rust-analyzer.github.io/manual.html" + ] + }, + { + "id": "faq", + "title": "FAQ", + "description": "What are these code hints that are being inserted into my code?\n\nThese hints are called inlay hints which rust-analyzer support and are enabled by default in VSCode. If you wish to disable them you can do so via the `editor.inlayHints.enabled` setting.", + "media": { + "image": "icon.png", + "altText": "rust-analyzer logo" + } + }, + { + "id": "changelog", + "title": "Changelog", + "description": "Stay up-to-date with the latest changes in rust-analyzer. Check out the changelog [here](https://rust-analyzer.github.io/thisweek)!", + "media": { + "image": "icon.png", + "altText": "rust-analyzer logo" + }, + "completionEvents": [ + "onLink:https://rust-analyzer.github.io/thisweek" + ] + } + ] + } ] } } diff --git a/src/tools/rust-analyzer/editors/code/src/bootstrap.ts b/src/tools/rust-analyzer/editors/code/src/bootstrap.ts index 5a92b285ae6..f2884ad0b05 100644 --- a/src/tools/rust-analyzer/editors/code/src/bootstrap.ts +++ b/src/tools/rust-analyzer/editors/code/src/bootstrap.ts @@ -36,6 +36,12 @@ async function getServer( config: Config, state: PersistentState, ): Promise<string | undefined> { + const packageJson: { + version: string; + releaseTag: string | null; + enableProposedApi: boolean | undefined; + } = context.extension.packageJSON; + const explicitPath = process.env["__RA_LSP_SERVER_DEBUG"] ?? config.serverPath; if (explicitPath) { if (explicitPath.startsWith("~/")) { @@ -43,7 +49,7 @@ async function getServer( } return explicitPath; } - if (config.package.releaseTag === null) return "rust-analyzer"; + if (packageJson.releaseTag === null) return "rust-analyzer"; const ext = process.platform === "win32" ? ".exe" : ""; const bundled = vscode.Uri.joinPath(context.extensionUri, "server", `rust-analyzer${ext}`); @@ -54,8 +60,15 @@ async function getServer( if (bundledExists) { let server = bundled; if (await isNixOs()) { - server = await getNixOsServer(config, ext, state, bundled, server); - await state.updateServerVersion(config.package.version); + server = await getNixOsServer( + context.globalStorageUri, + packageJson.version, + ext, + state, + bundled, + server, + ); + await state.updateServerVersion(packageJson.version); } return server.fsPath; } @@ -86,19 +99,20 @@ export function isValidExecutable(path: string, extraEnv: Env): boolean { } async function getNixOsServer( - config: Config, + globalStorageUri: vscode.Uri, + version: string, ext: string, state: PersistentState, bundled: vscode.Uri, server: vscode.Uri, ) { - await vscode.workspace.fs.createDirectory(config.globalStorageUri).then(); - const dest = vscode.Uri.joinPath(config.globalStorageUri, `rust-analyzer${ext}`); + await vscode.workspace.fs.createDirectory(globalStorageUri).then(); + const dest = vscode.Uri.joinPath(globalStorageUri, `rust-analyzer${ext}`); let exists = await vscode.workspace.fs.stat(dest).then( () => true, () => false, ); - if (exists && config.package.version !== state.serverVersion) { + if (exists && version !== state.serverVersion) { await vscode.workspace.fs.delete(dest); exists = false; } diff --git a/src/tools/rust-analyzer/editors/code/src/client.ts b/src/tools/rust-analyzer/editors/code/src/client.ts index 1c2a34b484d..542233e7b91 100644 --- a/src/tools/rust-analyzer/editors/code/src/client.ts +++ b/src/tools/rust-analyzer/editors/code/src/client.ts @@ -76,7 +76,8 @@ export async function createClient( // value === "unlinked-file" && value === "temporary-disabled" && !unlinkedFiles.includes(uri) && - diag.message !== "file not included in module tree" + (diag.message === "file not included in crate hierarchy" || + diag.message.startsWith("This file is not included in any crates")) ) { const config = vscode.workspace.getConfiguration("rust-analyzer"); if (config.get("showUnlinkedFileNotification")) { diff --git a/src/tools/rust-analyzer/editors/code/src/commands.ts b/src/tools/rust-analyzer/editors/code/src/commands.ts index f0f9fab1c64..2b0b3001062 100644 --- a/src/tools/rust-analyzer/editors/code/src/commands.ts +++ b/src/tools/rust-analyzer/editors/code/src/commands.ts @@ -1502,3 +1502,23 @@ export function toggleLSPLogs(ctx: Ctx): Cmd { } }; } + +export function openWalkthrough(_: Ctx): Cmd { + return async () => { + await vscode.commands.executeCommand( + "workbench.action.openWalkthrough", + "rust-lang.rust-analyzer#landing", + false, + ); + }; +} + +export function openFAQ(_: Ctx): Cmd { + return async () => { + await vscode.commands.executeCommand( + "workbench.action.openWalkthrough", + "rust-lang.rust-analyzer#faq", + true, + ); + }; +} diff --git a/src/tools/rust-analyzer/editors/code/src/config.ts b/src/tools/rust-analyzer/editors/code/src/config.ts index 1931cfe3813..ca77215004d 100644 --- a/src/tools/rust-analyzer/editors/code/src/config.ts +++ b/src/tools/rust-analyzer/editors/code/src/config.ts @@ -4,13 +4,14 @@ import * as path from "path"; import * as vscode from "vscode"; import { type Env, log, unwrapUndefinable, expectNotUndefined } from "./util"; import type { JsonProject } from "./rust_project"; +import type { Disposable } from "./ctx"; export type RunnableEnvCfgItem = { mask?: string; env: Record<string, string>; platform?: string | string[]; }; -export type RunnableEnvCfg = undefined | Record<string, string> | RunnableEnvCfgItem[]; +export type RunnableEnvCfg = Record<string, string> | RunnableEnvCfgItem[]; export class Config { readonly extensionId = "rust-lang.rust-analyzer"; @@ -29,22 +30,9 @@ export class Config { (opt) => `${this.rootSection}.${opt}`, ); - readonly package: { - version: string; - releaseTag: string | null; - enableProposedApi: boolean | undefined; - } = vscode.extensions.getExtension(this.extensionId)!.packageJSON; - - readonly globalStorageUri: vscode.Uri; - - constructor(ctx: vscode.ExtensionContext) { - this.globalStorageUri = ctx.globalStorageUri; + constructor(disposables: Disposable[]) { this.discoveredWorkspaces = []; - vscode.workspace.onDidChangeConfiguration( - this.onDidChangeConfiguration, - this, - ctx.subscriptions, - ); + vscode.workspace.onDidChangeConfiguration(this.onDidChangeConfiguration, this, disposables); this.refreshLogging(); this.configureLanguage(); } @@ -55,7 +43,10 @@ export class Config { private refreshLogging() { log.setEnabled(this.traceExtension ?? false); - log.info("Extension version:", this.package.version); + log.info( + "Extension version:", + vscode.extensions.getExtension(this.extensionId)!.packageJSON.version, + ); const cfg = Object.entries(this.cfg).filter(([_, val]) => !(val instanceof Function)); log.info("Using configuration", Object.fromEntries(cfg)); @@ -277,10 +268,6 @@ export class Config { return this.get<string[]>("runnables.problemMatcher") || []; } - get cargoRunner() { - return this.get<string | undefined>("cargoRunner"); - } - get testExplorer() { return this.get<boolean | undefined>("testExplorer"); } diff --git a/src/tools/rust-analyzer/editors/code/src/ctx.ts b/src/tools/rust-analyzer/editors/code/src/ctx.ts index bf0b84ec358..caa99d76194 100644 --- a/src/tools/rust-analyzer/editors/code/src/ctx.ts +++ b/src/tools/rust-analyzer/editors/code/src/ctx.ts @@ -118,7 +118,7 @@ export class Ctx implements RustAnalyzerExtensionApi { extCtx.subscriptions.push(this); this.version = extCtx.extension.packageJSON.version ?? "<unknown>"; this._serverVersion = "<not running>"; - this.config = new Config(extCtx); + this.config = new Config(extCtx.subscriptions); this.statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left); if (this.config.testExplorer) { this.testController = vscode.tests.createTestController( diff --git a/src/tools/rust-analyzer/editors/code/src/debug.ts b/src/tools/rust-analyzer/editors/code/src/debug.ts index 58fe1df51f4..f23e3680933 100644 --- a/src/tools/rust-analyzer/editors/code/src/debug.ts +++ b/src/tools/rust-analyzer/editors/code/src/debug.ts @@ -180,7 +180,7 @@ async function getDebugExecutable( env: Record<string, string>, ): Promise<string> { const cargo = new Cargo(runnableArgs.workspaceRoot || ".", debugOutput, env); - const executable = await cargo.executableFromArgs(runnableArgs.cargoArgs); + const executable = await cargo.executableFromArgs(runnableArgs); // if we are here, there were no compilation errors. return executable; diff --git a/src/tools/rust-analyzer/editors/code/src/lsp_ext.ts b/src/tools/rust-analyzer/editors/code/src/lsp_ext.ts index 699052e4d44..e24893b2509 100644 --- a/src/tools/rust-analyzer/editors/code/src/lsp_ext.ts +++ b/src/tools/rust-analyzer/editors/code/src/lsp_ext.ts @@ -235,22 +235,43 @@ type RunnableShell = { args: ShellRunnableArgs; }; +export type CommonRunnableArgs = { + /** + * Environment variables to set before running the command. + */ + environment?: Record<string, string>; + /** + * The working directory to run the command in. + */ + cwd: string; +}; + export type ShellRunnableArgs = { kind: string; program: string; args: string[]; - cwd: string; -}; +} & CommonRunnableArgs; export type CargoRunnableArgs = { + /** + * The workspace root directory of the cargo project. + */ workspaceRoot?: string; - cargoArgs: string[]; - cwd: string; - cargoExtraArgs: string[]; + /** + * Arguments to pass to the executable, these will be passed to the command after a `--` argument. + */ executableArgs: string[]; - expectTest?: boolean; + /** + * Arguments to pass to cargo. + */ + cargoArgs: string[]; + /** + * Command to execute instead of `cargo`. + */ + // This is supplied by the user via config. We could pull this through the client config in the + // extension directly, but that would prevent us from honoring the rust-analyzer.toml for it. overrideCargo?: string; -}; +} & CommonRunnableArgs; export type RunnablesParams = { textDocument: lc.TextDocumentIdentifier; diff --git a/src/tools/rust-analyzer/editors/code/src/main.ts b/src/tools/rust-analyzer/editors/code/src/main.ts index ff67bb7bd59..c96f2ae869e 100644 --- a/src/tools/rust-analyzer/editors/code/src/main.ts +++ b/src/tools/rust-analyzer/editors/code/src/main.ts @@ -178,6 +178,8 @@ function createCommands(): Record<string, CommandFactory> { viewMemoryLayout: { enabled: commands.viewMemoryLayout }, toggleCheckOnSave: { enabled: commands.toggleCheckOnSave }, toggleLSPLogs: { enabled: commands.toggleLSPLogs }, + openWalkthrough: { enabled: commands.openWalkthrough }, + openFAQ: { enabled: commands.openFAQ }, // Internal commands which are invoked by the server. applyActionGroup: { enabled: commands.applyActionGroup }, applySnippetWorkspaceEdit: { enabled: commands.applySnippetWorkspaceEditCommand }, diff --git a/src/tools/rust-analyzer/editors/code/src/run.ts b/src/tools/rust-analyzer/editors/code/src/run.ts index 7a9049af0de..783bbc1607d 100644 --- a/src/tools/rust-analyzer/editors/code/src/run.ts +++ b/src/tools/rust-analyzer/editors/code/src/run.ts @@ -66,23 +66,21 @@ export class RunnableQuickPick implements vscode.QuickPickItem { } } -export function prepareBaseEnv(): Record<string, string> { +export function prepareBaseEnv(base?: Record<string, string>): Record<string, string> { const env: Record<string, string> = { RUST_BACKTRACE: "short" }; - Object.assign(env, process.env as { [key: string]: string }); + Object.assign(env, process.env); + if (base) { + Object.assign(env, base); + } return env; } export function prepareEnv( label: string, runnableArgs: ra.CargoRunnableArgs, - runnableEnvCfg: RunnableEnvCfg, + runnableEnvCfg?: RunnableEnvCfg, ): Record<string, string> { - const env = prepareBaseEnv(); - - if (runnableArgs.expectTest) { - env["UPDATE_EXPECT"] = "1"; - } - + const env = prepareBaseEnv(runnableArgs.environment); const platform = process.platform; const checkPlatform = (it: RunnableEnvCfgItem) => { @@ -113,26 +111,31 @@ export async function createTaskFromRunnable( runnable: ra.Runnable, config: Config, ): Promise<vscode.Task> { - let definition: tasks.RustTargetDefinition; + const target = vscode.workspace.workspaceFolders?.[0]; + + let definition: tasks.TaskDefinition; + let options; + let cargo; if (runnable.kind === "cargo") { const runnableArgs = runnable.args; let args = createCargoArgs(runnableArgs); - let program: string; if (runnableArgs.overrideCargo) { // Split on spaces to allow overrides like "wrapper cargo". const cargoParts = runnableArgs.overrideCargo.split(" "); - program = unwrapUndefinable(cargoParts[0]); + cargo = unwrapUndefinable(cargoParts[0]); args = [...cargoParts.slice(1), ...args]; } else { - program = await toolchain.cargoPath(); + cargo = await toolchain.cargoPath(); } definition = { type: tasks.CARGO_TASK_TYPE, - command: program, - args, + command: unwrapUndefinable(args[0]), + args: args.slice(1), + }; + options = { cwd: runnableArgs.workspaceRoot || ".", env: prepareEnv(runnable.label, runnableArgs, config.runnablesExtraEnv), }; @@ -142,13 +145,14 @@ export async function createTaskFromRunnable( type: tasks.SHELL_TASK_TYPE, command: runnableArgs.program, args: runnableArgs.args, + }; + options = { cwd: runnableArgs.cwd, env: prepareBaseEnv(), }; } - const target = vscode.workspace.workspaceFolders?.[0]; - const exec = await tasks.targetToExecution(definition, config.cargoRunner, true); + const exec = await tasks.targetToExecution(definition, options, cargo); const task = await tasks.buildRustTask( target, definition, @@ -167,9 +171,6 @@ export async function createTaskFromRunnable( export function createCargoArgs(runnableArgs: ra.CargoRunnableArgs): string[] { const args = [...runnableArgs.cargoArgs]; // should be a copy! - if (runnableArgs.cargoExtraArgs) { - args.push(...runnableArgs.cargoExtraArgs); // Append user-specified cargo options. - } if (runnableArgs.executableArgs.length > 0) { args.push("--", ...runnableArgs.executableArgs); } diff --git a/src/tools/rust-analyzer/editors/code/src/tasks.ts b/src/tools/rust-analyzer/editors/code/src/tasks.ts index 6f4fbf91889..fac1cc6394f 100644 --- a/src/tools/rust-analyzer/editors/code/src/tasks.ts +++ b/src/tools/rust-analyzer/editors/code/src/tasks.ts @@ -1,6 +1,5 @@ import * as vscode from "vscode"; import type { Config } from "./config"; -import { log, unwrapUndefinable } from "./util"; import * as toolchain from "./toolchain"; // This ends up as the `type` key in tasks.json. RLS also uses `cargo` and @@ -10,21 +9,21 @@ export const SHELL_TASK_TYPE = "shell"; export const RUST_TASK_SOURCE = "rust"; -export type RustTargetDefinition = { +export type TaskDefinition = vscode.TaskDefinition & { readonly type: typeof CARGO_TASK_TYPE | typeof SHELL_TASK_TYPE; -} & vscode.TaskDefinition & - RustTarget; -export type RustTarget = { - // The command to run, usually `cargo`. - command: string; - // Additional arguments passed to the command. args?: string[]; - // The working directory to run the command in. - cwd?: string; - // The shell environment. - env?: { [key: string]: string }; + command: string; }; +export type CargoTaskDefinition = { + env?: Record<string, string>; + type: typeof CARGO_TASK_TYPE; +} & TaskDefinition; + +function isCargoTask(definition: vscode.TaskDefinition): definition is CargoTaskDefinition { + return definition.type === CARGO_TASK_TYPE; +} + class RustTaskProvider implements vscode.TaskProvider { private readonly config: Config; @@ -58,13 +57,13 @@ class RustTaskProvider implements vscode.TaskProvider { for (const workspaceTarget of vscode.workspace.workspaceFolders) { for (const def of defs) { const definition = { - command: cargo, - args: [def.command], - }; - const exec = await targetToExecution(definition, this.config.cargoRunner); + command: def.command, + type: CARGO_TASK_TYPE, + } as const; + const exec = await targetToExecution(definition, {}, cargo); const vscodeTask = await buildRustTask( workspaceTarget, - { ...definition, type: CARGO_TASK_TYPE }, + definition, `cargo ${def.command}`, this.config.problemMatcher, exec, @@ -81,23 +80,13 @@ class RustTaskProvider implements vscode.TaskProvider { // VSCode calls this for every cargo task in the user's tasks.json, // we need to inform VSCode how to execute that command by creating // a ShellExecution for it. - if (task.definition.type === CARGO_TASK_TYPE) { - const taskDefinition = task.definition as RustTargetDefinition; - const cargo = await toolchain.cargoPath(); - const exec = await targetToExecution( - { - command: cargo, - args: [taskDefinition.command].concat(taskDefinition.args || []), - cwd: taskDefinition.cwd, - env: taskDefinition.env, - }, - this.config.cargoRunner, - ); - return await buildRustTask( + if (isCargoTask(task.definition)) { + const exec = await targetToExecution(task.definition, { env: task.definition.env }); + return buildRustTask( task.scope, - taskDefinition, + task.definition, task.name, - this.config.problemMatcher, + task.problemMatchers, exec, ); } @@ -108,7 +97,7 @@ class RustTaskProvider implements vscode.TaskProvider { export async function buildRustTask( scope: vscode.WorkspaceFolder | vscode.TaskScope | undefined, - definition: RustTargetDefinition, + definition: TaskDefinition, name: string, problemMatcher: string[], exec: vscode.ProcessExecution | vscode.ShellExecution, @@ -126,40 +115,23 @@ export async function buildRustTask( } export async function targetToExecution( - definition: RustTarget, - customRunner?: string, - throwOnError: boolean = false, + definition: TaskDefinition, + options?: { + env?: { [key: string]: string }; + cwd?: string; + }, + cargo?: string, ): Promise<vscode.ProcessExecution | vscode.ShellExecution> { - if (customRunner) { - const runnerCommand = `${customRunner}.buildShellExecution`; - - try { - const runnerArgs = { - kind: CARGO_TASK_TYPE, - args: definition.args, - cwd: definition.cwd, - env: definition.env, - }; - const customExec = await vscode.commands.executeCommand(runnerCommand, runnerArgs); - if (customExec) { - if (customExec instanceof vscode.ShellExecution) { - return customExec; - } else { - log.debug("Invalid cargo ShellExecution", customExec); - throw "Invalid cargo ShellExecution."; - } - } - // fallback to default processing - } catch (e) { - if (throwOnError) throw `Cargo runner '${customRunner}' failed! ${e}`; - // fallback to default processing - } + let command, args; + if (isCargoTask(definition)) { + // FIXME: The server should provide cargo + command = cargo || (await toolchain.cargoPath()); + args = [definition.command].concat(definition.args || []); + } else { + command = definition.command; + args = definition.args || []; } - const args = unwrapUndefinable(definition.args); - return new vscode.ProcessExecution(definition.command, args, { - cwd: definition.cwd, - env: definition.env, - }); + return new vscode.ProcessExecution(command, args, options); } export function activateTaskProvider(config: Config): vscode.Disposable { diff --git a/src/tools/rust-analyzer/editors/code/src/toolchain.ts b/src/tools/rust-analyzer/editors/code/src/toolchain.ts index a48d2d90cce..6a0b5c26d82 100644 --- a/src/tools/rust-analyzer/editors/code/src/toolchain.ts +++ b/src/tools/rust-analyzer/editors/code/src/toolchain.ts @@ -4,6 +4,7 @@ import * as path from "path"; import * as readline from "readline"; import * as vscode from "vscode"; import { execute, log, memoizeAsync, unwrapNullable, unwrapUndefinable } from "./util"; +import type { CargoRunnableArgs } from "./lsp_ext"; interface CompilationArtifact { fileName: string; @@ -25,9 +26,8 @@ export class Cargo { ) {} // Made public for testing purposes - static artifactSpec(args: readonly string[]): ArtifactSpec { - const cargoArgs = [...args, "--message-format=json"]; - + static artifactSpec(cargoArgs: string[], executableArgs?: string[]): ArtifactSpec { + cargoArgs = [...cargoArgs, "--message-format=json"]; // arguments for a runnable from the quick pick should be updated. // see crates\rust-analyzer\src\main_loop\handlers.rs, handle_code_lens switch (cargoArgs[0]) { @@ -48,6 +48,9 @@ export class Cargo { // produce 2 artifacts: {"kind": "bin"} and {"kind": "test"} result.filter = (artifacts) => artifacts.filter((it) => it.isTest); } + if (executableArgs) { + cargoArgs.push("--", ...executableArgs); + } return result; } @@ -84,8 +87,10 @@ export class Cargo { return spec.filter?.(artifacts) ?? artifacts; } - async executableFromArgs(args: readonly string[]): Promise<string> { - const artifacts = await this.getArtifacts(Cargo.artifactSpec(args)); + async executableFromArgs(runnableArgs: CargoRunnableArgs): Promise<string> { + const artifacts = await this.getArtifacts( + Cargo.artifactSpec(runnableArgs.cargoArgs, runnableArgs.executableArgs), + ); if (artifacts.length === 0) { throw new Error("No compilation artifacts"); diff --git a/src/tools/rust-analyzer/editors/code/tests/unit/runnable_env.test.ts b/src/tools/rust-analyzer/editors/code/tests/unit/runnable_env.test.ts index 21bdaf5384d..81850e03f1c 100644 --- a/src/tools/rust-analyzer/editors/code/tests/unit/runnable_env.test.ts +++ b/src/tools/rust-analyzer/editors/code/tests/unit/runnable_env.test.ts @@ -12,12 +12,11 @@ function makeRunnable(label: string): ra.Runnable { cargoArgs: [], cwd: ".", executableArgs: [], - cargoExtraArgs: [], }, }; } -function fakePrepareEnv(runnableName: string, config: RunnableEnvCfg): Record<string, string> { +function fakePrepareEnv(runnableName: string, config?: RunnableEnvCfg): Record<string, string> { const runnable = makeRunnable(runnableName); const runnableArgs = runnable.args as ra.CargoRunnableArgs; return prepareEnv(runnable.label, runnableArgs, config); diff --git a/src/tools/rust-analyzer/editors/code/tests/unit/settings.test.ts b/src/tools/rust-analyzer/editors/code/tests/unit/settings.test.ts index bafb9569a1c..84501dde6ca 100644 --- a/src/tools/rust-analyzer/editors/code/tests/unit/settings.test.ts +++ b/src/tools/rust-analyzer/editors/code/tests/unit/settings.test.ts @@ -13,7 +13,7 @@ export async function getTests(ctx: Context) { USING_MY_VAR: "test test test", MY_VAR: "test", }; - const actualEnv = await substituteVariablesInEnv(envJson); + const actualEnv = substituteVariablesInEnv(envJson); assert.deepStrictEqual(actualEnv, expectedEnv); }); @@ -34,7 +34,7 @@ export async function getTests(ctx: Context) { E_IS_ISOLATED: "test", F_USES_E: "test", }; - const actualEnv = await substituteVariablesInEnv(envJson); + const actualEnv = substituteVariablesInEnv(envJson); assert.deepStrictEqual(actualEnv, expectedEnv); }); @@ -47,7 +47,7 @@ export async function getTests(ctx: Context) { USING_EXTERNAL_VAR: "test test test", }; - const actualEnv = await substituteVariablesInEnv(envJson); + const actualEnv = substituteVariablesInEnv(envJson); assert.deepStrictEqual(actualEnv, expectedEnv); delete process.env["TEST_VARIABLE"]; }); @@ -56,7 +56,7 @@ export async function getTests(ctx: Context) { const envJson = { USING_VSCODE_VAR: "${workspaceFolderBasename}", }; - const actualEnv = await substituteVariablesInEnv(envJson); + const actualEnv = substituteVariablesInEnv(envJson); assert.deepStrictEqual(actualEnv["USING_VSCODE_VAR"], "code"); }); }); diff --git a/src/tools/rust-analyzer/editors/code/tests/unit/tasks.test.ts b/src/tools/rust-analyzer/editors/code/tests/unit/tasks.test.ts new file mode 100644 index 00000000000..9bccaaf3d47 --- /dev/null +++ b/src/tools/rust-analyzer/editors/code/tests/unit/tasks.test.ts @@ -0,0 +1,139 @@ +import type { Context } from "."; +import * as vscode from "vscode"; +import * as assert from "assert"; +import { targetToExecution } from "../../src/tasks"; + +export async function getTests(ctx: Context) { + await ctx.suite("Tasks", (suite) => { + suite.addTest("cargo targetToExecution", async () => { + assert.deepStrictEqual( + await targetToExecution({ + type: "cargo", + command: "check", + args: ["foo"], + }).then(executionToSimple), + { + process: "cargo", + args: ["check", "foo"], + }, + ); + }); + + suite.addTest("shell targetToExecution", async () => { + assert.deepStrictEqual( + await targetToExecution({ + type: "shell", + command: "thing", + args: ["foo"], + }).then(executionToSimple), + { + process: "thing", + args: ["foo"], + }, + ); + }); + + suite.addTest("base tasks", async () => { + const tasks = await vscode.tasks.fetchTasks({ type: "cargo" }); + const expectedTasks = [ + { + definition: { type: "cargo", command: "build" }, + name: "cargo build", + execution: { + process: "cargo", + args: ["build"], + }, + }, + { + definition: { + type: "cargo", + command: "check", + }, + name: "cargo check", + execution: { + process: "cargo", + args: ["check"], + }, + }, + { + definition: { type: "cargo", command: "clippy" }, + name: "cargo clippy", + execution: { + process: "cargo", + args: ["clippy"], + }, + }, + { + definition: { type: "cargo", command: "test" }, + name: "cargo test", + execution: { + process: "cargo", + args: ["test"], + }, + }, + { + definition: { + type: "cargo", + command: "clean", + }, + name: "cargo clean", + execution: { + process: "cargo", + args: ["clean"], + }, + }, + { + definition: { type: "cargo", command: "run" }, + name: "cargo run", + execution: { + process: "cargo", + args: ["run"], + }, + }, + ]; + tasks.map(f).forEach((actual, i) => { + const expected = expectedTasks[i]; + assert.deepStrictEqual(actual, expected); + }); + }); + }); +} + +function f(task: vscode.Task): { + definition: vscode.TaskDefinition; + name: string; + execution: { + args: string[]; + } & ({ command: string } | { process: string }); +} { + const execution = executionToSimple(task.execution!); + + return { + definition: task.definition, + name: task.name, + execution, + }; +} +function executionToSimple( + taskExecution: vscode.ProcessExecution | vscode.ShellExecution | vscode.CustomExecution, +): { + args: string[]; +} & ({ command: string } | { process: string }) { + const exec = taskExecution as vscode.ProcessExecution | vscode.ShellExecution; + if (exec instanceof vscode.ShellExecution) { + return { + command: typeof exec.command === "string" ? exec.command : exec.command.value, + args: exec.args.map((arg) => { + if (typeof arg === "string") { + return arg; + } + return arg.value; + }), + }; + } else { + return { + process: exec.process, + args: exec.args, + }; + } +} |
