aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--editors/code/package.json12
-rw-r--r--editors/code/src/commands/cargo_watch.ts168
-rw-r--r--editors/code/src/commands/runnables.ts29
-rw-r--r--editors/code/src/extension.ts4
4 files changed, 177 insertions, 36 deletions
diff --git a/editors/code/package.json b/editors/code/package.json
index facb633d9..240aff6c9 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -223,18 +223,6 @@
223 "${workspaceRoot}" 223 "${workspaceRoot}"
224 ], 224 ],
225 "pattern": "$rustc" 225 "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"
238 } 226 }
239 ] 227 ]
240 } 228 }
diff --git a/editors/code/src/commands/cargo_watch.ts b/editors/code/src/commands/cargo_watch.ts
new file mode 100644
index 000000000..55a1909cb
--- /dev/null
+++ b/editors/code/src/commands/cargo_watch.ts
@@ -0,0 +1,168 @@
1import * as child_process from 'child_process';
2import * as path from 'path';
3import * as vscode from 'vscode';
4import { setInterval } from 'timers';
5
6const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
7
8class StatusDisplay {
9 private i = 0;
10 private statusBarItem: vscode.StatusBarItem;
11 private timer?: NodeJS.Timeout;
12
13 constructor(subscriptions: vscode.Disposable[]) {
14 this.statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 10);
15 subscriptions.push(this.statusBarItem);
16 this.statusBarItem.hide();
17 }
18
19 public show() {
20 this.timer = this.timer || setInterval(() => {
21 this.statusBarItem!.text = "cargo check " + this.frame();
22 }, 300);
23
24 this.statusBarItem!.show();
25 }
26
27 public hide() {
28 if(this.timer) {
29 clearInterval(this.timer);
30 this.timer = undefined;
31 }
32
33 this.statusBarItem!.hide();
34 }
35
36 frame() {
37 return spinnerFrames[this.i = ++this.i % spinnerFrames.length];
38 }
39}
40
41export class CargoWatchProvider {
42 private diagnosticCollection?: vscode.DiagnosticCollection;
43 private cargoProcess?: child_process.ChildProcess;
44 private outBuffer: string = "";
45 private statusDisplay? : StatusDisplay;
46
47 constructor() {
48 }
49
50 public activate(subscriptions: vscode.Disposable[]) {
51 subscriptions.push(this);
52 this.diagnosticCollection = vscode.languages.createDiagnosticCollection("rustc");
53
54 this.statusDisplay = new StatusDisplay(subscriptions);
55
56 // Start the cargo watch with json message
57 this.cargoProcess = child_process.spawn('cargo',
58 ["watch", "-x", "\"check --message-format json\""],
59 {
60 // stdio: ['ignore', 'pipe', 'ignore'],
61 shell: true,
62 cwd: vscode.workspace.rootPath,
63 });
64
65
66 this.cargoProcess.stdout.on('data', (s: string) => {
67 this.processOutput(s);
68 });
69
70 this.cargoProcess.stderr.on('data', (s: string) => {
71 console.error('Error on cargo watch : ' + s);
72 });
73
74 this.cargoProcess.on('error', (err: Error) => {
75 console.error('Error on spawn cargo process : ' + err);
76 });
77 }
78
79 public dispose(): void {
80 if (this.diagnosticCollection) {
81 this.diagnosticCollection.clear();
82 this.diagnosticCollection.dispose();
83 }
84
85 if (this.cargoProcess) {
86 this.cargoProcess.kill();
87 }
88 }
89
90 parseLine(line: string) {
91 if (line.startsWith("[Running")) {
92 this.diagnosticCollection!.clear();
93 this.statusDisplay!.show();
94 }
95
96 if (line.startsWith("[Finished running")) {
97 this.statusDisplay!.hide();
98 }
99
100 function getLevel(s: string): vscode.DiagnosticSeverity {
101 if (s === "error")
102 return vscode.DiagnosticSeverity.Error;
103
104 if (s.startsWith("warn"))
105 return vscode.DiagnosticSeverity.Warning;
106
107 return vscode.DiagnosticSeverity.Information;
108 }
109
110 // cargo-watch itself output non json format
111 // Ignore these lines
112 let data = null;
113 try {
114 data = JSON.parse(line.trim());
115 } catch (error) {
116 return;
117 }
118
119 // Only handle compiler-message now
120 if (data.reason !== "compiler-message") {
121 return;
122 }
123
124 let spans: any[] = data.message.spans;
125 spans = spans.filter(o => o.is_primary);
126 let file_name = null;
127
128 // We only handle primary span right now.
129 if (spans.length > 0) {
130 let o = spans[0];
131
132 console.log("o", o);
133 let rendered = data.message.rendered;
134 let level = getLevel(data.message.level);
135 let range = new vscode.Range(
136 new vscode.Position(o.line_start - 1, o.column_start - 1),
137 new vscode.Position(o.line_end - 1, o.column_end - 1)
138 );
139
140 file_name = path.join(vscode.workspace.rootPath!, o.file_name);
141 const diagnostic = new vscode.Diagnostic(range, rendered, level);
142
143 diagnostic.source = 'rustc';
144 diagnostic.code = data.message.code.code;
145 diagnostic.relatedInformation = [];
146
147 let fileUrl = vscode.Uri.file(file_name!);
148
149 let diagnostics: vscode.Diagnostic[] = [...(this.diagnosticCollection!.get(fileUrl) || [])];
150 diagnostics.push(diagnostic);
151
152 this.diagnosticCollection!.set(fileUrl, diagnostics);
153 }
154 }
155
156 processOutput(chunk: string) {
157 // The stdout is not line based, convert it to line based for proceess.
158 this.outBuffer += chunk;
159 let eolIndex;
160 while ((eolIndex = this.outBuffer.indexOf('\n')) >= 0) {
161 // line includes the EOL
162 const line = this.outBuffer.slice(0, eolIndex + 1);
163 this.parseLine(line);
164 this.outBuffer = this.outBuffer.slice(eolIndex + 1);
165 }
166 }
167
168}
diff --git a/editors/code/src/commands/runnables.ts b/editors/code/src/commands/runnables.ts
index 4187ef4d1..722db158a 100644
--- a/editors/code/src/commands/runnables.ts
+++ b/editors/code/src/commands/runnables.ts
@@ -1,9 +1,11 @@
1import * as child_process from 'child_process'; 1import * as child_process from 'child_process';
2
2import * as util from 'util'; 3import * as util from 'util';
3import * as vscode from 'vscode'; 4import * as vscode from 'vscode';
4import * as lc from 'vscode-languageclient'; 5import * as lc from 'vscode-languageclient';
5 6
6import { Server } from '../server'; 7import { Server } from '../server';
8import { CargoWatchProvider } from './cargo_watch';
7 9
8interface RunnablesParams { 10interface RunnablesParams {
9 textDocument: lc.TextDocumentIdentifier; 11 textDocument: lc.TextDocumentIdentifier;
@@ -127,32 +129,13 @@ export async function handleSingle(runnable: Runnable) {
127 return vscode.tasks.executeTask(task); 129 return vscode.tasks.executeTask(task);
128} 130}
129 131
130export const autoCargoWatchTask: vscode.Task = {
131 name: 'cargo watch',
132 source: 'rust-analyzer',
133 definition: {
134 type: 'watch'
135 },
136 execution: new vscode.ShellExecution('cargo', ['watch'], { cwd: '.' }),
137
138 isBackground: true,
139 problemMatchers: ['$rustc-watch'],
140 presentationOptions: {
141 clear: true
142 },
143 // Not yet exposed in the vscode.d.ts
144 // https://github.com/Microsoft/vscode/blob/ea7c31d770e04b51d586b0d3944f3a7feb03afb9/src/vs/workbench/contrib/tasks/common/tasks.ts#L444-L456
145 runOptions: ({
146 runOn: 2 // RunOnOptions.folderOpen
147 } as unknown) as vscode.RunOptions
148};
149
150/** 132/**
151 * Interactively asks the user whether we should run `cargo check` in order to 133 * Interactively asks the user whether we should run `cargo check` in order to
152 * provide inline diagnostics; the user is met with a series of dialog boxes 134 * provide inline diagnostics; the user is met with a series of dialog boxes
153 * that, when accepted, allow us to `cargo install cargo-watch` and then run it. 135 * that, when accepted, allow us to `cargo install cargo-watch` and then run it.
154 */ 136 */
155export async function interactivelyStartCargoWatch() { 137export async function interactivelyStartCargoWatch(context: vscode.ExtensionContext) {
138
156 if (Server.config.enableCargoWatchOnStartup === 'disabled') { 139 if (Server.config.enableCargoWatchOnStartup === 'disabled') {
157 return; 140 return;
158 } 141 }
@@ -212,5 +195,7 @@ export async function interactivelyStartCargoWatch() {
212 } 195 }
213 } 196 }
214 197
215 vscode.tasks.executeTask(autoCargoWatchTask); 198
199 let validater = new CargoWatchProvider();
200 validater.activate(context.subscriptions);
216} 201}
diff --git a/editors/code/src/extension.ts b/editors/code/src/extension.ts
index 2e13c87de..5cbf285e5 100644
--- a/editors/code/src/extension.ts
+++ b/editors/code/src/extension.ts
@@ -2,7 +2,7 @@ import * as vscode from 'vscode';
2import * as lc from 'vscode-languageclient'; 2import * as lc from 'vscode-languageclient';
3 3
4import * as commands from './commands'; 4import * as commands from './commands';
5import { interactivelyStartCargoWatch } from './commands/runnables'; 5import { interactivelyStartCargoWatch} from './commands/runnables';
6import { SyntaxTreeContentProvider } from './commands/syntaxTree'; 6import { SyntaxTreeContentProvider } from './commands/syntaxTree';
7import * as events from './events'; 7import * as events from './events';
8import * as notifications from './notifications'; 8import * as notifications from './notifications';
@@ -121,7 +121,7 @@ export function activate(context: vscode.ExtensionContext) {
121 ); 121 );
122 122
123 // Executing `cargo watch` provides us with inline diagnostics on save 123 // Executing `cargo watch` provides us with inline diagnostics on save
124 interactivelyStartCargoWatch(); 124 interactivelyStartCargoWatch(context);
125 125
126 // Start the language server, finally! 126 // Start the language server, finally!
127 Server.start(allNotifications); 127 Server.start(allNotifications);