diff options
Diffstat (limited to 'editors')
-rw-r--r-- | editors/code/package.json | 23 | ||||
-rw-r--r-- | editors/code/src/commands.ts | 14 | ||||
-rw-r--r-- | editors/code/src/config.ts | 5 | ||||
-rw-r--r-- | editors/code/src/debug.ts | 2 | ||||
-rw-r--r-- | editors/code/src/main.ts | 17 | ||||
-rw-r--r-- | editors/code/src/net.ts | 54 |
6 files changed, 102 insertions, 13 deletions
diff --git a/editors/code/package.json b/editors/code/package.json index e2027970d..e6ceb235f 100644 --- a/editors/code/package.json +++ b/editors/code/package.json | |||
@@ -510,6 +510,11 @@ | |||
510 | "type": "boolean", | 510 | "type": "boolean", |
511 | "default": true | 511 | "default": true |
512 | }, | 512 | }, |
513 | "rust-analyzer.hoverActions.gotoTypeDef": { | ||
514 | "markdownDescription": "Whether to show `Go to Type Definition` action. Only applies when `#rust-analyzer.hoverActions.enable#` is set.", | ||
515 | "type": "boolean", | ||
516 | "default": true | ||
517 | }, | ||
513 | "rust-analyzer.linkedProjects": { | 518 | "rust-analyzer.linkedProjects": { |
514 | "markdownDescription": "Disable project auto-discovery in favor of explicitly specified set of projects. \nElements must be paths pointing to Cargo.toml, rust-project.json, or JSON objects in rust-project.json format", | 519 | "markdownDescription": "Disable project auto-discovery in favor of explicitly specified set of projects. \nElements must be paths pointing to Cargo.toml, rust-project.json, or JSON objects in rust-project.json format", |
515 | "type": "array", | 520 | "type": "array", |
@@ -525,6 +530,24 @@ | |||
525 | "markdownDescription": "Internal config for debugging, disables loading of sysroot crates", | 530 | "markdownDescription": "Internal config for debugging, disables loading of sysroot crates", |
526 | "type": "boolean", | 531 | "type": "boolean", |
527 | "default": true | 532 | "default": true |
533 | }, | ||
534 | "rust-analyzer.diagnostics.warningsAsInfo": { | ||
535 | "type": "array", | ||
536 | "uniqueItems": true, | ||
537 | "items": { | ||
538 | "type": "string" | ||
539 | }, | ||
540 | "description": "List of warnings that should be displayed with info severity.\nThe warnings will be indicated by a blue squiggly underline in code and a blue icon in the problems panel.", | ||
541 | "default": [] | ||
542 | }, | ||
543 | "rust-analyzer.diagnostics.warningsAsHint": { | ||
544 | "type": "array", | ||
545 | "uniqueItems": true, | ||
546 | "items": { | ||
547 | "type": "string" | ||
548 | }, | ||
549 | "description": "List of warnings warnings that should be displayed with hint severity.\nThe warnings will be indicated by faded text or three dots in code and will not show up in te problems panel.", | ||
550 | "default": [] | ||
528 | } | 551 | } |
529 | } | 552 | } |
530 | }, | 553 | }, |
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts index 3e9c3aa0e..48a25495f 100644 --- a/editors/code/src/commands.ts +++ b/editors/code/src/commands.ts | |||
@@ -353,6 +353,20 @@ export function applyActionGroup(_ctx: Ctx): Cmd { | |||
353 | }; | 353 | }; |
354 | } | 354 | } |
355 | 355 | ||
356 | export function gotoLocation(ctx: Ctx): Cmd { | ||
357 | return async (locationLink: lc.LocationLink) => { | ||
358 | const client = ctx.client; | ||
359 | if (client) { | ||
360 | const uri = client.protocol2CodeConverter.asUri(locationLink.targetUri); | ||
361 | let range = client.protocol2CodeConverter.asRange(locationLink.targetSelectionRange); | ||
362 | // collapse the range to a cursor position | ||
363 | range = range.with({ end: range.start }); | ||
364 | |||
365 | await vscode.window.showTextDocument(uri, { selection: range }); | ||
366 | } | ||
367 | }; | ||
368 | } | ||
369 | |||
356 | export function resolveCodeAction(ctx: Ctx): Cmd { | 370 | export function resolveCodeAction(ctx: Ctx): Cmd { |
357 | const client = ctx.client; | 371 | const client = ctx.client; |
358 | return async (params: ra.ResolveCodeActionParams) => { | 372 | return async (params: ra.ResolveCodeActionParams) => { |
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index d8f0037d4..9591d4fe3 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts | |||
@@ -117,7 +117,7 @@ export class Config { | |||
117 | return { | 117 | return { |
118 | engine: this.get<string>("debug.engine"), | 118 | engine: this.get<string>("debug.engine"), |
119 | engineSettings: this.get<object>("debug.engineSettings"), | 119 | engineSettings: this.get<object>("debug.engineSettings"), |
120 | openUpDebugPane: this.get<boolean>("debug.openUpDebugPane"), | 120 | openDebugPane: this.get<boolean>("debug.openDebugPane"), |
121 | sourceFileMap: sourceFileMap | 121 | sourceFileMap: sourceFileMap |
122 | }; | 122 | }; |
123 | } | 123 | } |
@@ -135,6 +135,9 @@ export class Config { | |||
135 | return { | 135 | return { |
136 | enable: this.get<boolean>("hoverActions.enable"), | 136 | enable: this.get<boolean>("hoverActions.enable"), |
137 | implementations: this.get<boolean>("hoverActions.implementations"), | 137 | implementations: this.get<boolean>("hoverActions.implementations"), |
138 | run: this.get<boolean>("hoverActions.run"), | ||
139 | debug: this.get<boolean>("hoverActions.debug"), | ||
140 | gotoTypeDef: this.get<boolean>("hoverActions.gotoTypeDef"), | ||
138 | }; | 141 | }; |
139 | } | 142 | } |
140 | } | 143 | } |
diff --git a/editors/code/src/debug.ts b/editors/code/src/debug.ts index a0c9b3ab2..61c12dbe0 100644 --- a/editors/code/src/debug.ts +++ b/editors/code/src/debug.ts | |||
@@ -82,7 +82,7 @@ async function getDebugConfiguration(ctx: Ctx, runnable: ra.Runnable): Promise<v | |||
82 | } | 82 | } |
83 | 83 | ||
84 | debugOutput.clear(); | 84 | debugOutput.clear(); |
85 | if (ctx.config.debug.openUpDebugPane) { | 85 | if (ctx.config.debug.openDebugPane) { |
86 | debugOutput.show(true); | 86 | debugOutput.show(true); |
87 | } | 87 | } |
88 | 88 | ||
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index a92c676fa..670f2ebfd 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts | |||
@@ -42,7 +42,16 @@ export async function activate(context: vscode.ExtensionContext) { | |||
42 | 42 | ||
43 | const config = new Config(context); | 43 | const config = new Config(context); |
44 | const state = new PersistentState(context.globalState); | 44 | const state = new PersistentState(context.globalState); |
45 | const serverPath = await bootstrap(config, state); | 45 | const serverPath = await bootstrap(config, state).catch(err => { |
46 | let message = "Failed to bootstrap rust-analyzer."; | ||
47 | if (err.code === "EBUSY" || err.code === "ETXTBSY") { | ||
48 | message += " Other vscode windows might be using rust-analyzer, " + | ||
49 | "you should close them and reload this window to retry."; | ||
50 | } | ||
51 | message += " Open \"Help > Toggle Developer Tools > Console\" to see the logs"; | ||
52 | log.error("Bootstrap error", err); | ||
53 | throw new Error(message); | ||
54 | }); | ||
46 | 55 | ||
47 | const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; | 56 | const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; |
48 | if (workspaceFolder === undefined) { | 57 | if (workspaceFolder === undefined) { |
@@ -100,6 +109,7 @@ export async function activate(context: vscode.ExtensionContext) { | |||
100 | ctx.registerCommand('applySnippetWorkspaceEdit', commands.applySnippetWorkspaceEditCommand); | 109 | ctx.registerCommand('applySnippetWorkspaceEdit', commands.applySnippetWorkspaceEditCommand); |
101 | ctx.registerCommand('resolveCodeAction', commands.resolveCodeAction); | 110 | ctx.registerCommand('resolveCodeAction', commands.resolveCodeAction); |
102 | ctx.registerCommand('applyActionGroup', commands.applyActionGroup); | 111 | ctx.registerCommand('applyActionGroup', commands.applyActionGroup); |
112 | ctx.registerCommand('gotoLocation', commands.gotoLocation); | ||
103 | 113 | ||
104 | ctx.pushCleanup(activateTaskProvider(workspaceFolder)); | 114 | ctx.pushCleanup(activateTaskProvider(workspaceFolder)); |
105 | 115 | ||
@@ -284,6 +294,11 @@ async function getServer(config: Config, state: PersistentState): Promise<string | |||
284 | const artifact = release.assets.find(artifact => artifact.name === binaryName); | 294 | const artifact = release.assets.find(artifact => artifact.name === binaryName); |
285 | assert(!!artifact, `Bad release: ${JSON.stringify(release)}`); | 295 | assert(!!artifact, `Bad release: ${JSON.stringify(release)}`); |
286 | 296 | ||
297 | // Unlinking the exe file before moving new one on its place should prevent ETXTBSY error. | ||
298 | await fs.unlink(dest).catch(err => { | ||
299 | if (err.code !== "ENOENT") throw err; | ||
300 | }); | ||
301 | |||
287 | await download(artifact.browser_download_url, dest, "Downloading rust-analyzer server", { mode: 0o755 }); | 302 | await download(artifact.browser_download_url, dest, "Downloading rust-analyzer server", { mode: 0o755 }); |
288 | 303 | ||
289 | // Patching executable if that's NixOS. | 304 | // Patching executable if that's NixOS. |
diff --git a/editors/code/src/net.ts b/editors/code/src/net.ts index 492213937..0e7dd29c2 100644 --- a/editors/code/src/net.ts +++ b/editors/code/src/net.ts | |||
@@ -1,7 +1,9 @@ | |||
1 | import fetch from "node-fetch"; | 1 | import fetch from "node-fetch"; |
2 | import * as vscode from "vscode"; | 2 | import * as vscode from "vscode"; |
3 | import * as fs from "fs"; | ||
4 | import * as stream from "stream"; | 3 | import * as stream from "stream"; |
4 | import * as fs from "fs"; | ||
5 | import * as os from "os"; | ||
6 | import * as path from "path"; | ||
5 | import * as util from "util"; | 7 | import * as util from "util"; |
6 | import { log, assert } from "./util"; | 8 | import { log, assert } from "./util"; |
7 | 9 | ||
@@ -87,7 +89,7 @@ export async function download( | |||
87 | } | 89 | } |
88 | 90 | ||
89 | /** | 91 | /** |
90 | * Downloads file from `url` and stores it at `destFilePath` with `destFilePermissions`. | 92 | * Downloads file from `url` and stores it at `destFilePath` with `mode` (unix permissions). |
91 | * `onProgress` callback is called on recieveing each chunk of bytes | 93 | * `onProgress` callback is called on recieveing each chunk of bytes |
92 | * to track the progress of downloading, it gets the already read and total | 94 | * to track the progress of downloading, it gets the already read and total |
93 | * amount of bytes to read as its parameters. | 95 | * amount of bytes to read as its parameters. |
@@ -118,14 +120,46 @@ async function downloadFile( | |||
118 | onProgress(readBytes, totalBytes); | 120 | onProgress(readBytes, totalBytes); |
119 | }); | 121 | }); |
120 | 122 | ||
121 | const destFileStream = fs.createWriteStream(destFilePath, { mode }); | 123 | // Put the artifact into a temporary folder to prevent partially downloaded files when user kills vscode |
124 | await withTempFile(async tempFilePath => { | ||
125 | const destFileStream = fs.createWriteStream(tempFilePath, { mode }); | ||
126 | await pipeline(res.body, destFileStream); | ||
127 | await new Promise<void>(resolve => { | ||
128 | destFileStream.on("close", resolve); | ||
129 | destFileStream.destroy(); | ||
130 | // This workaround is awaiting to be removed when vscode moves to newer nodejs version: | ||
131 | // https://github.com/rust-analyzer/rust-analyzer/issues/3167 | ||
132 | }); | ||
133 | await moveFile(tempFilePath, destFilePath); | ||
134 | }); | ||
135 | } | ||
122 | 136 | ||
123 | await pipeline(res.body, destFileStream); | 137 | async function withTempFile(scope: (tempFilePath: string) => Promise<void>) { |
124 | return new Promise<void>(resolve => { | 138 | // Based on the great article: https://advancedweb.hu/secure-tempfiles-in-nodejs-without-dependencies/ |
125 | destFileStream.on("close", resolve); | ||
126 | destFileStream.destroy(); | ||
127 | 139 | ||
128 | // Details on workaround: https://github.com/rust-analyzer/rust-analyzer/pull/3092#discussion_r378191131 | 140 | // `.realpath()` should handle the cases where os.tmpdir() contains symlinks |
129 | // Issue at nodejs repo: https://github.com/nodejs/node/issues/31776 | 141 | const osTempDir = await fs.promises.realpath(os.tmpdir()); |
130 | }); | 142 | |
143 | const tempDir = await fs.promises.mkdtemp(path.join(osTempDir, "rust-analyzer")); | ||
144 | |||
145 | try { | ||
146 | return await scope(path.join(tempDir, "file")); | ||
147 | } finally { | ||
148 | // We are good citizens :D | ||
149 | void fs.promises.rmdir(tempDir, { recursive: true }).catch(log.error); | ||
150 | } | ||
151 | }; | ||
152 | |||
153 | async function moveFile(src: fs.PathLike, dest: fs.PathLike) { | ||
154 | try { | ||
155 | await fs.promises.rename(src, dest); | ||
156 | } catch (err) { | ||
157 | if (err.code === 'EXDEV') { | ||
158 | // We are probably moving the file across partitions/devices | ||
159 | await fs.promises.copyFile(src, dest); | ||
160 | await fs.promises.unlink(src); | ||
161 | } else { | ||
162 | log.error(`Failed to rename the file ${src} -> ${dest}`, err); | ||
163 | } | ||
164 | } | ||
131 | } | 165 | } |