diff options
Diffstat (limited to 'editors/code')
-rw-r--r-- | editors/code/package.json | 5 | ||||
-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, 84 insertions, 13 deletions
diff --git a/editors/code/package.json b/editors/code/package.json index 3acc375f6..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", |
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 | } |