diff options
Diffstat (limited to 'editors/code')
-rw-r--r-- | editors/code/package.json | 15 | ||||
-rw-r--r-- | editors/code/rollup.config.js | 7 | ||||
-rw-r--r-- | editors/code/src/commands.ts | 18 | ||||
-rw-r--r-- | editors/code/src/config.ts | 9 | ||||
-rw-r--r-- | editors/code/src/debug.ts | 2 | ||||
-rw-r--r-- | editors/code/src/main.ts | 39 | ||||
-rw-r--r-- | editors/code/src/net.ts | 39 | ||||
-rw-r--r-- | editors/code/src/run.ts | 58 | ||||
-rw-r--r-- | editors/code/src/status_display.ts | 100 | ||||
-rw-r--r-- | editors/code/src/tasks.ts | 111 | ||||
-rw-r--r-- | editors/code/src/util.ts | 1 |
11 files changed, 185 insertions, 214 deletions
diff --git a/editors/code/package.json b/editors/code/package.json index 3acc375f6..f542a490a 100644 --- a/editors/code/package.json +++ b/editors/code/package.json | |||
@@ -336,6 +336,14 @@ | |||
336 | "default": null, | 336 | "default": null, |
337 | "description": "List of features to activate. Defaults to `rust-analyzer.cargo.features`." | 337 | "description": "List of features to activate. Defaults to `rust-analyzer.cargo.features`." |
338 | }, | 338 | }, |
339 | "rust-analyzer.cargoRunner": { | ||
340 | "type": [ | ||
341 | "null", | ||
342 | "string" | ||
343 | ], | ||
344 | "default": null, | ||
345 | "description": "Custom cargo runner extension ID." | ||
346 | }, | ||
339 | "rust-analyzer.inlayHints.enable": { | 347 | "rust-analyzer.inlayHints.enable": { |
340 | "type": "boolean", | 348 | "type": "boolean", |
341 | "default": true, | 349 | "default": true, |
@@ -426,7 +434,7 @@ | |||
426 | "Full log" | 434 | "Full log" |
427 | ], | 435 | ], |
428 | "default": "off", | 436 | "default": "off", |
429 | "description": "Trace requests to the rust-analyzer" | 437 | "description": "Trace requests to the rust-analyzer (this is usually overly verbose and not recommended for regular users)" |
430 | }, | 438 | }, |
431 | "rust-analyzer.trace.extension": { | 439 | "rust-analyzer.trace.extension": { |
432 | "description": "Enable logging of VS Code extensions itself", | 440 | "description": "Enable logging of VS Code extensions itself", |
@@ -510,6 +518,11 @@ | |||
510 | "type": "boolean", | 518 | "type": "boolean", |
511 | "default": true | 519 | "default": true |
512 | }, | 520 | }, |
521 | "rust-analyzer.hoverActions.gotoTypeDef": { | ||
522 | "markdownDescription": "Whether to show `Go to Type Definition` action. Only applies when `#rust-analyzer.hoverActions.enable#` is set.", | ||
523 | "type": "boolean", | ||
524 | "default": true | ||
525 | }, | ||
513 | "rust-analyzer.linkedProjects": { | 526 | "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", | 527 | "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", | 528 | "type": "array", |
diff --git a/editors/code/rollup.config.js b/editors/code/rollup.config.js index 58360eabb..4b4c47f4a 100644 --- a/editors/code/rollup.config.js +++ b/editors/code/rollup.config.js | |||
@@ -11,12 +11,7 @@ export default { | |||
11 | resolve({ | 11 | resolve({ |
12 | preferBuiltins: true | 12 | preferBuiltins: true |
13 | }), | 13 | }), |
14 | commonjs({ | 14 | commonjs() |
15 | namedExports: { | ||
16 | // squelch missing import warnings | ||
17 | 'vscode-languageclient': ['CreateFile', 'RenameFile', 'ErrorCodes', 'WorkDoneProgress', 'WorkDoneProgressBegin', 'WorkDoneProgressReport', 'WorkDoneProgressEnd'] | ||
18 | } | ||
19 | }) | ||
20 | ], | 15 | ], |
21 | external: [...nodeBuiltins, 'vscode'], | 16 | external: [...nodeBuiltins, 'vscode'], |
22 | output: { | 17 | output: { |
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts index 3e9c3aa0e..8c9d7802f 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) => { |
@@ -380,7 +394,7 @@ export function run(ctx: Ctx): Cmd { | |||
380 | 394 | ||
381 | item.detail = 'rerun'; | 395 | item.detail = 'rerun'; |
382 | prevRunnable = item; | 396 | prevRunnable = item; |
383 | const task = createTask(item.runnable); | 397 | const task = await createTask(item.runnable, ctx.config); |
384 | return await vscode.tasks.executeTask(task); | 398 | return await vscode.tasks.executeTask(task); |
385 | }; | 399 | }; |
386 | } | 400 | } |
@@ -390,7 +404,7 @@ export function runSingle(ctx: Ctx): Cmd { | |||
390 | const editor = ctx.activeRustEditor; | 404 | const editor = ctx.activeRustEditor; |
391 | if (!editor) return; | 405 | if (!editor) return; |
392 | 406 | ||
393 | const task = createTask(runnable); | 407 | const task = await createTask(runnable, ctx.config); |
394 | task.group = vscode.TaskGroup.Build; | 408 | task.group = vscode.TaskGroup.Build; |
395 | task.presentationOptions = { | 409 | task.presentationOptions = { |
396 | reveal: vscode.TaskRevealKind.Always, | 410 | reveal: vscode.TaskRevealKind.Always, |
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index d8f0037d4..fc95a7de6 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts | |||
@@ -110,6 +110,10 @@ export class Config { | |||
110 | }; | 110 | }; |
111 | } | 111 | } |
112 | 112 | ||
113 | get cargoRunner() { | ||
114 | return this.get<string | undefined>("cargoRunner"); | ||
115 | } | ||
116 | |||
113 | get debug() { | 117 | get debug() { |
114 | // "/rustc/<id>" used by suggestions only. | 118 | // "/rustc/<id>" used by suggestions only. |
115 | const { ["/rustc/<id>"]: _, ...sourceFileMap } = this.get<Record<string, string>>("debug.sourceFileMap"); | 119 | const { ["/rustc/<id>"]: _, ...sourceFileMap } = this.get<Record<string, string>>("debug.sourceFileMap"); |
@@ -117,7 +121,7 @@ export class Config { | |||
117 | return { | 121 | return { |
118 | engine: this.get<string>("debug.engine"), | 122 | engine: this.get<string>("debug.engine"), |
119 | engineSettings: this.get<object>("debug.engineSettings"), | 123 | engineSettings: this.get<object>("debug.engineSettings"), |
120 | openUpDebugPane: this.get<boolean>("debug.openUpDebugPane"), | 124 | openDebugPane: this.get<boolean>("debug.openDebugPane"), |
121 | sourceFileMap: sourceFileMap | 125 | sourceFileMap: sourceFileMap |
122 | }; | 126 | }; |
123 | } | 127 | } |
@@ -135,6 +139,9 @@ export class Config { | |||
135 | return { | 139 | return { |
136 | enable: this.get<boolean>("hoverActions.enable"), | 140 | enable: this.get<boolean>("hoverActions.enable"), |
137 | implementations: this.get<boolean>("hoverActions.implementations"), | 141 | implementations: this.get<boolean>("hoverActions.implementations"), |
142 | run: this.get<boolean>("hoverActions.run"), | ||
143 | debug: this.get<boolean>("hoverActions.debug"), | ||
144 | gotoTypeDef: this.get<boolean>("hoverActions.gotoTypeDef"), | ||
138 | }; | 145 | }; |
139 | } | 146 | } |
140 | } | 147 | } |
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..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 | ||
6 | import * as commands from './commands'; | 6 | import * as commands from './commands'; |
7 | import { activateInlayHints } from './inlay_hints'; | 7 | import { activateInlayHints } from './inlay_hints'; |
8 | import { activateStatusDisplay } from './status_display'; | ||
9 | import { Ctx } from './ctx'; | 8 | import { Ctx } from './ctx'; |
10 | import { Config, NIGHTLY_TAG } from './config'; | 9 | import { Config, NIGHTLY_TAG } from './config'; |
11 | import { log, assert, isValidExecutable } from './util'; | 10 | import { log, assert, isValidExecutable } from './util'; |
@@ -42,7 +41,20 @@ export async function activate(context: vscode.ExtensionContext) { | |||
42 | 41 | ||
43 | const config = new Config(context); | 42 | const config = new Config(context); |
44 | const state = new PersistentState(context.globalState); | 43 | const state = new PersistentState(context.globalState); |
45 | const serverPath = await bootstrap(config, state); | 44 | const serverPath = await bootstrap(config, state).catch(err => { |
45 | let message = "bootstrap error. "; | ||
46 | |||
47 | if (err.code === "EBUSY" || err.code === "ETXTBSY") { | ||
48 | message += "Other vscode windows might be using rust-analyzer, "; | ||
49 | message += "you should close them and reload this window to retry. "; | ||
50 | } | ||
51 | |||
52 | message += 'Open "Help > Toggle Developer Tools > Console" to see the logs '; | ||
53 | message += '(enable verbose logs with "rust-analyzer.trace.extension")'; | ||
54 | |||
55 | log.error("Bootstrap error", err); | ||
56 | throw new Error(message); | ||
57 | }); | ||
46 | 58 | ||
47 | const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; | 59 | const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; |
48 | if (workspaceFolder === undefined) { | 60 | if (workspaceFolder === undefined) { |
@@ -100,10 +112,9 @@ export async function activate(context: vscode.ExtensionContext) { | |||
100 | ctx.registerCommand('applySnippetWorkspaceEdit', commands.applySnippetWorkspaceEditCommand); | 112 | ctx.registerCommand('applySnippetWorkspaceEdit', commands.applySnippetWorkspaceEditCommand); |
101 | ctx.registerCommand('resolveCodeAction', commands.resolveCodeAction); | 113 | ctx.registerCommand('resolveCodeAction', commands.resolveCodeAction); |
102 | ctx.registerCommand('applyActionGroup', commands.applyActionGroup); | 114 | ctx.registerCommand('applyActionGroup', commands.applyActionGroup); |
115 | ctx.registerCommand('gotoLocation', commands.gotoLocation); | ||
103 | 116 | ||
104 | ctx.pushCleanup(activateTaskProvider(workspaceFolder)); | 117 | ctx.pushCleanup(activateTaskProvider(workspaceFolder, ctx.config)); |
105 | |||
106 | activateStatusDisplay(ctx); | ||
107 | 118 | ||
108 | activateInlayHints(ctx); | 119 | activateInlayHints(ctx); |
109 | 120 | ||
@@ -168,7 +179,11 @@ async function bootstrapExtension(config: Config, state: PersistentState): Promi | |||
168 | assert(!!artifact, `Bad release: ${JSON.stringify(release)}`); | 179 | assert(!!artifact, `Bad release: ${JSON.stringify(release)}`); |
169 | 180 | ||
170 | const dest = path.join(config.globalStoragePath, "rust-analyzer.vsix"); | 181 | const dest = path.join(config.globalStoragePath, "rust-analyzer.vsix"); |
171 | await download(artifact.browser_download_url, dest, "Downloading rust-analyzer extension"); | 182 | await download({ |
183 | url: artifact.browser_download_url, | ||
184 | dest, | ||
185 | progressTitle: "Downloading rust-analyzer extension", | ||
186 | }); | ||
172 | 187 | ||
173 | await vscode.commands.executeCommand("workbench.extensions.installExtension", vscode.Uri.file(dest)); | 188 | await vscode.commands.executeCommand("workbench.extensions.installExtension", vscode.Uri.file(dest)); |
174 | await fs.unlink(dest); | 189 | await fs.unlink(dest); |
@@ -284,7 +299,17 @@ async function getServer(config: Config, state: PersistentState): Promise<string | |||
284 | const artifact = release.assets.find(artifact => artifact.name === binaryName); | 299 | const artifact = release.assets.find(artifact => artifact.name === binaryName); |
285 | assert(!!artifact, `Bad release: ${JSON.stringify(release)}`); | 300 | assert(!!artifact, `Bad release: ${JSON.stringify(release)}`); |
286 | 301 | ||
287 | await download(artifact.browser_download_url, dest, "Downloading rust-analyzer server", { mode: 0o755 }); | 302 | // Unlinking the exe file before moving new one on its place should prevent ETXTBSY error. |
303 | await fs.unlink(dest).catch(err => { | ||
304 | if (err.code !== "ENOENT") throw err; | ||
305 | }); | ||
306 | |||
307 | await download({ | ||
308 | url: artifact.browser_download_url, | ||
309 | dest, | ||
310 | progressTitle: "Downloading rust-analyzer server", | ||
311 | mode: 0o755 | ||
312 | }); | ||
288 | 313 | ||
289 | // Patching executable if that's NixOS. | 314 | // Patching executable if that's NixOS. |
290 | if (await fs.stat("/etc/nixos").then(_ => true).catch(_ => false)) { | 315 | if (await fs.stat("/etc/nixos").then(_ => true).catch(_ => false)) { |
diff --git a/editors/code/src/net.ts b/editors/code/src/net.ts index 492213937..866092882 100644 --- a/editors/code/src/net.ts +++ b/editors/code/src/net.ts | |||
@@ -1,8 +1,10 @@ | |||
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 crypto from "crypto"; | ||
5 | import * as fs from "fs"; | ||
5 | import * as util from "util"; | 6 | import * as util from "util"; |
7 | import * as path from "path"; | ||
6 | import { log, assert } from "./util"; | 8 | import { log, assert } from "./util"; |
7 | 9 | ||
8 | const pipeline = util.promisify(stream.pipeline); | 10 | const pipeline = util.promisify(stream.pipeline); |
@@ -58,22 +60,29 @@ export interface GithubRelease { | |||
58 | }>; | 60 | }>; |
59 | } | 61 | } |
60 | 62 | ||
63 | interface DownloadOpts { | ||
64 | progressTitle: string; | ||
65 | url: string; | ||
66 | dest: string; | ||
67 | mode?: number; | ||
68 | } | ||
69 | |||
70 | export async function download(opts: DownloadOpts) { | ||
71 | // Put artifact into a temporary file (in the same dir for simplicity) | ||
72 | // to prevent partially downloaded files when user kills vscode | ||
73 | const dest = path.parse(opts.dest); | ||
74 | const randomHex = crypto.randomBytes(5).toString("hex"); | ||
75 | const tempFile = path.join(dest.dir, `${dest.name}${randomHex}`); | ||
61 | 76 | ||
62 | export async function download( | ||
63 | downloadUrl: string, | ||
64 | destinationPath: string, | ||
65 | progressTitle: string, | ||
66 | { mode }: { mode?: number } = {}, | ||
67 | ) { | ||
68 | await vscode.window.withProgress( | 77 | await vscode.window.withProgress( |
69 | { | 78 | { |
70 | location: vscode.ProgressLocation.Notification, | 79 | location: vscode.ProgressLocation.Notification, |
71 | cancellable: false, | 80 | cancellable: false, |
72 | title: progressTitle | 81 | title: opts.progressTitle |
73 | }, | 82 | }, |
74 | async (progress, _cancellationToken) => { | 83 | async (progress, _cancellationToken) => { |
75 | let lastPercentage = 0; | 84 | let lastPercentage = 0; |
76 | await downloadFile(downloadUrl, destinationPath, mode, (readBytes, totalBytes) => { | 85 | await downloadFile(opts.url, tempFile, opts.mode, (readBytes, totalBytes) => { |
77 | const newPercentage = (readBytes / totalBytes) * 100; | 86 | const newPercentage = (readBytes / totalBytes) * 100; |
78 | progress.report({ | 87 | progress.report({ |
79 | message: newPercentage.toFixed(0) + "%", | 88 | message: newPercentage.toFixed(0) + "%", |
@@ -84,10 +93,12 @@ export async function download( | |||
84 | }); | 93 | }); |
85 | } | 94 | } |
86 | ); | 95 | ); |
96 | |||
97 | await fs.promises.rename(tempFile, opts.dest); | ||
87 | } | 98 | } |
88 | 99 | ||
89 | /** | 100 | /** |
90 | * Downloads file from `url` and stores it at `destFilePath` with `destFilePermissions`. | 101 | * Downloads file from `url` and stores it at `destFilePath` with `mode` (unix permissions). |
91 | * `onProgress` callback is called on recieveing each chunk of bytes | 102 | * `onProgress` callback is called on recieveing each chunk of bytes |
92 | * to track the progress of downloading, it gets the already read and total | 103 | * to track the progress of downloading, it gets the already read and total |
93 | * amount of bytes to read as its parameters. | 104 | * amount of bytes to read as its parameters. |
@@ -119,13 +130,11 @@ async function downloadFile( | |||
119 | }); | 130 | }); |
120 | 131 | ||
121 | const destFileStream = fs.createWriteStream(destFilePath, { mode }); | 132 | const destFileStream = fs.createWriteStream(destFilePath, { mode }); |
122 | |||
123 | await pipeline(res.body, destFileStream); | 133 | await pipeline(res.body, destFileStream); |
124 | return new Promise<void>(resolve => { | 134 | await new Promise<void>(resolve => { |
125 | destFileStream.on("close", resolve); | 135 | destFileStream.on("close", resolve); |
126 | destFileStream.destroy(); | 136 | destFileStream.destroy(); |
127 | 137 | // This workaround is awaiting to be removed when vscode moves to newer nodejs version: | |
128 | // Details on workaround: https://github.com/rust-analyzer/rust-analyzer/pull/3092#discussion_r378191131 | 138 | // https://github.com/rust-analyzer/rust-analyzer/issues/3167 |
129 | // Issue at nodejs repo: https://github.com/nodejs/node/issues/31776 | ||
130 | }); | 139 | }); |
131 | } | 140 | } |
diff --git a/editors/code/src/run.ts b/editors/code/src/run.ts index bb060cfe1..766b05112 100644 --- a/editors/code/src/run.ts +++ b/editors/code/src/run.ts | |||
@@ -1,10 +1,11 @@ | |||
1 | import * as vscode from 'vscode'; | 1 | import * as vscode from 'vscode'; |
2 | import * as lc from 'vscode-languageclient'; | 2 | import * as lc from 'vscode-languageclient'; |
3 | import * as ra from './lsp_ext'; | 3 | import * as ra from './lsp_ext'; |
4 | import * as toolchain from "./toolchain"; | 4 | import * as tasks from './tasks'; |
5 | 5 | ||
6 | import { Ctx } from './ctx'; | 6 | import { Ctx } from './ctx'; |
7 | import { makeDebugConfig } from './debug'; | 7 | import { makeDebugConfig } from './debug'; |
8 | import { Config } from './config'; | ||
8 | 9 | ||
9 | const quickPickButtons = [{ iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configurtation." }]; | 10 | const quickPickButtons = [{ iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configurtation." }]; |
10 | 11 | ||
@@ -95,52 +96,29 @@ export class RunnableQuickPick implements vscode.QuickPickItem { | |||
95 | } | 96 | } |
96 | } | 97 | } |
97 | 98 | ||
98 | interface CargoTaskDefinition extends vscode.TaskDefinition { | 99 | export async function createTask(runnable: ra.Runnable, config: Config): Promise<vscode.Task> { |
99 | type: 'cargo'; | 100 | if (runnable.kind !== "cargo") { |
100 | label: string; | 101 | // rust-analyzer supports only one kind, "cargo" |
101 | command: string; | 102 | // do not use tasks.TASK_TYPE here, these are completely different meanings. |
102 | args: string[]; | ||
103 | env?: { [key: string]: string }; | ||
104 | } | ||
105 | |||
106 | export function createTask(runnable: ra.Runnable): vscode.Task { | ||
107 | const TASK_SOURCE = 'Rust'; | ||
108 | 103 | ||
109 | let command; | 104 | throw `Unexpected runnable kind: ${runnable.kind}`; |
110 | switch (runnable.kind) { | ||
111 | case "cargo": command = toolchain.getPathForExecutable("cargo"); | ||
112 | } | 105 | } |
106 | |||
113 | const args = [...runnable.args.cargoArgs]; // should be a copy! | 107 | const args = [...runnable.args.cargoArgs]; // should be a copy! |
114 | if (runnable.args.executableArgs.length > 0) { | 108 | if (runnable.args.executableArgs.length > 0) { |
115 | args.push('--', ...runnable.args.executableArgs); | 109 | args.push('--', ...runnable.args.executableArgs); |
116 | } | 110 | } |
117 | const definition: CargoTaskDefinition = { | 111 | const definition: tasks.CargoTaskDefinition = { |
118 | type: 'cargo', | 112 | type: tasks.TASK_TYPE, |
119 | label: runnable.label, | 113 | command: args[0], // run, test, etc... |
120 | command, | 114 | args: args.slice(1), |
121 | args, | 115 | cwd: runnable.args.workspaceRoot, |
122 | env: Object.assign({}, process.env as { [key: string]: string }, { "RUST_BACKTRACE": "short" }), | 116 | env: Object.assign({}, process.env as { [key: string]: string }, { "RUST_BACKTRACE": "short" }), |
123 | }; | 117 | }; |
124 | 118 | ||
125 | const execOption: vscode.ShellExecutionOptions = { | 119 | const target = vscode.workspace.workspaceFolders![0]; // safe, see main activate() |
126 | cwd: runnable.args.workspaceRoot || '.', | 120 | const cargoTask = await tasks.buildCargoTask(target, definition, runnable.label, args, config.cargoRunner, true); |
127 | env: definition.env, | 121 | cargoTask.presentationOptions.clear = true; |
128 | }; | 122 | |
129 | const exec = new vscode.ShellExecution( | 123 | return cargoTask; |
130 | definition.command, | ||
131 | definition.args, | ||
132 | execOption, | ||
133 | ); | ||
134 | |||
135 | const f = vscode.workspace.workspaceFolders![0]; | ||
136 | const t = new vscode.Task( | ||
137 | definition, | ||
138 | f, | ||
139 | definition.label, | ||
140 | TASK_SOURCE, | ||
141 | exec, | ||
142 | ['$rustc'], | ||
143 | ); | ||
144 | t.presentationOptions.clear = true; | ||
145 | return t; | ||
146 | } | 124 | } |
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 @@ | |||
1 | import * as vscode from 'vscode'; | ||
2 | |||
3 | import { WorkDoneProgress, WorkDoneProgressBegin, WorkDoneProgressReport, WorkDoneProgressEnd, Disposable } from 'vscode-languageclient'; | ||
4 | |||
5 | import { Ctx } from './ctx'; | ||
6 | |||
7 | const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; | ||
8 | |||
9 | export 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 | |||
22 | class 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 | } | ||
diff --git a/editors/code/src/tasks.ts b/editors/code/src/tasks.ts index 9748824df..14abbd5b7 100644 --- a/editors/code/src/tasks.ts +++ b/editors/code/src/tasks.ts | |||
@@ -1,11 +1,14 @@ | |||
1 | import * as vscode from 'vscode'; | 1 | import * as vscode from 'vscode'; |
2 | import * as toolchain from "./toolchain"; | 2 | import * as toolchain from "./toolchain"; |
3 | import { Config } from './config'; | ||
4 | import { log } from './util'; | ||
3 | 5 | ||
4 | // This ends up as the `type` key in tasks.json. RLS also uses `cargo` and | 6 | // This ends up as the `type` key in tasks.json. RLS also uses `cargo` and |
5 | // our configuration should be compatible with it so use the same key. | 7 | // our configuration should be compatible with it so use the same key. |
6 | const TASK_TYPE = 'cargo'; | 8 | export const TASK_TYPE = 'cargo'; |
9 | export const TASK_SOURCE = 'rust'; | ||
7 | 10 | ||
8 | interface CargoTaskDefinition extends vscode.TaskDefinition { | 11 | export interface CargoTaskDefinition extends vscode.TaskDefinition { |
9 | command?: string; | 12 | command?: string; |
10 | args?: string[]; | 13 | args?: string[]; |
11 | cwd?: string; | 14 | cwd?: string; |
@@ -14,73 +17,101 @@ interface CargoTaskDefinition extends vscode.TaskDefinition { | |||
14 | 17 | ||
15 | class CargoTaskProvider implements vscode.TaskProvider { | 18 | class CargoTaskProvider implements vscode.TaskProvider { |
16 | private readonly target: vscode.WorkspaceFolder; | 19 | private readonly target: vscode.WorkspaceFolder; |
20 | private readonly config: Config; | ||
17 | 21 | ||
18 | constructor(target: vscode.WorkspaceFolder) { | 22 | constructor(target: vscode.WorkspaceFolder, config: Config) { |
19 | this.target = target; | 23 | this.target = target; |
24 | this.config = config; | ||
20 | } | 25 | } |
21 | 26 | ||
22 | provideTasks(): vscode.Task[] { | 27 | async provideTasks(): Promise<vscode.Task[]> { |
23 | // Detect Rust tasks. Currently we do not do any actual detection | 28 | // Detect Rust tasks. Currently we do not do any actual detection |
24 | // of tasks (e.g. aliases in .cargo/config) and just return a fixed | 29 | // of tasks (e.g. aliases in .cargo/config) and just return a fixed |
25 | // set of tasks that always exist. These tasks cannot be removed in | 30 | // set of tasks that always exist. These tasks cannot be removed in |
26 | // tasks.json - only tweaked. | 31 | // tasks.json - only tweaked. |
27 | 32 | ||
28 | const cargoPath = toolchain.cargoPath(); | 33 | const defs = [ |
29 | |||
30 | return [ | ||
31 | { command: 'build', group: vscode.TaskGroup.Build }, | 34 | { command: 'build', group: vscode.TaskGroup.Build }, |
32 | { command: 'check', group: vscode.TaskGroup.Build }, | 35 | { command: 'check', group: vscode.TaskGroup.Build }, |
33 | { command: 'test', group: vscode.TaskGroup.Test }, | 36 | { command: 'test', group: vscode.TaskGroup.Test }, |
34 | { command: 'clean', group: vscode.TaskGroup.Clean }, | 37 | { command: 'clean', group: vscode.TaskGroup.Clean }, |
35 | { command: 'run', group: undefined }, | 38 | { command: 'run', group: undefined }, |
36 | ] | 39 | ]; |
37 | .map(({ command, group }) => { | 40 | |
38 | const vscodeTask = new vscode.Task( | 41 | const tasks: vscode.Task[] = []; |
39 | // The contents of this object end up in the tasks.json entries. | 42 | for (const def of defs) { |
40 | { | 43 | const vscodeTask = await buildCargoTask(this.target, { type: TASK_TYPE, command: def.command }, `cargo ${def.command}`, [def.command], this.config.cargoRunner); |
41 | type: TASK_TYPE, | 44 | vscodeTask.group = def.group; |
42 | command, | 45 | tasks.push(vscodeTask); |
43 | }, | 46 | } |
44 | // The scope of the task - workspace or specific folder (global | 47 | |
45 | // is not supported). | 48 | return tasks; |
46 | this.target, | ||
47 | // The task name, and task source. These are shown in the UI as | ||
48 | // `${source}: ${name}`, e.g. `rust: cargo build`. | ||
49 | `cargo ${command}`, | ||
50 | 'rust', | ||
51 | // What to do when this command is executed. | ||
52 | new vscode.ShellExecution(cargoPath, [command]), | ||
53 | // Problem matchers. | ||
54 | ['$rustc'], | ||
55 | ); | ||
56 | vscodeTask.group = group; | ||
57 | return vscodeTask; | ||
58 | }); | ||
59 | } | 49 | } |
60 | 50 | ||
61 | resolveTask(task: vscode.Task): vscode.Task | undefined { | 51 | async resolveTask(task: vscode.Task): Promise<vscode.Task | undefined> { |
62 | // VSCode calls this for every cargo task in the user's tasks.json, | 52 | // VSCode calls this for every cargo task in the user's tasks.json, |
63 | // we need to inform VSCode how to execute that command by creating | 53 | // we need to inform VSCode how to execute that command by creating |
64 | // a ShellExecution for it. | 54 | // a ShellExecution for it. |
65 | 55 | ||
66 | const definition = task.definition as CargoTaskDefinition; | 56 | const definition = task.definition as CargoTaskDefinition; |
67 | 57 | ||
68 | if (definition.type === 'cargo' && definition.command) { | 58 | if (definition.type === TASK_TYPE && definition.command) { |
69 | const args = [definition.command].concat(definition.args ?? []); | 59 | const args = [definition.command].concat(definition.args ?? []); |
70 | 60 | ||
71 | return new vscode.Task( | 61 | return await buildCargoTask(this.target, definition, task.name, args, this.config.cargoRunner); |
72 | definition, | ||
73 | task.name, | ||
74 | 'rust', | ||
75 | new vscode.ShellExecution('cargo', args, definition), | ||
76 | ); | ||
77 | } | 62 | } |
78 | 63 | ||
79 | return undefined; | 64 | return undefined; |
80 | } | 65 | } |
81 | } | 66 | } |
82 | 67 | ||
83 | export function activateTaskProvider(target: vscode.WorkspaceFolder): vscode.Disposable { | 68 | export async function buildCargoTask( |
84 | const provider = new CargoTaskProvider(target); | 69 | target: vscode.WorkspaceFolder, |
70 | definition: CargoTaskDefinition, | ||
71 | name: string, | ||
72 | args: string[], | ||
73 | customRunner?: string, | ||
74 | throwOnError: boolean = false | ||
75 | ): Promise<vscode.Task> { | ||
76 | |||
77 | let exec: vscode.ShellExecution | undefined = undefined; | ||
78 | |||
79 | if (customRunner) { | ||
80 | const runnerCommand = `${customRunner}.buildShellExecution`; | ||
81 | try { | ||
82 | const runnerArgs = { kind: TASK_TYPE, args, cwd: definition.cwd, env: definition.env }; | ||
83 | const customExec = await vscode.commands.executeCommand(runnerCommand, runnerArgs); | ||
84 | if (customExec) { | ||
85 | if (customExec instanceof vscode.ShellExecution) { | ||
86 | exec = customExec; | ||
87 | } else { | ||
88 | log.debug("Invalid cargo ShellExecution", customExec); | ||
89 | throw "Invalid cargo ShellExecution."; | ||
90 | } | ||
91 | } | ||
92 | // fallback to default processing | ||
93 | |||
94 | } catch (e) { | ||
95 | if (throwOnError) throw `Cargo runner '${customRunner}' failed! ${e}`; | ||
96 | // fallback to default processing | ||
97 | } | ||
98 | } | ||
99 | |||
100 | if (!exec) { | ||
101 | exec = new vscode.ShellExecution(toolchain.cargoPath(), args, definition); | ||
102 | } | ||
103 | |||
104 | return new vscode.Task( | ||
105 | definition, | ||
106 | target, | ||
107 | name, | ||
108 | TASK_SOURCE, | ||
109 | exec, | ||
110 | ['$rustc'] | ||
111 | ); | ||
112 | } | ||
113 | |||
114 | export function activateTaskProvider(target: vscode.WorkspaceFolder, config: Config): vscode.Disposable { | ||
115 | const provider = new CargoTaskProvider(target, config); | ||
85 | return vscode.tasks.registerTaskProvider(TASK_TYPE, provider); | 116 | return vscode.tasks.registerTaskProvider(TASK_TYPE, provider); |
86 | } | 117 | } |
diff --git a/editors/code/src/util.ts b/editors/code/src/util.ts index fe3fb71cd..fec4c3295 100644 --- a/editors/code/src/util.ts +++ b/editors/code/src/util.ts | |||
@@ -26,7 +26,6 @@ export const log = new class { | |||
26 | } | 26 | } |
27 | 27 | ||
28 | error(message?: any, ...optionalParams: any[]): void { | 28 | error(message?: any, ...optionalParams: any[]): void { |
29 | if (!log.enabled) return; | ||
30 | debugger; | 29 | debugger; |
31 | // eslint-disable-next-line no-console | 30 | // eslint-disable-next-line no-console |
32 | console.error(message, ...optionalParams); | 31 | console.error(message, ...optionalParams); |