diff options
| author | Laurențiu Nicola <lnicola@dend.ro> | 2022-10-26 17:40:41 +0300 |
|---|---|---|
| committer | Laurențiu Nicola <lnicola@dend.ro> | 2022-10-26 17:40:41 +0300 |
| commit | 22a6bc4da0ce15606e3a7fb01c5cc6ff175f79af (patch) | |
| tree | 9832a32abb629ff63b4842c4ea706efd8740d750 /src/tools/rust-analyzer/editors/code | |
| parent | 43dd3d514b6b11c5195de2fd8e665828801d0972 (diff) | |
| parent | 43fb9563b2943d6abc5f3552195f3e27ac618966 (diff) | |
| download | rust-22a6bc4da0ce15606e3a7fb01c5cc6ff175f79af.tar.gz rust-22a6bc4da0ce15606e3a7fb01c5cc6ff175f79af.zip | |
:arrow_up: rust-analyzer
Diffstat (limited to 'src/tools/rust-analyzer/editors/code')
| -rw-r--r-- | src/tools/rust-analyzer/editors/code/package-lock.json | 64 | ||||
| -rw-r--r-- | src/tools/rust-analyzer/editors/code/package.json | 65 | ||||
| -rw-r--r-- | src/tools/rust-analyzer/editors/code/src/ast_inspector.ts | 8 | ||||
| -rw-r--r-- | src/tools/rust-analyzer/editors/code/src/bootstrap.ts | 148 | ||||
| -rw-r--r-- | src/tools/rust-analyzer/editors/code/src/client.ts | 65 | ||||
| -rw-r--r-- | src/tools/rust-analyzer/editors/code/src/commands.ts | 190 | ||||
| -rw-r--r-- | src/tools/rust-analyzer/editors/code/src/config.ts | 191 | ||||
| -rw-r--r-- | src/tools/rust-analyzer/editors/code/src/ctx.ts | 224 | ||||
| -rw-r--r-- | src/tools/rust-analyzer/editors/code/src/main.ts | 475 | ||||
| -rw-r--r-- | src/tools/rust-analyzer/editors/code/src/run.ts | 4 |
10 files changed, 805 insertions, 629 deletions
diff --git a/src/tools/rust-analyzer/editors/code/package-lock.json b/src/tools/rust-analyzer/editors/code/package-lock.json index 3ff4b6897a1..a72865d4fe4 100644 --- a/src/tools/rust-analyzer/editors/code/package-lock.json +++ b/src/tools/rust-analyzer/editors/code/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "d3": "^7.6.1", "d3-graphviz": "^4.1.1", - "vscode-languageclient": "^8.0.0-next.14" + "vscode-languageclient": "^8.0.2" }, "devDependencies": { "@types/node": "~16.11.7", @@ -3791,39 +3791,39 @@ } }, "node_modules/vscode-jsonrpc": { - "version": "8.0.0-next.7", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.0.0-next.7.tgz", - "integrity": "sha512-JX/F31LEsims0dAlOTKFE4E+AJMiJvdRSRViifFJSqSN7EzeYyWlfuDchF7g91oRNPZOIWfibTkDf3/UMsQGzQ==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.0.2.tgz", + "integrity": "sha512-RY7HwI/ydoC1Wwg4gJ3y6LpU9FJRZAUnTYMXthqhFXXu77ErDd/xkREpGuk4MyYkk4a+XDWAMqe0S3KkelYQEQ==", "engines": { "node": ">=14.0.0" } }, "node_modules/vscode-languageclient": { - "version": "8.0.0-next.14", - "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-8.0.0-next.14.tgz", - "integrity": "sha512-NqjkOuDTMu8uo+PhoMsV72VO9Gd3wBi/ZpOrkRUOrWKQo7yUdiIw183g8wjH8BImgbK9ZP51HM7TI0ZhCnI1Mw==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-8.0.2.tgz", + "integrity": "sha512-lHlthJtphG9gibGb/y72CKqQUxwPsMXijJVpHEC2bvbFqxmkj9LwQ3aGU9dwjBLqsX1S4KjShYppLvg1UJDF/Q==", "dependencies": { "minimatch": "^3.0.4", "semver": "^7.3.5", - "vscode-languageserver-protocol": "3.17.0-next.16" + "vscode-languageserver-protocol": "3.17.2" }, "engines": { - "vscode": "^1.66.0" + "vscode": "^1.67.0" } }, "node_modules/vscode-languageserver-protocol": { - "version": "3.17.0-next.16", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.0-next.16.tgz", - "integrity": "sha512-tx4DnXw9u3N7vw+bx6n2NKp6FoxoNwiP/biH83AS30I2AnTGyLd7afSeH6Oewn2E8jvB7K15bs12sMppkKOVeQ==", + "version": "3.17.2", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.2.tgz", + "integrity": "sha512-8kYisQ3z/SQ2kyjlNeQxbkkTNmVFoQCqkmGrzLH6A9ecPlgTbp3wDTnUNqaUxYr4vlAcloxx8zwy7G5WdguYNg==", "dependencies": { - "vscode-jsonrpc": "8.0.0-next.7", - "vscode-languageserver-types": "3.17.0-next.9" + "vscode-jsonrpc": "8.0.2", + "vscode-languageserver-types": "3.17.2" } }, "node_modules/vscode-languageserver-types": { - "version": "3.17.0-next.9", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.0-next.9.tgz", - "integrity": "sha512-9/PeDNPYduaoXRUzYpqmu4ZV9L01HGo0wH9FUt+sSHR7IXwA7xoXBfNUlv8gB9H0D2WwEmMomSy1NmhjKQyn3A==" + "version": "3.17.2", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.2.tgz", + "integrity": "sha512-zHhCWatviizPIq9B7Vh9uvrH6x3sK8itC84HkamnBWoDFJtzBf7SWlpLCZUit72b3os45h6RWQNC9xHRDF8dRA==" }, "node_modules/which": { "version": "2.0.2", @@ -6634,33 +6634,33 @@ } }, "vscode-jsonrpc": { - "version": "8.0.0-next.7", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.0.0-next.7.tgz", - "integrity": "sha512-JX/F31LEsims0dAlOTKFE4E+AJMiJvdRSRViifFJSqSN7EzeYyWlfuDchF7g91oRNPZOIWfibTkDf3/UMsQGzQ==" + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.0.2.tgz", + "integrity": "sha512-RY7HwI/ydoC1Wwg4gJ3y6LpU9FJRZAUnTYMXthqhFXXu77ErDd/xkREpGuk4MyYkk4a+XDWAMqe0S3KkelYQEQ==" }, "vscode-languageclient": { - "version": "8.0.0-next.14", - "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-8.0.0-next.14.tgz", - "integrity": "sha512-NqjkOuDTMu8uo+PhoMsV72VO9Gd3wBi/ZpOrkRUOrWKQo7yUdiIw183g8wjH8BImgbK9ZP51HM7TI0ZhCnI1Mw==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-8.0.2.tgz", + "integrity": "sha512-lHlthJtphG9gibGb/y72CKqQUxwPsMXijJVpHEC2bvbFqxmkj9LwQ3aGU9dwjBLqsX1S4KjShYppLvg1UJDF/Q==", "requires": { "minimatch": "^3.0.4", "semver": "^7.3.5", - "vscode-languageserver-protocol": "3.17.0-next.16" + "vscode-languageserver-protocol": "3.17.2" } }, "vscode-languageserver-protocol": { - "version": "3.17.0-next.16", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.0-next.16.tgz", - "integrity": "sha512-tx4DnXw9u3N7vw+bx6n2NKp6FoxoNwiP/biH83AS30I2AnTGyLd7afSeH6Oewn2E8jvB7K15bs12sMppkKOVeQ==", + "version": "3.17.2", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.2.tgz", + "integrity": "sha512-8kYisQ3z/SQ2kyjlNeQxbkkTNmVFoQCqkmGrzLH6A9ecPlgTbp3wDTnUNqaUxYr4vlAcloxx8zwy7G5WdguYNg==", "requires": { - "vscode-jsonrpc": "8.0.0-next.7", - "vscode-languageserver-types": "3.17.0-next.9" + "vscode-jsonrpc": "8.0.2", + "vscode-languageserver-types": "3.17.2" } }, "vscode-languageserver-types": { - "version": "3.17.0-next.9", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.0-next.9.tgz", - "integrity": "sha512-9/PeDNPYduaoXRUzYpqmu4ZV9L01HGo0wH9FUt+sSHR7IXwA7xoXBfNUlv8gB9H0D2WwEmMomSy1NmhjKQyn3A==" + "version": "3.17.2", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.2.tgz", + "integrity": "sha512-zHhCWatviizPIq9B7Vh9uvrH6x3sK8itC84HkamnBWoDFJtzBf7SWlpLCZUit72b3os45h6RWQNC9xHRDF8dRA==" }, "which": { "version": "2.0.2", diff --git a/src/tools/rust-analyzer/editors/code/package.json b/src/tools/rust-analyzer/editors/code/package.json index f1dd3aa79ff..6771cad28a7 100644 --- a/src/tools/rust-analyzer/editors/code/package.json +++ b/src/tools/rust-analyzer/editors/code/package.json @@ -37,7 +37,7 @@ "dependencies": { "d3": "^7.6.1", "d3-graphviz": "^4.1.1", - "vscode-languageclient": "^8.0.0-next.14" + "vscode-languageclient": "^8.0.2" }, "devDependencies": { "@types/node": "~16.11.7", @@ -60,6 +60,7 @@ "onCommand:rust-analyzer.analyzerStatus", "onCommand:rust-analyzer.memoryUsage", "onCommand:rust-analyzer.reloadWorkspace", + "onCommand:rust-analyzer.startServer", "workspaceContains:*/Cargo.toml", "workspaceContains:*/rust-project.json" ], @@ -192,6 +193,16 @@ "category": "rust-analyzer" }, { + "command": "rust-analyzer.startServer", + "title": "Start server", + "category": "rust-analyzer" + }, + { + "command": "rust-analyzer.stopServer", + "title": "Stop server", + "category": "rust-analyzer" + }, + { "command": "rust-analyzer.onEnter", "title": "Enhanced enter key", "category": "rust-analyzer" @@ -421,6 +432,32 @@ "default": true, "type": "boolean" }, + "rust-analyzer.cargo.buildScripts.invocationLocation": { + "markdownDescription": "Specifies the working directory for running build scripts.\n- \"workspace\": run build scripts for a workspace in the workspace's root directory.\n This is incompatible with `#rust-analyzer.cargo.buildScripts.invocationStrategy#` set to `once`.\n- \"root\": run build scripts in the project's root directory.\nThis config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#`\nis set.", + "default": "workspace", + "type": "string", + "enum": [ + "workspace", + "root" + ], + "enumDescriptions": [ + "The command will be executed in the corresponding workspace root.", + "The command will be executed in the project root." + ] + }, + "rust-analyzer.cargo.buildScripts.invocationStrategy": { + "markdownDescription": "Specifies the invocation strategy to use when running the build scripts command.\nIf `per_workspace` is set, the command will be executed for each workspace.\nIf `once` is set, the command will be executed once.\nThis config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#`\nis set.", + "default": "per_workspace", + "type": "string", + "enum": [ + "per_workspace", + "once" + ], + "enumDescriptions": [ + "The command will be executed for each workspace.", + "The command will be executed once." + ] + }, "rust-analyzer.cargo.buildScripts.overrideCommand": { "markdownDescription": "Override the command rust-analyzer uses to run build scripts and\nbuild procedural macros. The command is required to output json\nand should therefore include `--message-format=json` or a similar\noption.\n\nBy default, a cargo invocation will be constructed for the configured\ntargets and features, with the following base command line:\n\n```bash\ncargo check --quiet --workspace --message-format=json --all-targets\n```\n.", "default": null, @@ -546,6 +583,32 @@ } ] }, + "rust-analyzer.checkOnSave.invocationLocation": { + "markdownDescription": "Specifies the working directory for running checks.\n- \"workspace\": run checks for workspaces in the corresponding workspaces' root directories.\n This falls back to \"root\" if `#rust-analyzer.cargo.checkOnSave.invocationStrategy#` is set to `once`.\n- \"root\": run checks in the project's root directory.\nThis config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#`\nis set.", + "default": "workspace", + "type": "string", + "enum": [ + "workspace", + "root" + ], + "enumDescriptions": [ + "The command will be executed in the corresponding workspace root.", + "The command will be executed in the project root." + ] + }, + "rust-analyzer.checkOnSave.invocationStrategy": { + "markdownDescription": "Specifies the invocation strategy to use when running the checkOnSave command.\nIf `per_workspace` is set, the command will be executed for each workspace.\nIf `once` is set, the command will be executed once.\nThis config only has an effect when `#rust-analyzer.cargo.buildScripts.overrideCommand#`\nis set.", + "default": "per_workspace", + "type": "string", + "enum": [ + "per_workspace", + "once" + ], + "enumDescriptions": [ + "The command will be executed for each workspace.", + "The command will be executed once." + ] + }, "rust-analyzer.checkOnSave.noDefaultFeatures": { "markdownDescription": "Whether to pass `--no-default-features` to Cargo. Defaults to\n`#rust-analyzer.cargo.noDefaultFeatures#`.", "default": null, diff --git a/src/tools/rust-analyzer/editors/code/src/ast_inspector.ts b/src/tools/rust-analyzer/editors/code/src/ast_inspector.ts index e57fb20e2cf..176040120f4 100644 --- a/src/tools/rust-analyzer/editors/code/src/ast_inspector.ts +++ b/src/tools/rust-analyzer/editors/code/src/ast_inspector.ts @@ -35,8 +35,10 @@ export class AstInspector implements vscode.HoverProvider, vscode.DefinitionProv }); constructor(ctx: Ctx) { - ctx.pushCleanup(vscode.languages.registerHoverProvider({ scheme: "rust-analyzer" }, this)); - ctx.pushCleanup(vscode.languages.registerDefinitionProvider({ language: "rust" }, this)); + ctx.pushExtCleanup( + vscode.languages.registerHoverProvider({ scheme: "rust-analyzer" }, this) + ); + ctx.pushExtCleanup(vscode.languages.registerDefinitionProvider({ language: "rust" }, this)); vscode.workspace.onDidCloseTextDocument( this.onDidCloseTextDocument, this, @@ -52,8 +54,6 @@ export class AstInspector implements vscode.HoverProvider, vscode.DefinitionProv this, ctx.subscriptions ); - - ctx.pushCleanup(this); } dispose() { this.setRustEditor(undefined); diff --git a/src/tools/rust-analyzer/editors/code/src/bootstrap.ts b/src/tools/rust-analyzer/editors/code/src/bootstrap.ts new file mode 100644 index 00000000000..374c3b8144c --- /dev/null +++ b/src/tools/rust-analyzer/editors/code/src/bootstrap.ts @@ -0,0 +1,148 @@ +import * as vscode from "vscode"; +import * as os from "os"; +import { Config } from "./config"; +import { log, isValidExecutable } from "./util"; +import { PersistentState } from "./persistent_state"; +import { exec } from "child_process"; + +export async function bootstrap( + context: vscode.ExtensionContext, + config: Config, + state: PersistentState +): Promise<string> { + const path = await getServer(context, config, state); + if (!path) { + throw new Error( + "Rust Analyzer Language Server is not available. " + + "Please, ensure its [proper installation](https://rust-analyzer.github.io/manual.html#installation)." + ); + } + + log.info("Using server binary at", path); + + if (!isValidExecutable(path)) { + if (config.serverPath) { + throw new Error(`Failed to execute ${path} --version. \`config.server.path\` or \`config.serverPath\` has been set explicitly.\ + Consider removing this config or making a valid server binary available at that path.`); + } else { + throw new Error(`Failed to execute ${path} --version`); + } + } + + return path; +} + +async function patchelf(dest: vscode.Uri): Promise<void> { + await vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: "Patching rust-analyzer for NixOS", + }, + async (progress, _) => { + const expression = ` + {srcStr, pkgs ? import <nixpkgs> {}}: + pkgs.stdenv.mkDerivation { + name = "rust-analyzer"; + src = /. + srcStr; + phases = [ "installPhase" "fixupPhase" ]; + installPhase = "cp $src $out"; + fixupPhase = '' + chmod 755 $out + patchelf --set-interpreter "$(cat $NIX_CC/nix-support/dynamic-linker)" $out + ''; + } + `; + const origFile = vscode.Uri.file(dest.fsPath + "-orig"); + await vscode.workspace.fs.rename(dest, origFile, { overwrite: true }); + try { + progress.report({ message: "Patching executable", increment: 20 }); + await new Promise((resolve, reject) => { + const handle = exec( + `nix-build -E - --argstr srcStr '${origFile.fsPath}' -o '${dest.fsPath}'`, + (err, stdout, stderr) => { + if (err != null) { + reject(Error(stderr)); + } else { + resolve(stdout); + } + } + ); + handle.stdin?.write(expression); + handle.stdin?.end(); + }); + } finally { + await vscode.workspace.fs.delete(origFile); + } + } + ); +} + +async function getServer( + context: vscode.ExtensionContext, + config: Config, + state: PersistentState +): Promise<string | undefined> { + const explicitPath = serverPath(config); + if (explicitPath) { + if (explicitPath.startsWith("~/")) { + return os.homedir() + explicitPath.slice("~".length); + } + return explicitPath; + } + if (config.package.releaseTag === null) return "rust-analyzer"; + + const ext = process.platform === "win32" ? ".exe" : ""; + const bundled = vscode.Uri.joinPath(context.extensionUri, "server", `rust-analyzer${ext}`); + const bundledExists = await vscode.workspace.fs.stat(bundled).then( + () => true, + () => false + ); + if (bundledExists) { + let server = bundled; + if (await isNixOs()) { + await vscode.workspace.fs.createDirectory(config.globalStorageUri).then(); + const dest = vscode.Uri.joinPath(config.globalStorageUri, `rust-analyzer${ext}`); + let exists = await vscode.workspace.fs.stat(dest).then( + () => true, + () => false + ); + if (exists && config.package.version !== state.serverVersion) { + await vscode.workspace.fs.delete(dest); + exists = false; + } + if (!exists) { + await vscode.workspace.fs.copy(bundled, dest); + await patchelf(dest); + } + server = dest; + } + await state.updateServerVersion(config.package.version); + return server.fsPath; + } + + await state.updateServerVersion(undefined); + await vscode.window.showErrorMessage( + "Unfortunately we don't ship binaries for your platform yet. " + + "You need to manually clone the rust-analyzer repository and " + + "run `cargo xtask install --server` to build the language server from sources. " + + "If you feel that your platform should be supported, please create an issue " + + "about that [here](https://github.com/rust-lang/rust-analyzer/issues) and we " + + "will consider it." + ); + return undefined; +} +function serverPath(config: Config): string | null { + return process.env.__RA_LSP_SERVER_DEBUG ?? config.serverPath; +} + +async function isNixOs(): Promise<boolean> { + try { + const contents = ( + await vscode.workspace.fs.readFile(vscode.Uri.file("/etc/os-release")) + ).toString(); + const idString = contents.split("\n").find((a) => a.startsWith("ID=")) || "ID=linux"; + return idString.indexOf("nixos") !== -1; + } catch { + return false; + } +} diff --git a/src/tools/rust-analyzer/editors/code/src/client.ts b/src/tools/rust-analyzer/editors/code/src/client.ts index 05d4d08f70b..fb667619c86 100644 --- a/src/tools/rust-analyzer/editors/code/src/client.ts +++ b/src/tools/rust-analyzer/editors/code/src/client.ts @@ -4,9 +4,7 @@ import * as ra from "../src/lsp_ext"; import * as Is from "vscode-languageclient/lib/common/utils/is"; import { assert } from "./util"; import { WorkspaceEdit } from "vscode"; -import { Workspace } from "./ctx"; -import { substituteVariablesInEnv } from "./config"; -import { outputChannel, traceOutputChannel } from "./main"; +import { substituteVSCodeVariables } from "./config"; import { randomUUID } from "crypto"; export interface Env { @@ -65,40 +63,42 @@ function renderHoverActions(actions: ra.CommandLinkGroup[]): vscode.MarkdownStri } export async function createClient( - serverPath: string, - workspace: Workspace, - extraEnv: Env + traceOutputChannel: vscode.OutputChannel, + outputChannel: vscode.OutputChannel, + initializationOptions: vscode.WorkspaceConfiguration, + serverOptions: lc.ServerOptions ): Promise<lc.LanguageClient> { - // '.' Is the fallback if no folder is open - // TODO?: Workspace folders support Uri's (eg: file://test.txt). - // It might be a good idea to test if the uri points to a file. - - const newEnv = substituteVariablesInEnv(Object.assign({}, process.env, extraEnv)); - const run: lc.Executable = { - command: serverPath, - options: { env: newEnv }, - }; - const serverOptions: lc.ServerOptions = { - run, - debug: run, - }; - - let initializationOptions = vscode.workspace.getConfiguration("rust-analyzer"); - - if (workspace.kind === "Detached Files") { - initializationOptions = { - detachedFiles: workspace.files.map((file) => file.uri.fsPath), - ...initializationOptions, - }; - } - const clientOptions: lc.LanguageClientOptions = { documentSelector: [{ scheme: "file", language: "rust" }], initializationOptions, diagnosticCollectionName: "rustc", - traceOutputChannel: traceOutputChannel(), - outputChannel: outputChannel(), + traceOutputChannel, + outputChannel, middleware: { + workspace: { + // HACK: This is a workaround, when the client has been disposed, VSCode + // continues to emit events to the client and the default one for this event + // attempt to restart the client for no reason + async didChangeWatchedFile(event, next) { + if (client.isRunning()) { + await next(event); + } + }, + async configuration( + params: lc.ConfigurationParams, + token: vscode.CancellationToken, + next: lc.ConfigurationRequest.HandlerSignature + ) { + const resp = await next(params, token); + if (resp && Array.isArray(resp)) { + return resp.map((val) => { + return substituteVSCodeVariables(val); + }); + } else { + return resp; + } + }, + }, async provideHover( document: vscode.TextDocument, position: vscode.Position, @@ -255,6 +255,9 @@ export async function createClient( } class ExperimentalFeatures implements lc.StaticFeature { + getState(): lc.FeatureState { + return { kind: "static" }; + } fillClientCapabilities(capabilities: lc.ClientCapabilities): void { const caps: any = capabilities.experimental ?? {}; caps.snippetTextEdit = true; diff --git a/src/tools/rust-analyzer/editors/code/src/commands.ts b/src/tools/rust-analyzer/editors/code/src/commands.ts index b9ad525e361..12ceb4f2df8 100644 --- a/src/tools/rust-analyzer/editors/code/src/commands.ts +++ b/src/tools/rust-analyzer/editors/code/src/commands.ts @@ -21,16 +21,16 @@ export function analyzerStatus(ctx: Ctx): Cmd { readonly uri = vscode.Uri.parse("rust-analyzer-status://status"); readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>(); - provideTextDocumentContent(_uri: vscode.Uri): vscode.ProviderResult<string> { + async provideTextDocumentContent(_uri: vscode.Uri): Promise<string> { if (!vscode.window.activeTextEditor) return ""; + const client = await ctx.getClient(); const params: ra.AnalyzerStatusParams = {}; const doc = ctx.activeRustEditor?.document; if (doc != null) { - params.textDocument = - ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(doc); + params.textDocument = client.code2ProtocolConverter.asTextDocumentIdentifier(doc); } - return ctx.client.sendRequest(ra.analyzerStatus, params); + return await client.sendRequest(ra.analyzerStatus, params); } get onDidChange(): vscode.Event<vscode.Uri> { @@ -38,7 +38,7 @@ export function analyzerStatus(ctx: Ctx): Cmd { } })(); - ctx.pushCleanup( + ctx.pushExtCleanup( vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-status", tdcp) ); @@ -60,9 +60,14 @@ export function memoryUsage(ctx: Ctx): Cmd { provideTextDocumentContent(_uri: vscode.Uri): vscode.ProviderResult<string> { if (!vscode.window.activeTextEditor) return ""; - return ctx.client.sendRequest(ra.memoryUsage).then((mem: any) => { - return "Per-query memory usage:\n" + mem + "\n(note: database has been cleared)"; - }); + return ctx + .getClient() + .then((it) => it.sendRequest(ra.memoryUsage)) + .then((mem: any) => { + return ( + "Per-query memory usage:\n" + mem + "\n(note: database has been cleared)" + ); + }); } get onDidChange(): vscode.Event<vscode.Uri> { @@ -70,7 +75,7 @@ export function memoryUsage(ctx: Ctx): Cmd { } })(); - ctx.pushCleanup( + ctx.pushExtCleanup( vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-memory", tdcp) ); @@ -83,23 +88,19 @@ export function memoryUsage(ctx: Ctx): Cmd { export function shuffleCrateGraph(ctx: Ctx): Cmd { return async () => { - const client = ctx.client; - if (!client) return; - - await client.sendRequest(ra.shuffleCrateGraph); + return ctx.getClient().then((it) => it.sendRequest(ra.shuffleCrateGraph)); }; } export function matchingBrace(ctx: Ctx): Cmd { return async () => { const editor = ctx.activeRustEditor; - const client = ctx.client; - if (!editor || !client) return; + if (!editor) return; + + const client = await ctx.getClient(); const response = await client.sendRequest(ra.matchingBrace, { - textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier( - editor.document - ), + textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document), positions: editor.selections.map((s) => client.code2ProtocolConverter.asPosition(s.active) ), @@ -116,14 +117,13 @@ export function matchingBrace(ctx: Ctx): Cmd { export function joinLines(ctx: Ctx): Cmd { return async () => { const editor = ctx.activeRustEditor; - const client = ctx.client; - if (!editor || !client) return; + if (!editor) return; + + const client = await ctx.getClient(); const items: lc.TextEdit[] = await client.sendRequest(ra.joinLines, { ranges: editor.selections.map((it) => client.code2ProtocolConverter.asRange(it)), - textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier( - editor.document - ), + textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document), }); const textEdits = await client.protocol2CodeConverter.asTextEdits(items); await editor.edit((builder) => { @@ -145,14 +145,12 @@ export function moveItemDown(ctx: Ctx): Cmd { export function moveItem(ctx: Ctx, direction: ra.Direction): Cmd { return async () => { const editor = ctx.activeRustEditor; - const client = ctx.client; - if (!editor || !client) return; + if (!editor) return; + const client = await ctx.getClient(); const lcEdits = await client.sendRequest(ra.moveItem, { range: client.code2ProtocolConverter.asRange(editor.selection), - textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier( - editor.document - ), + textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document), direction, }); @@ -166,13 +164,13 @@ export function moveItem(ctx: Ctx, direction: ra.Direction): Cmd { export function onEnter(ctx: Ctx): Cmd { async function handleKeypress() { const editor = ctx.activeRustEditor; - const client = ctx.client; - if (!editor || !client) return false; + if (!editor) return false; + const client = await ctx.getClient(); const lcEdits = await client .sendRequest(ra.onEnter, { - textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier( + textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier( editor.document ), position: client.code2ProtocolConverter.asPosition(editor.selection.active), @@ -198,14 +196,13 @@ export function onEnter(ctx: Ctx): Cmd { export function parentModule(ctx: Ctx): Cmd { return async () => { const editor = vscode.window.activeTextEditor; - const client = ctx.client; - if (!editor || !client) return; + if (!editor) return; if (!(isRustDocument(editor.document) || isCargoTomlDocument(editor.document))) return; + const client = await ctx.getClient(); + const locations = await client.sendRequest(ra.parentModule, { - textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier( - editor.document - ), + textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document), position: client.code2ProtocolConverter.asPosition(editor.selection.active), }); if (!locations) return; @@ -236,13 +233,11 @@ export function parentModule(ctx: Ctx): Cmd { export function openCargoToml(ctx: Ctx): Cmd { return async () => { const editor = ctx.activeRustEditor; - const client = ctx.client; - if (!editor || !client) return; + if (!editor) return; + const client = await ctx.getClient(); const response = await client.sendRequest(ra.openCargoToml, { - textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier( - editor.document - ), + textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document), }); if (!response) return; @@ -259,12 +254,13 @@ export function openCargoToml(ctx: Ctx): Cmd { export function ssr(ctx: Ctx): Cmd { return async () => { const editor = vscode.window.activeTextEditor; - const client = ctx.client; - if (!editor || !client) return; + if (!editor) return; + + const client = await ctx.getClient(); const position = editor.selection.active; const selections = editor.selections; - const textDocument = ctx.client.code2ProtocolConverter.asTextDocumentIdentifier( + const textDocument = client.code2ProtocolConverter.asTextDocumentIdentifier( editor.document ); @@ -314,6 +310,10 @@ export function ssr(ctx: Ctx): Cmd { export function serverVersion(ctx: Ctx): Cmd { return async () => { + if (!ctx.serverPath) { + void vscode.window.showWarningMessage(`rust-analyzer server is not running`); + return; + } const { stdout } = spawnSync(ctx.serverPath, ["--version"], { encoding: "utf8" }); const versionString = stdout.slice(`rust-analyzer `.length).trim(); @@ -354,21 +354,22 @@ export function syntaxTree(ctx: Ctx): Cmd { } } - provideTextDocumentContent( + async provideTextDocumentContent( uri: vscode.Uri, ct: vscode.CancellationToken - ): vscode.ProviderResult<string> { + ): Promise<string> { const rustEditor = ctx.activeRustEditor; if (!rustEditor) return ""; + const client = await ctx.getClient(); // When the range based query is enabled we take the range of the selection const range = uri.query === "range=true" && !rustEditor.selection.isEmpty - ? ctx.client.code2ProtocolConverter.asRange(rustEditor.selection) + ? client.code2ProtocolConverter.asRange(rustEditor.selection) : null; const params = { textDocument: { uri: rustEditor.document.uri.toString() }, range }; - return ctx.client.sendRequest(ra.syntaxTree, params, ct); + return client.sendRequest(ra.syntaxTree, params, ct); } get onDidChange(): vscode.Event<vscode.Uri> { @@ -376,12 +377,11 @@ export function syntaxTree(ctx: Ctx): Cmd { } })(); - void new AstInspector(ctx); - - ctx.pushCleanup( + ctx.pushExtCleanup(new AstInspector(ctx)); + ctx.pushExtCleanup( vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-syntax-tree", tdcp) ); - ctx.pushCleanup( + ctx.pushExtCleanup( vscode.languages.setLanguageConfiguration("ra_syntax_tree", { brackets: [["[", ")"]], }) @@ -437,14 +437,14 @@ export function viewHir(ctx: Ctx): Cmd { } } - provideTextDocumentContent( + async provideTextDocumentContent( _uri: vscode.Uri, ct: vscode.CancellationToken - ): vscode.ProviderResult<string> { + ): Promise<string> { const rustEditor = ctx.activeRustEditor; - const client = ctx.client; - if (!rustEditor || !client) return ""; + if (!rustEditor) return ""; + const client = await ctx.getClient(); const params = { textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier( rustEditor.document @@ -459,7 +459,7 @@ export function viewHir(ctx: Ctx): Cmd { } })(); - ctx.pushCleanup( + ctx.pushExtCleanup( vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-hir", tdcp) ); @@ -503,13 +503,13 @@ export function viewFileText(ctx: Ctx): Cmd { } } - provideTextDocumentContent( + async provideTextDocumentContent( _uri: vscode.Uri, ct: vscode.CancellationToken - ): vscode.ProviderResult<string> { + ): Promise<string> { const rustEditor = ctx.activeRustEditor; - const client = ctx.client; - if (!rustEditor || !client) return ""; + if (!rustEditor) return ""; + const client = await ctx.getClient(); const params = client.code2ProtocolConverter.asTextDocumentIdentifier( rustEditor.document @@ -522,7 +522,7 @@ export function viewFileText(ctx: Ctx): Cmd { } })(); - ctx.pushCleanup( + ctx.pushExtCleanup( vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-file-text", tdcp) ); @@ -566,13 +566,13 @@ export function viewItemTree(ctx: Ctx): Cmd { } } - provideTextDocumentContent( + async provideTextDocumentContent( _uri: vscode.Uri, ct: vscode.CancellationToken - ): vscode.ProviderResult<string> { + ): Promise<string> { const rustEditor = ctx.activeRustEditor; - const client = ctx.client; - if (!rustEditor || !client) return ""; + if (!rustEditor) return ""; + const client = await ctx.getClient(); const params = { textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier( @@ -587,7 +587,7 @@ export function viewItemTree(ctx: Ctx): Cmd { } })(); - ctx.pushCleanup( + ctx.pushExtCleanup( vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-item-tree", tdcp) ); @@ -618,8 +618,8 @@ function crateGraph(ctx: Ctx, full: boolean): Cmd { const params = { full: full, }; - - const dot = await ctx.client.sendRequest(ra.viewCrateGraph, params); + const client = await ctx.getClient(); + const dot = await client.sendRequest(ra.viewCrateGraph, params); const uri = panel.webview.asWebviewUri(nodeModulesPath); const html = ` @@ -690,13 +690,13 @@ export function expandMacro(ctx: Ctx): Cmd { eventEmitter = new vscode.EventEmitter<vscode.Uri>(); async provideTextDocumentContent(_uri: vscode.Uri): Promise<string> { const editor = vscode.window.activeTextEditor; - const client = ctx.client; - if (!editor || !client) return ""; + if (!editor) return ""; + const client = await ctx.getClient(); const position = editor.selection.active; const expanded = await client.sendRequest(ra.expandMacro, { - textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier( + textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier( editor.document ), position, @@ -712,7 +712,7 @@ export function expandMacro(ctx: Ctx): Cmd { } })(); - ctx.pushCleanup( + ctx.pushExtCleanup( vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-expand-macro", tdcp) ); @@ -724,11 +724,11 @@ export function expandMacro(ctx: Ctx): Cmd { } export function reloadWorkspace(ctx: Ctx): Cmd { - return async () => ctx.client.sendRequest(ra.reloadWorkspace); + return async () => (await ctx.getClient()).sendRequest(ra.reloadWorkspace); } async function showReferencesImpl( - client: LanguageClient, + client: LanguageClient | undefined, uri: string, position: lc.Position, locations: lc.Location[] @@ -745,7 +745,7 @@ async function showReferencesImpl( export function showReferences(ctx: Ctx): Cmd { return async (uri: string, position: lc.Position, locations: lc.Location[]) => { - await showReferencesImpl(ctx.client, uri, position, locations); + await showReferencesImpl(await ctx.getClient(), uri, position, locations); }; } @@ -762,25 +762,23 @@ export function applyActionGroup(_ctx: Ctx): Cmd { export function gotoLocation(ctx: Ctx): Cmd { return async (locationLink: lc.LocationLink) => { - const client = ctx.client; - if (client) { - const uri = client.protocol2CodeConverter.asUri(locationLink.targetUri); - let range = client.protocol2CodeConverter.asRange(locationLink.targetSelectionRange); - // collapse the range to a cursor position - range = range.with({ end: range.start }); + const client = await ctx.getClient(); + const uri = client.protocol2CodeConverter.asUri(locationLink.targetUri); + let range = client.protocol2CodeConverter.asRange(locationLink.targetSelectionRange); + // collapse the range to a cursor position + range = range.with({ end: range.start }); - await vscode.window.showTextDocument(uri, { selection: range }); - } + await vscode.window.showTextDocument(uri, { selection: range }); }; } export function openDocs(ctx: Ctx): Cmd { return async () => { - const client = ctx.client; const editor = vscode.window.activeTextEditor; - if (!editor || !client) { + if (!editor) { return; } + const client = await ctx.getClient(); const position = editor.selection.active; const textDocument = { uri: editor.document.uri.toString() }; @@ -795,20 +793,21 @@ export function openDocs(ctx: Ctx): Cmd { export function cancelFlycheck(ctx: Ctx): Cmd { return async () => { - await ctx.client.sendRequest(ra.cancelFlycheck); + const client = await ctx.getClient(); + await client.sendRequest(ra.cancelFlycheck); }; } export function resolveCodeAction(ctx: Ctx): Cmd { - const client = ctx.client; return async (params: lc.CodeAction) => { + const client = await ctx.getClient(); params.command = undefined; - const item = await client.sendRequest(lc.CodeActionResolveRequest.type, params); - if (!item.edit) { + const item = await client?.sendRequest(lc.CodeActionResolveRequest.type, params); + if (!item?.edit) { return; } const itemEdit = item.edit; - const edit = await client.protocol2CodeConverter.asWorkspaceEdit(itemEdit); + const edit = await client?.protocol2CodeConverter.asWorkspaceEdit(itemEdit); // filter out all text edits and recreate the WorkspaceEdit without them so we can apply // snippet edits on our own const lcFileSystemEdit = { @@ -847,11 +846,10 @@ export function run(ctx: Ctx): Cmd { } export function peekTests(ctx: Ctx): Cmd { - const client = ctx.client; - return async () => { const editor = ctx.activeRustEditor; - if (!editor || !client) return; + if (!editor) return; + const client = await ctx.getClient(); await vscode.window.withProgress( { @@ -937,10 +935,10 @@ export function newDebugConfig(ctx: Ctx): Cmd { }; } -export function linkToCommand(ctx: Ctx): Cmd { +export function linkToCommand(_: Ctx): Cmd { return async (commandId: string) => { const link = LINKED_COMMANDS.get(commandId); - if (ctx.client && link) { + if (link) { const { command, arguments: args = [] } = link; await vscode.commands.executeCommand(command, ...args); } diff --git a/src/tools/rust-analyzer/editors/code/src/config.ts b/src/tools/rust-analyzer/editors/code/src/config.ts index 15846a5e864..632a7d86faa 100644 --- a/src/tools/rust-analyzer/editors/code/src/config.ts +++ b/src/tools/rust-analyzer/editors/code/src/config.ts @@ -1,4 +1,5 @@ -import path = require("path"); +import * as path from "path"; +import * as os from "os"; import * as vscode from "vscode"; import { Env } from "./client"; import { log } from "./util"; @@ -10,23 +11,17 @@ export type RunnableEnvCfg = export class Config { readonly extensionId = "rust-lang.rust-analyzer"; + configureLang: vscode.Disposable | undefined; readonly rootSection = "rust-analyzer"; - private readonly requiresWorkspaceReloadOpts = [ - "serverPath", - "server", - // FIXME: This shouldn't be here, changing this setting should reload - // `continueCommentsOnNewline` behavior without restart - "typing", - ].map((opt) => `${this.rootSection}.${opt}`); private readonly requiresReloadOpts = [ "cargo", "procMacro", + "serverPath", + "server", "files", "lens", // works as lens.* - ] - .map((opt) => `${this.rootSection}.${opt}`) - .concat(this.requiresWorkspaceReloadOpts); + ].map((opt) => `${this.rootSection}.${opt}`); readonly package: { version: string; @@ -44,6 +39,11 @@ export class Config { ctx.subscriptions ); this.refreshLogging(); + this.configureLanguage(); + } + + dispose() { + this.configureLang?.dispose(); } private refreshLogging() { @@ -57,33 +57,86 @@ export class Config { private async onDidChangeConfiguration(event: vscode.ConfigurationChangeEvent) { this.refreshLogging(); + this.configureLanguage(); + const requiresReloadOpt = this.requiresReloadOpts.find((opt) => event.affectsConfiguration(opt) ); if (!requiresReloadOpt) return; - const requiresWorkspaceReloadOpt = this.requiresWorkspaceReloadOpts.find((opt) => - event.affectsConfiguration(opt) - ); - - if (!requiresWorkspaceReloadOpt && this.restartServerOnConfigChange) { + if (this.restartServerOnConfigChange) { await vscode.commands.executeCommand("rust-analyzer.reload"); return; } - const message = requiresWorkspaceReloadOpt - ? `Changing "${requiresWorkspaceReloadOpt}" requires a window reload` - : `Changing "${requiresReloadOpt}" requires a reload`; - const userResponse = await vscode.window.showInformationMessage(message, "Reload now"); - - if (userResponse === "Reload now") { - const command = requiresWorkspaceReloadOpt - ? "workbench.action.reloadWindow" - : "rust-analyzer.reload"; - if (userResponse === "Reload now") { - await vscode.commands.executeCommand(command); - } + const message = `Changing "${requiresReloadOpt}" requires a server restart`; + const userResponse = await vscode.window.showInformationMessage(message, "Restart now"); + + if (userResponse) { + const command = "rust-analyzer.reload"; + await vscode.commands.executeCommand(command); + } + } + + /** + * Sets up additional language configuration that's impossible to do via a + * separate language-configuration.json file. See [1] for more information. + * + * [1]: https://github.com/Microsoft/vscode/issues/11514#issuecomment-244707076 + */ + private configureLanguage() { + if (this.typingContinueCommentsOnNewline && !this.configureLang) { + const indentAction = vscode.IndentAction.None; + + this.configureLang = vscode.languages.setLanguageConfiguration("rust", { + onEnterRules: [ + { + // Doc single-line comment + // e.g. ///| + beforeText: /^\s*\/{3}.*$/, + action: { indentAction, appendText: "/// " }, + }, + { + // Parent doc single-line comment + // e.g. //!| + beforeText: /^\s*\/{2}\!.*$/, + action: { indentAction, appendText: "//! " }, + }, + { + // Begins an auto-closed multi-line comment (standard or parent doc) + // e.g. /** | */ or /*! | */ + beforeText: /^\s*\/\*(\*|\!)(?!\/)([^\*]|\*(?!\/))*$/, + afterText: /^\s*\*\/$/, + action: { + indentAction: vscode.IndentAction.IndentOutdent, + appendText: " * ", + }, + }, + { + // Begins a multi-line comment (standard or parent doc) + // e.g. /** ...| or /*! ...| + beforeText: /^\s*\/\*(\*|\!)(?!\/)([^\*]|\*(?!\/))*$/, + action: { indentAction, appendText: " * " }, + }, + { + // Continues a multi-line comment + // e.g. * ...| + beforeText: /^(\ \ )*\ \*(\ ([^\*]|\*(?!\/))*)?$/, + action: { indentAction, appendText: "* " }, + }, + { + // Dedents after closing a multi-line comment + // e.g. */| + beforeText: /^(\ \ )*\ \*\/\s*$/, + action: { indentAction, removeText: 1 }, + }, + ], + }); + } + if (!this.typingContinueCommentsOnNewline && this.configureLang) { + this.configureLang.dispose(); + this.configureLang = undefined; } } @@ -187,6 +240,37 @@ export class Config { } } +const VarRegex = new RegExp(/\$\{(.+?)\}/g); + +export function substituteVSCodeVariableInString(val: string): string { + return val.replace(VarRegex, (substring: string, varName) => { + if (typeof varName === "string") { + return computeVscodeVar(varName) || substring; + } else { + return substring; + } + }); +} + +export function substituteVSCodeVariables(resp: any): any { + if (typeof resp === "string") { + return substituteVSCodeVariableInString(resp); + } else if (resp && Array.isArray(resp)) { + return resp.map((val) => { + return substituteVSCodeVariables(val); + }); + } else if (resp && typeof resp === "object") { + const res: { [key: string]: any } = {}; + for (const key in resp) { + const val = resp[key]; + res[key] = substituteVSCodeVariables(val); + } + return res; + } else if (typeof resp === "function") { + return null; + } + return resp; +} export function substituteVariablesInEnv(env: Env): Env { const missingDeps = new Set<string>(); // vscode uses `env:ENV_NAME` for env vars resolution, and it's easier @@ -233,7 +317,7 @@ export function substituteVariablesInEnv(env: Env): Env { } } else { envWithDeps[dep] = { - value: computeVscodeVar(dep), + value: computeVscodeVar(dep) || "${" + dep + "}", deps: [], }; } @@ -264,37 +348,34 @@ export function substituteVariablesInEnv(env: Env): Env { return resolvedEnv; } -function computeVscodeVar(varName: string): string { +function computeVscodeVar(varName: string): string | null { + const workspaceFolder = () => { + const folders = vscode.workspace.workspaceFolders ?? []; + if (folders.length === 1) { + // TODO: support for remote workspaces? + return folders[0].uri.fsPath; + } else if (folders.length > 1) { + // could use currently opened document to detect the correct + // workspace. However, that would be determined by the document + // user has opened on Editor startup. Could lead to + // unpredictable workspace selection in practice. + // It's better to pick the first one + return folders[0].uri.fsPath; + } else { + // no workspace opened + return ""; + } + }; // https://code.visualstudio.com/docs/editor/variables-reference const supportedVariables: { [k: string]: () => string } = { - workspaceFolder: () => { - const folders = vscode.workspace.workspaceFolders ?? []; - if (folders.length === 1) { - // TODO: support for remote workspaces? - return folders[0].uri.fsPath; - } else if (folders.length > 1) { - // could use currently opened document to detect the correct - // workspace. However, that would be determined by the document - // user has opened on Editor startup. Could lead to - // unpredictable workspace selection in practice. - // It's better to pick the first one - return folders[0].uri.fsPath; - } else { - // no workspace opened - return ""; - } - }, + workspaceFolder, workspaceFolderBasename: () => { - const workspaceFolder = computeVscodeVar("workspaceFolder"); - if (workspaceFolder) { - return path.basename(workspaceFolder); - } else { - return ""; - } + return path.basename(workspaceFolder()); }, cwd: () => process.cwd(), + userHome: () => os.homedir(), // see // https://github.com/microsoft/vscode/blob/08ac1bb67ca2459496b272d8f4a908757f24f56f/src/vs/workbench/api/common/extHostVariableResolverService.ts#L81 @@ -308,7 +389,7 @@ function computeVscodeVar(varName: string): string { if (varName in supportedVariables) { return supportedVariables[varName](); } else { - // can't resolve, keep the expression as is - return "${" + varName + "}"; + // return "${" + varName + "}"; + return null; } } diff --git a/src/tools/rust-analyzer/editors/code/src/ctx.ts b/src/tools/rust-analyzer/editors/code/src/ctx.ts index 26510011d43..044a9470aa9 100644 --- a/src/tools/rust-analyzer/editors/code/src/ctx.ts +++ b/src/tools/rust-analyzer/editors/code/src/ctx.ts @@ -2,10 +2,12 @@ import * as vscode from "vscode"; import * as lc from "vscode-languageclient/node"; import * as ra from "./lsp_ext"; -import { Config } from "./config"; +import { Config, substituteVariablesInEnv, substituteVSCodeVariables } from "./config"; import { createClient } from "./client"; -import { isRustEditor, RustEditor } from "./util"; +import { isRustEditor, log, RustEditor } from "./util"; import { ServerStatusParams } from "./lsp_ext"; +import { PersistentState } from "./persistent_state"; +import { bootstrap } from "./bootstrap"; export type Workspace = | { @@ -16,66 +18,192 @@ export type Workspace = files: vscode.TextDocument[]; }; +export type CommandFactory = { + enabled: (ctx: Ctx) => Cmd; + disabled?: (ctx: Ctx) => Cmd; +}; + export class Ctx { - private constructor( - readonly config: Config, - private readonly extCtx: vscode.ExtensionContext, - readonly client: lc.LanguageClient, - readonly serverPath: string, - readonly statusBar: vscode.StatusBarItem - ) {} - - static async create( - config: Config, - extCtx: vscode.ExtensionContext, - serverPath: string, - workspace: Workspace - ): Promise<Ctx> { - const client = await createClient(serverPath, workspace, config.serverExtraEnv); - - const statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left); - extCtx.subscriptions.push(statusBar); - statusBar.text = "rust-analyzer"; - statusBar.tooltip = "ready"; - statusBar.command = "rust-analyzer.analyzerStatus"; - statusBar.show(); - - const res = new Ctx(config, extCtx, client, serverPath, statusBar); - - res.pushCleanup(client.start()); - await client.onReady(); - client.onNotification(ra.serverStatus, (params) => res.setServerStatus(params)); - return res; + readonly statusBar: vscode.StatusBarItem; + readonly config: Config; + + private client: lc.LanguageClient | undefined; + private _serverPath: string | undefined; + private traceOutputChannel: vscode.OutputChannel | undefined; + private outputChannel: vscode.OutputChannel | undefined; + private clientSubscriptions: Disposable[]; + private state: PersistentState; + private commandFactories: Record<string, CommandFactory>; + private commandDisposables: Disposable[]; + + workspace: Workspace; + + constructor( + readonly extCtx: vscode.ExtensionContext, + workspace: Workspace, + commandFactories: Record<string, CommandFactory> + ) { + extCtx.subscriptions.push(this); + this.statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left); + this.statusBar.text = "rust-analyzer"; + this.statusBar.tooltip = "ready"; + this.statusBar.command = "rust-analyzer.analyzerStatus"; + this.statusBar.show(); + this.workspace = workspace; + this.clientSubscriptions = []; + this.commandDisposables = []; + this.commandFactories = commandFactories; + + this.state = new PersistentState(extCtx.globalState); + this.config = new Config(extCtx); + + this.updateCommands(); } - get activeRustEditor(): RustEditor | undefined { - const editor = vscode.window.activeTextEditor; - return editor && isRustEditor(editor) ? editor : undefined; + dispose() { + this.config.dispose(); + this.statusBar.dispose(); + void this.disposeClient(); + this.commandDisposables.forEach((disposable) => disposable.dispose()); } - get visibleRustEditors(): RustEditor[] { - return vscode.window.visibleTextEditors.filter(isRustEditor); + clientFetcher() { + const self = this; + return { + get client(): lc.LanguageClient | undefined { + return self.client; + }, + }; } - registerCommand(name: string, factory: (ctx: Ctx) => Cmd) { - const fullName = `rust-analyzer.${name}`; - const cmd = factory(this); - const d = vscode.commands.registerCommand(fullName, cmd); - this.pushCleanup(d); + async getClient() { + if (!this.traceOutputChannel) { + this.traceOutputChannel = vscode.window.createOutputChannel( + "Rust Analyzer Language Server Trace" + ); + this.pushExtCleanup(this.traceOutputChannel); + } + if (!this.outputChannel) { + this.outputChannel = vscode.window.createOutputChannel("Rust Analyzer Language Server"); + this.pushExtCleanup(this.outputChannel); + } + + if (!this.client) { + this._serverPath = await bootstrap(this.extCtx, this.config, this.state).catch( + (err) => { + let message = "bootstrap error. "; + + message += + 'See the logs in "OUTPUT > Rust Analyzer Client" (should open automatically). '; + message += + 'To enable verbose logs use { "rust-analyzer.trace.extension": true }'; + + log.error("Bootstrap error", err); + throw new Error(message); + } + ); + const newEnv = substituteVariablesInEnv( + Object.assign({}, process.env, this.config.serverExtraEnv) + ); + const run: lc.Executable = { + command: this._serverPath, + options: { env: newEnv }, + }; + const serverOptions = { + run, + debug: run, + }; + + let rawInitializationOptions = vscode.workspace.getConfiguration("rust-analyzer"); + + if (this.workspace.kind === "Detached Files") { + rawInitializationOptions = { + detachedFiles: this.workspace.files.map((file) => file.uri.fsPath), + ...rawInitializationOptions, + }; + } + + const initializationOptions = substituteVSCodeVariables(rawInitializationOptions); + + this.client = await createClient( + this.traceOutputChannel, + this.outputChannel, + initializationOptions, + serverOptions + ); + this.pushClientCleanup( + this.client.onNotification(ra.serverStatus, (params) => + this.setServerStatus(params) + ) + ); + } + return this.client; } - get extensionPath(): string { - return this.extCtx.extensionPath; + async activate() { + log.info("Activating language client"); + const client = await this.getClient(); + await client.start(); + this.updateCommands(); + return client; + } + + async deactivate() { + log.info("Deactivating language client"); + await this.client?.stop(); + this.updateCommands(); + } + + async stop() { + log.info("Stopping language client"); + await this.disposeClient(); + this.updateCommands(); + } + + private async disposeClient() { + this.clientSubscriptions?.forEach((disposable) => disposable.dispose()); + this.clientSubscriptions = []; + await this.client?.dispose(); + this._serverPath = undefined; + this.client = undefined; + } + + get activeRustEditor(): RustEditor | undefined { + const editor = vscode.window.activeTextEditor; + return editor && isRustEditor(editor) ? editor : undefined; } - get globalState(): vscode.Memento { - return this.extCtx.globalState; + get extensionPath(): string { + return this.extCtx.extensionPath; } get subscriptions(): Disposable[] { return this.extCtx.subscriptions; } + get serverPath(): string | undefined { + return this._serverPath; + } + + private updateCommands() { + this.commandDisposables.forEach((disposable) => disposable.dispose()); + this.commandDisposables = []; + const fetchFactory = (factory: CommandFactory, fullName: string) => { + return this.client && this.client.isRunning() + ? factory.enabled + : factory.disabled || + ((_) => () => + vscode.window.showErrorMessage( + `command ${fullName} failed: rust-analyzer server is not running` + )); + }; + for (const [name, factory] of Object.entries(this.commandFactories)) { + const fullName = `rust-analyzer.${name}`; + const callback = fetchFactory(factory, fullName)(this); + this.commandDisposables.push(vscode.commands.registerCommand(fullName, callback)); + } + } + setServerStatus(status: ServerStatusParams) { let icon = ""; const statusBar = this.statusBar; @@ -111,9 +239,13 @@ export class Ctx { statusBar.text = `${icon}rust-analyzer`; } - pushCleanup(d: Disposable) { + pushExtCleanup(d: Disposable) { this.extCtx.subscriptions.push(d); } + + private pushClientCleanup(d: Disposable) { + this.clientSubscriptions.push(d); + } } export interface Disposable { diff --git a/src/tools/rust-analyzer/editors/code/src/main.ts b/src/tools/rust-analyzer/editors/code/src/main.ts index 41bde4195e0..8c3a676ffb0 100644 --- a/src/tools/rust-analyzer/editors/code/src/main.ts +++ b/src/tools/rust-analyzer/editors/code/src/main.ts @@ -1,53 +1,37 @@ import * as vscode from "vscode"; import * as lc from "vscode-languageclient/node"; -import * as os from "os"; import * as commands from "./commands"; -import { Ctx } from "./ctx"; -import { Config } from "./config"; -import { log, isValidExecutable, isRustDocument } from "./util"; -import { PersistentState } from "./persistent_state"; +import { CommandFactory, Ctx, Workspace } from "./ctx"; +import { isRustDocument } from "./util"; import { activateTaskProvider } from "./tasks"; import { setContextValue } from "./util"; -import { exec } from "child_process"; - -let ctx: Ctx | undefined; const RUST_PROJECT_CONTEXT_NAME = "inRustProject"; -let TRACE_OUTPUT_CHANNEL: vscode.OutputChannel | null = null; -export function traceOutputChannel() { - if (!TRACE_OUTPUT_CHANNEL) { - TRACE_OUTPUT_CHANNEL = vscode.window.createOutputChannel( - "Rust Analyzer Language Server Trace" - ); - } - return TRACE_OUTPUT_CHANNEL; -} -let OUTPUT_CHANNEL: vscode.OutputChannel | null = null; -export function outputChannel() { - if (!OUTPUT_CHANNEL) { - OUTPUT_CHANNEL = vscode.window.createOutputChannel("Rust Analyzer Language Server"); - } - return OUTPUT_CHANNEL; +export interface RustAnalyzerExtensionApi { + // FIXME: this should be non-optional + readonly client?: lc.LanguageClient; } -export interface RustAnalyzerExtensionApi { - client?: lc.LanguageClient; +export async function deactivate() { + await setContextValue(RUST_PROJECT_CONTEXT_NAME, undefined); } export async function activate( context: vscode.ExtensionContext ): Promise<RustAnalyzerExtensionApi> { - // VS Code doesn't show a notification when an extension fails to activate - // so we do it ourselves. - return await tryActivate(context).catch((err) => { - void vscode.window.showErrorMessage(`Cannot activate rust-analyzer: ${err.message}`); - throw err; - }); -} + if (vscode.extensions.getExtension("rust-lang.rust")) { + vscode.window + .showWarningMessage( + `You have both the rust-analyzer (rust-lang.rust-analyzer) and Rust (rust-lang.rust) ` + + "plugins enabled. These are known to conflict and cause various functions of " + + "both plugins to not work correctly. You should disable one of them.", + "Got it" + ) + .then(() => {}, console.error); + } -async function tryActivate(context: vscode.ExtensionContext): Promise<RustAnalyzerExtensionApi> { // We only support local folders, not eg. Live Share (`vlsl:` scheme), so don't activate if // only those are in use. // (r-a still somewhat works with Live Share, because commands are tunneled to the host) @@ -65,351 +49,118 @@ async function tryActivate(context: vscode.ExtensionContext): Promise<RustAnalyz return {}; } - const config = new Config(context); - const state = new PersistentState(context.globalState); - const serverPath = await bootstrap(context, config, state).catch((err) => { - let message = "bootstrap error. "; - - message += 'See the logs in "OUTPUT > Rust Analyzer Client" (should open automatically). '; - message += 'To enable verbose logs use { "rust-analyzer.trace.extension": true }'; + const workspace: Workspace = + folders.length === 0 + ? { + kind: "Detached Files", + files: rustDocuments, + } + : { kind: "Workspace Folder" }; - log.error("Bootstrap error", err); - throw new Error(message); + const ctx = new Ctx(context, workspace, createCommands()); + // VS Code doesn't show a notification when an extension fails to activate + // so we do it ourselves. + const api = await activateServer(ctx).catch((err) => { + void vscode.window.showErrorMessage( + `Cannot activate rust-analyzer extension: ${err.message}` + ); + throw err; }); + await setContextValue(RUST_PROJECT_CONTEXT_NAME, true); + return api; +} - if (folders.length === 0) { - ctx = await Ctx.create(config, context, serverPath, { - kind: "Detached Files", - files: rustDocuments, - }); - } else { - // Note: we try to start the server before we activate type hints so that it - // registers its `onDidChangeDocument` handler before us. - // - // This a horribly, horribly wrong way to deal with this problem. - ctx = await Ctx.create(config, context, serverPath, { kind: "Workspace Folder" }); - ctx.pushCleanup(activateTaskProvider(ctx.config)); - } - await initCommonContext(context, ctx); - - warnAboutExtensionConflicts(); - - if (config.typingContinueCommentsOnNewline) { - ctx.pushCleanup(configureLanguage()); +async function activateServer(ctx: Ctx): Promise<RustAnalyzerExtensionApi> { + if (ctx.workspace.kind === "Workspace Folder") { + ctx.pushExtCleanup(activateTaskProvider(ctx.config)); } vscode.workspace.onDidChangeConfiguration( - (_) => - ctx?.client - ?.sendNotification("workspace/didChangeConfiguration", { settings: "" }) - .catch(log.error), + async (_) => { + await ctx + .clientFetcher() + .client?.sendNotification("workspace/didChangeConfiguration", { settings: "" }); + }, null, ctx.subscriptions ); - return { - client: ctx.client, - }; -} - -async function initCommonContext(context: vscode.ExtensionContext, ctx: Ctx) { - // Register a "dumb" onEnter command for the case where server fails to - // start. - // - // FIXME: refactor command registration code such that commands are - // **always** registered, even if the server does not start. Use API like - // this perhaps? - // - // ```TypeScript - // registerCommand( - // factory: (Ctx) => ((Ctx) => any), - // fallback: () => any = () => vscode.window.showErrorMessage( - // "rust-analyzer is not available" - // ), - // ) - const defaultOnEnter = vscode.commands.registerCommand("rust-analyzer.onEnter", () => - vscode.commands.executeCommand("default:type", { text: "\n" }) - ); - context.subscriptions.push(defaultOnEnter); - - await setContextValue(RUST_PROJECT_CONTEXT_NAME, true); - - // Commands which invokes manually via command palette, shortcut, etc. - - // Reloading is inspired by @DanTup maneuver: https://github.com/microsoft/vscode/issues/45774#issuecomment-373423895 - ctx.registerCommand("reload", (_) => async () => { - void vscode.window.showInformationMessage("Reloading rust-analyzer..."); - await doDeactivate(); - while (context.subscriptions.length > 0) { - try { - context.subscriptions.pop()!.dispose(); - } catch (err) { - log.error("Dispose error:", err); - } - } - await activate(context).catch(log.error); - }); - - ctx.registerCommand("analyzerStatus", commands.analyzerStatus); - ctx.registerCommand("memoryUsage", commands.memoryUsage); - ctx.registerCommand("shuffleCrateGraph", commands.shuffleCrateGraph); - ctx.registerCommand("reloadWorkspace", commands.reloadWorkspace); - ctx.registerCommand("matchingBrace", commands.matchingBrace); - ctx.registerCommand("joinLines", commands.joinLines); - ctx.registerCommand("parentModule", commands.parentModule); - ctx.registerCommand("syntaxTree", commands.syntaxTree); - ctx.registerCommand("viewHir", commands.viewHir); - ctx.registerCommand("viewFileText", commands.viewFileText); - ctx.registerCommand("viewItemTree", commands.viewItemTree); - ctx.registerCommand("viewCrateGraph", commands.viewCrateGraph); - ctx.registerCommand("viewFullCrateGraph", commands.viewFullCrateGraph); - ctx.registerCommand("expandMacro", commands.expandMacro); - ctx.registerCommand("run", commands.run); - ctx.registerCommand("copyRunCommandLine", commands.copyRunCommandLine); - ctx.registerCommand("debug", commands.debug); - ctx.registerCommand("newDebugConfig", commands.newDebugConfig); - ctx.registerCommand("openDocs", commands.openDocs); - ctx.registerCommand("openCargoToml", commands.openCargoToml); - ctx.registerCommand("peekTests", commands.peekTests); - ctx.registerCommand("moveItemUp", commands.moveItemUp); - ctx.registerCommand("moveItemDown", commands.moveItemDown); - ctx.registerCommand("cancelFlycheck", commands.cancelFlycheck); - - defaultOnEnter.dispose(); - ctx.registerCommand("onEnter", commands.onEnter); - - ctx.registerCommand("ssr", commands.ssr); - ctx.registerCommand("serverVersion", commands.serverVersion); - - // Internal commands which are invoked by the server. - ctx.registerCommand("runSingle", commands.runSingle); - ctx.registerCommand("debugSingle", commands.debugSingle); - ctx.registerCommand("showReferences", commands.showReferences); - ctx.registerCommand("applySnippetWorkspaceEdit", commands.applySnippetWorkspaceEditCommand); - ctx.registerCommand("resolveCodeAction", commands.resolveCodeAction); - ctx.registerCommand("applyActionGroup", commands.applyActionGroup); - ctx.registerCommand("gotoLocation", commands.gotoLocation); - - ctx.registerCommand("linkToCommand", commands.linkToCommand); -} - -export async function deactivate() { - TRACE_OUTPUT_CHANNEL?.dispose(); - TRACE_OUTPUT_CHANNEL = null; - OUTPUT_CHANNEL?.dispose(); - OUTPUT_CHANNEL = null; - await doDeactivate(); -} - -async function doDeactivate() { - await setContextValue(RUST_PROJECT_CONTEXT_NAME, undefined); - await ctx?.client.stop(); - ctx = undefined; -} - -async function bootstrap( - context: vscode.ExtensionContext, - config: Config, - state: PersistentState -): Promise<string> { - const path = await getServer(context, config, state); - if (!path) { - throw new Error( - "Rust Analyzer Language Server is not available. " + - "Please, ensure its [proper installation](https://rust-analyzer.github.io/manual.html#installation)." - ); - } - - log.info("Using server binary at", path); - - if (!isValidExecutable(path)) { - if (config.serverPath) { - throw new Error(`Failed to execute ${path} --version. \`config.server.path\` or \`config.serverPath\` has been set explicitly.\ - Consider removing this config or making a valid server binary available at that path.`); - } else { - throw new Error(`Failed to execute ${path} --version`); - } - } - - return path; + await ctx.activate(); + return ctx.clientFetcher(); } -async function patchelf(dest: vscode.Uri): Promise<void> { - await vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: "Patching rust-analyzer for NixOS", +function createCommands(): Record<string, CommandFactory> { + return { + onEnter: { + enabled: commands.onEnter, + disabled: (_) => () => vscode.commands.executeCommand("default:type", { text: "\n" }), }, - async (progress, _) => { - const expression = ` - {srcStr, pkgs ? import <nixpkgs> {}}: - pkgs.stdenv.mkDerivation { - name = "rust-analyzer"; - src = /. + srcStr; - phases = [ "installPhase" "fixupPhase" ]; - installPhase = "cp $src $out"; - fixupPhase = '' - chmod 755 $out - patchelf --set-interpreter "$(cat $NIX_CC/nix-support/dynamic-linker)" $out - ''; - } - `; - const origFile = vscode.Uri.file(dest.fsPath + "-orig"); - await vscode.workspace.fs.rename(dest, origFile, { overwrite: true }); - try { - progress.report({ message: "Patching executable", increment: 20 }); - await new Promise((resolve, reject) => { - const handle = exec( - `nix-build -E - --argstr srcStr '${origFile.fsPath}' -o '${dest.fsPath}'`, - (err, stdout, stderr) => { - if (err != null) { - reject(Error(stderr)); - } else { - resolve(stdout); - } - } - ); - handle.stdin?.write(expression); - handle.stdin?.end(); - }); - } finally { - await vscode.workspace.fs.delete(origFile); - } - } - ); -} - -async function getServer( - context: vscode.ExtensionContext, - config: Config, - state: PersistentState -): Promise<string | undefined> { - const explicitPath = serverPath(config); - if (explicitPath) { - if (explicitPath.startsWith("~/")) { - return os.homedir() + explicitPath.slice("~".length); - } - return explicitPath; - } - if (config.package.releaseTag === null) return "rust-analyzer"; - - const ext = process.platform === "win32" ? ".exe" : ""; - const bundled = vscode.Uri.joinPath(context.extensionUri, "server", `rust-analyzer${ext}`); - const bundledExists = await vscode.workspace.fs.stat(bundled).then( - () => true, - () => false - ); - if (bundledExists) { - let server = bundled; - if (await isNixOs()) { - await vscode.workspace.fs.createDirectory(config.globalStorageUri).then(); - const dest = vscode.Uri.joinPath(config.globalStorageUri, `rust-analyzer${ext}`); - let exists = await vscode.workspace.fs.stat(dest).then( - () => true, - () => false - ); - if (exists && config.package.version !== state.serverVersion) { - await vscode.workspace.fs.delete(dest); - exists = false; - } - if (!exists) { - await vscode.workspace.fs.copy(bundled, dest); - await patchelf(dest); - } - server = dest; - } - await state.updateServerVersion(config.package.version); - return server.fsPath; - } - - await state.updateServerVersion(undefined); - await vscode.window.showErrorMessage( - "Unfortunately we don't ship binaries for your platform yet. " + - "You need to manually clone the rust-analyzer repository and " + - "run `cargo xtask install --server` to build the language server from sources. " + - "If you feel that your platform should be supported, please create an issue " + - "about that [here](https://github.com/rust-lang/rust-analyzer/issues) and we " + - "will consider it." - ); - return undefined; -} - -function serverPath(config: Config): string | null { - return process.env.__RA_LSP_SERVER_DEBUG ?? config.serverPath; -} - -async function isNixOs(): Promise<boolean> { - try { - const contents = ( - await vscode.workspace.fs.readFile(vscode.Uri.file("/etc/os-release")) - ).toString(); - const idString = contents.split("\n").find((a) => a.startsWith("ID=")) || "ID=linux"; - return idString.indexOf("nixos") !== -1; - } catch { - return false; - } -} - -function warnAboutExtensionConflicts() { - if (vscode.extensions.getExtension("rust-lang.rust")) { - vscode.window - .showWarningMessage( - `You have both the rust-analyzer (rust-lang.rust-analyzer) and Rust (rust-lang.rust) ` + - "plugins enabled. These are known to conflict and cause various functions of " + - "both plugins to not work correctly. You should disable one of them.", - "Got it" - ) - .then(() => {}, console.error); - } -} - -/** - * Sets up additional language configuration that's impossible to do via a - * separate language-configuration.json file. See [1] for more information. - * - * [1]: https://github.com/Microsoft/vscode/issues/11514#issuecomment-244707076 - */ -function configureLanguage(): vscode.Disposable { - const indentAction = vscode.IndentAction.None; - return vscode.languages.setLanguageConfiguration("rust", { - onEnterRules: [ - { - // Doc single-line comment - // e.g. ///| - beforeText: /^\s*\/{3}.*$/, - action: { indentAction, appendText: "/// " }, - }, - { - // Parent doc single-line comment - // e.g. //!| - beforeText: /^\s*\/{2}\!.*$/, - action: { indentAction, appendText: "//! " }, + reload: { + enabled: (ctx) => async () => { + void vscode.window.showInformationMessage("Reloading rust-analyzer..."); + // FIXME: We should re-use the client, that is ctx.deactivate() if none of the configs have changed + await ctx.stop(); + await ctx.activate(); }, - { - // Begins an auto-closed multi-line comment (standard or parent doc) - // e.g. /** | */ or /*! | */ - beforeText: /^\s*\/\*(\*|\!)(?!\/)([^\*]|\*(?!\/))*$/, - afterText: /^\s*\*\/$/, - action: { indentAction: vscode.IndentAction.IndentOutdent, appendText: " * " }, + disabled: (ctx) => async () => { + void vscode.window.showInformationMessage("Reloading rust-analyzer..."); + await ctx.activate(); }, - { - // Begins a multi-line comment (standard or parent doc) - // e.g. /** ...| or /*! ...| - beforeText: /^\s*\/\*(\*|\!)(?!\/)([^\*]|\*(?!\/))*$/, - action: { indentAction, appendText: " * " }, + }, + startServer: { + enabled: (ctx) => async () => { + await ctx.activate(); }, - { - // Continues a multi-line comment - // e.g. * ...| - beforeText: /^(\ \ )*\ \*(\ ([^\*]|\*(?!\/))*)?$/, - action: { indentAction, appendText: "* " }, + disabled: (ctx) => async () => { + await ctx.activate(); }, - { - // Dedents after closing a multi-line comment - // e.g. */| - beforeText: /^(\ \ )*\ \*\/\s*$/, - action: { indentAction, removeText: 1 }, + }, + stopServer: { + enabled: (ctx) => async () => { + // FIXME: We should re-use the client, that is ctx.deactivate() if none of the configs have changed + await ctx.stop(); + ctx.setServerStatus({ + health: "ok", + quiescent: true, + message: "server is not running", + }); }, - ], - }); + }, + + analyzerStatus: { enabled: commands.analyzerStatus }, + memoryUsage: { enabled: commands.memoryUsage }, + shuffleCrateGraph: { enabled: commands.shuffleCrateGraph }, + reloadWorkspace: { enabled: commands.reloadWorkspace }, + matchingBrace: { enabled: commands.matchingBrace }, + joinLines: { enabled: commands.joinLines }, + parentModule: { enabled: commands.parentModule }, + syntaxTree: { enabled: commands.syntaxTree }, + viewHir: { enabled: commands.viewHir }, + viewFileText: { enabled: commands.viewFileText }, + viewItemTree: { enabled: commands.viewItemTree }, + viewCrateGraph: { enabled: commands.viewCrateGraph }, + viewFullCrateGraph: { enabled: commands.viewFullCrateGraph }, + expandMacro: { enabled: commands.expandMacro }, + run: { enabled: commands.run }, + copyRunCommandLine: { enabled: commands.copyRunCommandLine }, + debug: { enabled: commands.debug }, + newDebugConfig: { enabled: commands.newDebugConfig }, + openDocs: { enabled: commands.openDocs }, + openCargoToml: { enabled: commands.openCargoToml }, + peekTests: { enabled: commands.peekTests }, + moveItemUp: { enabled: commands.moveItemUp }, + moveItemDown: { enabled: commands.moveItemDown }, + cancelFlycheck: { enabled: commands.cancelFlycheck }, + ssr: { enabled: commands.ssr }, + serverVersion: { enabled: commands.serverVersion }, + // Internal commands which are invoked by the server. + applyActionGroup: { enabled: commands.applyActionGroup }, + applySnippetWorkspaceEdit: { enabled: commands.applySnippetWorkspaceEditCommand }, + debugSingle: { enabled: commands.debugSingle }, + gotoLocation: { enabled: commands.gotoLocation }, + linkToCommand: { enabled: commands.linkToCommand }, + resolveCodeAction: { enabled: commands.resolveCodeAction }, + runSingle: { enabled: commands.runSingle }, + showReferences: { enabled: commands.showReferences }, + }; } diff --git a/src/tools/rust-analyzer/editors/code/src/run.ts b/src/tools/rust-analyzer/editors/code/src/run.ts index 22e5eda6827..dadaa41b1d1 100644 --- a/src/tools/rust-analyzer/editors/code/src/run.ts +++ b/src/tools/rust-analyzer/editors/code/src/run.ts @@ -18,9 +18,9 @@ export async function selectRunnable( showButtons: boolean = true ): Promise<RunnableQuickPick | undefined> { const editor = ctx.activeRustEditor; - const client = ctx.client; - if (!editor || !client) return; + if (!editor) return; + const client = await ctx.getClient(); const textDocument: lc.TextDocumentIdentifier = { uri: editor.document.uri.toString(), }; |
