Merge pull request #21359 from Veykril/push-syvnrvtmlsqk

Prompt the user in VSCode to add the rust-anaylzer componenet to the toolchain file
diff --git a/src/tools/rust-analyzer/editors/code/src/bootstrap.ts b/src/tools/rust-analyzer/editors/code/src/bootstrap.ts
index bddf195..ca5b7e3 100644
--- a/src/tools/rust-analyzer/editors/code/src/bootstrap.ts
+++ b/src/tools/rust-analyzer/editors/code/src/bootstrap.ts
@@ -1,7 +1,7 @@
 import * as vscode from "vscode";
 import * as os from "os";
 import type { Config } from "./config";
-import { type Env, log, spawnAsync } from "./util";
+import { type Env, log, RUST_TOOLCHAIN_FILES, spawnAsync } from "./util";
 import type { PersistentState } from "./persistent_state";
 import { exec } from "child_process";
 import { TextDecoder } from "node:util";
@@ -59,8 +59,12 @@
             // otherwise check if there is a toolchain override for the current vscode workspace
             // and if the toolchain of this override has a rust-analyzer component
             // if so, use the rust-analyzer component
-            const toolchainUri = vscode.Uri.joinPath(workspaceFolder.uri, "rust-toolchain.toml");
-            if (await hasToolchainFileWithRaDeclared(toolchainUri)) {
+            // Check both rust-toolchain.toml and rust-toolchain files
+            for (const toolchainFile of RUST_TOOLCHAIN_FILES) {
+                const toolchainUri = vscode.Uri.joinPath(workspaceFolder.uri, toolchainFile);
+                if (!(await hasToolchainFileWithRaDeclared(toolchainUri))) {
+                    continue;
+                }
                 const res = await spawnAsync("rustup", ["which", "rust-analyzer"], {
                     env: { ...process.env },
                     cwd: workspaceFolder.uri.fsPath,
@@ -71,6 +75,7 @@
                         res.stdout.trim(),
                         raVersionResolver,
                     );
+                    break;
                 }
             }
         }
diff --git a/src/tools/rust-analyzer/editors/code/src/ctx.ts b/src/tools/rust-analyzer/editors/code/src/ctx.ts
index a7b7be0..e07fbb8 100644
--- a/src/tools/rust-analyzer/editors/code/src/ctx.ts
+++ b/src/tools/rust-analyzer/editors/code/src/ctx.ts
@@ -1,10 +1,11 @@
 import * as vscode from "vscode";
-import type * as lc from "vscode-languageclient/node";
+import * as lc from "vscode-languageclient/node";
 import * as ra from "./lsp_ext";
 
 import { Config, prepareVSCodeConfig } from "./config";
 import { createClient } from "./client";
 import {
+    findRustToolchainFiles,
     isCargoTomlEditor,
     isDocumentInWorkspace,
     isRustDocument,
@@ -266,6 +267,17 @@
                     this.outputChannel!.show();
                 }),
             );
+            this.pushClientCleanup(
+                this._client.onNotification(
+                    lc.ShowMessageNotification.type,
+                    async (params: lc.ShowMessageParams) => {
+                        // When an MSRV warning is detected and a rust-toolchain file exists,
+                        // show an additional message with actionable guidance about adding
+                        // the rust-analyzer component.
+                        await handleMsrvWarning(params.message);
+                    },
+                ),
+            );
         }
         return this._client;
     }
@@ -592,3 +604,43 @@
 
 // eslint-disable-next-line @typescript-eslint/no-explicit-any
 export type Cmd = (...args: any[]) => unknown;
+
+/**
+ * Pattern to detect MSRV warning messages from the rust-analyzer server.
+ */
+const MSRV_WARNING_PATTERN = /using an outdated toolchain version.*rust-analyzer only supports/is;
+
+/**
+ * Handles the MSRV warning by checking for rust-toolchain files and showing
+ * an enhanced message if found.
+ */
+export async function handleMsrvWarning(message: string): Promise<boolean> {
+    if (!MSRV_WARNING_PATTERN.test(message)) {
+        return false;
+    }
+
+    const toolchainFiles = await findRustToolchainFiles();
+    if (toolchainFiles.length === 0) {
+        return false;
+    }
+
+    const openFile = "Open rust-toolchain file";
+    const result = await vscode.window.showWarningMessage(
+        "Your workspace uses a rust-toolchain file with a toolchain too old for the extension shipped rust-analyzer to work properly. " +
+            "Consider adding the rust-analyzer component to the toolchain file to use a compatible rust-analyzer version. " +
+            "Add the following to your rust-toolchain file's `[toolchain]` section:\n" +
+            'components = ["rust-analyzer"]',
+        { modal: true },
+        openFile,
+    );
+
+    if (result === openFile) {
+        const fileToOpen = toolchainFiles[0];
+        if (fileToOpen) {
+            const document = await vscode.workspace.openTextDocument(fileToOpen);
+            await vscode.window.showTextDocument(document);
+        }
+    }
+
+    return true;
+}
diff --git a/src/tools/rust-analyzer/editors/code/src/util.ts b/src/tools/rust-analyzer/editors/code/src/util.ts
index 410b055..05b4750 100644
--- a/src/tools/rust-analyzer/editors/code/src/util.ts
+++ b/src/tools/rust-analyzer/editors/code/src/util.ts
@@ -328,3 +328,28 @@
 
     return path;
 }
+
+export const RUST_TOOLCHAIN_FILES = ["rust-toolchain.toml", "rust-toolchain"] as const;
+
+export async function findRustToolchainFiles(): Promise<vscode.Uri[]> {
+    const found: vscode.Uri[] = [];
+    const workspaceFolders = vscode.workspace.workspaceFolders;
+    if (!workspaceFolders) {
+        return found;
+    }
+
+    for (const folder of workspaceFolders) {
+        for (const filename of RUST_TOOLCHAIN_FILES) {
+            const toolchainUri = vscode.Uri.joinPath(folder.uri, filename);
+            try {
+                await vscode.workspace.fs.stat(toolchainUri);
+                found.push(toolchainUri);
+                // Only add the first toolchain file found per workspace folder
+                break;
+            } catch {
+                // File doesn't exist, continue
+            }
+        }
+    }
+    return found;
+}