about summary refs log tree commit diff
path: root/src/tools/rust-analyzer/editors/code
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/rust-analyzer/editors/code')
-rw-r--r--src/tools/rust-analyzer/editors/code/package.json145
-rw-r--r--src/tools/rust-analyzer/editors/code/src/bootstrap.ts28
-rw-r--r--src/tools/rust-analyzer/editors/code/src/client.ts3
-rw-r--r--src/tools/rust-analyzer/editors/code/src/commands.ts20
-rw-r--r--src/tools/rust-analyzer/editors/code/src/config.ts29
-rw-r--r--src/tools/rust-analyzer/editors/code/src/ctx.ts2
-rw-r--r--src/tools/rust-analyzer/editors/code/src/debug.ts2
-rw-r--r--src/tools/rust-analyzer/editors/code/src/lsp_ext.ts35
-rw-r--r--src/tools/rust-analyzer/editors/code/src/main.ts2
-rw-r--r--src/tools/rust-analyzer/editors/code/src/run.ts41
-rw-r--r--src/tools/rust-analyzer/editors/code/src/tasks.ts102
-rw-r--r--src/tools/rust-analyzer/editors/code/src/toolchain.ts15
-rw-r--r--src/tools/rust-analyzer/editors/code/tests/unit/runnable_env.test.ts3
-rw-r--r--src/tools/rust-analyzer/editors/code/tests/unit/settings.test.ts8
-rw-r--r--src/tools/rust-analyzer/editors/code/tests/unit/tasks.test.ts139
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,
+        };
+    }
+}