From 66636939a63237fb7a6d1198dd9f92514807621e Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Thu, 16 Mar 2023 16:26:19 +0100 Subject: [PATCH 1/2] feat: Pop a notification prompting the user to add a Cargo.toml of unlinked file to the linkedProjects --- crates/ide-diagnostics/src/lib.rs | 1 + crates/rust-analyzer/src/global_state.rs | 4 +- crates/rust-analyzer/src/reload.rs | 3 +- editors/code/package.json | 5 ++ editors/code/src/client.ts | 74 ++++++++++++++++++++---- editors/code/src/ctx.ts | 5 +- 6 files changed, 78 insertions(+), 14 deletions(-) diff --git a/crates/ide-diagnostics/src/lib.rs b/crates/ide-diagnostics/src/lib.rs index 71f136b8c9..0dc5343f94 100644 --- a/crates/ide-diagnostics/src/lib.rs +++ b/crates/ide-diagnostics/src/lib.rs @@ -74,6 +74,7 @@ use ide_db::{ }; use syntax::{algo::find_node_at_range, ast::AstNode, SyntaxNodePtr, TextRange}; +// FIXME: Make this an enum #[derive(Copy, Clone, Debug, PartialEq)] pub struct DiagnosticCode(pub &'static str); diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs index aca6c92357..649f856db4 100644 --- a/crates/rust-analyzer/src/global_state.rs +++ b/crates/rust-analyzer/src/global_state.rs @@ -331,7 +331,7 @@ impl GlobalState { } pub(crate) fn send_notification( - &mut self, + &self, params: N::Params, ) { let not = lsp_server::Notification::new(N::METHOD.to_string(), params); @@ -372,7 +372,7 @@ impl GlobalState { self.req_queue.incoming.is_completed(&request.id) } - fn send(&mut self, message: lsp_server::Message) { + fn send(&self, message: lsp_server::Message) { self.sender.send(message).unwrap() } } diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs index 1a6e1af2eb..247d5b0438 100644 --- a/crates/rust-analyzer/src/reload.rs +++ b/crates/rust-analyzer/src/reload.rs @@ -112,7 +112,8 @@ impl GlobalState { && self.config.notifications().cargo_toml_not_found { status.health = lsp_ext::Health::Warning; - message.push_str("Failed to discover workspace.\n\n"); + message.push_str("Failed to discover workspace.\n"); + message.push_str("Consider adding the `Cargo.toml` of the workspace to the [`linkedProjects`](https://rust-analyzer.github.io/manual.html#rust-analyzer.linkedProjects) setting.\n\n"); } for ws in self.workspaces.iter() { diff --git a/editors/code/package.json b/editors/code/package.json index c5eb08748b..d0c77db7b8 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -444,6 +444,11 @@ "type": "string" } }, + "rust-analyzer.showUnlinkedFileNotification": { + "markdownDescription": "Whether to show a notification for unlinked files asking the user to add the corresponding Cargo.toml to the linked projects setting.", + "default": true, + "type": "boolean" + }, "$generated-start": {}, "rust-analyzer.assist.emitMustUse": { "markdownDescription": "Whether to insert #[must_use] when generating `as_` methods\nfor enum variants.", diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts index 565cb9c643..2a1c757dfe 100644 --- a/editors/code/src/client.ts +++ b/editors/code/src/client.ts @@ -8,6 +8,7 @@ import * as diagnostics from "./diagnostics"; import { WorkspaceEdit } from "vscode"; import { Config, prepareVSCodeConfig } from "./config"; import { randomUUID } from "crypto"; +import { sep as pathSeparator } from "path"; export interface Env { [name: string]: string; @@ -69,7 +70,8 @@ export async function createClient( outputChannel: vscode.OutputChannel, initializationOptions: vscode.WorkspaceConfiguration, serverOptions: lc.ServerOptions, - config: Config + config: Config, + unlinkedFiles: vscode.Uri[] ): Promise { const clientOptions: lc.LanguageClientOptions = { documentSelector: [{ scheme: "file", language: "rust" }], @@ -119,6 +121,65 @@ export async function createClient( const preview = config.previewRustcOutput; const errorCode = config.useRustcErrorCode; diagnosticList.forEach((diag, idx) => { + let value = + typeof diag.code === "string" || typeof diag.code === "number" + ? diag.code + : diag.code?.value; + if (value === "unlinked-file" && !unlinkedFiles.includes(uri)) { + let config = vscode.workspace.getConfiguration("rust-analyzer"); + if (config.get("showUnlinkedFileNotification")) { + unlinkedFiles.push(uri); + let folder = vscode.workspace.getWorkspaceFolder(uri)?.uri.fsPath; + if (folder) { + let parent_backslash = uri.fsPath.lastIndexOf( + pathSeparator + "src" + ); + let parent = uri.fsPath.substring(0, parent_backslash); + + if (parent.startsWith(folder)) { + let path = vscode.Uri.file( + parent + pathSeparator + "Cargo.toml" + ); + void vscode.workspace.fs.stat(path).then(() => { + 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}, do you want to add it to the linked Projects?`, + "Yes", + "No", + "Don't show this again" + ) + .then((choice) => { + switch (choice) { + case "Yes": + break; + case "No": + config.update( + "linkedProjects", + config + .get("linkedProjects") + ?.concat( + path.fsPath.substring( + folder!.length + ) + ), + false + ); + break; + case "Don't show this again": + config.update( + "showUnlinkedFileNotification", + false, + false + ); + break; + } + }); + }); + } + } + } + } + // Abuse the fact that VSCode leaks the LSP diagnostics data field through the // Diagnostic class, if they ever break this we are out of luck and have to go // back to the worst diagnostics experience ever:) @@ -138,14 +199,6 @@ export async function createClient( .substring(0, index) .replace(/^ -->[^\n]+\n/m, ""); } - let value; - if (errorCode) { - if (typeof diag.code === "string" || typeof diag.code === "number") { - value = diag.code; - } else { - value = diag.code?.value; - } - } diag.code = { target: vscode.Uri.from({ scheme: diagnostics.URI_SCHEME, @@ -153,7 +206,8 @@ export async function createClient( fragment: uri.toString(), query: idx.toString(), }), - value: value ?? "Click for full compiler diagnostic", + value: + errorCode && value ? value : "Click for full compiler diagnostic", }; } }); diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index c2dca733df..5515921ed1 100644 --- a/editors/code/src/ctx.ts +++ b/editors/code/src/ctx.ts @@ -82,6 +82,7 @@ export class Ctx { private state: PersistentState; private commandFactories: Record; private commandDisposables: Disposable[]; + private unlinkedFiles: vscode.Uri[]; get client() { return this._client; @@ -99,6 +100,7 @@ export class Ctx { this.clientSubscriptions = []; this.commandDisposables = []; this.commandFactories = commandFactories; + this.unlinkedFiles = []; this.state = new PersistentState(extCtx.globalState); this.config = new Config(extCtx); @@ -218,7 +220,8 @@ export class Ctx { this.outputChannel, initializationOptions, serverOptions, - this.config + this.config, + this.unlinkedFiles ); this.pushClientCleanup( this._client.onNotification(ra.serverStatus, (params) => From 3622fb645632fcf81af82919259be4b0012a3939 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Mon, 20 Mar 2023 21:24:53 +0100 Subject: [PATCH 2/2] Fix lints --- editors/code/src/client.ts | 75 ++++++++++++++++++-------------------- editors/code/src/ctx.ts | 4 +- 2 files changed, 37 insertions(+), 42 deletions(-) diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts index 2a1c757dfe..4ca6601a6a 100644 --- a/editors/code/src/client.ts +++ b/editors/code/src/client.ts @@ -121,59 +121,54 @@ export async function createClient( const preview = config.previewRustcOutput; const errorCode = config.useRustcErrorCode; diagnosticList.forEach((diag, idx) => { - let value = + const value = typeof diag.code === "string" || typeof diag.code === "number" ? diag.code : diag.code?.value; if (value === "unlinked-file" && !unlinkedFiles.includes(uri)) { - let config = vscode.workspace.getConfiguration("rust-analyzer"); + const config = vscode.workspace.getConfiguration("rust-analyzer"); if (config.get("showUnlinkedFileNotification")) { unlinkedFiles.push(uri); - let folder = vscode.workspace.getWorkspaceFolder(uri)?.uri.fsPath; + const folder = vscode.workspace.getWorkspaceFolder(uri)?.uri.fsPath; if (folder) { - let parent_backslash = uri.fsPath.lastIndexOf( + const parentBackslash = uri.fsPath.lastIndexOf( pathSeparator + "src" ); - let parent = uri.fsPath.substring(0, parent_backslash); + const parent = uri.fsPath.substring(0, parentBackslash); if (parent.startsWith(folder)) { - let path = vscode.Uri.file( + const path = vscode.Uri.file( parent + pathSeparator + "Cargo.toml" ); - void vscode.workspace.fs.stat(path).then(() => { - 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}, do you want to add it to the linked Projects?`, - "Yes", - "No", - "Don't show this again" - ) - .then((choice) => { - switch (choice) { - case "Yes": - break; - case "No": - config.update( - "linkedProjects", - config - .get("linkedProjects") - ?.concat( - path.fsPath.substring( - folder!.length - ) - ), - false - ); - break; - case "Don't show this again": - config.update( - "showUnlinkedFileNotification", - false, - false - ); - break; - } - }); + 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}, do you want to add it to the linked Projects?`, + "Yes", + "No", + "Don't show this again" + ); + switch (choice) { + case "Yes": + break; + case "No": + await config.update( + "linkedProjects", + config + .get("linkedProjects") + ?.concat( + path.fsPath.substring(folder.length) + ), + false + ); + break; + case "Don't show this again": + await config.update( + "showUnlinkedFileNotification", + false, + false + ); + break; + } }); } } diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index 5515921ed1..8a0f4ea477 100644 --- a/editors/code/src/ctx.ts +++ b/editors/code/src/ctx.ts @@ -95,7 +95,6 @@ export class Ctx { ) { extCtx.subscriptions.push(this); this.statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left); - this.statusBar.show(); this.workspace = workspace; this.clientSubscriptions = []; this.commandDisposables = []; @@ -338,6 +337,7 @@ export class Ctx { setServerStatus(status: ServerStatusParams | { health: "stopped" }) { let icon = ""; const statusBar = this.statusBar; + statusBar.show(); statusBar.tooltip = new vscode.MarkdownString("", true); statusBar.tooltip.isTrusted = true; switch (status.health) { @@ -386,7 +386,7 @@ export class Ctx { ); statusBar.tooltip.appendMarkdown("\n\n[Open logs](command:rust-analyzer.openLogs)"); statusBar.tooltip.appendMarkdown("\n\n[Restart server](command:rust-analyzer.startServer)"); - statusBar.tooltip.appendMarkdown("[Stop server](command:rust-analyzer.stopServer)"); + statusBar.tooltip.appendMarkdown("\n\n[Stop server](command:rust-analyzer.stopServer)"); if (!status.quiescent) icon = "$(sync~spin) "; statusBar.text = `${icon}rust-analyzer`; }