diff options
-rw-r--r-- | docs/user/features.md | 8 | ||||
-rw-r--r-- | editors/code/package.json | 22 | ||||
-rw-r--r-- | editors/code/src/commands/cargo_watch.ts | 102 | ||||
-rw-r--r-- | editors/code/src/commands/runnables.ts | 17 | ||||
-rw-r--r-- | editors/code/src/commands/watch_status.ts | 18 | ||||
-rw-r--r-- | editors/code/src/extension.ts | 25 |
6 files changed, 137 insertions, 55 deletions
diff --git a/docs/user/features.md b/docs/user/features.md index 09a7f5a43..cffbb4c7f 100644 --- a/docs/user/features.md +++ b/docs/user/features.md | |||
@@ -76,6 +76,14 @@ Shows internal statistic about memory usage of rust-analyzer | |||
76 | 76 | ||
77 | Manually triggers GC | 77 | Manually triggers GC |
78 | 78 | ||
79 | #### Start Cargo Watch | ||
80 | |||
81 | Start `cargo watch` for live error highlighting. Will prompt to install if it's not already installed. | ||
82 | |||
83 | #### Stop Cargo Watch | ||
84 | |||
85 | Stop `cargo watch` | ||
86 | |||
79 | ### Code Actions (Assists) | 87 | ### Code Actions (Assists) |
80 | 88 | ||
81 | These are triggered in a particular context via light bulb. We use custom code on | 89 | These are triggered in a particular context via light bulb. We use custom code on |
diff --git a/editors/code/package.json b/editors/code/package.json index a0454191a..83ceb19f7 100644 --- a/editors/code/package.json +++ b/editors/code/package.json | |||
@@ -119,6 +119,16 @@ | |||
119 | "command": "rust-analyzer.reload", | 119 | "command": "rust-analyzer.reload", |
120 | "title": "Restart server", | 120 | "title": "Restart server", |
121 | "category": "Rust Analyzer" | 121 | "category": "Rust Analyzer" |
122 | }, | ||
123 | { | ||
124 | "command": "rust-analyzer.startCargoWatch", | ||
125 | "title": "Start Cargo Watch", | ||
126 | "category": "Rust Analyzer" | ||
127 | }, | ||
128 | { | ||
129 | "command": "rust-analyzer.stopCargoWatch", | ||
130 | "title": "Stop Cargo Watch", | ||
131 | "category": "Rust Analyzer" | ||
122 | } | 132 | } |
123 | ], | 133 | ], |
124 | "keybindings": [ | 134 | "keybindings": [ |
@@ -250,6 +260,18 @@ | |||
250 | "${workspaceRoot}" | 260 | "${workspaceRoot}" |
251 | ], | 261 | ], |
252 | "pattern": "$rustc" | 262 | "pattern": "$rustc" |
263 | }, | ||
264 | { | ||
265 | "name": "rustc-watch", | ||
266 | "fileLocation": [ | ||
267 | "relative", | ||
268 | "${workspaceRoot}" | ||
269 | ], | ||
270 | "background": { | ||
271 | "beginsPattern": "^\\[Running\\b", | ||
272 | "endsPattern": "^\\[Finished running\\b" | ||
273 | }, | ||
274 | "pattern": "$rustc" | ||
253 | } | 275 | } |
254 | ] | 276 | ] |
255 | } | 277 | } |
diff --git a/editors/code/src/commands/cargo_watch.ts b/editors/code/src/commands/cargo_watch.ts index 32bd38a1c..1d939e28c 100644 --- a/editors/code/src/commands/cargo_watch.ts +++ b/editors/code/src/commands/cargo_watch.ts | |||
@@ -7,44 +7,55 @@ import { terminate } from '../utils/processes'; | |||
7 | import { LineBuffer } from './line_buffer'; | 7 | import { LineBuffer } from './line_buffer'; |
8 | import { StatusDisplay } from './watch_status'; | 8 | import { StatusDisplay } from './watch_status'; |
9 | 9 | ||
10 | export class CargoWatchProvider { | 10 | export function registerCargoWatchProvider( |
11 | private diagnosticCollection?: vscode.DiagnosticCollection; | 11 | subscriptions: vscode.Disposable[] |
12 | private cargoProcess?: child_process.ChildProcess; | 12 | ): CargoWatchProvider | undefined { |
13 | private outBuffer: string = ''; | 13 | let cargoExists = false; |
14 | private statusDisplay?: StatusDisplay; | 14 | const cargoTomlFile = path.join(vscode.workspace.rootPath!, 'Cargo.toml'); |
15 | private outputChannel?: vscode.OutputChannel; | 15 | // Check if the working directory is valid cargo root path |
16 | 16 | try { | |
17 | public activate(subscriptions: vscode.Disposable[]) { | 17 | if (fs.existsSync(cargoTomlFile)) { |
18 | let cargoExists = false; | 18 | cargoExists = true; |
19 | const cargoTomlFile = path.join( | 19 | } |
20 | vscode.workspace.rootPath!, | 20 | } catch (err) { |
21 | 'Cargo.toml' | 21 | cargoExists = false; |
22 | } | ||
23 | |||
24 | if (!cargoExists) { | ||
25 | vscode.window.showErrorMessage( | ||
26 | `Couldn\'t find \'Cargo.toml\' in ${cargoTomlFile}` | ||
22 | ); | 27 | ); |
23 | // Check if the working directory is valid cargo root path | 28 | return; |
24 | try { | 29 | } |
25 | if (fs.existsSync(cargoTomlFile)) { | ||
26 | cargoExists = true; | ||
27 | } | ||
28 | } catch (err) { | ||
29 | cargoExists = false; | ||
30 | } | ||
31 | 30 | ||
32 | if (!cargoExists) { | 31 | const provider = new CargoWatchProvider(); |
33 | vscode.window.showErrorMessage( | 32 | subscriptions.push(provider); |
34 | `Couldn\'t find \'Cargo.toml\' in ${cargoTomlFile}` | 33 | return provider; |
35 | ); | 34 | } |
36 | return; | ||
37 | } | ||
38 | 35 | ||
39 | subscriptions.push(this); | 36 | export class CargoWatchProvider implements vscode.Disposable { |
37 | private readonly diagnosticCollection: vscode.DiagnosticCollection; | ||
38 | private readonly statusDisplay: StatusDisplay; | ||
39 | private readonly outputChannel: vscode.OutputChannel; | ||
40 | private cargoProcess?: child_process.ChildProcess; | ||
41 | |||
42 | constructor() { | ||
40 | this.diagnosticCollection = vscode.languages.createDiagnosticCollection( | 43 | this.diagnosticCollection = vscode.languages.createDiagnosticCollection( |
41 | 'rustc' | 44 | 'rustc' |
42 | ); | 45 | ); |
43 | 46 | this.statusDisplay = new StatusDisplay(); | |
44 | this.statusDisplay = new StatusDisplay(subscriptions); | ||
45 | this.outputChannel = vscode.window.createOutputChannel( | 47 | this.outputChannel = vscode.window.createOutputChannel( |
46 | 'Cargo Watch Trace' | 48 | 'Cargo Watch Trace' |
47 | ); | 49 | ); |
50 | } | ||
51 | |||
52 | public start() { | ||
53 | if (this.cargoProcess) { | ||
54 | vscode.window.showInformationMessage( | ||
55 | 'Cargo Watch is already running' | ||
56 | ); | ||
57 | return; | ||
58 | } | ||
48 | 59 | ||
49 | let args = 'check --message-format json'; | 60 | let args = 'check --message-format json'; |
50 | if (Server.config.cargoWatchOptions.checkArguments.length > 0) { | 61 | if (Server.config.cargoWatchOptions.checkArguments.length > 0) { |
@@ -95,25 +106,28 @@ export class CargoWatchProvider { | |||
95 | this.logInfo('cargo-watch started.'); | 106 | this.logInfo('cargo-watch started.'); |
96 | } | 107 | } |
97 | 108 | ||
98 | public dispose(): void { | 109 | public stop() { |
99 | if (this.diagnosticCollection) { | ||
100 | this.diagnosticCollection.clear(); | ||
101 | this.diagnosticCollection.dispose(); | ||
102 | } | ||
103 | |||
104 | if (this.cargoProcess) { | 110 | if (this.cargoProcess) { |
105 | this.cargoProcess.kill(); | 111 | this.cargoProcess.kill(); |
106 | terminate(this.cargoProcess); | 112 | terminate(this.cargoProcess); |
113 | this.cargoProcess = undefined; | ||
114 | } else { | ||
115 | vscode.window.showInformationMessage('Cargo Watch is not running'); | ||
107 | } | 116 | } |
117 | } | ||
108 | 118 | ||
109 | if (this.outputChannel) { | 119 | public dispose(): void { |
110 | this.outputChannel.dispose(); | 120 | this.stop(); |
111 | } | 121 | |
122 | this.diagnosticCollection.clear(); | ||
123 | this.diagnosticCollection.dispose(); | ||
124 | this.outputChannel.dispose(); | ||
125 | this.statusDisplay.dispose(); | ||
112 | } | 126 | } |
113 | 127 | ||
114 | private logInfo(line: string) { | 128 | private logInfo(line: string) { |
115 | if (Server.config.cargoWatchOptions.trace === 'verbose') { | 129 | if (Server.config.cargoWatchOptions.trace === 'verbose') { |
116 | this.outputChannel!.append(line); | 130 | this.outputChannel.append(line); |
117 | } | 131 | } |
118 | } | 132 | } |
119 | 133 | ||
@@ -122,18 +136,18 @@ export class CargoWatchProvider { | |||
122 | Server.config.cargoWatchOptions.trace === 'error' || | 136 | Server.config.cargoWatchOptions.trace === 'error' || |
123 | Server.config.cargoWatchOptions.trace === 'verbose' | 137 | Server.config.cargoWatchOptions.trace === 'verbose' |
124 | ) { | 138 | ) { |
125 | this.outputChannel!.append(line); | 139 | this.outputChannel.append(line); |
126 | } | 140 | } |
127 | } | 141 | } |
128 | 142 | ||
129 | private parseLine(line: string) { | 143 | private parseLine(line: string) { |
130 | if (line.startsWith('[Running')) { | 144 | if (line.startsWith('[Running')) { |
131 | this.diagnosticCollection!.clear(); | 145 | this.diagnosticCollection.clear(); |
132 | this.statusDisplay!.show(); | 146 | this.statusDisplay.show(); |
133 | } | 147 | } |
134 | 148 | ||
135 | if (line.startsWith('[Finished running')) { | 149 | if (line.startsWith('[Finished running')) { |
136 | this.statusDisplay!.hide(); | 150 | this.statusDisplay.hide(); |
137 | } | 151 | } |
138 | 152 | ||
139 | function getLevel(s: string): vscode.DiagnosticSeverity { | 153 | function getLevel(s: string): vscode.DiagnosticSeverity { |
@@ -193,7 +207,7 @@ export class CargoWatchProvider { | |||
193 | 207 | ||
194 | // The format of the package_id is "{name} {version} ({source_id})", | 208 | // The format of the package_id is "{name} {version} ({source_id})", |
195 | // https://github.com/rust-lang/cargo/blob/37ad03f86e895bb80b474c1c088322634f4725f5/src/cargo/core/package_id.rs#L53 | 209 | // https://github.com/rust-lang/cargo/blob/37ad03f86e895bb80b474c1c088322634f4725f5/src/cargo/core/package_id.rs#L53 |
196 | this.statusDisplay!.packageName = msg.package_id.split(' ')[0]; | 210 | this.statusDisplay.packageName = msg.package_id.split(' ')[0]; |
197 | } else if (data.reason === 'compiler-message') { | 211 | } else if (data.reason === 'compiler-message') { |
198 | const msg = data.message as RustDiagnostic; | 212 | const msg = data.message as RustDiagnostic; |
199 | 213 | ||
diff --git a/editors/code/src/commands/runnables.ts b/editors/code/src/commands/runnables.ts index c4df24c79..26372c1e8 100644 --- a/editors/code/src/commands/runnables.ts +++ b/editors/code/src/commands/runnables.ts | |||
@@ -5,7 +5,7 @@ import * as vscode from 'vscode'; | |||
5 | import * as lc from 'vscode-languageclient'; | 5 | import * as lc from 'vscode-languageclient'; |
6 | 6 | ||
7 | import { Server } from '../server'; | 7 | import { Server } from '../server'; |
8 | import { CargoWatchProvider } from './cargo_watch'; | 8 | import { CargoWatchProvider, registerCargoWatchProvider } from './cargo_watch'; |
9 | 9 | ||
10 | interface RunnablesParams { | 10 | interface RunnablesParams { |
11 | textDocument: lc.TextDocumentIdentifier; | 11 | textDocument: lc.TextDocumentIdentifier; |
@@ -137,7 +137,7 @@ export async function handleSingle(runnable: Runnable) { | |||
137 | */ | 137 | */ |
138 | export async function interactivelyStartCargoWatch( | 138 | export async function interactivelyStartCargoWatch( |
139 | context: vscode.ExtensionContext | 139 | context: vscode.ExtensionContext |
140 | ) { | 140 | ): Promise<CargoWatchProvider | undefined> { |
141 | if (Server.config.cargoWatchOptions.enableOnStartup === 'disabled') { | 141 | if (Server.config.cargoWatchOptions.enableOnStartup === 'disabled') { |
142 | return; | 142 | return; |
143 | } | 143 | } |
@@ -153,6 +153,12 @@ export async function interactivelyStartCargoWatch( | |||
153 | } | 153 | } |
154 | } | 154 | } |
155 | 155 | ||
156 | return startCargoWatch(context); | ||
157 | } | ||
158 | |||
159 | export async function startCargoWatch( | ||
160 | context: vscode.ExtensionContext | ||
161 | ): Promise<CargoWatchProvider | undefined> { | ||
156 | const execPromise = util.promisify(child_process.exec); | 162 | const execPromise = util.promisify(child_process.exec); |
157 | 163 | ||
158 | const { stderr } = await execPromise('cargo watch --version').catch(e => e); | 164 | const { stderr } = await execPromise('cargo watch --version').catch(e => e); |
@@ -197,6 +203,9 @@ export async function interactivelyStartCargoWatch( | |||
197 | } | 203 | } |
198 | } | 204 | } |
199 | 205 | ||
200 | const validater = new CargoWatchProvider(); | 206 | const provider = await registerCargoWatchProvider(context.subscriptions); |
201 | validater.activate(context.subscriptions); | 207 | if (provider) { |
208 | provider.start(); | ||
209 | } | ||
210 | return provider; | ||
202 | } | 211 | } |
diff --git a/editors/code/src/commands/watch_status.ts b/editors/code/src/commands/watch_status.ts index 86ae821de..a3b0178f2 100644 --- a/editors/code/src/commands/watch_status.ts +++ b/editors/code/src/commands/watch_status.ts | |||
@@ -2,19 +2,18 @@ import * as vscode from 'vscode'; | |||
2 | 2 | ||
3 | const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; | 3 | const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; |
4 | 4 | ||
5 | export class StatusDisplay { | 5 | export class StatusDisplay implements vscode.Disposable { |
6 | public packageName?: string; | 6 | public packageName?: string; |
7 | 7 | ||
8 | private i = 0; | 8 | private i = 0; |
9 | private statusBarItem: vscode.StatusBarItem; | 9 | private statusBarItem: vscode.StatusBarItem; |
10 | private timer?: NodeJS.Timeout; | 10 | private timer?: NodeJS.Timeout; |
11 | 11 | ||
12 | constructor(subscriptions: vscode.Disposable[]) { | 12 | constructor() { |
13 | this.statusBarItem = vscode.window.createStatusBarItem( | 13 | this.statusBarItem = vscode.window.createStatusBarItem( |
14 | vscode.StatusBarAlignment.Left, | 14 | vscode.StatusBarAlignment.Left, |
15 | 10 | 15 | 10 |
16 | ); | 16 | ); |
17 | subscriptions.push(this.statusBarItem); | ||
18 | this.statusBarItem.hide(); | 17 | this.statusBarItem.hide(); |
19 | } | 18 | } |
20 | 19 | ||
@@ -33,7 +32,7 @@ export class StatusDisplay { | |||
33 | } | 32 | } |
34 | }, 300); | 33 | }, 300); |
35 | 34 | ||
36 | this.statusBarItem!.show(); | 35 | this.statusBarItem.show(); |
37 | } | 36 | } |
38 | 37 | ||
39 | public hide() { | 38 | public hide() { |
@@ -42,7 +41,16 @@ export class StatusDisplay { | |||
42 | this.timer = undefined; | 41 | this.timer = undefined; |
43 | } | 42 | } |
44 | 43 | ||
45 | this.statusBarItem!.hide(); | 44 | this.statusBarItem.hide(); |
45 | } | ||
46 | |||
47 | public dispose() { | ||
48 | if (this.timer) { | ||
49 | clearInterval(this.timer); | ||
50 | this.timer = undefined; | ||
51 | } | ||
52 | |||
53 | this.statusBarItem.dispose(); | ||
46 | } | 54 | } |
47 | 55 | ||
48 | private frame() { | 56 | private frame() { |
diff --git a/editors/code/src/extension.ts b/editors/code/src/extension.ts index 1073a36a0..48dd2a614 100644 --- a/editors/code/src/extension.ts +++ b/editors/code/src/extension.ts | |||
@@ -2,7 +2,11 @@ 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 { interactivelyStartCargoWatch } from './commands/runnables'; | 5 | import { CargoWatchProvider } from './commands/cargo_watch'; |
6 | import { | ||
7 | interactivelyStartCargoWatch, | ||
8 | startCargoWatch | ||
9 | } from './commands/runnables'; | ||
6 | import { SyntaxTreeContentProvider } from './commands/syntaxTree'; | 10 | import { SyntaxTreeContentProvider } from './commands/syntaxTree'; |
7 | import * as events from './events'; | 11 | import * as events from './events'; |
8 | import * as notifications from './notifications'; | 12 | import * as notifications from './notifications'; |
@@ -126,7 +130,24 @@ export function activate(context: vscode.ExtensionContext) { | |||
126 | vscode.commands.registerCommand('rust-analyzer.reload', reloadCommand); | 130 | vscode.commands.registerCommand('rust-analyzer.reload', reloadCommand); |
127 | 131 | ||
128 | // Executing `cargo watch` provides us with inline diagnostics on save | 132 | // Executing `cargo watch` provides us with inline diagnostics on save |
129 | interactivelyStartCargoWatch(context); | 133 | let provider: CargoWatchProvider | undefined; |
134 | interactivelyStartCargoWatch(context).then(p => { | ||
135 | provider = p; | ||
136 | }); | ||
137 | registerCommand('rust-analyzer.startCargoWatch', () => { | ||
138 | if (provider) { | ||
139 | provider.start(); | ||
140 | } else { | ||
141 | startCargoWatch(context).then(p => { | ||
142 | provider = p; | ||
143 | }); | ||
144 | } | ||
145 | }); | ||
146 | registerCommand('rust-analyzer.stopCargoWatch', () => { | ||
147 | if (provider) { | ||
148 | provider.stop(); | ||
149 | } | ||
150 | }); | ||
130 | 151 | ||
131 | // Start the language server, finally! | 152 | // Start the language server, finally! |
132 | startServer(); | 153 | startServer(); |