aboutsummaryrefslogtreecommitdiff
path: root/editors
diff options
context:
space:
mode:
Diffstat (limited to 'editors')
-rw-r--r--editors/code/package.json23
-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, 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
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}