Auto merge of #13426 - Veykril:client-refactor, r=Veykril
Refactor language client handling Follow up to https://github.com/rust-lang/rust-analyzer/pull/12847 (turns out they fixed parts of the problem) The PR will attempt to allow us to dispose more resources at will, so that we can implement restarts for the server properly instead of restating the entire extension as well as allowing us to implement a stop command. Closes https://github.com/rust-lang/rust-analyzer/issues/12936 Closes https://github.com/rust-lang/rust-analyzer/issues/4697
This commit is contained in:
commit
067c410c45
64
editors/code/package-lock.json
generated
64
editors/code/package-lock.json
generated
@ -11,7 +11,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"d3": "^7.6.1",
|
"d3": "^7.6.1",
|
||||||
"d3-graphviz": "^4.1.1",
|
"d3-graphviz": "^4.1.1",
|
||||||
"vscode-languageclient": "^8.0.0-next.14"
|
"vscode-languageclient": "^8.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "~16.11.7",
|
"@types/node": "~16.11.7",
|
||||||
@ -3791,39 +3791,39 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vscode-jsonrpc": {
|
"node_modules/vscode-jsonrpc": {
|
||||||
"version": "8.0.0-next.7",
|
"version": "8.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.0.0-next.7.tgz",
|
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.0.2.tgz",
|
||||||
"integrity": "sha512-JX/F31LEsims0dAlOTKFE4E+AJMiJvdRSRViifFJSqSN7EzeYyWlfuDchF7g91oRNPZOIWfibTkDf3/UMsQGzQ==",
|
"integrity": "sha512-RY7HwI/ydoC1Wwg4gJ3y6LpU9FJRZAUnTYMXthqhFXXu77ErDd/xkREpGuk4MyYkk4a+XDWAMqe0S3KkelYQEQ==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vscode-languageclient": {
|
"node_modules/vscode-languageclient": {
|
||||||
"version": "8.0.0-next.14",
|
"version": "8.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-8.0.0-next.14.tgz",
|
"resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-8.0.2.tgz",
|
||||||
"integrity": "sha512-NqjkOuDTMu8uo+PhoMsV72VO9Gd3wBi/ZpOrkRUOrWKQo7yUdiIw183g8wjH8BImgbK9ZP51HM7TI0ZhCnI1Mw==",
|
"integrity": "sha512-lHlthJtphG9gibGb/y72CKqQUxwPsMXijJVpHEC2bvbFqxmkj9LwQ3aGU9dwjBLqsX1S4KjShYppLvg1UJDF/Q==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"minimatch": "^3.0.4",
|
"minimatch": "^3.0.4",
|
||||||
"semver": "^7.3.5",
|
"semver": "^7.3.5",
|
||||||
"vscode-languageserver-protocol": "3.17.0-next.16"
|
"vscode-languageserver-protocol": "3.17.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"vscode": "^1.66.0"
|
"vscode": "^1.67.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vscode-languageserver-protocol": {
|
"node_modules/vscode-languageserver-protocol": {
|
||||||
"version": "3.17.0-next.16",
|
"version": "3.17.2",
|
||||||
"resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.0-next.16.tgz",
|
"resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.2.tgz",
|
||||||
"integrity": "sha512-tx4DnXw9u3N7vw+bx6n2NKp6FoxoNwiP/biH83AS30I2AnTGyLd7afSeH6Oewn2E8jvB7K15bs12sMppkKOVeQ==",
|
"integrity": "sha512-8kYisQ3z/SQ2kyjlNeQxbkkTNmVFoQCqkmGrzLH6A9ecPlgTbp3wDTnUNqaUxYr4vlAcloxx8zwy7G5WdguYNg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"vscode-jsonrpc": "8.0.0-next.7",
|
"vscode-jsonrpc": "8.0.2",
|
||||||
"vscode-languageserver-types": "3.17.0-next.9"
|
"vscode-languageserver-types": "3.17.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vscode-languageserver-types": {
|
"node_modules/vscode-languageserver-types": {
|
||||||
"version": "3.17.0-next.9",
|
"version": "3.17.2",
|
||||||
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.0-next.9.tgz",
|
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.2.tgz",
|
||||||
"integrity": "sha512-9/PeDNPYduaoXRUzYpqmu4ZV9L01HGo0wH9FUt+sSHR7IXwA7xoXBfNUlv8gB9H0D2WwEmMomSy1NmhjKQyn3A=="
|
"integrity": "sha512-zHhCWatviizPIq9B7Vh9uvrH6x3sK8itC84HkamnBWoDFJtzBf7SWlpLCZUit72b3os45h6RWQNC9xHRDF8dRA=="
|
||||||
},
|
},
|
||||||
"node_modules/which": {
|
"node_modules/which": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
@ -6634,33 +6634,33 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"vscode-jsonrpc": {
|
"vscode-jsonrpc": {
|
||||||
"version": "8.0.0-next.7",
|
"version": "8.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.0.0-next.7.tgz",
|
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.0.2.tgz",
|
||||||
"integrity": "sha512-JX/F31LEsims0dAlOTKFE4E+AJMiJvdRSRViifFJSqSN7EzeYyWlfuDchF7g91oRNPZOIWfibTkDf3/UMsQGzQ=="
|
"integrity": "sha512-RY7HwI/ydoC1Wwg4gJ3y6LpU9FJRZAUnTYMXthqhFXXu77ErDd/xkREpGuk4MyYkk4a+XDWAMqe0S3KkelYQEQ=="
|
||||||
},
|
},
|
||||||
"vscode-languageclient": {
|
"vscode-languageclient": {
|
||||||
"version": "8.0.0-next.14",
|
"version": "8.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-8.0.0-next.14.tgz",
|
"resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-8.0.2.tgz",
|
||||||
"integrity": "sha512-NqjkOuDTMu8uo+PhoMsV72VO9Gd3wBi/ZpOrkRUOrWKQo7yUdiIw183g8wjH8BImgbK9ZP51HM7TI0ZhCnI1Mw==",
|
"integrity": "sha512-lHlthJtphG9gibGb/y72CKqQUxwPsMXijJVpHEC2bvbFqxmkj9LwQ3aGU9dwjBLqsX1S4KjShYppLvg1UJDF/Q==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"minimatch": "^3.0.4",
|
"minimatch": "^3.0.4",
|
||||||
"semver": "^7.3.5",
|
"semver": "^7.3.5",
|
||||||
"vscode-languageserver-protocol": "3.17.0-next.16"
|
"vscode-languageserver-protocol": "3.17.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"vscode-languageserver-protocol": {
|
"vscode-languageserver-protocol": {
|
||||||
"version": "3.17.0-next.16",
|
"version": "3.17.2",
|
||||||
"resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.0-next.16.tgz",
|
"resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.2.tgz",
|
||||||
"integrity": "sha512-tx4DnXw9u3N7vw+bx6n2NKp6FoxoNwiP/biH83AS30I2AnTGyLd7afSeH6Oewn2E8jvB7K15bs12sMppkKOVeQ==",
|
"integrity": "sha512-8kYisQ3z/SQ2kyjlNeQxbkkTNmVFoQCqkmGrzLH6A9ecPlgTbp3wDTnUNqaUxYr4vlAcloxx8zwy7G5WdguYNg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"vscode-jsonrpc": "8.0.0-next.7",
|
"vscode-jsonrpc": "8.0.2",
|
||||||
"vscode-languageserver-types": "3.17.0-next.9"
|
"vscode-languageserver-types": "3.17.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"vscode-languageserver-types": {
|
"vscode-languageserver-types": {
|
||||||
"version": "3.17.0-next.9",
|
"version": "3.17.2",
|
||||||
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.0-next.9.tgz",
|
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.2.tgz",
|
||||||
"integrity": "sha512-9/PeDNPYduaoXRUzYpqmu4ZV9L01HGo0wH9FUt+sSHR7IXwA7xoXBfNUlv8gB9H0D2WwEmMomSy1NmhjKQyn3A=="
|
"integrity": "sha512-zHhCWatviizPIq9B7Vh9uvrH6x3sK8itC84HkamnBWoDFJtzBf7SWlpLCZUit72b3os45h6RWQNC9xHRDF8dRA=="
|
||||||
},
|
},
|
||||||
"which": {
|
"which": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"d3": "^7.6.1",
|
"d3": "^7.6.1",
|
||||||
"d3-graphviz": "^4.1.1",
|
"d3-graphviz": "^4.1.1",
|
||||||
"vscode-languageclient": "^8.0.0-next.14"
|
"vscode-languageclient": "^8.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "~16.11.7",
|
"@types/node": "~16.11.7",
|
||||||
@ -60,6 +60,7 @@
|
|||||||
"onCommand:rust-analyzer.analyzerStatus",
|
"onCommand:rust-analyzer.analyzerStatus",
|
||||||
"onCommand:rust-analyzer.memoryUsage",
|
"onCommand:rust-analyzer.memoryUsage",
|
||||||
"onCommand:rust-analyzer.reloadWorkspace",
|
"onCommand:rust-analyzer.reloadWorkspace",
|
||||||
|
"onCommand:rust-analyzer.startServer",
|
||||||
"workspaceContains:*/Cargo.toml",
|
"workspaceContains:*/Cargo.toml",
|
||||||
"workspaceContains:*/rust-project.json"
|
"workspaceContains:*/rust-project.json"
|
||||||
],
|
],
|
||||||
@ -191,6 +192,16 @@
|
|||||||
"title": "Restart server",
|
"title": "Restart server",
|
||||||
"category": "rust-analyzer"
|
"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",
|
"command": "rust-analyzer.onEnter",
|
||||||
"title": "Enhanced enter key",
|
"title": "Enhanced enter key",
|
||||||
|
@ -35,8 +35,10 @@ export class AstInspector implements vscode.HoverProvider, vscode.DefinitionProv
|
|||||||
});
|
});
|
||||||
|
|
||||||
constructor(ctx: Ctx) {
|
constructor(ctx: Ctx) {
|
||||||
ctx.pushCleanup(vscode.languages.registerHoverProvider({ scheme: "rust-analyzer" }, this));
|
ctx.pushExtCleanup(
|
||||||
ctx.pushCleanup(vscode.languages.registerDefinitionProvider({ language: "rust" }, this));
|
vscode.languages.registerHoverProvider({ scheme: "rust-analyzer" }, this)
|
||||||
|
);
|
||||||
|
ctx.pushExtCleanup(vscode.languages.registerDefinitionProvider({ language: "rust" }, this));
|
||||||
vscode.workspace.onDidCloseTextDocument(
|
vscode.workspace.onDidCloseTextDocument(
|
||||||
this.onDidCloseTextDocument,
|
this.onDidCloseTextDocument,
|
||||||
this,
|
this,
|
||||||
@ -52,8 +54,6 @@ export class AstInspector implements vscode.HoverProvider, vscode.DefinitionProv
|
|||||||
this,
|
this,
|
||||||
ctx.subscriptions
|
ctx.subscriptions
|
||||||
);
|
);
|
||||||
|
|
||||||
ctx.pushCleanup(this);
|
|
||||||
}
|
}
|
||||||
dispose() {
|
dispose() {
|
||||||
this.setRustEditor(undefined);
|
this.setRustEditor(undefined);
|
||||||
|
148
editors/code/src/bootstrap.ts
Normal file
148
editors/code/src/bootstrap.ts
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -4,9 +4,7 @@ import * as ra from "../src/lsp_ext";
|
|||||||
import * as Is from "vscode-languageclient/lib/common/utils/is";
|
import * as Is from "vscode-languageclient/lib/common/utils/is";
|
||||||
import { assert } from "./util";
|
import { assert } from "./util";
|
||||||
import { WorkspaceEdit } from "vscode";
|
import { WorkspaceEdit } from "vscode";
|
||||||
import { Workspace } from "./ctx";
|
import { substituteVSCodeVariables } from "./config";
|
||||||
import { substituteVariablesInEnv, substituteVSCodeVariables } from "./config";
|
|
||||||
import { outputChannel, traceOutputChannel } from "./main";
|
|
||||||
import { randomUUID } from "crypto";
|
import { randomUUID } from "crypto";
|
||||||
|
|
||||||
export interface Env {
|
export interface Env {
|
||||||
@ -65,43 +63,27 @@ function renderHoverActions(actions: ra.CommandLinkGroup[]): vscode.MarkdownStri
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function createClient(
|
export async function createClient(
|
||||||
serverPath: string,
|
traceOutputChannel: vscode.OutputChannel,
|
||||||
workspace: Workspace,
|
outputChannel: vscode.OutputChannel,
|
||||||
extraEnv: Env
|
initializationOptions: vscode.WorkspaceConfiguration,
|
||||||
|
serverOptions: lc.ServerOptions
|
||||||
): Promise<lc.LanguageClient> {
|
): 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 rawInitializationOptions = vscode.workspace.getConfiguration("rust-analyzer");
|
|
||||||
|
|
||||||
if (workspace.kind === "Detached Files") {
|
|
||||||
rawInitializationOptions = {
|
|
||||||
detachedFiles: workspace.files.map((file) => file.uri.fsPath),
|
|
||||||
...rawInitializationOptions,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const initializationOptions = substituteVSCodeVariables(rawInitializationOptions);
|
|
||||||
|
|
||||||
const clientOptions: lc.LanguageClientOptions = {
|
const clientOptions: lc.LanguageClientOptions = {
|
||||||
documentSelector: [{ scheme: "file", language: "rust" }],
|
documentSelector: [{ scheme: "file", language: "rust" }],
|
||||||
initializationOptions,
|
initializationOptions,
|
||||||
diagnosticCollectionName: "rustc",
|
diagnosticCollectionName: "rustc",
|
||||||
traceOutputChannel: traceOutputChannel(),
|
traceOutputChannel,
|
||||||
outputChannel: outputChannel(),
|
outputChannel,
|
||||||
middleware: {
|
middleware: {
|
||||||
workspace: {
|
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(
|
async configuration(
|
||||||
params: lc.ConfigurationParams,
|
params: lc.ConfigurationParams,
|
||||||
token: vscode.CancellationToken,
|
token: vscode.CancellationToken,
|
||||||
@ -273,6 +255,9 @@ export async function createClient(
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ExperimentalFeatures implements lc.StaticFeature {
|
class ExperimentalFeatures implements lc.StaticFeature {
|
||||||
|
getState(): lc.FeatureState {
|
||||||
|
return { kind: "static" };
|
||||||
|
}
|
||||||
fillClientCapabilities(capabilities: lc.ClientCapabilities): void {
|
fillClientCapabilities(capabilities: lc.ClientCapabilities): void {
|
||||||
const caps: any = capabilities.experimental ?? {};
|
const caps: any = capabilities.experimental ?? {};
|
||||||
caps.snippetTextEdit = true;
|
caps.snippetTextEdit = true;
|
||||||
|
@ -21,16 +21,16 @@ export function analyzerStatus(ctx: Ctx): Cmd {
|
|||||||
readonly uri = vscode.Uri.parse("rust-analyzer-status://status");
|
readonly uri = vscode.Uri.parse("rust-analyzer-status://status");
|
||||||
readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>();
|
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 "";
|
if (!vscode.window.activeTextEditor) return "";
|
||||||
|
const client = await ctx.getClient();
|
||||||
|
|
||||||
const params: ra.AnalyzerStatusParams = {};
|
const params: ra.AnalyzerStatusParams = {};
|
||||||
const doc = ctx.activeRustEditor?.document;
|
const doc = ctx.activeRustEditor?.document;
|
||||||
if (doc != null) {
|
if (doc != null) {
|
||||||
params.textDocument =
|
params.textDocument = client.code2ProtocolConverter.asTextDocumentIdentifier(doc);
|
||||||
ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(doc);
|
|
||||||
}
|
}
|
||||||
return ctx.client.sendRequest(ra.analyzerStatus, params);
|
return await client.sendRequest(ra.analyzerStatus, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
get onDidChange(): vscode.Event<vscode.Uri> {
|
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)
|
vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-status", tdcp)
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -60,9 +60,14 @@ export function memoryUsage(ctx: Ctx): Cmd {
|
|||||||
provideTextDocumentContent(_uri: vscode.Uri): vscode.ProviderResult<string> {
|
provideTextDocumentContent(_uri: vscode.Uri): vscode.ProviderResult<string> {
|
||||||
if (!vscode.window.activeTextEditor) return "";
|
if (!vscode.window.activeTextEditor) return "";
|
||||||
|
|
||||||
return ctx.client.sendRequest(ra.memoryUsage).then((mem: any) => {
|
return ctx
|
||||||
return "Per-query memory usage:\n" + mem + "\n(note: database has been cleared)";
|
.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> {
|
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)
|
vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-memory", tdcp)
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -83,23 +88,19 @@ export function memoryUsage(ctx: Ctx): Cmd {
|
|||||||
|
|
||||||
export function shuffleCrateGraph(ctx: Ctx): Cmd {
|
export function shuffleCrateGraph(ctx: Ctx): Cmd {
|
||||||
return async () => {
|
return async () => {
|
||||||
const client = ctx.client;
|
return ctx.getClient().then((it) => it.sendRequest(ra.shuffleCrateGraph));
|
||||||
if (!client) return;
|
|
||||||
|
|
||||||
await client.sendRequest(ra.shuffleCrateGraph);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function matchingBrace(ctx: Ctx): Cmd {
|
export function matchingBrace(ctx: Ctx): Cmd {
|
||||||
return async () => {
|
return async () => {
|
||||||
const editor = ctx.activeRustEditor;
|
const editor = ctx.activeRustEditor;
|
||||||
const client = ctx.client;
|
if (!editor) return;
|
||||||
if (!editor || !client) return;
|
|
||||||
|
const client = await ctx.getClient();
|
||||||
|
|
||||||
const response = await client.sendRequest(ra.matchingBrace, {
|
const response = await client.sendRequest(ra.matchingBrace, {
|
||||||
textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(
|
textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document),
|
||||||
editor.document
|
|
||||||
),
|
|
||||||
positions: editor.selections.map((s) =>
|
positions: editor.selections.map((s) =>
|
||||||
client.code2ProtocolConverter.asPosition(s.active)
|
client.code2ProtocolConverter.asPosition(s.active)
|
||||||
),
|
),
|
||||||
@ -116,14 +117,13 @@ export function matchingBrace(ctx: Ctx): Cmd {
|
|||||||
export function joinLines(ctx: Ctx): Cmd {
|
export function joinLines(ctx: Ctx): Cmd {
|
||||||
return async () => {
|
return async () => {
|
||||||
const editor = ctx.activeRustEditor;
|
const editor = ctx.activeRustEditor;
|
||||||
const client = ctx.client;
|
if (!editor) return;
|
||||||
if (!editor || !client) return;
|
|
||||||
|
const client = await ctx.getClient();
|
||||||
|
|
||||||
const items: lc.TextEdit[] = await client.sendRequest(ra.joinLines, {
|
const items: lc.TextEdit[] = await client.sendRequest(ra.joinLines, {
|
||||||
ranges: editor.selections.map((it) => client.code2ProtocolConverter.asRange(it)),
|
ranges: editor.selections.map((it) => client.code2ProtocolConverter.asRange(it)),
|
||||||
textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(
|
textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document),
|
||||||
editor.document
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
const textEdits = await client.protocol2CodeConverter.asTextEdits(items);
|
const textEdits = await client.protocol2CodeConverter.asTextEdits(items);
|
||||||
await editor.edit((builder) => {
|
await editor.edit((builder) => {
|
||||||
@ -145,14 +145,12 @@ export function moveItemDown(ctx: Ctx): Cmd {
|
|||||||
export function moveItem(ctx: Ctx, direction: ra.Direction): Cmd {
|
export function moveItem(ctx: Ctx, direction: ra.Direction): Cmd {
|
||||||
return async () => {
|
return async () => {
|
||||||
const editor = ctx.activeRustEditor;
|
const editor = ctx.activeRustEditor;
|
||||||
const client = ctx.client;
|
if (!editor) return;
|
||||||
if (!editor || !client) return;
|
const client = await ctx.getClient();
|
||||||
|
|
||||||
const lcEdits = await client.sendRequest(ra.moveItem, {
|
const lcEdits = await client.sendRequest(ra.moveItem, {
|
||||||
range: client.code2ProtocolConverter.asRange(editor.selection),
|
range: client.code2ProtocolConverter.asRange(editor.selection),
|
||||||
textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(
|
textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document),
|
||||||
editor.document
|
|
||||||
),
|
|
||||||
direction,
|
direction,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -166,13 +164,13 @@ export function moveItem(ctx: Ctx, direction: ra.Direction): Cmd {
|
|||||||
export function onEnter(ctx: Ctx): Cmd {
|
export function onEnter(ctx: Ctx): Cmd {
|
||||||
async function handleKeypress() {
|
async function handleKeypress() {
|
||||||
const editor = ctx.activeRustEditor;
|
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
|
const lcEdits = await client
|
||||||
.sendRequest(ra.onEnter, {
|
.sendRequest(ra.onEnter, {
|
||||||
textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(
|
textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(
|
||||||
editor.document
|
editor.document
|
||||||
),
|
),
|
||||||
position: client.code2ProtocolConverter.asPosition(editor.selection.active),
|
position: client.code2ProtocolConverter.asPosition(editor.selection.active),
|
||||||
@ -198,14 +196,13 @@ export function onEnter(ctx: Ctx): Cmd {
|
|||||||
export function parentModule(ctx: Ctx): Cmd {
|
export function parentModule(ctx: Ctx): Cmd {
|
||||||
return async () => {
|
return async () => {
|
||||||
const editor = vscode.window.activeTextEditor;
|
const editor = vscode.window.activeTextEditor;
|
||||||
const client = ctx.client;
|
if (!editor) return;
|
||||||
if (!editor || !client) return;
|
|
||||||
if (!(isRustDocument(editor.document) || isCargoTomlDocument(editor.document))) return;
|
if (!(isRustDocument(editor.document) || isCargoTomlDocument(editor.document))) return;
|
||||||
|
|
||||||
|
const client = await ctx.getClient();
|
||||||
|
|
||||||
const locations = await client.sendRequest(ra.parentModule, {
|
const locations = await client.sendRequest(ra.parentModule, {
|
||||||
textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(
|
textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document),
|
||||||
editor.document
|
|
||||||
),
|
|
||||||
position: client.code2ProtocolConverter.asPosition(editor.selection.active),
|
position: client.code2ProtocolConverter.asPosition(editor.selection.active),
|
||||||
});
|
});
|
||||||
if (!locations) return;
|
if (!locations) return;
|
||||||
@ -236,13 +233,11 @@ export function parentModule(ctx: Ctx): Cmd {
|
|||||||
export function openCargoToml(ctx: Ctx): Cmd {
|
export function openCargoToml(ctx: Ctx): Cmd {
|
||||||
return async () => {
|
return async () => {
|
||||||
const editor = ctx.activeRustEditor;
|
const editor = ctx.activeRustEditor;
|
||||||
const client = ctx.client;
|
if (!editor) return;
|
||||||
if (!editor || !client) return;
|
|
||||||
|
|
||||||
|
const client = await ctx.getClient();
|
||||||
const response = await client.sendRequest(ra.openCargoToml, {
|
const response = await client.sendRequest(ra.openCargoToml, {
|
||||||
textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(
|
textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document),
|
||||||
editor.document
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
if (!response) return;
|
if (!response) return;
|
||||||
|
|
||||||
@ -259,12 +254,13 @@ export function openCargoToml(ctx: Ctx): Cmd {
|
|||||||
export function ssr(ctx: Ctx): Cmd {
|
export function ssr(ctx: Ctx): Cmd {
|
||||||
return async () => {
|
return async () => {
|
||||||
const editor = vscode.window.activeTextEditor;
|
const editor = vscode.window.activeTextEditor;
|
||||||
const client = ctx.client;
|
if (!editor) return;
|
||||||
if (!editor || !client) return;
|
|
||||||
|
const client = await ctx.getClient();
|
||||||
|
|
||||||
const position = editor.selection.active;
|
const position = editor.selection.active;
|
||||||
const selections = editor.selections;
|
const selections = editor.selections;
|
||||||
const textDocument = ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(
|
const textDocument = client.code2ProtocolConverter.asTextDocumentIdentifier(
|
||||||
editor.document
|
editor.document
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -314,6 +310,10 @@ export function ssr(ctx: Ctx): Cmd {
|
|||||||
|
|
||||||
export function serverVersion(ctx: Ctx): Cmd {
|
export function serverVersion(ctx: Ctx): Cmd {
|
||||||
return async () => {
|
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 { stdout } = spawnSync(ctx.serverPath, ["--version"], { encoding: "utf8" });
|
||||||
const versionString = stdout.slice(`rust-analyzer `.length).trim();
|
const versionString = stdout.slice(`rust-analyzer `.length).trim();
|
||||||
|
|
||||||
@ -354,21 +354,22 @@ export function syntaxTree(ctx: Ctx): Cmd {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
provideTextDocumentContent(
|
async provideTextDocumentContent(
|
||||||
uri: vscode.Uri,
|
uri: vscode.Uri,
|
||||||
ct: vscode.CancellationToken
|
ct: vscode.CancellationToken
|
||||||
): vscode.ProviderResult<string> {
|
): Promise<string> {
|
||||||
const rustEditor = ctx.activeRustEditor;
|
const rustEditor = ctx.activeRustEditor;
|
||||||
if (!rustEditor) return "";
|
if (!rustEditor) return "";
|
||||||
|
const client = await ctx.getClient();
|
||||||
|
|
||||||
// When the range based query is enabled we take the range of the selection
|
// When the range based query is enabled we take the range of the selection
|
||||||
const range =
|
const range =
|
||||||
uri.query === "range=true" && !rustEditor.selection.isEmpty
|
uri.query === "range=true" && !rustEditor.selection.isEmpty
|
||||||
? ctx.client.code2ProtocolConverter.asRange(rustEditor.selection)
|
? client.code2ProtocolConverter.asRange(rustEditor.selection)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const params = { textDocument: { uri: rustEditor.document.uri.toString() }, range };
|
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> {
|
get onDidChange(): vscode.Event<vscode.Uri> {
|
||||||
@ -376,12 +377,11 @@ export function syntaxTree(ctx: Ctx): Cmd {
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
void new AstInspector(ctx);
|
ctx.pushExtCleanup(new AstInspector(ctx));
|
||||||
|
ctx.pushExtCleanup(
|
||||||
ctx.pushCleanup(
|
|
||||||
vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-syntax-tree", tdcp)
|
vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-syntax-tree", tdcp)
|
||||||
);
|
);
|
||||||
ctx.pushCleanup(
|
ctx.pushExtCleanup(
|
||||||
vscode.languages.setLanguageConfiguration("ra_syntax_tree", {
|
vscode.languages.setLanguageConfiguration("ra_syntax_tree", {
|
||||||
brackets: [["[", ")"]],
|
brackets: [["[", ")"]],
|
||||||
})
|
})
|
||||||
@ -437,14 +437,14 @@ export function viewHir(ctx: Ctx): Cmd {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
provideTextDocumentContent(
|
async provideTextDocumentContent(
|
||||||
_uri: vscode.Uri,
|
_uri: vscode.Uri,
|
||||||
ct: vscode.CancellationToken
|
ct: vscode.CancellationToken
|
||||||
): vscode.ProviderResult<string> {
|
): Promise<string> {
|
||||||
const rustEditor = ctx.activeRustEditor;
|
const rustEditor = ctx.activeRustEditor;
|
||||||
const client = ctx.client;
|
if (!rustEditor) return "";
|
||||||
if (!rustEditor || !client) return "";
|
|
||||||
|
|
||||||
|
const client = await ctx.getClient();
|
||||||
const params = {
|
const params = {
|
||||||
textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(
|
textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(
|
||||||
rustEditor.document
|
rustEditor.document
|
||||||
@ -459,7 +459,7 @@ export function viewHir(ctx: Ctx): Cmd {
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
ctx.pushCleanup(
|
ctx.pushExtCleanup(
|
||||||
vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-hir", tdcp)
|
vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-hir", tdcp)
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -503,13 +503,13 @@ export function viewFileText(ctx: Ctx): Cmd {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
provideTextDocumentContent(
|
async provideTextDocumentContent(
|
||||||
_uri: vscode.Uri,
|
_uri: vscode.Uri,
|
||||||
ct: vscode.CancellationToken
|
ct: vscode.CancellationToken
|
||||||
): vscode.ProviderResult<string> {
|
): Promise<string> {
|
||||||
const rustEditor = ctx.activeRustEditor;
|
const rustEditor = ctx.activeRustEditor;
|
||||||
const client = ctx.client;
|
if (!rustEditor) return "";
|
||||||
if (!rustEditor || !client) return "";
|
const client = await ctx.getClient();
|
||||||
|
|
||||||
const params = client.code2ProtocolConverter.asTextDocumentIdentifier(
|
const params = client.code2ProtocolConverter.asTextDocumentIdentifier(
|
||||||
rustEditor.document
|
rustEditor.document
|
||||||
@ -522,7 +522,7 @@ export function viewFileText(ctx: Ctx): Cmd {
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
ctx.pushCleanup(
|
ctx.pushExtCleanup(
|
||||||
vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-file-text", tdcp)
|
vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-file-text", tdcp)
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -566,13 +566,13 @@ export function viewItemTree(ctx: Ctx): Cmd {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
provideTextDocumentContent(
|
async provideTextDocumentContent(
|
||||||
_uri: vscode.Uri,
|
_uri: vscode.Uri,
|
||||||
ct: vscode.CancellationToken
|
ct: vscode.CancellationToken
|
||||||
): vscode.ProviderResult<string> {
|
): Promise<string> {
|
||||||
const rustEditor = ctx.activeRustEditor;
|
const rustEditor = ctx.activeRustEditor;
|
||||||
const client = ctx.client;
|
if (!rustEditor) return "";
|
||||||
if (!rustEditor || !client) return "";
|
const client = await ctx.getClient();
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(
|
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)
|
vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-item-tree", tdcp)
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -618,8 +618,8 @@ function crateGraph(ctx: Ctx, full: boolean): Cmd {
|
|||||||
const params = {
|
const params = {
|
||||||
full: full,
|
full: full,
|
||||||
};
|
};
|
||||||
|
const client = await ctx.getClient();
|
||||||
const dot = await ctx.client.sendRequest(ra.viewCrateGraph, params);
|
const dot = await client.sendRequest(ra.viewCrateGraph, params);
|
||||||
const uri = panel.webview.asWebviewUri(nodeModulesPath);
|
const uri = panel.webview.asWebviewUri(nodeModulesPath);
|
||||||
|
|
||||||
const html = `
|
const html = `
|
||||||
@ -690,13 +690,13 @@ export function expandMacro(ctx: Ctx): Cmd {
|
|||||||
eventEmitter = new vscode.EventEmitter<vscode.Uri>();
|
eventEmitter = new vscode.EventEmitter<vscode.Uri>();
|
||||||
async provideTextDocumentContent(_uri: vscode.Uri): Promise<string> {
|
async provideTextDocumentContent(_uri: vscode.Uri): Promise<string> {
|
||||||
const editor = vscode.window.activeTextEditor;
|
const editor = vscode.window.activeTextEditor;
|
||||||
const client = ctx.client;
|
if (!editor) return "";
|
||||||
if (!editor || !client) return "";
|
const client = await ctx.getClient();
|
||||||
|
|
||||||
const position = editor.selection.active;
|
const position = editor.selection.active;
|
||||||
|
|
||||||
const expanded = await client.sendRequest(ra.expandMacro, {
|
const expanded = await client.sendRequest(ra.expandMacro, {
|
||||||
textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(
|
textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(
|
||||||
editor.document
|
editor.document
|
||||||
),
|
),
|
||||||
position,
|
position,
|
||||||
@ -712,7 +712,7 @@ export function expandMacro(ctx: Ctx): Cmd {
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
ctx.pushCleanup(
|
ctx.pushExtCleanup(
|
||||||
vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-expand-macro", tdcp)
|
vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-expand-macro", tdcp)
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -724,11 +724,11 @@ export function expandMacro(ctx: Ctx): Cmd {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function reloadWorkspace(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(
|
async function showReferencesImpl(
|
||||||
client: LanguageClient,
|
client: LanguageClient | undefined,
|
||||||
uri: string,
|
uri: string,
|
||||||
position: lc.Position,
|
position: lc.Position,
|
||||||
locations: lc.Location[]
|
locations: lc.Location[]
|
||||||
@ -745,7 +745,7 @@ async function showReferencesImpl(
|
|||||||
|
|
||||||
export function showReferences(ctx: Ctx): Cmd {
|
export function showReferences(ctx: Ctx): Cmd {
|
||||||
return async (uri: string, position: lc.Position, locations: lc.Location[]) => {
|
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 {
|
export function gotoLocation(ctx: Ctx): Cmd {
|
||||||
return async (locationLink: lc.LocationLink) => {
|
return async (locationLink: lc.LocationLink) => {
|
||||||
const client = ctx.client;
|
const client = await ctx.getClient();
|
||||||
if (client) {
|
const uri = client.protocol2CodeConverter.asUri(locationLink.targetUri);
|
||||||
const uri = client.protocol2CodeConverter.asUri(locationLink.targetUri);
|
let range = client.protocol2CodeConverter.asRange(locationLink.targetSelectionRange);
|
||||||
let range = client.protocol2CodeConverter.asRange(locationLink.targetSelectionRange);
|
// collapse the range to a cursor position
|
||||||
// collapse the range to a cursor position
|
range = range.with({ end: range.start });
|
||||||
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 {
|
export function openDocs(ctx: Ctx): Cmd {
|
||||||
return async () => {
|
return async () => {
|
||||||
const client = ctx.client;
|
|
||||||
const editor = vscode.window.activeTextEditor;
|
const editor = vscode.window.activeTextEditor;
|
||||||
if (!editor || !client) {
|
if (!editor) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const client = await ctx.getClient();
|
||||||
|
|
||||||
const position = editor.selection.active;
|
const position = editor.selection.active;
|
||||||
const textDocument = { uri: editor.document.uri.toString() };
|
const textDocument = { uri: editor.document.uri.toString() };
|
||||||
@ -795,20 +793,21 @@ export function openDocs(ctx: Ctx): Cmd {
|
|||||||
|
|
||||||
export function cancelFlycheck(ctx: Ctx): Cmd {
|
export function cancelFlycheck(ctx: Ctx): Cmd {
|
||||||
return async () => {
|
return async () => {
|
||||||
await ctx.client.sendRequest(ra.cancelFlycheck);
|
const client = await ctx.getClient();
|
||||||
|
await client.sendRequest(ra.cancelFlycheck);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveCodeAction(ctx: Ctx): Cmd {
|
export function resolveCodeAction(ctx: Ctx): Cmd {
|
||||||
const client = ctx.client;
|
|
||||||
return async (params: lc.CodeAction) => {
|
return async (params: lc.CodeAction) => {
|
||||||
|
const client = await ctx.getClient();
|
||||||
params.command = undefined;
|
params.command = undefined;
|
||||||
const item = await client.sendRequest(lc.CodeActionResolveRequest.type, params);
|
const item = await client?.sendRequest(lc.CodeActionResolveRequest.type, params);
|
||||||
if (!item.edit) {
|
if (!item?.edit) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const itemEdit = item.edit;
|
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
|
// filter out all text edits and recreate the WorkspaceEdit without them so we can apply
|
||||||
// snippet edits on our own
|
// snippet edits on our own
|
||||||
const lcFileSystemEdit = {
|
const lcFileSystemEdit = {
|
||||||
@ -847,11 +846,10 @@ export function run(ctx: Ctx): Cmd {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function peekTests(ctx: Ctx): Cmd {
|
export function peekTests(ctx: Ctx): Cmd {
|
||||||
const client = ctx.client;
|
|
||||||
|
|
||||||
return async () => {
|
return async () => {
|
||||||
const editor = ctx.activeRustEditor;
|
const editor = ctx.activeRustEditor;
|
||||||
if (!editor || !client) return;
|
if (!editor) return;
|
||||||
|
const client = await ctx.getClient();
|
||||||
|
|
||||||
await vscode.window.withProgress(
|
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) => {
|
return async (commandId: string) => {
|
||||||
const link = LINKED_COMMANDS.get(commandId);
|
const link = LINKED_COMMANDS.get(commandId);
|
||||||
if (ctx.client && link) {
|
if (link) {
|
||||||
const { command, arguments: args = [] } = link;
|
const { command, arguments: args = [] } = link;
|
||||||
await vscode.commands.executeCommand(command, ...args);
|
await vscode.commands.executeCommand(command, ...args);
|
||||||
}
|
}
|
||||||
|
@ -14,8 +14,6 @@ export class Config {
|
|||||||
|
|
||||||
readonly rootSection = "rust-analyzer";
|
readonly rootSection = "rust-analyzer";
|
||||||
private readonly requiresWorkspaceReloadOpts = [
|
private readonly requiresWorkspaceReloadOpts = [
|
||||||
"serverPath",
|
|
||||||
"server",
|
|
||||||
// FIXME: This shouldn't be here, changing this setting should reload
|
// FIXME: This shouldn't be here, changing this setting should reload
|
||||||
// `continueCommentsOnNewline` behavior without restart
|
// `continueCommentsOnNewline` behavior without restart
|
||||||
"typing",
|
"typing",
|
||||||
@ -23,6 +21,8 @@ export class Config {
|
|||||||
private readonly requiresReloadOpts = [
|
private readonly requiresReloadOpts = [
|
||||||
"cargo",
|
"cargo",
|
||||||
"procMacro",
|
"procMacro",
|
||||||
|
"serverPath",
|
||||||
|
"server",
|
||||||
"files",
|
"files",
|
||||||
"lens", // works as lens.*
|
"lens", // works as lens.*
|
||||||
]
|
]
|
||||||
|
@ -2,10 +2,12 @@ import * as vscode from "vscode";
|
|||||||
import * as lc from "vscode-languageclient/node";
|
import * as lc from "vscode-languageclient/node";
|
||||||
import * as ra from "./lsp_ext";
|
import * as ra from "./lsp_ext";
|
||||||
|
|
||||||
import { Config } from "./config";
|
import { Config, substituteVariablesInEnv, substituteVSCodeVariables } from "./config";
|
||||||
import { createClient } from "./client";
|
import { createClient } from "./client";
|
||||||
import { isRustEditor, RustEditor } from "./util";
|
import { isRustEditor, log, RustEditor } from "./util";
|
||||||
import { ServerStatusParams } from "./lsp_ext";
|
import { ServerStatusParams } from "./lsp_ext";
|
||||||
|
import { PersistentState } from "./persistent_state";
|
||||||
|
import { bootstrap } from "./bootstrap";
|
||||||
|
|
||||||
export type Workspace =
|
export type Workspace =
|
||||||
| {
|
| {
|
||||||
@ -17,35 +19,121 @@ export type Workspace =
|
|||||||
};
|
};
|
||||||
|
|
||||||
export class Ctx {
|
export class Ctx {
|
||||||
private constructor(
|
readonly statusBar: vscode.StatusBarItem;
|
||||||
readonly config: Config,
|
readonly config: Config;
|
||||||
private readonly extCtx: vscode.ExtensionContext,
|
|
||||||
readonly client: lc.LanguageClient,
|
|
||||||
readonly serverPath: string,
|
|
||||||
readonly statusBar: vscode.StatusBarItem
|
|
||||||
) {}
|
|
||||||
|
|
||||||
static async create(
|
private client: lc.LanguageClient | undefined;
|
||||||
config: Config,
|
private _serverPath: string | undefined;
|
||||||
extCtx: vscode.ExtensionContext,
|
private traceOutputChannel: vscode.OutputChannel | undefined;
|
||||||
serverPath: string,
|
private outputChannel: vscode.OutputChannel | undefined;
|
||||||
workspace: Workspace
|
private state: PersistentState;
|
||||||
): Promise<Ctx> {
|
|
||||||
const client = await createClient(serverPath, workspace, config.serverExtraEnv);
|
|
||||||
|
|
||||||
const statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
|
workspace: Workspace;
|
||||||
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);
|
constructor(readonly extCtx: vscode.ExtensionContext, workspace: Workspace) {
|
||||||
|
this.statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
|
||||||
|
extCtx.subscriptions.push(this.statusBar);
|
||||||
|
extCtx.subscriptions.push({
|
||||||
|
dispose() {
|
||||||
|
this.dispose();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.statusBar.text = "rust-analyzer";
|
||||||
|
this.statusBar.tooltip = "ready";
|
||||||
|
this.statusBar.command = "rust-analyzer.analyzerStatus";
|
||||||
|
this.statusBar.show();
|
||||||
|
this.workspace = workspace;
|
||||||
|
|
||||||
res.pushCleanup(client.start());
|
this.state = new PersistentState(extCtx.globalState);
|
||||||
await client.onReady();
|
this.config = new Config(extCtx);
|
||||||
client.onNotification(ra.serverStatus, (params) => res.setServerStatus(params));
|
}
|
||||||
return res;
|
|
||||||
|
clientFetcher() {
|
||||||
|
return {
|
||||||
|
get client(): lc.LanguageClient | undefined {
|
||||||
|
return this.client;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getClient() {
|
||||||
|
// if server path changes -> dispose
|
||||||
|
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.client.onNotification(ra.serverStatus, (params) => this.setServerStatus(params));
|
||||||
|
}
|
||||||
|
return this.client;
|
||||||
|
}
|
||||||
|
|
||||||
|
async activate() {
|
||||||
|
log.info("Activating language client");
|
||||||
|
const client = await this.getClient();
|
||||||
|
await client.start();
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
async deactivate() {
|
||||||
|
log.info("Deactivating language client");
|
||||||
|
await this.client?.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
async disposeClient() {
|
||||||
|
log.info("Deactivating language client");
|
||||||
|
await this.client?.dispose();
|
||||||
|
this._serverPath = undefined;
|
||||||
|
this.client = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
get activeRustEditor(): RustEditor | undefined {
|
get activeRustEditor(): RustEditor | undefined {
|
||||||
@ -53,29 +141,18 @@ export class Ctx {
|
|||||||
return editor && isRustEditor(editor) ? editor : undefined;
|
return editor && isRustEditor(editor) ? editor : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
get visibleRustEditors(): RustEditor[] {
|
|
||||||
return vscode.window.visibleTextEditors.filter(isRustEditor);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
get extensionPath(): string {
|
get extensionPath(): string {
|
||||||
return this.extCtx.extensionPath;
|
return this.extCtx.extensionPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
get globalState(): vscode.Memento {
|
|
||||||
return this.extCtx.globalState;
|
|
||||||
}
|
|
||||||
|
|
||||||
get subscriptions(): Disposable[] {
|
get subscriptions(): Disposable[] {
|
||||||
return this.extCtx.subscriptions;
|
return this.extCtx.subscriptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get serverPath(): string | undefined {
|
||||||
|
return this._serverPath;
|
||||||
|
}
|
||||||
|
|
||||||
setServerStatus(status: ServerStatusParams) {
|
setServerStatus(status: ServerStatusParams) {
|
||||||
let icon = "";
|
let icon = "";
|
||||||
const statusBar = this.statusBar;
|
const statusBar = this.statusBar;
|
||||||
@ -111,7 +188,14 @@ export class Ctx {
|
|||||||
statusBar.text = `${icon}rust-analyzer`;
|
statusBar.text = `${icon}rust-analyzer`;
|
||||||
}
|
}
|
||||||
|
|
||||||
pushCleanup(d: Disposable) {
|
registerCommand(name: string, factory: (ctx: Ctx) => Cmd) {
|
||||||
|
const fullName = `rust-analyzer.${name}`;
|
||||||
|
const cmd = factory(this);
|
||||||
|
const d = vscode.commands.registerCommand(fullName, cmd);
|
||||||
|
this.pushExtCleanup(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
pushExtCleanup(d: Disposable) {
|
||||||
this.extCtx.subscriptions.push(d);
|
this.extCtx.subscriptions.push(d);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,53 +1,37 @@
|
|||||||
import * as vscode from "vscode";
|
import * as vscode from "vscode";
|
||||||
import * as lc from "vscode-languageclient/node";
|
import * as lc from "vscode-languageclient/node";
|
||||||
import * as os from "os";
|
|
||||||
|
|
||||||
import * as commands from "./commands";
|
import * as commands from "./commands";
|
||||||
import { Ctx } from "./ctx";
|
import { Ctx, Workspace } from "./ctx";
|
||||||
import { Config } from "./config";
|
import { isRustDocument } from "./util";
|
||||||
import { log, isValidExecutable, isRustDocument } from "./util";
|
|
||||||
import { PersistentState } from "./persistent_state";
|
|
||||||
import { activateTaskProvider } from "./tasks";
|
import { activateTaskProvider } from "./tasks";
|
||||||
import { setContextValue } from "./util";
|
import { setContextValue } from "./util";
|
||||||
import { exec } from "child_process";
|
|
||||||
|
|
||||||
let ctx: Ctx | undefined;
|
|
||||||
|
|
||||||
const RUST_PROJECT_CONTEXT_NAME = "inRustProject";
|
const RUST_PROJECT_CONTEXT_NAME = "inRustProject";
|
||||||
|
|
||||||
let TRACE_OUTPUT_CHANNEL: vscode.OutputChannel | null = null;
|
export interface RustAnalyzerExtensionApi {
|
||||||
export function traceOutputChannel() {
|
// FIXME: this should be non-optional
|
||||||
if (!TRACE_OUTPUT_CHANNEL) {
|
readonly client?: lc.LanguageClient;
|
||||||
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 {
|
export async function deactivate() {
|
||||||
client?: lc.LanguageClient;
|
await setContextValue(RUST_PROJECT_CONTEXT_NAME, undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function activate(
|
export async function activate(
|
||||||
context: vscode.ExtensionContext
|
context: vscode.ExtensionContext
|
||||||
): Promise<RustAnalyzerExtensionApi> {
|
): Promise<RustAnalyzerExtensionApi> {
|
||||||
// VS Code doesn't show a notification when an extension fails to activate
|
if (vscode.extensions.getExtension("rust-lang.rust")) {
|
||||||
// so we do it ourselves.
|
vscode.window
|
||||||
return await tryActivate(context).catch((err) => {
|
.showWarningMessage(
|
||||||
void vscode.window.showErrorMessage(`Cannot activate rust-analyzer: ${err.message}`);
|
`You have both the rust-analyzer (rust-lang.rust-analyzer) and Rust (rust-lang.rust) ` +
|
||||||
throw err;
|
"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
|
// We only support local folders, not eg. Live Share (`vlsl:` scheme), so don't activate if
|
||||||
// only those are in use.
|
// only those are in use.
|
||||||
// (r-a still somewhat works with Live Share, because commands are tunneled to the host)
|
// (r-a still somewhat works with Live Share, because commands are tunneled to the host)
|
||||||
@ -65,54 +49,56 @@ async function tryActivate(context: vscode.ExtensionContext): Promise<RustAnalyz
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = new Config(context);
|
const workspace: Workspace =
|
||||||
const state = new PersistentState(context.globalState);
|
folders.length === 0
|
||||||
const serverPath = await bootstrap(context, config, state).catch((err) => {
|
? {
|
||||||
let message = "bootstrap error. ";
|
kind: "Detached Files",
|
||||||
|
files: rustDocuments,
|
||||||
|
}
|
||||||
|
: { kind: "Workspace Folder" };
|
||||||
|
|
||||||
message += 'See the logs in "OUTPUT > Rust Analyzer Client" (should open automatically). ';
|
const ctx = new Ctx(context, workspace);
|
||||||
message += 'To enable verbose logs use { "rust-analyzer.trace.extension": true }';
|
// VS Code doesn't show a notification when an extension fails to activate
|
||||||
|
// so we do it ourselves.
|
||||||
log.error("Bootstrap error", err);
|
const api = await activateServer(ctx).catch((err) => {
|
||||||
throw new Error(message);
|
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) {
|
async function activateServer(ctx: Ctx): Promise<RustAnalyzerExtensionApi> {
|
||||||
ctx = await Ctx.create(config, context, serverPath, {
|
if (ctx.workspace.kind === "Workspace Folder") {
|
||||||
kind: "Detached Files",
|
ctx.pushExtCleanup(activateTaskProvider(ctx.config));
|
||||||
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();
|
await initCommonContext(ctx);
|
||||||
|
|
||||||
if (config.typingContinueCommentsOnNewline) {
|
if (ctx.config.typingContinueCommentsOnNewline) {
|
||||||
ctx.pushCleanup(configureLanguage());
|
ctx.pushExtCleanup(configureLanguage());
|
||||||
}
|
}
|
||||||
|
|
||||||
vscode.workspace.onDidChangeConfiguration(
|
vscode.workspace.onDidChangeConfiguration(
|
||||||
(_) =>
|
async (_) => {
|
||||||
ctx?.client
|
await ctx
|
||||||
?.sendNotification("workspace/didChangeConfiguration", { settings: "" })
|
.clientFetcher()
|
||||||
.catch(log.error),
|
.client?.sendNotification("workspace/didChangeConfiguration", { settings: "" });
|
||||||
|
},
|
||||||
null,
|
null,
|
||||||
ctx.subscriptions
|
ctx.subscriptions
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
await ctx.activate().catch((err) => {
|
||||||
client: ctx.client,
|
void vscode.window.showErrorMessage(`Cannot activate rust-analyzer server: ${err.message}`);
|
||||||
};
|
});
|
||||||
|
|
||||||
|
return ctx.clientFetcher();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function initCommonContext(context: vscode.ExtensionContext, ctx: Ctx) {
|
async function initCommonContext(ctx: Ctx) {
|
||||||
// Register a "dumb" onEnter command for the case where server fails to
|
// Register a "dumb" onEnter command for the case where server fails to
|
||||||
// start.
|
// start.
|
||||||
//
|
//
|
||||||
@ -130,26 +116,23 @@ async function initCommonContext(context: vscode.ExtensionContext, ctx: Ctx) {
|
|||||||
const defaultOnEnter = vscode.commands.registerCommand("rust-analyzer.onEnter", () =>
|
const defaultOnEnter = vscode.commands.registerCommand("rust-analyzer.onEnter", () =>
|
||||||
vscode.commands.executeCommand("default:type", { text: "\n" })
|
vscode.commands.executeCommand("default:type", { text: "\n" })
|
||||||
);
|
);
|
||||||
context.subscriptions.push(defaultOnEnter);
|
ctx.pushExtCleanup(defaultOnEnter);
|
||||||
|
|
||||||
await setContextValue(RUST_PROJECT_CONTEXT_NAME, true);
|
|
||||||
|
|
||||||
// Commands which invokes manually via command palette, shortcut, etc.
|
// 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 () => {
|
ctx.registerCommand("reload", (_) => async () => {
|
||||||
void vscode.window.showInformationMessage("Reloading rust-analyzer...");
|
void vscode.window.showInformationMessage("Reloading rust-analyzer...");
|
||||||
await doDeactivate();
|
// FIXME: We should re-use the client, that is ctx.deactivate() if none of the configs have changed
|
||||||
while (context.subscriptions.length > 0) {
|
await ctx.disposeClient();
|
||||||
try {
|
await ctx.activate();
|
||||||
context.subscriptions.pop()!.dispose();
|
|
||||||
} catch (err) {
|
|
||||||
log.error("Dispose error:", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await activate(context).catch(log.error);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ctx.registerCommand("startServer", (_) => async () => {
|
||||||
|
await ctx.activate();
|
||||||
|
});
|
||||||
|
ctx.registerCommand("stopServer", (_) => async () => {
|
||||||
|
// FIXME: We should re-use the client, that is ctx.deactivate() if none of the configs have changed
|
||||||
|
await ctx.disposeClient();
|
||||||
|
});
|
||||||
ctx.registerCommand("analyzerStatus", commands.analyzerStatus);
|
ctx.registerCommand("analyzerStatus", commands.analyzerStatus);
|
||||||
ctx.registerCommand("memoryUsage", commands.memoryUsage);
|
ctx.registerCommand("memoryUsage", commands.memoryUsage);
|
||||||
ctx.registerCommand("shuffleCrateGraph", commands.shuffleCrateGraph);
|
ctx.registerCommand("shuffleCrateGraph", commands.shuffleCrateGraph);
|
||||||
@ -175,9 +158,6 @@ async function initCommonContext(context: vscode.ExtensionContext, ctx: Ctx) {
|
|||||||
ctx.registerCommand("moveItemDown", commands.moveItemDown);
|
ctx.registerCommand("moveItemDown", commands.moveItemDown);
|
||||||
ctx.registerCommand("cancelFlycheck", commands.cancelFlycheck);
|
ctx.registerCommand("cancelFlycheck", commands.cancelFlycheck);
|
||||||
|
|
||||||
defaultOnEnter.dispose();
|
|
||||||
ctx.registerCommand("onEnter", commands.onEnter);
|
|
||||||
|
|
||||||
ctx.registerCommand("ssr", commands.ssr);
|
ctx.registerCommand("ssr", commands.ssr);
|
||||||
ctx.registerCommand("serverVersion", commands.serverVersion);
|
ctx.registerCommand("serverVersion", commands.serverVersion);
|
||||||
|
|
||||||
@ -191,176 +171,9 @@ async function initCommonContext(context: vscode.ExtensionContext, ctx: Ctx) {
|
|||||||
ctx.registerCommand("gotoLocation", commands.gotoLocation);
|
ctx.registerCommand("gotoLocation", commands.gotoLocation);
|
||||||
|
|
||||||
ctx.registerCommand("linkToCommand", commands.linkToCommand);
|
ctx.registerCommand("linkToCommand", commands.linkToCommand);
|
||||||
}
|
|
||||||
|
|
||||||
export async function deactivate() {
|
defaultOnEnter.dispose();
|
||||||
TRACE_OUTPUT_CHANNEL?.dispose();
|
ctx.registerCommand("onEnter", commands.onEnter);
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -18,9 +18,9 @@ export async function selectRunnable(
|
|||||||
showButtons: boolean = true
|
showButtons: boolean = true
|
||||||
): Promise<RunnableQuickPick | undefined> {
|
): Promise<RunnableQuickPick | undefined> {
|
||||||
const editor = ctx.activeRustEditor;
|
const editor = ctx.activeRustEditor;
|
||||||
const client = ctx.client;
|
if (!editor) return;
|
||||||
if (!editor || !client) return;
|
|
||||||
|
|
||||||
|
const client = await ctx.getClient();
|
||||||
const textDocument: lc.TextDocumentIdentifier = {
|
const textDocument: lc.TextDocumentIdentifier = {
|
||||||
uri: editor.document.uri.toString(),
|
uri: editor.document.uri.toString(),
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user