@opensumi/ide-task 2.21.13 → 2.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/lib/browser/index.js +2 -2
  2. package/lib/browser/index.js.map +1 -1
  3. package/lib/browser/parser.js +4 -4
  4. package/lib/browser/parser.js.map +1 -1
  5. package/lib/browser/task-config.d.ts +4 -4
  6. package/lib/browser/task-config.d.ts.map +1 -1
  7. package/lib/browser/task-config.js +31 -31
  8. package/lib/browser/task-config.js.map +1 -1
  9. package/lib/browser/task-executor.d.ts.map +1 -1
  10. package/lib/browser/task-executor.js +16 -22
  11. package/lib/browser/task-executor.js.map +1 -1
  12. package/lib/browser/task-preferences.contribution.js.map +1 -1
  13. package/lib/browser/task-preferences.provider.d.ts +2 -2
  14. package/lib/browser/task-preferences.provider.d.ts.map +1 -1
  15. package/lib/browser/task-preferences.provider.js +2 -2
  16. package/lib/browser/task-preferences.provider.js.map +1 -1
  17. package/lib/browser/task.contribution.js.map +1 -1
  18. package/lib/browser/task.schema.d.ts +2 -2
  19. package/lib/browser/task.schema.d.ts.map +1 -1
  20. package/lib/browser/task.schema.js +5 -1
  21. package/lib/browser/task.schema.js.map +1 -1
  22. package/lib/browser/task.service.d.ts +3 -1
  23. package/lib/browser/task.service.d.ts.map +1 -1
  24. package/lib/browser/task.service.js +10 -5
  25. package/lib/browser/task.service.js.map +1 -1
  26. package/lib/browser/terminal-task-system.js +11 -11
  27. package/lib/browser/terminal-task-system.js.map +1 -1
  28. package/lib/common/index.d.ts +1 -1
  29. package/lib/common/index.d.ts.map +1 -1
  30. package/lib/common/task.d.ts +4 -4
  31. package/lib/common/task.d.ts.map +1 -1
  32. package/lib/common/task.js +7 -7
  33. package/lib/common/task.js.map +1 -1
  34. package/package.json +15 -14
  35. package/src/browser/index.ts +33 -0
  36. package/src/browser/parser.ts +944 -0
  37. package/src/browser/problem-collector.ts +71 -0
  38. package/src/browser/problem-line-matcher.ts +461 -0
  39. package/src/browser/task-config.ts +2302 -0
  40. package/src/browser/task-executor.ts +296 -0
  41. package/src/browser/task-preferences.contribution.ts +9 -0
  42. package/src/browser/task-preferences.provider.ts +23 -0
  43. package/src/browser/task-preferences.ts +14 -0
  44. package/src/browser/task.contribution.ts +70 -0
  45. package/src/browser/task.schema.ts +368 -0
  46. package/src/browser/task.service.ts +504 -0
  47. package/src/browser/terminal-task-system.ts +340 -0
  48. package/src/common/index.ts +165 -0
  49. package/src/common/task.ts +1174 -0
  50. package/src/index.ts +1 -0
@@ -0,0 +1,504 @@
1
+ import { Autowired, Injectable } from '@opensumi/di';
2
+ import {
3
+ Disposable,
4
+ Uri,
5
+ PreferenceService,
6
+ localize,
7
+ IDisposable,
8
+ QuickOpenItem,
9
+ QuickOpenService,
10
+ formatLocalize,
11
+ getIcon,
12
+ IStringDictionary,
13
+ isString,
14
+ Mode,
15
+ } from '@opensumi/ide-core-browser';
16
+ import {
17
+ ITaskDefinitionRegistry,
18
+ IProblemMatcherRegistry,
19
+ Event,
20
+ IProblemPatternRegistry,
21
+ Emitter,
22
+ platform,
23
+ } from '@opensumi/ide-core-common';
24
+ import { OutputChannel } from '@opensumi/ide-output/lib/browser/output.channel';
25
+ import { OutputService } from '@opensumi/ide-output/lib/browser/output.service';
26
+ import { ITerminalClient } from '@opensumi/ide-terminal-next/lib/common/client';
27
+ import { IWorkspaceService } from '@opensumi/ide-workspace';
28
+
29
+ import { ITaskService, WorkspaceFolderTaskResult, ITaskProvider, ITaskSystem, ITaskSummary } from '../common';
30
+ import {
31
+ ConfiguringTask,
32
+ TaskSet,
33
+ Task,
34
+ ContributedTask,
35
+ CustomTask,
36
+ TaskIdentifier,
37
+ KeyedTaskIdentifier,
38
+ TaskEvent,
39
+ } from '../common/task';
40
+
41
+ import { ValidationState, ValidationStatus } from './parser';
42
+ import { parse, IProblemReporter, createCustomTask } from './task-config';
43
+
44
+ class ProblemReporter implements IProblemReporter {
45
+ private _validationStatus: ValidationStatus;
46
+
47
+ constructor(private _outputChannel: OutputChannel) {
48
+ this._validationStatus = new ValidationStatus();
49
+ }
50
+
51
+ public info(message: string): void {
52
+ this._validationStatus.state = ValidationState.Info;
53
+ this._outputChannel.append(message + '\n');
54
+ }
55
+
56
+ public warn(message: string): void {
57
+ this._validationStatus.state = ValidationState.Warning;
58
+ this._outputChannel.append(message + '\n');
59
+ }
60
+
61
+ public error(message: string): void {
62
+ this._validationStatus.state = ValidationState.Error;
63
+ this._outputChannel.append(message + '\n');
64
+ }
65
+
66
+ public fatal(message: string): void {
67
+ this._validationStatus.state = ValidationState.Fatal;
68
+ this._outputChannel.append(message + '\n');
69
+ }
70
+
71
+ public get status(): ValidationStatus {
72
+ return this._validationStatus;
73
+ }
74
+ }
75
+
76
+ @Injectable()
77
+ export class TaskService extends Disposable implements ITaskService {
78
+ @Autowired(OutputService)
79
+ protected readonly outputService: OutputService;
80
+
81
+ @Autowired(IWorkspaceService)
82
+ protected readonly workspaceService: IWorkspaceService;
83
+
84
+ @Autowired(QuickOpenService)
85
+ protected readonly quickOpenService: QuickOpenService;
86
+
87
+ @Autowired(PreferenceService)
88
+ protected readonly preferences: PreferenceService;
89
+
90
+ @Autowired(ITaskSystem)
91
+ protected readonly taskSystem: ITaskSystem;
92
+
93
+ @Autowired(ITaskDefinitionRegistry)
94
+ protected readonly taskDefinitionRegistry: ITaskDefinitionRegistry;
95
+
96
+ @Autowired(IProblemMatcherRegistry)
97
+ problemMatcher: IProblemMatcherRegistry;
98
+
99
+ @Autowired(IProblemPatternRegistry)
100
+ problemPattern: IProblemPatternRegistry;
101
+
102
+ private _onDidStateChange: Emitter<TaskEvent> = new Emitter();
103
+ public onDidStateChange: Event<TaskEvent> = this._onDidStateChange.event;
104
+
105
+ private _onDidRegisterTaskProvider: Emitter<string> = new Emitter();
106
+ public onDidRegisterTaskProvider: Event<string> = this._onDidRegisterTaskProvider.event;
107
+
108
+ private providerHandler = 0;
109
+
110
+ private _outputChannel: OutputChannel;
111
+
112
+ private _workspaceFolders: Uri[];
113
+
114
+ private runningTasks: Map<string, Task | ConfiguringTask> = new Map();
115
+
116
+ private providers: Map<number, ITaskProvider>;
117
+ private providerTypes: Map<string, number>;
118
+
119
+ constructor() {
120
+ super();
121
+ this.providers = new Map();
122
+ this.providerTypes = new Map();
123
+ this.addDispose([
124
+ this.taskSystem.onDidStateChange((e) => this._onDidStateChange.fire(e)),
125
+ this.taskSystem.onDidBackgroundTaskBegin((e) => this._onDidStateChange.fire(e)),
126
+ this.taskSystem.onDidBackgroundTaskEnded((e) => this._onDidStateChange.fire(e)),
127
+ this.taskSystem.onDidProblemMatched((e) => this._onDidStateChange.fire(e)),
128
+ ]);
129
+ }
130
+
131
+ get outputChannel() {
132
+ if (!this._outputChannel) {
133
+ this._outputChannel = this.outputService.getChannel(localize('task.outputchannel.name'));
134
+ }
135
+ return this._outputChannel;
136
+ }
137
+
138
+ private get workspaceFolders() {
139
+ if (!this._workspaceFolders) {
140
+ this.tryGetWorkspaceFolders();
141
+ }
142
+ return this._workspaceFolders;
143
+ }
144
+
145
+ private tryGetWorkspaceFolders() {
146
+ this._workspaceFolders = this.workspaceService.tryGetRoots().map((stat) => Uri.parse(stat.uri));
147
+ }
148
+
149
+ public async runTaskCommand() {
150
+ const groupedTaskSet: TaskSet[] = await this.getGroupedTasks();
151
+ const workspaceTasks = await this.getWorkspaceTasks(groupedTaskSet);
152
+ const [workspaces, grouped] = this.combineQuickItems(groupedTaskSet, workspaceTasks);
153
+ this.quickOpenService.open(
154
+ {
155
+ onType: (lookFor: string, acceptor) => acceptor([...workspaces, ...grouped]),
156
+ },
157
+ { placeholder: formatLocalize('TaskService.pickRunTask') },
158
+ );
159
+ }
160
+
161
+ public run(task: Task) {
162
+ return this.runTask(task);
163
+ }
164
+
165
+ public async attach(taskId: string, terminal: ITerminalClient) {
166
+ if (this.runningTasks.has(taskId)) {
167
+ return;
168
+ }
169
+
170
+ const [, , , taskType] = taskId.split(',');
171
+ if (!taskType) {
172
+ return;
173
+ }
174
+
175
+ if (this.providerTypes.has(taskType)) {
176
+ const task = await this.getTask(this.workspaceFolders[0], taskId);
177
+ if (task) {
178
+ this.taskSystem.attach(task, terminal);
179
+ this.runningTasks.set(taskId, task);
180
+ }
181
+ } else {
182
+ // wait for task provider to be registered
183
+ const disposable = this._onDidRegisterTaskProvider.event(async (e) => {
184
+ if (e === taskType) {
185
+ const task = await this.getTask(this.workspaceFolders[0], taskId);
186
+ if (task) {
187
+ this.taskSystem.attach(task, terminal);
188
+ this.runningTasks.set(taskId, task);
189
+ disposable.dispose();
190
+ }
191
+ }
192
+ });
193
+ }
194
+ }
195
+
196
+ public async terminateTask(taskId: string) {
197
+ const activeTasks = this.taskSystem.getActiveTasks();
198
+ for (const t of activeTasks) {
199
+ if (t._id === taskId) {
200
+ await this.taskSystem.terminate(t);
201
+ }
202
+ }
203
+ }
204
+
205
+ public getTask(
206
+ workspaceFolder: Uri,
207
+ identifier: string | TaskIdentifier,
208
+ compareId = false,
209
+ ): Promise<Task | undefined> {
210
+ const key: string | KeyedTaskIdentifier | undefined = !isString(identifier)
211
+ ? this.taskDefinitionRegistry.createTaskIdentifier(identifier, console)
212
+ : identifier;
213
+
214
+ if (key === undefined) {
215
+ return Promise.resolve(undefined);
216
+ }
217
+
218
+ return this.getWorkspaceGroupedTasks(workspaceFolder).then((taskMap) => {
219
+ if (!taskMap) {
220
+ return Promise.resolve(undefined);
221
+ }
222
+ const tasks = taskMap.get(workspaceFolder.toString());
223
+ if (!tasks) {
224
+ return Promise.resolve(undefined);
225
+ }
226
+ for (const task of tasks!) {
227
+ if (task.matches(key, compareId)) {
228
+ return task;
229
+ }
230
+ if (task._id === identifier) {
231
+ return task;
232
+ }
233
+ if (task._label === identifier) {
234
+ return task;
235
+ }
236
+ }
237
+ });
238
+ }
239
+
240
+ private async runTask(task: Task | ConfiguringTask): Promise<ITaskSummary> {
241
+ const result = await this.taskSystem.run(task);
242
+
243
+ result.promise.then((res) => {
244
+ if (this.runningTasks.has(task._id)) {
245
+ this.runningTasks.delete(task._id);
246
+ }
247
+ this.outputChannel.appendLine(`Task ${task._label} done, exit code ${res.exitCode}`);
248
+ });
249
+
250
+ this.runningTasks.set(task._id, task);
251
+
252
+ return Promise.resolve(result.promise);
253
+ }
254
+
255
+ public async tasks(filter): Promise<Task[]> {
256
+ const workspaceTasks = await this.getWorkspaceGroupedTasks();
257
+ const result: Task[] = [];
258
+ for (const taskMap of workspaceTasks.values()) {
259
+ for (const task of taskMap) {
260
+ if (filter && filter.type && task.getDefinition()?.type === filter.type) {
261
+ result.push(task);
262
+ }
263
+ continue;
264
+ }
265
+ }
266
+ return result;
267
+ }
268
+
269
+ private async getWorkspaceGroupedTasks(folder: Uri = this.workspaceFolders[0]): Promise<Map<string, Task[]>> {
270
+ const contributedTaskSet = await this.getGroupedTasks();
271
+ const workspaceTasks = await this.getWorkspaceTasks(contributedTaskSet);
272
+ const result: Array<CustomTask | ContributedTask | Task> = [];
273
+ for (const contributed of contributedTaskSet) {
274
+ if (contributed.tasks && contributed.tasks.length > 0) {
275
+ result.push(...contributed.tasks);
276
+ }
277
+ }
278
+
279
+ const tasks = workspaceTasks?.get(folder.toString());
280
+ if (tasks && tasks.set) {
281
+ result.push(...tasks.set.tasks);
282
+ }
283
+ const taskMap = new Map();
284
+ taskMap.set(folder.toString(), result);
285
+ return taskMap;
286
+ }
287
+
288
+ private async getGroupedTasks(): Promise<TaskSet[]> {
289
+ const valideTaskTypes: { [prop: string]: boolean } = {};
290
+ for (const defintion of this.taskDefinitionRegistry.all()) {
291
+ valideTaskTypes[defintion.taskType] = true;
292
+ }
293
+ valideTaskTypes['shell'] = true;
294
+ valideTaskTypes['process'] = true;
295
+ const result: TaskSet[] = [];
296
+ for (const [, provider] of this.providers) {
297
+ const value = await provider.provideTasks(valideTaskTypes);
298
+ result.push(value);
299
+ }
300
+ return result;
301
+ }
302
+
303
+ private toQuickOpenItem = (task: Task | ConfiguringTask): QuickOpenItem =>
304
+ new QuickOpenItem({
305
+ label: task._label || '',
306
+ detail:
307
+ task instanceof ContributedTask
308
+ ? `${task.command.name || ''} ${task.command.args ? task.command.args?.join(' ') : ''}`
309
+ : undefined,
310
+ run: (mode: Mode) => {
311
+ if (mode === Mode.OPEN) {
312
+ this.runTask(task);
313
+ return true;
314
+ }
315
+ return false;
316
+ },
317
+ });
318
+
319
+ private toQuickOpenGroupItem(showBorder: boolean, run, type?: string): QuickOpenItem {
320
+ return new QuickOpenItem({
321
+ groupLabel: showBorder ? formatLocalize('task.contribute') : undefined,
322
+ run,
323
+ showBorder,
324
+ label: type,
325
+ value: { type, grouped: true },
326
+ iconClass: getIcon('folder'),
327
+ });
328
+ }
329
+
330
+ private combineQuickItems(
331
+ contributedTaskSet: TaskSet[],
332
+ workspaceTasks: Map<string, WorkspaceFolderTaskResult> | undefined,
333
+ ) {
334
+ const groups: QuickOpenItem[] = [];
335
+ const workspace: QuickOpenItem[] = [];
336
+ let showBorder = true;
337
+ for (const taskSet of contributedTaskSet) {
338
+ const run = (mode: Mode) => {
339
+ if (mode === Mode.OPEN) {
340
+ this.quickOpenService.open({
341
+ onType: (lookFor, acceptor) => {
342
+ if (taskSet.tasks.length === 0) {
343
+ return acceptor([
344
+ new QuickOpenItem({
345
+ value: 'none',
346
+ label: formatLocalize('task.cannotFindTask', taskSet.type),
347
+ run: (mode: Mode) => {
348
+ if (mode === Mode.OPEN) {
349
+ return true;
350
+ }
351
+ return false;
352
+ },
353
+ }),
354
+ ]);
355
+ }
356
+ return acceptor(taskSet.tasks.map(this.toQuickOpenItem));
357
+ },
358
+ });
359
+ return false;
360
+ }
361
+ return true;
362
+ };
363
+ const groupItem = this.toQuickOpenGroupItem(showBorder, run, taskSet.type);
364
+ groups.push(groupItem);
365
+ showBorder = false;
366
+ }
367
+ if (workspaceTasks) {
368
+ for (const taskSet of workspaceTasks.values()) {
369
+ if (taskSet.configurations?.byIdentifier) {
370
+ Object.keys(taskSet.configurations?.byIdentifier).forEach((t) => {
371
+ const task = taskSet.configurations?.byIdentifier[t];
372
+ workspace.push(this.toQuickOpenItem(task!));
373
+ });
374
+ }
375
+
376
+ if (taskSet.set && taskSet.set.tasks.length > 0) {
377
+ for (const task of taskSet.set.tasks) {
378
+ workspace.push(this.toQuickOpenItem(task));
379
+ }
380
+ }
381
+ }
382
+ }
383
+
384
+ return [workspace, groups];
385
+ }
386
+
387
+ private async getWorkspaceTasks(
388
+ groupedTaskSet: TaskSet[],
389
+ ): Promise<Map<string, WorkspaceFolderTaskResult> | undefined> {
390
+ return this.updateWorkspaceTasks(groupedTaskSet);
391
+ }
392
+
393
+ public updateWorkspaceTasks(groupedTaskSet: TaskSet[]): Promise<Map<string, WorkspaceFolderTaskResult> | undefined> {
394
+ if (this.workspaceFolders.length === 0) {
395
+ return Promise.resolve(new Map<string, WorkspaceFolderTaskResult>());
396
+ } else if (this.workspaceFolders.length === 1) {
397
+ /**
398
+ * 由于 registerSchema 不支持 **\/tasks.json 通配符
399
+ * 多 workspace 下无法默认从 preferences 获取到 tasks.json
400
+ */
401
+ return this.computeWorkspaceFolderTasks(this.workspaceFolders[0], groupedTaskSet).then((configuringTasks) => {
402
+ const taskMap = new Map<string, WorkspaceFolderTaskResult>();
403
+ taskMap.set(this.workspaceFolders[0].toString(), configuringTasks);
404
+ return taskMap;
405
+ });
406
+ } else {
407
+ return Promise.resolve(undefined);
408
+ // TODO 多工作区支持
409
+ }
410
+ }
411
+
412
+ private computeWorkspaceFolderTasks(folderUri: Uri, groupedTaskSet: TaskSet[]): Promise<WorkspaceFolderTaskResult> {
413
+ return new Promise(async (resolve) => {
414
+ const tasksConfig = this.preferences.get<{ version: string; tasks: any[] }>('tasks');
415
+ const contributedTask = new Map<string, Task>();
416
+ for (const taskSet of groupedTaskSet) {
417
+ for (const contributed of taskSet.tasks) {
418
+ if (!ContributedTask.is(contributed)) {
419
+ continue;
420
+ }
421
+ contributedTask.set(contributed.defines._key, contributed);
422
+ }
423
+ }
424
+ if (tasksConfig && tasksConfig.tasks) {
425
+ let customizedTasks: { byIdentifier: IStringDictionary<ConfiguringTask> } | undefined;
426
+ const taskSet: CustomTask[] = [];
427
+ let hasErrors = false;
428
+ const problemReporter = new ProblemReporter(this.outputChannel);
429
+ const parseResult = parse(
430
+ { uri: folderUri, name: folderUri.path, index: 0 },
431
+ platform,
432
+ tasksConfig,
433
+ problemReporter,
434
+ this.taskDefinitionRegistry,
435
+ this.problemMatcher,
436
+ this.problemPattern,
437
+ );
438
+ if (!parseResult.validationStatus.isOK()) {
439
+ hasErrors = true;
440
+ this.showOutput();
441
+ }
442
+ if (parseResult.configured && parseResult.configured.length > 0) {
443
+ customizedTasks = {
444
+ byIdentifier: Object.create(null),
445
+ };
446
+ for (const task of parseResult.configured) {
447
+ customizedTasks.byIdentifier[task.configures._key] = task;
448
+ }
449
+ }
450
+ taskSet.push(...parseResult.custom);
451
+ /**
452
+ * Converter configuringTask to customTask
453
+ */
454
+ if (customizedTasks && customizedTasks.byIdentifier) {
455
+ Object.keys(customizedTasks.byIdentifier).forEach((key) => {
456
+ if (contributedTask.has(key)) {
457
+ const customTask = createCustomTask(
458
+ contributedTask.get(key) as ContributedTask,
459
+ customizedTasks!.byIdentifier[key],
460
+ );
461
+ // @ts-ignore
462
+ customizedTasks.byIdentifier[key] = customTask;
463
+ }
464
+ });
465
+ }
466
+ resolve({
467
+ workspaceFolder: { uri: folderUri, name: folderUri.path, index: 0 },
468
+ set: { tasks: taskSet },
469
+ configurations: customizedTasks,
470
+ hasErrors,
471
+ });
472
+ } else {
473
+ resolve({
474
+ workspaceFolder: { uri: folderUri, name: folderUri.path, index: 0 },
475
+ set: { tasks: [] },
476
+ configurations: { byIdentifier: {} },
477
+ hasErrors: false,
478
+ });
479
+ }
480
+ });
481
+ }
482
+
483
+ protected showOutput(): void {
484
+ this.outputChannel.appendLine('There are task errors. See the output for details.');
485
+ }
486
+
487
+ public rerunLastTask() {
488
+ return this.taskSystem.rerun();
489
+ }
490
+
491
+ public registerTaskProvider(provider: ITaskProvider, type: string): IDisposable {
492
+ const handler = (this.providerHandler += 1);
493
+ this.providers.set(handler, provider);
494
+ this.providerTypes.set(type, handler);
495
+ this._onDidRegisterTaskProvider.fire(type);
496
+
497
+ return {
498
+ dispose: () => {
499
+ this.providers.delete(handler);
500
+ this.providerTypes.delete(type);
501
+ },
502
+ };
503
+ }
504
+ }