diff options
Diffstat (limited to 'editors/code/src')
-rw-r--r-- | editors/code/src/commands.ts | 32 | ||||
-rw-r--r-- | editors/code/src/config.ts | 8 | ||||
-rw-r--r-- | editors/code/src/lsp_ext.ts | 1 | ||||
-rw-r--r-- | editors/code/src/main.ts | 26 | ||||
-rw-r--r-- | editors/code/src/net.ts | 21 | ||||
-rw-r--r-- | editors/code/src/persistent_state.ts | 2 | ||||
-rw-r--r-- | editors/code/src/util.ts | 45 |
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 | ||
58 | export 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 | |||
58 | export function matchingBrace(ctx: Ctx): Cmd { | 90 | export 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 @@ | |||
5 | import * as lc from "vscode-languageclient"; | 5 | import * as lc from "vscode-languageclient"; |
6 | 6 | ||
7 | export const analyzerStatus = new lc.RequestType<null, string, void>("rust-analyzer/analyzerStatus"); | 7 | export const analyzerStatus = new lc.RequestType<null, string, void>("rust-analyzer/analyzerStatus"); |
8 | export const memoryUsage = new lc.RequestType<null, string, void>("rust-analyzer/memoryUsage"); | ||
8 | 9 | ||
9 | export type Status = "loading" | "ready" | "invalid" | "needsReload"; | 10 | export type Status = "loading" | "ready" | "invalid" | "needsReload"; |
10 | export const status = new lc.NotificationType<Status>("rust-analyzer/status"); | 11 | export 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 @@ | |||
1 | import 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 | ||
3 | const fetch = require("node-fetch") as typeof import("node-fetch")["default"]; | ||
4 | |||
2 | import * as vscode from "vscode"; | 5 | import * as vscode from "vscode"; |
3 | import * as stream from "stream"; | 6 | import * as stream from "stream"; |
4 | import * as crypto from "crypto"; | 7 | import * as crypto from "crypto"; |
5 | import * as fs from "fs"; | 8 | import * as fs from "fs"; |
9 | import * as zlib from "zlib"; | ||
6 | import * as util from "util"; | 10 | import * as util from "util"; |
7 | import * as path from "path"; | 11 | import * as path from "path"; |
8 | import { log, assert } from "./util"; | 12 | import { 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 | ||
70 | export async function download(opts: DownloadOpts) { | 75 | export 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 | */ | ||
106 | async function downloadFile( | 105 | async 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'; | |||
4 | export class PersistentState { | 4 | export 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"; | |||
2 | import * as vscode from "vscode"; | 2 | import * as vscode from "vscode"; |
3 | import { strict as nativeAssert } from "assert"; | 3 | import { strict as nativeAssert } from "assert"; |
4 | import { spawnSync } from "child_process"; | 4 | import { spawnSync } from "child_process"; |
5 | import { inspect } from "util"; | ||
5 | 6 | ||
6 | export function assert(condition: boolean, explanation: string): asserts condition { | 7 | export 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 | ||
15 | export const log = new class { | 16 | export 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 | } |