aboutsummaryrefslogtreecommitdiff
path: root/editors/code
diff options
context:
space:
mode:
Diffstat (limited to 'editors/code')
-rw-r--r--editors/code/package.json5
-rw-r--r--editors/code/src/commands.ts14
-rw-r--r--editors/code/src/config.ts5
-rw-r--r--editors/code/src/debug.ts2
-rw-r--r--editors/code/src/main.ts17
-rw-r--r--editors/code/src/net.ts54
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
356export 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
356export function resolveCodeAction(ctx: Ctx): Cmd { 370export 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 @@
1import fetch from "node-fetch"; 1import fetch from "node-fetch";
2import * as vscode from "vscode"; 2import * as vscode from "vscode";
3import * as fs from "fs";
4import * as stream from "stream"; 3import * as stream from "stream";
4import * as fs from "fs";
5import * as os from "os";
6import * as path from "path";
5import * as util from "util"; 7import * as util from "util";
6import { log, assert } from "./util"; 8import { 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); 137async 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
153async 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}