diff options
-rw-r--r-- | editors/code/package.json | 12 | ||||
-rw-r--r-- | editors/code/src/commands/cargo_watch.ts | 264 | ||||
-rw-r--r-- | editors/code/src/commands/runnables.ts | 91 | ||||
-rw-r--r-- | editors/code/src/extension.ts | 25 | ||||
-rw-r--r-- | editors/code/src/utils/processes.ts | 51 | ||||
-rw-r--r-- | editors/code/src/utils/terminateProcess.sh | 12 |
6 files changed, 1 insertions, 454 deletions
diff --git a/editors/code/package.json b/editors/code/package.json index f75fafeb9..6cb24a3ce 100644 --- a/editors/code/package.json +++ b/editors/code/package.json | |||
@@ -18,7 +18,7 @@ | |||
18 | "scripts": { | 18 | "scripts": { |
19 | "vscode:prepublish": "npm run compile", | 19 | "vscode:prepublish": "npm run compile", |
20 | "package": "vsce package", | 20 | "package": "vsce package", |
21 | "compile": "rollup -c && shx cp src/utils/terminateProcess.sh bundle/terminateProcess.sh", | 21 | "compile": "rollup -c", |
22 | "watch": "tsc -watch -p ./", | 22 | "watch": "tsc -watch -p ./", |
23 | "fix": "prettier **/*.{json,ts} --write && tslint --project . --fix", | 23 | "fix": "prettier **/*.{json,ts} --write && tslint --project . --fix", |
24 | "lint": "tslint --project .", | 24 | "lint": "tslint --project .", |
@@ -133,16 +133,6 @@ | |||
133 | "command": "rust-analyzer.reload", | 133 | "command": "rust-analyzer.reload", |
134 | "title": "Restart server", | 134 | "title": "Restart server", |
135 | "category": "Rust Analyzer" | 135 | "category": "Rust Analyzer" |
136 | }, | ||
137 | { | ||
138 | "command": "rust-analyzer.startCargoWatch", | ||
139 | "title": "Start Cargo Watch", | ||
140 | "category": "Rust Analyzer" | ||
141 | }, | ||
142 | { | ||
143 | "command": "rust-analyzer.stopCargoWatch", | ||
144 | "title": "Stop Cargo Watch", | ||
145 | "category": "Rust Analyzer" | ||
146 | } | 136 | } |
147 | ], | 137 | ], |
148 | "keybindings": [ | 138 | "keybindings": [ |
diff --git a/editors/code/src/commands/cargo_watch.ts b/editors/code/src/commands/cargo_watch.ts deleted file mode 100644 index ac62bdd48..000000000 --- a/editors/code/src/commands/cargo_watch.ts +++ /dev/null | |||
@@ -1,264 +0,0 @@ | |||
1 | import * as child_process from 'child_process'; | ||
2 | import * as path from 'path'; | ||
3 | import * as vscode from 'vscode'; | ||
4 | |||
5 | import { Server } from '../server'; | ||
6 | import { terminate } from '../utils/processes'; | ||
7 | import { LineBuffer } from './line_buffer'; | ||
8 | import { StatusDisplay } from './watch_status'; | ||
9 | |||
10 | import { | ||
11 | mapRustDiagnosticToVsCode, | ||
12 | RustDiagnostic, | ||
13 | } from '../utils/diagnostics/rust'; | ||
14 | import SuggestedFixCollection from '../utils/diagnostics/SuggestedFixCollection'; | ||
15 | import { areDiagnosticsEqual } from '../utils/diagnostics/vscode'; | ||
16 | |||
17 | export async function registerCargoWatchProvider( | ||
18 | subscriptions: vscode.Disposable[], | ||
19 | ): Promise<CargoWatchProvider | undefined> { | ||
20 | let cargoExists = false; | ||
21 | |||
22 | // Check if the working directory is valid cargo root path | ||
23 | const cargoTomlPath = path.join(vscode.workspace.rootPath!, 'Cargo.toml'); | ||
24 | const cargoTomlUri = vscode.Uri.file(cargoTomlPath); | ||
25 | const cargoTomlFileInfo = await vscode.workspace.fs.stat(cargoTomlUri); | ||
26 | |||
27 | if (cargoTomlFileInfo) { | ||
28 | cargoExists = true; | ||
29 | } | ||
30 | |||
31 | if (!cargoExists) { | ||
32 | vscode.window.showErrorMessage( | ||
33 | `Couldn\'t find \'Cargo.toml\' at ${cargoTomlPath}`, | ||
34 | ); | ||
35 | return; | ||
36 | } | ||
37 | |||
38 | const provider = new CargoWatchProvider(); | ||
39 | subscriptions.push(provider); | ||
40 | return provider; | ||
41 | } | ||
42 | |||
43 | export class CargoWatchProvider implements vscode.Disposable { | ||
44 | private readonly diagnosticCollection: vscode.DiagnosticCollection; | ||
45 | private readonly statusDisplay: StatusDisplay; | ||
46 | private readonly outputChannel: vscode.OutputChannel; | ||
47 | |||
48 | private suggestedFixCollection: SuggestedFixCollection; | ||
49 | private codeActionDispose: vscode.Disposable; | ||
50 | |||
51 | private cargoProcess?: child_process.ChildProcess; | ||
52 | |||
53 | constructor() { | ||
54 | this.diagnosticCollection = vscode.languages.createDiagnosticCollection( | ||
55 | 'rustc', | ||
56 | ); | ||
57 | this.statusDisplay = new StatusDisplay( | ||
58 | Server.config.cargoWatchOptions.command, | ||
59 | ); | ||
60 | this.outputChannel = vscode.window.createOutputChannel( | ||
61 | 'Cargo Watch Trace', | ||
62 | ); | ||
63 | |||
64 | // Track `rustc`'s suggested fixes so we can convert them to code actions | ||
65 | this.suggestedFixCollection = new SuggestedFixCollection(); | ||
66 | this.codeActionDispose = vscode.languages.registerCodeActionsProvider( | ||
67 | [{ scheme: 'file', language: 'rust' }], | ||
68 | this.suggestedFixCollection, | ||
69 | { | ||
70 | providedCodeActionKinds: | ||
71 | SuggestedFixCollection.PROVIDED_CODE_ACTION_KINDS, | ||
72 | }, | ||
73 | ); | ||
74 | } | ||
75 | |||
76 | public start() { | ||
77 | if (this.cargoProcess) { | ||
78 | vscode.window.showInformationMessage( | ||
79 | 'Cargo Watch is already running', | ||
80 | ); | ||
81 | return; | ||
82 | } | ||
83 | |||
84 | let args = | ||
85 | Server.config.cargoWatchOptions.command + ' --message-format json'; | ||
86 | if (Server.config.cargoWatchOptions.allTargets) { | ||
87 | args += ' --all-targets'; | ||
88 | } | ||
89 | if (Server.config.cargoWatchOptions.command.length > 0) { | ||
90 | // Excape the double quote string: | ||
91 | args += ' ' + Server.config.cargoWatchOptions.arguments; | ||
92 | } | ||
93 | // Windows handles arguments differently than the unix-likes, so we need to wrap the args in double quotes | ||
94 | if (process.platform === 'win32') { | ||
95 | args = '"' + args + '"'; | ||
96 | } | ||
97 | |||
98 | const ignoreFlags = Server.config.cargoWatchOptions.ignore.reduce( | ||
99 | (flags, pattern) => [...flags, '--ignore', pattern], | ||
100 | [] as string[], | ||
101 | ); | ||
102 | |||
103 | // Start the cargo watch with json message | ||
104 | this.cargoProcess = child_process.spawn( | ||
105 | 'cargo', | ||
106 | ['watch', '-x', args, ...ignoreFlags], | ||
107 | { | ||
108 | stdio: ['ignore', 'pipe', 'pipe'], | ||
109 | cwd: vscode.workspace.rootPath, | ||
110 | windowsVerbatimArguments: true, | ||
111 | }, | ||
112 | ); | ||
113 | |||
114 | if (!this.cargoProcess) { | ||
115 | vscode.window.showErrorMessage('Cargo Watch failed to start'); | ||
116 | return; | ||
117 | } | ||
118 | |||
119 | const stdoutData = new LineBuffer(); | ||
120 | this.cargoProcess.stdout?.on('data', (s: string) => { | ||
121 | stdoutData.processOutput(s, line => { | ||
122 | this.logInfo(line); | ||
123 | try { | ||
124 | this.parseLine(line); | ||
125 | } catch (err) { | ||
126 | this.logError(`Failed to parse: ${err}, content : ${line}`); | ||
127 | } | ||
128 | }); | ||
129 | }); | ||
130 | |||
131 | const stderrData = new LineBuffer(); | ||
132 | this.cargoProcess.stderr?.on('data', (s: string) => { | ||
133 | stderrData.processOutput(s, line => { | ||
134 | this.logError('Error on cargo-watch : {\n' + line + '}\n'); | ||
135 | }); | ||
136 | }); | ||
137 | |||
138 | this.cargoProcess.on('error', (err: Error) => { | ||
139 | this.logError( | ||
140 | 'Error on cargo-watch process : {\n' + err.message + '}\n', | ||
141 | ); | ||
142 | }); | ||
143 | |||
144 | this.logInfo('cargo-watch started.'); | ||
145 | } | ||
146 | |||
147 | public stop() { | ||
148 | if (this.cargoProcess) { | ||
149 | this.cargoProcess.kill(); | ||
150 | terminate(this.cargoProcess); | ||
151 | this.cargoProcess = undefined; | ||
152 | } else { | ||
153 | vscode.window.showInformationMessage('Cargo Watch is not running'); | ||
154 | } | ||
155 | } | ||
156 | |||
157 | public dispose(): void { | ||
158 | this.stop(); | ||
159 | |||
160 | this.diagnosticCollection.clear(); | ||
161 | this.diagnosticCollection.dispose(); | ||
162 | this.outputChannel.dispose(); | ||
163 | this.statusDisplay.dispose(); | ||
164 | this.codeActionDispose.dispose(); | ||
165 | } | ||
166 | |||
167 | private logInfo(line: string) { | ||
168 | if (Server.config.cargoWatchOptions.trace === 'verbose') { | ||
169 | this.outputChannel.append(line); | ||
170 | } | ||
171 | } | ||
172 | |||
173 | private logError(line: string) { | ||
174 | if ( | ||
175 | Server.config.cargoWatchOptions.trace === 'error' || | ||
176 | Server.config.cargoWatchOptions.trace === 'verbose' | ||
177 | ) { | ||
178 | this.outputChannel.append(line); | ||
179 | } | ||
180 | } | ||
181 | |||
182 | private parseLine(line: string) { | ||
183 | if (line.startsWith('[Running')) { | ||
184 | this.diagnosticCollection.clear(); | ||
185 | this.suggestedFixCollection.clear(); | ||
186 | this.statusDisplay.show(); | ||
187 | } | ||
188 | |||
189 | if (line.startsWith('[Finished running')) { | ||
190 | this.statusDisplay.hide(); | ||
191 | } | ||
192 | |||
193 | interface CargoArtifact { | ||
194 | reason: string; | ||
195 | package_id: string; | ||
196 | } | ||
197 | |||
198 | // https://github.com/rust-lang/cargo/blob/master/src/cargo/util/machine_message.rs | ||
199 | interface CargoMessage { | ||
200 | reason: string; | ||
201 | package_id: string; | ||
202 | message: RustDiagnostic; | ||
203 | } | ||
204 | |||
205 | // cargo-watch itself output non json format | ||
206 | // Ignore these lines | ||
207 | let data: CargoMessage; | ||
208 | try { | ||
209 | data = JSON.parse(line.trim()); | ||
210 | } catch (error) { | ||
211 | this.logError(`Fail to parse to json : { ${error} }`); | ||
212 | return; | ||
213 | } | ||
214 | |||
215 | if (data.reason === 'compiler-artifact') { | ||
216 | const msg = data as CargoArtifact; | ||
217 | |||
218 | // The format of the package_id is "{name} {version} ({source_id})", | ||
219 | // https://github.com/rust-lang/cargo/blob/37ad03f86e895bb80b474c1c088322634f4725f5/src/cargo/core/package_id.rs#L53 | ||
220 | this.statusDisplay.packageName = msg.package_id.split(' ')[0]; | ||
221 | } else if (data.reason === 'compiler-message') { | ||
222 | const msg = data.message as RustDiagnostic; | ||
223 | |||
224 | const mapResult = mapRustDiagnosticToVsCode(msg); | ||
225 | if (!mapResult) { | ||
226 | return; | ||
227 | } | ||
228 | |||
229 | const { location, diagnostic, suggestedFixes } = mapResult; | ||
230 | const fileUri = location.uri; | ||
231 | |||
232 | const diagnostics: vscode.Diagnostic[] = [ | ||
233 | ...(this.diagnosticCollection!.get(fileUri) || []), | ||
234 | ]; | ||
235 | |||
236 | // If we're building multiple targets it's possible we've already seen this diagnostic | ||
237 | const isDuplicate = diagnostics.some(d => | ||
238 | areDiagnosticsEqual(d, diagnostic), | ||
239 | ); | ||
240 | if (isDuplicate) { | ||
241 | return; | ||
242 | } | ||
243 | |||
244 | diagnostics.push(diagnostic); | ||
245 | this.diagnosticCollection!.set(fileUri, diagnostics); | ||
246 | |||
247 | if (suggestedFixes.length) { | ||
248 | for (const suggestedFix of suggestedFixes) { | ||
249 | this.suggestedFixCollection.addSuggestedFixForDiagnostic( | ||
250 | suggestedFix, | ||
251 | diagnostic, | ||
252 | ); | ||
253 | } | ||
254 | |||
255 | // Have VsCode query us for the code actions | ||
256 | vscode.commands.executeCommand( | ||
257 | 'vscode.executeCodeActionProvider', | ||
258 | fileUri, | ||
259 | diagnostic.range, | ||
260 | ); | ||
261 | } | ||
262 | } | ||
263 | } | ||
264 | } | ||
diff --git a/editors/code/src/commands/runnables.ts b/editors/code/src/commands/runnables.ts index cf980e257..7728541de 100644 --- a/editors/code/src/commands/runnables.ts +++ b/editors/code/src/commands/runnables.ts | |||
@@ -1,11 +1,7 @@ | |||
1 | import * as child_process from 'child_process'; | ||
2 | |||
3 | import * as util from 'util'; | ||
4 | import * as vscode from 'vscode'; | 1 | import * as vscode from 'vscode'; |
5 | import * as lc from 'vscode-languageclient'; | 2 | import * as lc from 'vscode-languageclient'; |
6 | 3 | ||
7 | import { Server } from '../server'; | 4 | import { Server } from '../server'; |
8 | import { CargoWatchProvider, registerCargoWatchProvider } from './cargo_watch'; | ||
9 | 5 | ||
10 | interface RunnablesParams { | 6 | interface RunnablesParams { |
11 | textDocument: lc.TextDocumentIdentifier; | 7 | textDocument: lc.TextDocumentIdentifier; |
@@ -131,90 +127,3 @@ export async function handleSingle(runnable: Runnable) { | |||
131 | 127 | ||
132 | return vscode.tasks.executeTask(task); | 128 | return vscode.tasks.executeTask(task); |
133 | } | 129 | } |
134 | |||
135 | /** | ||
136 | * Interactively asks the user whether we should run `cargo check` in order to | ||
137 | * provide inline diagnostics; the user is met with a series of dialog boxes | ||
138 | * that, when accepted, allow us to `cargo install cargo-watch` and then run it. | ||
139 | */ | ||
140 | export async function interactivelyStartCargoWatch( | ||
141 | context: vscode.ExtensionContext, | ||
142 | ): Promise<CargoWatchProvider | undefined> { | ||
143 | if (Server.config.cargoWatchOptions.enableOnStartup === 'disabled') { | ||
144 | return; | ||
145 | } | ||
146 | |||
147 | if (Server.config.cargoWatchOptions.enableOnStartup === 'ask') { | ||
148 | const watch = await vscode.window.showInformationMessage( | ||
149 | 'Start watching changes with cargo? (Executes `cargo watch`, provides inline diagnostics)', | ||
150 | 'yes', | ||
151 | 'no', | ||
152 | ); | ||
153 | if (watch !== 'yes') { | ||
154 | return; | ||
155 | } | ||
156 | } | ||
157 | |||
158 | return startCargoWatch(context); | ||
159 | } | ||
160 | |||
161 | export async function startCargoWatch( | ||
162 | context: vscode.ExtensionContext, | ||
163 | ): Promise<CargoWatchProvider | undefined> { | ||
164 | const execPromise = util.promisify(child_process.exec); | ||
165 | |||
166 | const { stderr, code = 0 } = await execPromise( | ||
167 | 'cargo watch --version', | ||
168 | ).catch(e => e); | ||
169 | |||
170 | if (stderr.includes('no such subcommand: `watch`')) { | ||
171 | const msg = | ||
172 | 'The `cargo-watch` subcommand is not installed. Install? (takes ~1-2 minutes)'; | ||
173 | const install = await vscode.window.showInformationMessage( | ||
174 | msg, | ||
175 | 'yes', | ||
176 | 'no', | ||
177 | ); | ||
178 | if (install !== 'yes') { | ||
179 | return; | ||
180 | } | ||
181 | |||
182 | const label = 'install-cargo-watch'; | ||
183 | const taskFinished = new Promise((resolve, _reject) => { | ||
184 | const disposable = vscode.tasks.onDidEndTask(({ execution }) => { | ||
185 | if (execution.task.name === label) { | ||
186 | disposable.dispose(); | ||
187 | resolve(); | ||
188 | } | ||
189 | }); | ||
190 | }); | ||
191 | |||
192 | vscode.tasks.executeTask( | ||
193 | createTask({ | ||
194 | label, | ||
195 | bin: 'cargo', | ||
196 | args: ['install', 'cargo-watch'], | ||
197 | env: {}, | ||
198 | }), | ||
199 | ); | ||
200 | await taskFinished; | ||
201 | const output = await execPromise('cargo watch --version').catch(e => e); | ||
202 | if (output.stderr !== '') { | ||
203 | vscode.window.showErrorMessage( | ||
204 | `Couldn't install \`cargo-\`watch: ${output.stderr}`, | ||
205 | ); | ||
206 | return; | ||
207 | } | ||
208 | } else if (code !== 0) { | ||
209 | vscode.window.showErrorMessage( | ||
210 | `\`cargo watch\` failed with ${code}: ${stderr}`, | ||
211 | ); | ||
212 | return; | ||
213 | } | ||
214 | |||
215 | const provider = await registerCargoWatchProvider(context.subscriptions); | ||
216 | if (provider) { | ||
217 | provider.start(); | ||
218 | } | ||
219 | return provider; | ||
220 | } | ||
diff --git a/editors/code/src/extension.ts b/editors/code/src/extension.ts index 815f3692c..72a4d4bf2 100644 --- a/editors/code/src/extension.ts +++ b/editors/code/src/extension.ts | |||
@@ -2,13 +2,8 @@ import * as vscode from 'vscode'; | |||
2 | import * as lc from 'vscode-languageclient'; | 2 | import * as lc from 'vscode-languageclient'; |
3 | 3 | ||
4 | import * as commands from './commands'; | 4 | import * as commands from './commands'; |
5 | import { CargoWatchProvider } from './commands/cargo_watch'; | ||
6 | import { ExpandMacroContentProvider } from './commands/expand_macro'; | 5 | import { ExpandMacroContentProvider } from './commands/expand_macro'; |
7 | import { HintsUpdater } from './commands/inlay_hints'; | 6 | import { HintsUpdater } from './commands/inlay_hints'; |
8 | import { | ||
9 | interactivelyStartCargoWatch, | ||
10 | startCargoWatch, | ||
11 | } from './commands/runnables'; | ||
12 | import { SyntaxTreeContentProvider } from './commands/syntaxTree'; | 7 | import { SyntaxTreeContentProvider } from './commands/syntaxTree'; |
13 | import * as events from './events'; | 8 | import * as events from './events'; |
14 | import * as notifications from './notifications'; | 9 | import * as notifications from './notifications'; |
@@ -139,26 +134,6 @@ export async function activate(context: vscode.ExtensionContext) { | |||
139 | 134 | ||
140 | vscode.commands.registerCommand('rust-analyzer.reload', reloadCommand); | 135 | vscode.commands.registerCommand('rust-analyzer.reload', reloadCommand); |
141 | 136 | ||
142 | // Executing `cargo watch` provides us with inline diagnostics on save | ||
143 | let provider: CargoWatchProvider | undefined; | ||
144 | interactivelyStartCargoWatch(context).then(p => { | ||
145 | provider = p; | ||
146 | }); | ||
147 | registerCommand('rust-analyzer.startCargoWatch', () => { | ||
148 | if (provider) { | ||
149 | provider.start(); | ||
150 | } else { | ||
151 | startCargoWatch(context).then(p => { | ||
152 | provider = p; | ||
153 | }); | ||
154 | } | ||
155 | }); | ||
156 | registerCommand('rust-analyzer.stopCargoWatch', () => { | ||
157 | if (provider) { | ||
158 | provider.stop(); | ||
159 | } | ||
160 | }); | ||
161 | |||
162 | // Start the language server, finally! | 137 | // Start the language server, finally! |
163 | try { | 138 | try { |
164 | await startServer(); | 139 | await startServer(); |
diff --git a/editors/code/src/utils/processes.ts b/editors/code/src/utils/processes.ts deleted file mode 100644 index a1d6b7eaf..000000000 --- a/editors/code/src/utils/processes.ts +++ /dev/null | |||
@@ -1,51 +0,0 @@ | |||
1 | 'use strict'; | ||
2 | |||
3 | import * as cp from 'child_process'; | ||
4 | import ChildProcess = cp.ChildProcess; | ||
5 | |||
6 | import { join } from 'path'; | ||
7 | |||
8 | const isWindows = process.platform === 'win32'; | ||
9 | const isMacintosh = process.platform === 'darwin'; | ||
10 | const isLinux = process.platform === 'linux'; | ||
11 | |||
12 | // this is very complex, but is basically copy-pased from VSCode implementation here: | ||
13 | // https://github.com/Microsoft/vscode-languageserver-node/blob/dbfd37e35953ad0ee14c4eeced8cfbc41697b47e/client/src/utils/processes.ts#L15 | ||
14 | |||
15 | // And see discussion at | ||
16 | // https://github.com/rust-analyzer/rust-analyzer/pull/1079#issuecomment-478908109 | ||
17 | |||
18 | export function terminate(process: ChildProcess, cwd?: string): boolean { | ||
19 | if (isWindows) { | ||
20 | try { | ||
21 | // This we run in Atom execFileSync is available. | ||
22 | // Ignore stderr since this is otherwise piped to parent.stderr | ||
23 | // which might be already closed. | ||
24 | const options: any = { | ||
25 | stdio: ['pipe', 'pipe', 'ignore'], | ||
26 | }; | ||
27 | if (cwd) { | ||
28 | options.cwd = cwd; | ||
29 | } | ||
30 | cp.execFileSync( | ||
31 | 'taskkill', | ||
32 | ['/T', '/F', '/PID', process.pid.toString()], | ||
33 | options, | ||
34 | ); | ||
35 | return true; | ||
36 | } catch (err) { | ||
37 | return false; | ||
38 | } | ||
39 | } else if (isLinux || isMacintosh) { | ||
40 | try { | ||
41 | const cmd = join(__dirname, 'terminateProcess.sh'); | ||
42 | const result = cp.spawnSync(cmd, [process.pid.toString()]); | ||
43 | return result.error ? false : true; | ||
44 | } catch (err) { | ||
45 | return false; | ||
46 | } | ||
47 | } else { | ||
48 | process.kill('SIGKILL'); | ||
49 | return true; | ||
50 | } | ||
51 | } | ||
diff --git a/editors/code/src/utils/terminateProcess.sh b/editors/code/src/utils/terminateProcess.sh deleted file mode 100644 index 2ec9e1c2e..000000000 --- a/editors/code/src/utils/terminateProcess.sh +++ /dev/null | |||
@@ -1,12 +0,0 @@ | |||
1 | #!/bin/bash | ||
2 | |||
3 | terminateTree() { | ||
4 | for cpid in $(pgrep -P $1); do | ||
5 | terminateTree $cpid | ||
6 | done | ||
7 | kill -9 $1 > /dev/null 2>&1 | ||
8 | } | ||
9 | |||
10 | for pid in $*; do | ||
11 | terminateTree $pid | ||
12 | done \ No newline at end of file | ||