Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/jake/src/main.ts
3291 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import * as path from 'path';
7
import * as fs from 'fs';
8
import * as cp from 'child_process';
9
import * as vscode from 'vscode';
10
11
type AutoDetect = 'on' | 'off';
12
13
function exists(file: string): Promise<boolean> {
14
return new Promise<boolean>((resolve, _reject) => {
15
fs.exists(file, (value) => {
16
resolve(value);
17
});
18
});
19
}
20
21
function exec(command: string, options: cp.ExecOptions): Promise<{ stdout: string; stderr: string }> {
22
return new Promise<{ stdout: string; stderr: string }>((resolve, reject) => {
23
cp.exec(command, options, (error, stdout, stderr) => {
24
if (error) {
25
reject({ error, stdout, stderr });
26
}
27
resolve({ stdout, stderr });
28
});
29
});
30
}
31
32
const buildNames: string[] = ['build', 'compile', 'watch'];
33
function isBuildTask(name: string): boolean {
34
for (const buildName of buildNames) {
35
if (name.indexOf(buildName) !== -1) {
36
return true;
37
}
38
}
39
return false;
40
}
41
42
const testNames: string[] = ['test'];
43
function isTestTask(name: string): boolean {
44
for (const testName of testNames) {
45
if (name.indexOf(testName) !== -1) {
46
return true;
47
}
48
}
49
return false;
50
}
51
52
let _channel: vscode.OutputChannel;
53
function getOutputChannel(): vscode.OutputChannel {
54
if (!_channel) {
55
_channel = vscode.window.createOutputChannel('Jake Auto Detection');
56
}
57
return _channel;
58
}
59
60
function showError() {
61
vscode.window.showWarningMessage(vscode.l10n.t("Problem finding jake tasks. See the output for more information."),
62
vscode.l10n.t("Go to output")).then(() => {
63
getOutputChannel().show(true);
64
});
65
}
66
67
async function findJakeCommand(rootPath: string): Promise<string> {
68
let jakeCommand: string;
69
const platform = process.platform;
70
if (platform === 'win32' && await exists(path.join(rootPath!, 'node_modules', '.bin', 'jake.cmd'))) {
71
jakeCommand = path.join('.', 'node_modules', '.bin', 'jake.cmd');
72
} else if ((platform === 'linux' || platform === 'darwin') && await exists(path.join(rootPath!, 'node_modules', '.bin', 'jake'))) {
73
jakeCommand = path.join('.', 'node_modules', '.bin', 'jake');
74
} else {
75
jakeCommand = 'jake';
76
}
77
return jakeCommand;
78
}
79
80
interface JakeTaskDefinition extends vscode.TaskDefinition {
81
task: string;
82
file?: string;
83
}
84
85
class FolderDetector {
86
87
private fileWatcher: vscode.FileSystemWatcher | undefined;
88
private promise: Thenable<vscode.Task[]> | undefined;
89
90
constructor(
91
private _workspaceFolder: vscode.WorkspaceFolder,
92
private _jakeCommand: Promise<string>) {
93
}
94
95
public get workspaceFolder(): vscode.WorkspaceFolder {
96
return this._workspaceFolder;
97
}
98
99
public isEnabled(): boolean {
100
return vscode.workspace.getConfiguration('jake', this._workspaceFolder.uri).get<AutoDetect>('autoDetect') === 'on';
101
}
102
103
public start(): void {
104
const pattern = path.join(this._workspaceFolder.uri.fsPath, '{node_modules,Jakefile,Jakefile.js}');
105
this.fileWatcher = vscode.workspace.createFileSystemWatcher(pattern);
106
this.fileWatcher.onDidChange(() => this.promise = undefined);
107
this.fileWatcher.onDidCreate(() => this.promise = undefined);
108
this.fileWatcher.onDidDelete(() => this.promise = undefined);
109
}
110
111
public async getTasks(): Promise<vscode.Task[]> {
112
if (this.isEnabled()) {
113
if (!this.promise) {
114
this.promise = this.computeTasks();
115
}
116
return this.promise;
117
} else {
118
return [];
119
}
120
}
121
122
public async getTask(_task: vscode.Task): Promise<vscode.Task | undefined> {
123
const jakeTask = (<any>_task.definition).task;
124
if (jakeTask) {
125
const kind: JakeTaskDefinition = (<any>_task.definition);
126
const options: vscode.ShellExecutionOptions = { cwd: this.workspaceFolder.uri.fsPath };
127
const task = new vscode.Task(kind, this.workspaceFolder, jakeTask, 'jake', new vscode.ShellExecution(await this._jakeCommand, [jakeTask], options));
128
return task;
129
}
130
return undefined;
131
}
132
133
private async computeTasks(): Promise<vscode.Task[]> {
134
const rootPath = this._workspaceFolder.uri.scheme === 'file' ? this._workspaceFolder.uri.fsPath : undefined;
135
const emptyTasks: vscode.Task[] = [];
136
if (!rootPath) {
137
return emptyTasks;
138
}
139
let jakefile = path.join(rootPath, 'Jakefile');
140
if (!await exists(jakefile)) {
141
jakefile = path.join(rootPath, 'Jakefile.js');
142
if (! await exists(jakefile)) {
143
return emptyTasks;
144
}
145
}
146
147
const commandLine = `${await this._jakeCommand} --tasks`;
148
try {
149
const { stdout, stderr } = await exec(commandLine, { cwd: rootPath });
150
if (stderr) {
151
getOutputChannel().appendLine(stderr);
152
showError();
153
}
154
const result: vscode.Task[] = [];
155
if (stdout) {
156
const lines = stdout.split(/\r{0,1}\n/);
157
for (const line of lines) {
158
if (line.length === 0) {
159
continue;
160
}
161
const regExp = /^jake\s+([^\s]+)\s/g;
162
const matches = regExp.exec(line);
163
if (matches && matches.length === 2) {
164
const taskName = matches[1];
165
const kind: JakeTaskDefinition = {
166
type: 'jake',
167
task: taskName
168
};
169
const options: vscode.ShellExecutionOptions = { cwd: this.workspaceFolder.uri.fsPath };
170
const task = new vscode.Task(kind, taskName, 'jake', new vscode.ShellExecution(`${await this._jakeCommand} ${taskName}`, options));
171
result.push(task);
172
const lowerCaseLine = line.toLowerCase();
173
if (isBuildTask(lowerCaseLine)) {
174
task.group = vscode.TaskGroup.Build;
175
} else if (isTestTask(lowerCaseLine)) {
176
task.group = vscode.TaskGroup.Test;
177
}
178
}
179
}
180
}
181
return result;
182
} catch (err) {
183
const channel = getOutputChannel();
184
if (err.stderr) {
185
channel.appendLine(err.stderr);
186
}
187
if (err.stdout) {
188
channel.appendLine(err.stdout);
189
}
190
channel.appendLine(vscode.l10n.t("Auto detecting Jake for folder {0} failed with error: {1}', this.workspaceFolder.name, err.error ? err.error.toString() : 'unknown"));
191
showError();
192
return emptyTasks;
193
}
194
}
195
196
public dispose() {
197
this.promise = undefined;
198
if (this.fileWatcher) {
199
this.fileWatcher.dispose();
200
}
201
}
202
}
203
204
class TaskDetector {
205
206
private taskProvider: vscode.Disposable | undefined;
207
private detectors: Map<string, FolderDetector> = new Map();
208
209
constructor() {
210
}
211
212
public start(): void {
213
const folders = vscode.workspace.workspaceFolders;
214
if (folders) {
215
this.updateWorkspaceFolders(folders, []);
216
}
217
vscode.workspace.onDidChangeWorkspaceFolders((event) => this.updateWorkspaceFolders(event.added, event.removed));
218
vscode.workspace.onDidChangeConfiguration(this.updateConfiguration, this);
219
}
220
221
public dispose(): void {
222
if (this.taskProvider) {
223
this.taskProvider.dispose();
224
this.taskProvider = undefined;
225
}
226
this.detectors.clear();
227
}
228
229
private updateWorkspaceFolders(added: readonly vscode.WorkspaceFolder[], removed: readonly vscode.WorkspaceFolder[]): void {
230
for (const remove of removed) {
231
const detector = this.detectors.get(remove.uri.toString());
232
if (detector) {
233
detector.dispose();
234
this.detectors.delete(remove.uri.toString());
235
}
236
}
237
for (const add of added) {
238
const detector = new FolderDetector(add, findJakeCommand(add.uri.fsPath));
239
this.detectors.set(add.uri.toString(), detector);
240
if (detector.isEnabled()) {
241
detector.start();
242
}
243
}
244
this.updateProvider();
245
}
246
247
private updateConfiguration(): void {
248
for (const detector of this.detectors.values()) {
249
detector.dispose();
250
this.detectors.delete(detector.workspaceFolder.uri.toString());
251
}
252
const folders = vscode.workspace.workspaceFolders;
253
if (folders) {
254
for (const folder of folders) {
255
if (!this.detectors.has(folder.uri.toString())) {
256
const detector = new FolderDetector(folder, findJakeCommand(folder.uri.fsPath));
257
this.detectors.set(folder.uri.toString(), detector);
258
if (detector.isEnabled()) {
259
detector.start();
260
}
261
}
262
}
263
}
264
this.updateProvider();
265
}
266
267
private updateProvider(): void {
268
if (!this.taskProvider && this.detectors.size > 0) {
269
const thisCapture = this;
270
this.taskProvider = vscode.tasks.registerTaskProvider('jake', {
271
provideTasks(): Promise<vscode.Task[]> {
272
return thisCapture.getTasks();
273
},
274
resolveTask(_task: vscode.Task): Promise<vscode.Task | undefined> {
275
return thisCapture.getTask(_task);
276
}
277
});
278
}
279
else if (this.taskProvider && this.detectors.size === 0) {
280
this.taskProvider.dispose();
281
this.taskProvider = undefined;
282
}
283
}
284
285
public getTasks(): Promise<vscode.Task[]> {
286
return this.computeTasks();
287
}
288
289
private computeTasks(): Promise<vscode.Task[]> {
290
if (this.detectors.size === 0) {
291
return Promise.resolve([]);
292
} else if (this.detectors.size === 1) {
293
return this.detectors.values().next().value!.getTasks();
294
} else {
295
const promises: Promise<vscode.Task[]>[] = [];
296
for (const detector of this.detectors.values()) {
297
promises.push(detector.getTasks().then((value) => value, () => []));
298
}
299
return Promise.all(promises).then((values) => {
300
const result: vscode.Task[] = [];
301
for (const tasks of values) {
302
if (tasks && tasks.length > 0) {
303
result.push(...tasks);
304
}
305
}
306
return result;
307
});
308
}
309
}
310
311
public async getTask(task: vscode.Task): Promise<vscode.Task | undefined> {
312
if (this.detectors.size === 0) {
313
return undefined;
314
} else if (this.detectors.size === 1) {
315
return this.detectors.values().next().value!.getTask(task);
316
} else {
317
if ((task.scope === vscode.TaskScope.Workspace) || (task.scope === vscode.TaskScope.Global)) {
318
// Not supported, we don't have enough info to create the task.
319
return undefined;
320
} else if (task.scope) {
321
const detector = this.detectors.get(task.scope.uri.toString());
322
if (detector) {
323
return detector.getTask(task);
324
}
325
}
326
return undefined;
327
}
328
}
329
}
330
331
let detector: TaskDetector;
332
export function activate(_context: vscode.ExtensionContext): void {
333
detector = new TaskDetector();
334
detector.start();
335
}
336
337
export function deactivate(): void {
338
detector.dispose();
339
}
340
341