aboutsummaryrefslogtreecommitdiff
path: root/editors/code/src
diff options
context:
space:
mode:
Diffstat (limited to 'editors/code/src')
-rw-r--r--editors/code/src/main.ts3
-rw-r--r--editors/code/src/net.ts84
-rw-r--r--editors/code/src/status_display.ts100
3 files changed, 27 insertions, 160 deletions
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts
index 5b4f453c8..5ceab8b44 100644
--- a/editors/code/src/main.ts
+++ b/editors/code/src/main.ts
@@ -5,7 +5,6 @@ import { promises as fs, PathLike } from "fs";
5 5
6import * as commands from './commands'; 6import * as commands from './commands';
7import { activateInlayHints } from './inlay_hints'; 7import { activateInlayHints } from './inlay_hints';
8import { activateStatusDisplay } from './status_display';
9import { Ctx } from './ctx'; 8import { Ctx } from './ctx';
10import { Config, NIGHTLY_TAG } from './config'; 9import { Config, NIGHTLY_TAG } from './config';
11import { log, assert, isValidExecutable } from './util'; 10import { log, assert, isValidExecutable } from './util';
@@ -117,8 +116,6 @@ export async function activate(context: vscode.ExtensionContext) {
117 116
118 ctx.pushCleanup(activateTaskProvider(workspaceFolder, ctx.config)); 117 ctx.pushCleanup(activateTaskProvider(workspaceFolder, ctx.config));
119 118
120 activateStatusDisplay(ctx);
121
122 activateInlayHints(ctx); 119 activateInlayHints(ctx);
123 120
124 vscode.workspace.onDidChangeConfiguration( 121 vscode.workspace.onDidChangeConfiguration(
diff --git a/editors/code/src/net.ts b/editors/code/src/net.ts
index e02fd6d4f..866092882 100644
--- a/editors/code/src/net.ts
+++ b/editors/code/src/net.ts
@@ -1,10 +1,10 @@
1import fetch from "node-fetch"; 1import fetch from "node-fetch";
2import * as vscode from "vscode"; 2import * as vscode from "vscode";
3import * as stream from "stream"; 3import * as stream from "stream";
4import * as crypto from "crypto";
4import * as fs from "fs"; 5import * as fs from "fs";
5import * as os from "os";
6import * as path from "path";
7import * as util from "util"; 6import * as util from "util";
7import * as path from "path";
8import { log, assert } from "./util"; 8import { log, assert } from "./util";
9 9
10const pipeline = util.promisify(stream.pipeline); 10const pipeline = util.promisify(stream.pipeline);
@@ -68,32 +68,33 @@ interface DownloadOpts {
68} 68}
69 69
70export async function download(opts: DownloadOpts) { 70export async function download(opts: DownloadOpts) {
71 // Put the artifact into a temporary folder to prevent partially downloaded files when user kills vscode 71 // Put artifact into a temporary file (in the same dir for simplicity)
72 await withTempDir(async tempDir => { 72 // to prevent partially downloaded files when user kills vscode
73 const tempFile = path.join(tempDir, path.basename(opts.dest)); 73 const dest = path.parse(opts.dest);
74 74 const randomHex = crypto.randomBytes(5).toString("hex");
75 await vscode.window.withProgress( 75 const tempFile = path.join(dest.dir, `${dest.name}${randomHex}`);
76 { 76
77 location: vscode.ProgressLocation.Notification, 77 await vscode.window.withProgress(
78 cancellable: false, 78 {
79 title: opts.progressTitle 79 location: vscode.ProgressLocation.Notification,
80 }, 80 cancellable: false,
81 async (progress, _cancellationToken) => { 81 title: opts.progressTitle
82 let lastPercentage = 0; 82 },
83 await downloadFile(opts.url, tempFile, opts.mode, (readBytes, totalBytes) => { 83 async (progress, _cancellationToken) => {
84 const newPercentage = (readBytes / totalBytes) * 100; 84 let lastPercentage = 0;
85 progress.report({ 85 await downloadFile(opts.url, tempFile, opts.mode, (readBytes, totalBytes) => {
86 message: newPercentage.toFixed(0) + "%", 86 const newPercentage = (readBytes / totalBytes) * 100;
87 increment: newPercentage - lastPercentage 87 progress.report({
88 }); 88 message: newPercentage.toFixed(0) + "%",
89 89 increment: newPercentage - lastPercentage
90 lastPercentage = newPercentage;
91 }); 90 });
92 }
93 );
94 91
95 await moveFile(tempFile, opts.dest); 92 lastPercentage = newPercentage;
96 }); 93 });
94 }
95 );
96
97 await fs.promises.rename(tempFile, opts.dest);
97} 98}
98 99
99/** 100/**
@@ -137,34 +138,3 @@ async function downloadFile(
137 // https://github.com/rust-analyzer/rust-analyzer/issues/3167 138 // https://github.com/rust-analyzer/rust-analyzer/issues/3167
138 }); 139 });
139} 140}
140
141async function withTempDir(scope: (tempDirPath: string) => Promise<void>) {
142 // Based on the great article: https://advancedweb.hu/secure-tempfiles-in-nodejs-without-dependencies/
143
144 // `.realpath()` should handle the cases where os.tmpdir() contains symlinks
145 const osTempDir = await fs.promises.realpath(os.tmpdir());
146
147 const tempDir = await fs.promises.mkdtemp(path.join(osTempDir, "rust-analyzer"));
148
149 try {
150 return await scope(tempDir);
151 } finally {
152 // We are good citizens :D
153 void fs.promises.rmdir(tempDir, { recursive: true }).catch(log.error);
154 }
155};
156
157async function moveFile(src: fs.PathLike, dest: fs.PathLike) {
158 try {
159 await fs.promises.rename(src, dest);
160 } catch (err) {
161 if (err.code === 'EXDEV') {
162 // We are probably moving the file across partitions/devices
163 await fs.promises.copyFile(src, dest);
164 await fs.promises.unlink(src);
165 } else {
166 log.error(`Failed to rename the file ${src} -> ${dest}`, err);
167 throw err;
168 }
169 }
170}
diff --git a/editors/code/src/status_display.ts b/editors/code/src/status_display.ts
deleted file mode 100644
index f9cadc8a2..000000000
--- a/editors/code/src/status_display.ts
+++ /dev/null
@@ -1,100 +0,0 @@
1import * as vscode from 'vscode';
2
3import { WorkDoneProgress, WorkDoneProgressBegin, WorkDoneProgressReport, WorkDoneProgressEnd, Disposable } from 'vscode-languageclient';
4
5import { Ctx } from './ctx';
6
7const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
8
9export function activateStatusDisplay(ctx: Ctx) {
10 const statusDisplay = new StatusDisplay(ctx.config.checkOnSave.command);
11 ctx.pushCleanup(statusDisplay);
12 const client = ctx.client;
13 if (client != null) {
14 ctx.pushCleanup(client.onProgress(
15 WorkDoneProgress.type,
16 'rustAnalyzer/cargoWatcher',
17 params => statusDisplay.handleProgressNotification(params)
18 ));
19 }
20}
21
22class StatusDisplay implements Disposable {
23 packageName?: string;
24
25 private i: number = 0;
26 private statusBarItem: vscode.StatusBarItem;
27 private command: string;
28 private timer?: NodeJS.Timeout;
29
30 constructor(command: string) {
31 this.statusBarItem = vscode.window.createStatusBarItem(
32 vscode.StatusBarAlignment.Left,
33 10,
34 );
35 this.command = command;
36 this.statusBarItem.hide();
37 }
38
39 show() {
40 this.packageName = undefined;
41
42 this.timer =
43 this.timer ||
44 setInterval(() => {
45 this.tick();
46 this.refreshLabel();
47 }, 300);
48
49 this.statusBarItem.show();
50 }
51
52 hide() {
53 if (this.timer) {
54 clearInterval(this.timer);
55 this.timer = undefined;
56 }
57
58 this.statusBarItem.hide();
59 }
60
61 dispose() {
62 if (this.timer) {
63 clearInterval(this.timer);
64 this.timer = undefined;
65 }
66
67 this.statusBarItem.dispose();
68 }
69
70 refreshLabel() {
71 if (this.packageName) {
72 this.statusBarItem.text = `${spinnerFrames[this.i]} cargo ${this.command} [${this.packageName}]`;
73 } else {
74 this.statusBarItem.text = `${spinnerFrames[this.i]} cargo ${this.command}`;
75 }
76 }
77
78 handleProgressNotification(params: WorkDoneProgressBegin | WorkDoneProgressReport | WorkDoneProgressEnd) {
79 switch (params.kind) {
80 case 'begin':
81 this.show();
82 break;
83
84 case 'report':
85 if (params.message) {
86 this.packageName = params.message;
87 this.refreshLabel();
88 }
89 break;
90
91 case 'end':
92 this.hide();
93 break;
94 }
95 }
96
97 private tick() {
98 this.i = (this.i + 1) % spinnerFrames.length;
99 }
100}