Skip to content

Commit bb3b159

Browse files
bors[bot]edwin0chengedwin0minv
committed
Merge #1079
1079: Improve cargo-watch usage in vscode plugin r=matklad a=edwin0cheng *This PR try to improve current cargo-watch usage in VSCode :* 1. Add Multi-lines error support : ![multilines-error](https://i.imgur.com/gbLEwMG.gif) 2. Add cargo-watch status animation : ![cargo-watch-status](https://i.imgur.com/GbHwzjj.gif) *Implementation Details* * Current VSCode `ProblemMatcher` still do not support multiple line parsing. * However we can, spawn a cargo watch process instead of using vscode.Task to allow more control. * Use `cargo-check --message-format json` to get json format of compiler-message. * Use `vscode.DiagnosticCollection` to manage the problems directly, which allow multiple lines diagnostic. However, * VSCode use non mono-space font for problems, at this moment i cannot find a good solution about it. * I am not so good in typescript, please let me know if anything is bad in this PR. Co-authored-by: Edwin Cheng <[email protected]> Co-authored-by: Edwin Cheng <[email protected]>
2 parents b0d2447 + b60e2f7 commit bb3b159

11 files changed

+448
-44
lines changed

docs/user/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,10 @@ for details.
5959
* `rust-analyzer.raLspServerPath`: path to `ra_lsp_server` executable
6060
* `rust-analyzer.enableCargoWatchOnStartup`: prompt to install & enable `cargo
6161
watch` for live error highlighting (note, this **does not** use rust-analyzer)
62+
* `rust-analyzer.cargo-watch.check-arguments`: cargo-watch check arguments.
63+
(e.g: `--features="shumway,pdf"` will run as `cargo watch -x "check --features="shumway,pdf""` )
6264
* `rust-analyzer.trace.server`: enables internal logging
65+
* `rust-analyzer.trace.cargo-watch`: enables cargo-watch logging
6366

6467

6568
## Emacs

editors/code/package-lock.json

Lines changed: 51 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

editors/code/package.json

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"scripts": {
1919
"vscode:prepublish": "npm run compile",
2020
"package": "vsce package",
21-
"compile": "tsc -p ./",
21+
"compile": "tsc -p ./ && shx cp src/utils/terminateProcess.sh out/utils/terminateProcess.sh",
2222
"watch": "tsc -watch -p ./",
2323
"postinstall": "node ./node_modules/vscode/bin/install",
2424
"fix": "prettier **/*.{json,ts} --write && tslint --project . --fix",
@@ -41,7 +41,8 @@
4141
"tslint-config-prettier": "^1.18.0",
4242
"typescript": "^3.3.1",
4343
"vsce": "^1.57.0",
44-
"vscode": "^1.1.29"
44+
"vscode": "^1.1.29",
45+
"shx": "^0.3.1"
4546
},
4647
"activationEvents": [
4748
"onLanguage:rust",
@@ -183,6 +184,11 @@
183184
],
184185
"description": "Whether to run `cargo watch` on startup"
185186
},
187+
"rust-analyzer.cargo-watch.check-arguments": {
188+
"type": "string",
189+
"description": "`cargo-watch` check arguments. (e.g: `--features=\"shumway,pdf\"` will run as `cargo watch -x \"check --features=\"shumway,pdf\"\"` )",
190+
"default": ""
191+
},
186192
"rust-analyzer.trace.server": {
187193
"type": "string",
188194
"scope": "window",
@@ -191,8 +197,24 @@
191197
"messages",
192198
"verbose"
193199
],
200+
"enumDescriptions": [
201+
"No traces",
202+
"Error only",
203+
"Full log"
204+
],
194205
"default": "off",
195206
"description": "Trace requests to the ra_lsp_server"
207+
},
208+
"rust-analyzer.trace.cargo-watch": {
209+
"type": "string",
210+
"scope": "window",
211+
"enum": [
212+
"off",
213+
"error",
214+
"verbose"
215+
],
216+
"default": "off",
217+
"description": "Trace output of cargo-watch"
196218
}
197219
}
198220
},
@@ -223,18 +245,6 @@
223245
"${workspaceRoot}"
224246
],
225247
"pattern": "$rustc"
226-
},
227-
{
228-
"name": "rustc-watch",
229-
"fileLocation": [
230-
"relative",
231-
"${workspaceRoot}"
232-
],
233-
"background": {
234-
"beginsPattern": "^\\[Running\\b",
235-
"endsPattern": "^\\[Finished running\\b"
236-
},
237-
"pattern": "$rustc"
238248
}
239249
]
240250
}
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
import * as child_process from 'child_process';
2+
import * as fs from 'fs';
3+
import * as path from 'path';
4+
import * as vscode from 'vscode';
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+
export class CargoWatchProvider {
11+
private diagnosticCollection?: vscode.DiagnosticCollection;
12+
private cargoProcess?: child_process.ChildProcess;
13+
private outBuffer: string = '';
14+
private statusDisplay?: StatusDisplay;
15+
private outputChannel?: vscode.OutputChannel;
16+
17+
public activate(subscriptions: vscode.Disposable[]) {
18+
let cargoExists = false;
19+
const cargoTomlFile = path.join(
20+
vscode.workspace.rootPath!,
21+
'Cargo.toml'
22+
);
23+
// Check if the working directory is valid cargo root path
24+
try {
25+
if (fs.existsSync(cargoTomlFile)) {
26+
cargoExists = true;
27+
}
28+
} catch (err) {
29+
cargoExists = false;
30+
}
31+
32+
if (!cargoExists) {
33+
vscode.window.showErrorMessage(
34+
`Couldn\'t find \'Cargo.toml\' in ${cargoTomlFile}`
35+
);
36+
return;
37+
}
38+
39+
subscriptions.push(this);
40+
this.diagnosticCollection = vscode.languages.createDiagnosticCollection(
41+
'rustc'
42+
);
43+
44+
this.statusDisplay = new StatusDisplay(subscriptions);
45+
this.outputChannel = vscode.window.createOutputChannel(
46+
'Cargo Watch Trace'
47+
);
48+
49+
let args = '"check --message-format json';
50+
if (Server.config.cargoWatchOptions.checkArguments.length > 0) {
51+
// Excape the double quote string:
52+
args += ' ' + Server.config.cargoWatchOptions.checkArguments;
53+
}
54+
args += '"';
55+
56+
// Start the cargo watch with json message
57+
this.cargoProcess = child_process.spawn(
58+
'cargo',
59+
['watch', '-x', args],
60+
{
61+
stdio: ['ignore', 'pipe', 'pipe'],
62+
cwd: vscode.workspace.rootPath,
63+
windowsVerbatimArguments: true
64+
}
65+
);
66+
67+
const stdoutData = new LineBuffer();
68+
this.cargoProcess.stdout.on('data', (s: string) => {
69+
stdoutData.processOutput(s, line => {
70+
this.logInfo(line);
71+
this.parseLine(line);
72+
});
73+
});
74+
75+
const stderrData = new LineBuffer();
76+
this.cargoProcess.stderr.on('data', (s: string) => {
77+
stderrData.processOutput(s, line => {
78+
this.logError('Error on cargo-watch : {\n' + line + '}\n');
79+
});
80+
});
81+
82+
this.cargoProcess.on('error', (err: Error) => {
83+
this.logError(
84+
'Error on cargo-watch process : {\n' + err.message + '}\n'
85+
);
86+
});
87+
88+
this.logInfo('cargo-watch started.');
89+
}
90+
91+
public dispose(): void {
92+
if (this.diagnosticCollection) {
93+
this.diagnosticCollection.clear();
94+
this.diagnosticCollection.dispose();
95+
}
96+
97+
if (this.cargoProcess) {
98+
this.cargoProcess.kill();
99+
terminate(this.cargoProcess);
100+
}
101+
102+
if (this.outputChannel) {
103+
this.outputChannel.dispose();
104+
}
105+
}
106+
107+
private logInfo(line: string) {
108+
if (Server.config.cargoWatchOptions.trace === 'verbose') {
109+
this.outputChannel!.append(line);
110+
}
111+
}
112+
113+
private logError(line: string) {
114+
if (
115+
Server.config.cargoWatchOptions.trace === 'error' ||
116+
Server.config.cargoWatchOptions.trace === 'verbose'
117+
) {
118+
this.outputChannel!.append(line);
119+
}
120+
}
121+
122+
private parseLine(line: string) {
123+
if (line.startsWith('[Running')) {
124+
this.diagnosticCollection!.clear();
125+
this.statusDisplay!.show();
126+
}
127+
128+
if (line.startsWith('[Finished running')) {
129+
this.statusDisplay!.hide();
130+
}
131+
132+
function getLevel(s: string): vscode.DiagnosticSeverity {
133+
if (s === 'error') {
134+
return vscode.DiagnosticSeverity.Error;
135+
}
136+
137+
if (s.startsWith('warn')) {
138+
return vscode.DiagnosticSeverity.Warning;
139+
}
140+
141+
return vscode.DiagnosticSeverity.Information;
142+
}
143+
144+
interface ErrorSpan {
145+
line_start: number;
146+
line_end: number;
147+
column_start: number;
148+
column_end: number;
149+
}
150+
151+
interface ErrorMessage {
152+
reason: string;
153+
message: {
154+
spans: ErrorSpan[];
155+
rendered: string;
156+
level: string;
157+
code?: {
158+
code: string;
159+
};
160+
};
161+
}
162+
163+
// cargo-watch itself output non json format
164+
// Ignore these lines
165+
let data: ErrorMessage;
166+
try {
167+
data = JSON.parse(line.trim());
168+
} catch (error) {
169+
this.logError(`Fail to pass to json : { ${error} }`);
170+
return;
171+
}
172+
173+
// Only handle compiler-message now
174+
if (data.reason !== 'compiler-message') {
175+
return;
176+
}
177+
178+
let spans: any[] = data.message.spans;
179+
spans = spans.filter(o => o.is_primary);
180+
181+
// We only handle primary span right now.
182+
if (spans.length > 0) {
183+
const o = spans[0];
184+
185+
const rendered = data.message.rendered;
186+
const level = getLevel(data.message.level);
187+
const range = new vscode.Range(
188+
new vscode.Position(o.line_start - 1, o.column_start - 1),
189+
new vscode.Position(o.line_end - 1, o.column_end - 1)
190+
);
191+
192+
const fileName = path.join(vscode.workspace.rootPath!, o.file_name);
193+
const diagnostic = new vscode.Diagnostic(range, rendered, level);
194+
195+
diagnostic.source = 'rustc';
196+
diagnostic.code = data.message.code
197+
? data.message.code.code
198+
: undefined;
199+
diagnostic.relatedInformation = [];
200+
201+
const fileUrl = vscode.Uri.file(fileName!);
202+
203+
const diagnostics: vscode.Diagnostic[] = [
204+
...(this.diagnosticCollection!.get(fileUrl) || [])
205+
];
206+
diagnostics.push(diagnostic);
207+
208+
this.diagnosticCollection!.set(fileUrl, diagnostics);
209+
}
210+
}
211+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
export class LineBuffer {
2+
private outBuffer: string = '';
3+
4+
public processOutput(chunk: string, cb: (line: string) => void) {
5+
this.outBuffer += chunk;
6+
let eolIndex = this.outBuffer.indexOf('\n');
7+
while (eolIndex >= 0) {
8+
// line includes the EOL
9+
const line = this.outBuffer.slice(0, eolIndex + 1);
10+
cb(line);
11+
this.outBuffer = this.outBuffer.slice(eolIndex + 1);
12+
13+
eolIndex = this.outBuffer.indexOf('\n');
14+
}
15+
}
16+
}

0 commit comments

Comments
 (0)