aboutsummaryrefslogtreecommitdiff
path: root/editors/code/src
diff options
context:
space:
mode:
Diffstat (limited to 'editors/code/src')
-rw-r--r--editors/code/src/commands.ts32
-rw-r--r--editors/code/src/config.ts8
-rw-r--r--editors/code/src/lsp_ext.ts1
-rw-r--r--editors/code/src/main.ts26
-rw-r--r--editors/code/src/net.ts21
-rw-r--r--editors/code/src/persistent_state.ts2
-rw-r--r--editors/code/src/util.ts45
7 files changed, 100 insertions, 35 deletions
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts
index 19a9c2a0d..1f3a7cf7e 100644
--- a/editors/code/src/commands.ts
+++ b/editors/code/src/commands.ts
@@ -55,6 +55,38 @@ export function analyzerStatus(ctx: Ctx): Cmd {
55 }; 55 };
56} 56}
57 57
58export function memoryUsage(ctx: Ctx): Cmd {
59 const tdcp = new class implements vscode.TextDocumentContentProvider {
60 readonly uri = vscode.Uri.parse('rust-analyzer-memory://memory');
61 readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>();
62
63 provideTextDocumentContent(_uri: vscode.Uri): vscode.ProviderResult<string> {
64 if (!vscode.window.activeTextEditor) return '';
65
66 return ctx.client.sendRequest(ra.memoryUsage, null).then((mem) => {
67 return 'Per-query memory usage:\n' + mem + '\n(note: database has been cleared)';
68 });
69 }
70
71 get onDidChange(): vscode.Event<vscode.Uri> {
72 return this.eventEmitter.event;
73 }
74 }();
75
76 ctx.pushCleanup(
77 vscode.workspace.registerTextDocumentContentProvider(
78 'rust-analyzer-memory',
79 tdcp,
80 ),
81 );
82
83 return async () => {
84 tdcp.eventEmitter.fire(tdcp.uri);
85 const document = await vscode.workspace.openTextDocument(tdcp.uri);
86 return vscode.window.showTextDocument(document, vscode.ViewColumn.Two, true);
87 };
88}
89
58export function matchingBrace(ctx: Ctx): Cmd { 90export function matchingBrace(ctx: Ctx): Cmd {
59 return async () => { 91 return async () => {
60 const editor = ctx.activeRustEditor; 92 const editor = ctx.activeRustEditor;
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts
index 23975c726..033b04b60 100644
--- a/editors/code/src/config.ts
+++ b/editors/code/src/config.ts
@@ -39,10 +39,10 @@ export class Config {
39 39
40 private refreshLogging() { 40 private refreshLogging() {
41 log.setEnabled(this.traceExtension); 41 log.setEnabled(this.traceExtension);
42 log.debug( 42 log.info("Extension version:", this.package.version);
43 "Extension version:", this.package.version, 43
44 "using configuration:", this.cfg 44 const cfg = Object.entries(this.cfg).filter(([_, val]) => !(val instanceof Function));
45 ); 45 log.info("Using configuration", Object.fromEntries(cfg));
46 } 46 }
47 47
48 private async onDidChangeConfiguration(event: vscode.ConfigurationChangeEvent) { 48 private async onDidChangeConfiguration(event: vscode.ConfigurationChangeEvent) {
diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts
index bf4703239..5f32cb40e 100644
--- a/editors/code/src/lsp_ext.ts
+++ b/editors/code/src/lsp_ext.ts
@@ -5,6 +5,7 @@
5import * as lc from "vscode-languageclient"; 5import * as lc from "vscode-languageclient";
6 6
7export const analyzerStatus = new lc.RequestType<null, string, void>("rust-analyzer/analyzerStatus"); 7export const analyzerStatus = new lc.RequestType<null, string, void>("rust-analyzer/analyzerStatus");
8export const memoryUsage = new lc.RequestType<null, string, void>("rust-analyzer/memoryUsage");
8 9
9export type Status = "loading" | "ready" | "invalid" | "needsReload"; 10export type Status = "loading" | "ready" | "invalid" | "needsReload";
10export const status = new lc.NotificationType<Status>("rust-analyzer/status"); 11export const status = new lc.NotificationType<Status>("rust-analyzer/status");
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts
index a1521a93b..bd99d696a 100644
--- a/editors/code/src/main.ts
+++ b/editors/code/src/main.ts
@@ -54,13 +54,13 @@ async function tryActivate(context: vscode.ExtensionContext) {
54 const serverPath = await bootstrap(config, state).catch(err => { 54 const serverPath = await bootstrap(config, state).catch(err => {
55 let message = "bootstrap error. "; 55 let message = "bootstrap error. ";
56 56
57 if (err.code === "EBUSY" || err.code === "ETXTBSY") { 57 if (err.code === "EBUSY" || err.code === "ETXTBSY" || err.code === "EPERM") {
58 message += "Other vscode windows might be using rust-analyzer, "; 58 message += "Other vscode windows might be using rust-analyzer, ";
59 message += "you should close them and reload this window to retry. "; 59 message += "you should close them and reload this window to retry. ";
60 } 60 }
61 61
62 message += 'Open "Help > Toggle Developer Tools > Console" to see the logs '; 62 message += 'See the logs in "OUTPUT > Rust Analyzer Client" (should open automatically). ';
63 message += '(enable verbose logs with "rust-analyzer.trace.extension")'; 63 message += 'To enable verbose logs use { "rust-analyzer.trace.extension": true }';
64 64
65 log.error("Bootstrap error", err); 65 log.error("Bootstrap error", err);
66 throw new Error(message); 66 throw new Error(message);
@@ -96,6 +96,7 @@ async function tryActivate(context: vscode.ExtensionContext) {
96 }); 96 });
97 97
98 ctx.registerCommand('analyzerStatus', commands.analyzerStatus); 98 ctx.registerCommand('analyzerStatus', commands.analyzerStatus);
99 ctx.registerCommand('memoryUsage', commands.memoryUsage);
99 ctx.registerCommand('reloadWorkspace', commands.reloadWorkspace); 100 ctx.registerCommand('reloadWorkspace', commands.reloadWorkspace);
100 ctx.registerCommand('matchingBrace', commands.matchingBrace); 101 ctx.registerCommand('matchingBrace', commands.matchingBrace);
101 ctx.registerCommand('joinLines', commands.joinLines); 102 ctx.registerCommand('joinLines', commands.joinLines);
@@ -214,7 +215,7 @@ async function bootstrapServer(config: Config, state: PersistentState): Promise<
214 ); 215 );
215 } 216 }
216 217
217 log.debug("Using server binary at", path); 218 log.info("Using server binary at", path);
218 219
219 if (!isValidExecutable(path)) { 220 if (!isValidExecutable(path)) {
220 throw new Error(`Failed to execute ${path} --version`); 221 throw new Error(`Failed to execute ${path} --version`);
@@ -273,13 +274,13 @@ async function getServer(config: Config, state: PersistentState): Promise<string
273 }; 274 };
274 if (config.package.releaseTag === null) return "rust-analyzer"; 275 if (config.package.releaseTag === null) return "rust-analyzer";
275 276
276 let binaryName: string | undefined = undefined; 277 let platform: string | undefined;
277 if (process.arch === "x64" || process.arch === "ia32") { 278 if (process.arch === "x64" || process.arch === "ia32") {
278 if (process.platform === "linux") binaryName = "rust-analyzer-linux"; 279 if (process.platform === "linux") platform = "linux";
279 if (process.platform === "darwin") binaryName = "rust-analyzer-mac"; 280 if (process.platform === "darwin") platform = "mac";
280 if (process.platform === "win32") binaryName = "rust-analyzer-windows.exe"; 281 if (process.platform === "win32") platform = "windows";
281 } 282 }
282 if (binaryName === undefined) { 283 if (platform === undefined) {
283 vscode.window.showErrorMessage( 284 vscode.window.showErrorMessage(
284 "Unfortunately we don't ship binaries for your platform yet. " + 285 "Unfortunately we don't ship binaries for your platform yet. " +
285 "You need to manually clone rust-analyzer repository and " + 286 "You need to manually clone rust-analyzer repository and " +
@@ -290,8 +291,8 @@ async function getServer(config: Config, state: PersistentState): Promise<string
290 ); 291 );
291 return undefined; 292 return undefined;
292 } 293 }
293 294 const ext = platform === "windows" ? ".exe" : "";
294 const dest = path.join(config.globalStoragePath, binaryName); 295 const dest = path.join(config.globalStoragePath, `rust-analyzer-${platform}${ext}`);
295 const exists = await fs.stat(dest).then(() => true, () => false); 296 const exists = await fs.stat(dest).then(() => true, () => false);
296 if (!exists) { 297 if (!exists) {
297 await state.updateServerVersion(undefined); 298 await state.updateServerVersion(undefined);
@@ -308,7 +309,7 @@ async function getServer(config: Config, state: PersistentState): Promise<string
308 } 309 }
309 310
310 const release = await fetchRelease(config.package.releaseTag); 311 const release = await fetchRelease(config.package.releaseTag);
311 const artifact = release.assets.find(artifact => artifact.name === binaryName); 312 const artifact = release.assets.find(artifact => artifact.name === `rust-analyzer-${platform}.gz`);
312 assert(!!artifact, `Bad release: ${JSON.stringify(release)}`); 313 assert(!!artifact, `Bad release: ${JSON.stringify(release)}`);
313 314
314 // Unlinking the exe file before moving new one on its place should prevent ETXTBSY error. 315 // Unlinking the exe file before moving new one on its place should prevent ETXTBSY error.
@@ -320,6 +321,7 @@ async function getServer(config: Config, state: PersistentState): Promise<string
320 url: artifact.browser_download_url, 321 url: artifact.browser_download_url,
321 dest, 322 dest,
322 progressTitle: "Downloading rust-analyzer server", 323 progressTitle: "Downloading rust-analyzer server",
324 gunzip: true,
323 mode: 0o755 325 mode: 0o755
324 }); 326 });
325 327
diff --git a/editors/code/src/net.ts b/editors/code/src/net.ts
index 866092882..681eaa9c9 100644
--- a/editors/code/src/net.ts
+++ b/editors/code/src/net.ts
@@ -1,8 +1,12 @@
1import fetch from "node-fetch"; 1// Replace with `import fetch from "node-fetch"` once this is fixed in rollup:
2// https://github.com/rollup/plugins/issues/491
3const fetch = require("node-fetch") as typeof import("node-fetch")["default"];
4
2import * as vscode from "vscode"; 5import * as vscode from "vscode";
3import * as stream from "stream"; 6import * as stream from "stream";
4import * as crypto from "crypto"; 7import * as crypto from "crypto";
5import * as fs from "fs"; 8import * as fs from "fs";
9import * as zlib from "zlib";
6import * as util from "util"; 10import * as util from "util";
7import * as path from "path"; 11import * as path from "path";
8import { log, assert } from "./util"; 12import { log, assert } from "./util";
@@ -65,6 +69,7 @@ interface DownloadOpts {
65 url: string; 69 url: string;
66 dest: string; 70 dest: string;
67 mode?: number; 71 mode?: number;
72 gunzip?: boolean;
68} 73}
69 74
70export async function download(opts: DownloadOpts) { 75export async function download(opts: DownloadOpts) {
@@ -82,7 +87,7 @@ export async function download(opts: DownloadOpts) {
82 }, 87 },
83 async (progress, _cancellationToken) => { 88 async (progress, _cancellationToken) => {
84 let lastPercentage = 0; 89 let lastPercentage = 0;
85 await downloadFile(opts.url, tempFile, opts.mode, (readBytes, totalBytes) => { 90 await downloadFile(opts.url, tempFile, opts.mode, !!opts.gunzip, (readBytes, totalBytes) => {
86 const newPercentage = (readBytes / totalBytes) * 100; 91 const newPercentage = (readBytes / totalBytes) * 100;
87 progress.report({ 92 progress.report({
88 message: newPercentage.toFixed(0) + "%", 93 message: newPercentage.toFixed(0) + "%",
@@ -97,16 +102,11 @@ export async function download(opts: DownloadOpts) {
97 await fs.promises.rename(tempFile, opts.dest); 102 await fs.promises.rename(tempFile, opts.dest);
98} 103}
99 104
100/**
101 * Downloads file from `url` and stores it at `destFilePath` with `mode` (unix permissions).
102 * `onProgress` callback is called on recieveing each chunk of bytes
103 * to track the progress of downloading, it gets the already read and total
104 * amount of bytes to read as its parameters.
105 */
106async function downloadFile( 105async function downloadFile(
107 url: string, 106 url: string,
108 destFilePath: fs.PathLike, 107 destFilePath: fs.PathLike,
109 mode: number | undefined, 108 mode: number | undefined,
109 gunzip: boolean,
110 onProgress: (readBytes: number, totalBytes: number) => void 110 onProgress: (readBytes: number, totalBytes: number) => void
111): Promise<void> { 111): Promise<void> {
112 const res = await fetch(url); 112 const res = await fetch(url);
@@ -130,7 +130,10 @@ async function downloadFile(
130 }); 130 });
131 131
132 const destFileStream = fs.createWriteStream(destFilePath, { mode }); 132 const destFileStream = fs.createWriteStream(destFilePath, { mode });
133 await pipeline(res.body, destFileStream); 133 const srcStream = gunzip ? res.body.pipe(zlib.createGunzip()) : res.body;
134
135 await pipeline(srcStream, destFileStream);
136
134 await new Promise<void>(resolve => { 137 await new Promise<void>(resolve => {
135 destFileStream.on("close", resolve); 138 destFileStream.on("close", resolve);
136 destFileStream.destroy(); 139 destFileStream.destroy();
diff --git a/editors/code/src/persistent_state.ts b/editors/code/src/persistent_state.ts
index 138d11b89..5705eed81 100644
--- a/editors/code/src/persistent_state.ts
+++ b/editors/code/src/persistent_state.ts
@@ -4,7 +4,7 @@ import { log } from './util';
4export class PersistentState { 4export class PersistentState {
5 constructor(private readonly globalState: vscode.Memento) { 5 constructor(private readonly globalState: vscode.Memento) {
6 const { lastCheck, releaseId, serverVersion } = this; 6 const { lastCheck, releaseId, serverVersion } = this;
7 log.debug("PersistentState: ", { lastCheck, releaseId, serverVersion }); 7 log.info("PersistentState:", { lastCheck, releaseId, serverVersion });
8 } 8 }
9 9
10 /** 10 /**
diff --git a/editors/code/src/util.ts b/editors/code/src/util.ts
index fec4c3295..970fedb37 100644
--- a/editors/code/src/util.ts
+++ b/editors/code/src/util.ts
@@ -2,6 +2,7 @@ import * as lc from "vscode-languageclient";
2import * as vscode from "vscode"; 2import * as vscode from "vscode";
3import { strict as nativeAssert } from "assert"; 3import { strict as nativeAssert } from "assert";
4import { spawnSync } from "child_process"; 4import { spawnSync } from "child_process";
5import { inspect } from "util";
5 6
6export function assert(condition: boolean, explanation: string): asserts condition { 7export function assert(condition: boolean, explanation: string): asserts condition {
7 try { 8 try {
@@ -14,21 +15,46 @@ export function assert(condition: boolean, explanation: string): asserts conditi
14 15
15export const log = new class { 16export const log = new class {
16 private enabled = true; 17 private enabled = true;
18 private readonly output = vscode.window.createOutputChannel("Rust Analyzer Client");
17 19
18 setEnabled(yes: boolean): void { 20 setEnabled(yes: boolean): void {
19 log.enabled = yes; 21 log.enabled = yes;
20 } 22 }
21 23
22 debug(message?: any, ...optionalParams: any[]): void { 24 // Hint: the type [T, ...T[]] means a non-empty array
25 debug(...msg: [unknown, ...unknown[]]): void {
23 if (!log.enabled) return; 26 if (!log.enabled) return;
24 // eslint-disable-next-line no-console 27 log.write("DEBUG", ...msg);
25 console.log(message, ...optionalParams); 28 log.output.toString();
26 } 29 }
27 30
28 error(message?: any, ...optionalParams: any[]): void { 31 info(...msg: [unknown, ...unknown[]]): void {
32 log.write("INFO", ...msg);
33 }
34
35 warn(...msg: [unknown, ...unknown[]]): void {
36 debugger;
37 log.write("WARN", ...msg);
38 }
39
40 error(...msg: [unknown, ...unknown[]]): void {
29 debugger; 41 debugger;
30 // eslint-disable-next-line no-console 42 log.write("ERROR", ...msg);
31 console.error(message, ...optionalParams); 43 log.output.show(true);
44 }
45
46 private write(label: string, ...messageParts: unknown[]): void {
47 const message = messageParts.map(log.stringify).join(" ");
48 const dateTime = new Date().toLocaleString();
49 log.output.appendLine(`${label} [${dateTime}]: ${message}`);
50 }
51
52 private stringify(val: unknown): string {
53 if (typeof val === "string") return val;
54 return inspect(val, {
55 colors: false,
56 depth: 6, // heuristic
57 });
32 } 58 }
33}; 59};
34 60
@@ -46,7 +72,7 @@ export async function sendRequestWithRetry<TParam, TRet>(
46 ); 72 );
47 } catch (error) { 73 } catch (error) {
48 if (delay === null) { 74 if (delay === null) {
49 log.error("LSP request timed out", { method: reqType.method, param, error }); 75 log.warn("LSP request timed out", { method: reqType.method, param, error });
50 throw error; 76 throw error;
51 } 77 }
52 78
@@ -55,7 +81,7 @@ export async function sendRequestWithRetry<TParam, TRet>(
55 } 81 }
56 82
57 if (error.code !== lc.ErrorCodes.ContentModified) { 83 if (error.code !== lc.ErrorCodes.ContentModified) {
58 log.error("LSP request failed", { method: reqType.method, param, error }); 84 log.warn("LSP request failed", { method: reqType.method, param, error });
59 throw error; 85 throw error;
60 } 86 }
61 87
@@ -89,7 +115,8 @@ export function isValidExecutable(path: string): boolean {
89 115
90 const res = spawnSync(path, ["--version"], { encoding: 'utf8' }); 116 const res = spawnSync(path, ["--version"], { encoding: 'utf8' });
91 117
92 log.debug(res, "--version output:", res.output); 118 const printOutput = res.error && (res.error as any).code !== 'ENOENT' ? log.warn : log.debug;
119 printOutput(path, "--version:", res);
93 120
94 return res.status === 0; 121 return res.status === 0;
95} 122}