@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,340 @@
1
+ import { Injectable, Autowired, Injector, INJECTOR_TOKEN } from '@opensumi/di';
2
+ import {
3
+ Event,
4
+ formatLocalize,
5
+ IProblemMatcherRegistry,
6
+ Disposable,
7
+ ProblemMatcher,
8
+ isString,
9
+ Emitter,
10
+ objects,
11
+ path,
12
+ } from '@opensumi/ide-core-common';
13
+ import { ITerminalClient, IShellLaunchConfig } from '@opensumi/ide-terminal-next/lib/common';
14
+ import { IVariableResolverService } from '@opensumi/ide-variable';
15
+
16
+ import {
17
+ ITaskSystem,
18
+ ITaskExecuteResult,
19
+ TaskExecuteKind,
20
+ IActivateTaskExecutorData,
21
+ TaskTerminateResponse,
22
+ } from '../common';
23
+ import {
24
+ Task,
25
+ ContributedTask,
26
+ CommandString,
27
+ CommandConfiguration,
28
+ TaskEvent,
29
+ TaskEventKind,
30
+ RuntimeType,
31
+ } from '../common/task';
32
+ import { CustomTask } from '../common/task';
33
+
34
+ import { ProblemCollector } from './problem-collector';
35
+ import { TaskStatus, TerminalTaskExecutor } from './task-executor';
36
+
37
+ const { deepClone } = objects;
38
+ const { Path } = path;
39
+
40
+ @Injectable()
41
+ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
42
+ @Autowired(INJECTOR_TOKEN)
43
+ private readonly injector: Injector;
44
+
45
+ @Autowired(IProblemMatcherRegistry)
46
+ problemMatcher: IProblemMatcherRegistry;
47
+
48
+ @Autowired(IVariableResolverService)
49
+ variableResolver: IVariableResolverService;
50
+
51
+ private executorId = 0;
52
+
53
+ private lastTask: CustomTask | ContributedTask | undefined;
54
+ protected currentTask: Task;
55
+
56
+ private activeTaskExecutors: Map<string, IActivateTaskExecutorData> = new Map();
57
+
58
+ private _onDidStateChange: Emitter<TaskEvent> = new Emitter();
59
+
60
+ private _onDidBackgroundTaskBegin: Emitter<TaskEvent> = new Emitter();
61
+
62
+ private _onDidBackgroundTaskEnded: Emitter<TaskEvent> = new Emitter();
63
+
64
+ private _onDidProblemMatched: Emitter<TaskEvent> = new Emitter();
65
+
66
+ private taskExecutors: TerminalTaskExecutor[] = [];
67
+
68
+ onDidStateChange: Event<TaskEvent> = this._onDidStateChange.event;
69
+ onDidBackgroundTaskBegin: Event<TaskEvent> = this._onDidBackgroundTaskBegin.event;
70
+ onDidBackgroundTaskEnded: Event<TaskEvent> = this._onDidBackgroundTaskEnded.event;
71
+ onDidProblemMatched: Event<TaskEvent> = this._onDidProblemMatched.event;
72
+
73
+ run(task: CustomTask | ContributedTask): Promise<ITaskExecuteResult> {
74
+ this.currentTask = task;
75
+ return this.executeTask(task);
76
+ }
77
+
78
+ attach(task: CustomTask | ContributedTask, terminalClient: ITerminalClient): Promise<ITaskExecuteResult> {
79
+ this.currentTask = task;
80
+ return this.attachTask(task, terminalClient);
81
+ }
82
+
83
+ private async buildShellConfig(command: CommandConfiguration) {
84
+ let subCommand = '';
85
+ const commandName = command.name;
86
+ const commandArgs = command.args;
87
+ const subArgs: string[] = [];
88
+ const result: string[] = [];
89
+
90
+ if (commandName) {
91
+ if (typeof commandName === 'string') {
92
+ subCommand = commandName;
93
+ } else {
94
+ subCommand = commandName.value;
95
+ }
96
+ }
97
+
98
+ subArgs.push(subCommand);
99
+
100
+ if (commandArgs) {
101
+ for (const arg of commandArgs) {
102
+ if (typeof arg === 'string') {
103
+ subArgs.push(arg);
104
+ } else {
105
+ subArgs.push(arg.value);
106
+ }
107
+ }
108
+ }
109
+
110
+ for (const arg of subArgs) {
111
+ if (arg.indexOf(Path.separator) > -1) {
112
+ result.push(await this.resolveVariables(arg.split(Path.separator)));
113
+ } else {
114
+ result.push(await this.resolveVariable(arg));
115
+ }
116
+ }
117
+ return { args: ['-c', `${result.join(' ')}`] };
118
+ }
119
+
120
+ private findAvailableExecutor(): TerminalTaskExecutor | undefined {
121
+ return this.taskExecutors.find((e) => e.taskStatus === TaskStatus.PROCESS_EXITED);
122
+ }
123
+
124
+ private async createTaskExecutor(task: CustomTask | ContributedTask, launchConfig: IShellLaunchConfig) {
125
+ const matchers = await this.resolveMatchers(task.configurationProperties.problemMatchers);
126
+ const collector = new ProblemCollector(matchers);
127
+ const executor = this.injector.get(TerminalTaskExecutor, [task, launchConfig, collector, this.executorId]);
128
+ this.executorId += 1;
129
+ this.taskExecutors.push(executor);
130
+ this.addDispose(
131
+ executor.onDidTerminalWidgetRemove(() => {
132
+ this.taskExecutors = this.taskExecutors.filter((t) => t.executorId !== executor.executorId);
133
+ }),
134
+ );
135
+
136
+ return executor;
137
+ }
138
+
139
+ private async attachTask(
140
+ task: CustomTask | ContributedTask,
141
+ terminalClient: ITerminalClient,
142
+ ): Promise<ITaskExecuteResult> {
143
+ const taskExecutor = await this.createTaskExecutor(task, terminalClient.launchConfig);
144
+ const p = taskExecutor.attach(terminalClient);
145
+ this.lastTask = task;
146
+ return {
147
+ task,
148
+ kind: TaskExecuteKind.Started,
149
+ promise: p,
150
+ };
151
+ }
152
+
153
+ private async executeTask(task: CustomTask | ContributedTask): Promise<ITaskExecuteResult> {
154
+ // CustomExecution
155
+ const isCustomExecution = task.command && task.command.runtime === RuntimeType.CustomExecution;
156
+
157
+ const matchers = await this.resolveMatchers(task.configurationProperties.problemMatchers);
158
+ const collector = new ProblemCollector(matchers);
159
+ const { args } = await this.buildShellConfig(task.command);
160
+
161
+ const launchConfig: IShellLaunchConfig = {
162
+ name: this.createTerminalName(task),
163
+ args,
164
+ isExtensionOwnedTerminal: isCustomExecution,
165
+ env: task.command.options?.env || {},
166
+ cwd: task.command.options?.cwd
167
+ ? await this.resolveVariable(task.command.options?.cwd)
168
+ : await this.resolveVariable('${workspaceFolder}'),
169
+ // 不需要历史记录
170
+ disablePreserveHistory: true,
171
+ };
172
+
173
+ let executor: TerminalTaskExecutor | undefined = this.findAvailableExecutor();
174
+ let reuse = false;
175
+ if (!executor) {
176
+ executor = await this.createTaskExecutor(task, launchConfig);
177
+ } else {
178
+ reuse = true;
179
+ executor.updateProblemCollector(collector);
180
+ executor.updateLaunchConfig(launchConfig);
181
+ executor.reset();
182
+ }
183
+
184
+ if (reuse) {
185
+ // 插件进程中 CustomExecution 会等待前台终端实例创建完成后进行 attach
186
+ // 重用终端的情况下,要再次发出事件确保 attach 成功 (ext.host.task.ts#$onDidStartTask)
187
+ this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Start, task, executor.terminalId));
188
+ } else {
189
+ this.addDispose(
190
+ executor.onDidTerminalCreated((terminalId) => {
191
+ // 当 task 使用 CustomExecution 时,发出 TaskEventKind.Start 事件后
192
+ // 插件进程将尝试 attach 这个 Pseudoterminal (ext.host.task.ts#$onDidStartTask)
193
+ // attach 成功便会创建一个 ExtensionTerminal 实例
194
+ // 确保后续调用 $startExtensionTerminal 时已经建立了连接
195
+ this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Start, task, terminalId));
196
+ }),
197
+ );
198
+ }
199
+
200
+ if (!reuse) {
201
+ this.addDispose(
202
+ executor.onDidTaskProcessExit((code) => {
203
+ this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessEnded, task, code));
204
+ this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.End, task));
205
+ }),
206
+ );
207
+ this.addDispose(
208
+ executor.onDidBackgroundTaskBegin(() =>
209
+ this._onDidBackgroundTaskBegin.fire(TaskEvent.create(TaskEventKind.BackgroundTaskBegin, task)),
210
+ ),
211
+ );
212
+ this.addDispose(
213
+ executor.onDidBackgroundTaskEnd(() =>
214
+ this._onDidBackgroundTaskEnded.fire(TaskEvent.create(TaskEventKind.BackgroundTaskEnded, task)),
215
+ ),
216
+ );
217
+ this.addDispose(
218
+ executor.onDidProblemMatched((problems) =>
219
+ this._onDidProblemMatched.fire(TaskEvent.create(TaskEventKind.ProblemMatched, task, problems)),
220
+ ),
221
+ );
222
+ }
223
+
224
+ const result = executor.execute(task, reuse);
225
+
226
+ const mapKey = task.getMapKey();
227
+ this.activeTaskExecutors.set(mapKey, { promise: Promise.resolve(result), task, executor });
228
+ this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Active, task));
229
+ await executor.processReady.promise;
230
+
231
+ this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessStarted, task, executor.processId));
232
+ this.lastTask = task;
233
+ return {
234
+ task,
235
+ kind: TaskExecuteKind.Started,
236
+ promise: result,
237
+ };
238
+ }
239
+
240
+ private createTerminalName(task: CustomTask | ContributedTask): string {
241
+ return formatLocalize(
242
+ 'TerminalTaskSystem.terminalName',
243
+ task.getQualifiedLabel() || task.configurationProperties.name,
244
+ );
245
+ }
246
+
247
+ private async resolveVariables(value: string[]): Promise<string> {
248
+ const result: string[] = [];
249
+ for (const item of value) {
250
+ result.push(await this.resolveVariable(item));
251
+ }
252
+ return result.join(Path.separator);
253
+ }
254
+
255
+ private async resolveVariable(value: string | undefined): Promise<string>;
256
+ private async resolveVariable(value: CommandString | undefined): Promise<CommandString>;
257
+ private async resolveVariable(value: CommandString | undefined): Promise<CommandString> {
258
+ if (isString(value)) {
259
+ return await this.variableResolver.resolve<string>(value);
260
+ } else if (value !== undefined) {
261
+ return {
262
+ value: await this.variableResolver.resolve<string>(value.value),
263
+ quoting: value.quoting,
264
+ };
265
+ } else {
266
+ // This should never happen
267
+ throw new Error('Should never try to resolve undefined.');
268
+ }
269
+ }
270
+
271
+ private async resolveMatchers(values: Array<string | ProblemMatcher> | undefined): Promise<ProblemMatcher[]> {
272
+ if (values === undefined || values === null || values.length === 0) {
273
+ return [];
274
+ }
275
+ const result: ProblemMatcher[] = [];
276
+ for (const value of values) {
277
+ let matcher: ProblemMatcher | undefined;
278
+ if (isString(value)) {
279
+ if (value[0].startsWith('$')) {
280
+ matcher = this.problemMatcher.get(value.substring(1));
281
+ } else {
282
+ matcher = this.problemMatcher.get(value);
283
+ }
284
+ } else {
285
+ matcher = value;
286
+ }
287
+ if (!matcher) {
288
+ continue;
289
+ }
290
+ const hasFilePrefix = matcher.filePrefix !== undefined;
291
+ if (!hasFilePrefix) {
292
+ result.push(matcher);
293
+ } else {
294
+ const copy = deepClone(matcher);
295
+ if (hasFilePrefix) {
296
+ copy.filePrefix = await this.resolveVariable(copy.filePrefix);
297
+ }
298
+ result.push(copy);
299
+ }
300
+ }
301
+ return result;
302
+ }
303
+ getActiveTasks(): Task[] {
304
+ return Array.from(this.activeTaskExecutors.values()).map((e) => e.task);
305
+ }
306
+ async terminate(task: Task): Promise<TaskTerminateResponse> {
307
+ const key = task.getMapKey();
308
+ const activeExecutor = this.activeTaskExecutors.get(key);
309
+ if (!activeExecutor) {
310
+ return Promise.resolve({ task: undefined, success: true });
311
+ }
312
+ const { success } = await activeExecutor.executor.terminate();
313
+ this.activeTaskExecutors.delete(key);
314
+ return { task, success };
315
+ }
316
+ async rerun(): Promise<ITaskExecuteResult | undefined> {
317
+ return this.lastTask && (await this.executeTask(this.lastTask));
318
+ }
319
+ isActive(): Promise<boolean> {
320
+ throw new Error('Method not implemented.');
321
+ }
322
+ isActiveSync(): boolean {
323
+ throw new Error('Method not implemented.');
324
+ }
325
+ getBusyTasks(): Task[] {
326
+ throw new Error('Method not implemented.');
327
+ }
328
+ canAutoTerminate(): boolean {
329
+ throw new Error('Method not implemented.');
330
+ }
331
+ terminateAll(): Promise<TaskTerminateResponse[]> {
332
+ throw new Error('Method not implemented.');
333
+ }
334
+ revealTask(task: Task): boolean {
335
+ throw new Error('Method not implemented.');
336
+ }
337
+ customExecutionComplete(task: Task, result: number): Promise<void> {
338
+ throw new Error('Method not implemented.');
339
+ }
340
+ }
@@ -0,0 +1,165 @@
1
+ import { IDisposable, Event, URI, TaskIdentifier, Uri, Deferred, IJSONSchemaMap } from '@opensumi/ide-core-common';
2
+ import { UriComponents } from '@opensumi/ide-editor';
3
+ import { IShellLaunchConfig, ITerminalClient } from '@opensumi/ide-terminal-next/lib/common';
4
+
5
+ // eslint-disable-next-line import/no-restricted-paths
6
+ import type { ProblemCollector } from '../browser/problem-collector';
7
+
8
+ import { Task, ConfiguringTask, ContributedTask, TaskSet, KeyedTaskIdentifier, TaskEvent } from './task';
9
+
10
+ // eslint-disable-next-line @typescript-eslint/no-empty-interface
11
+ interface TaskMap {}
12
+
13
+ interface TaskFilter {
14
+ version?: string;
15
+ type?: string;
16
+ }
17
+
18
+ interface WorkspaceTaskResult {
19
+ set: TaskSet | undefined;
20
+ configurations:
21
+ | {
22
+ byIdentifier: Record<string, ConfiguringTask>;
23
+ }
24
+ | undefined;
25
+ hasErrors: boolean;
26
+ }
27
+
28
+ export interface WorkspaceFolder {
29
+ uri: UriComponents;
30
+ name: string;
31
+ index: number;
32
+ }
33
+
34
+ // tslint:disable-next-line: no-empty-interface
35
+ export type IWorkspaceFolder = WorkspaceFolder;
36
+
37
+ export interface WorkspaceFolderTaskResult extends WorkspaceTaskResult {
38
+ workspaceFolder: IWorkspaceFolder;
39
+ }
40
+
41
+ export interface TaskDefinition {
42
+ type?: string;
43
+ required?: string[];
44
+ properties?: IJSONSchemaMap;
45
+ }
46
+
47
+ export const ITaskService = Symbol('ITaskService');
48
+
49
+ export interface ITaskProvider {
50
+ provideTasks(validTypes?: Record<string, boolean>): Promise<TaskSet>;
51
+ resolveTask(task: ConfiguringTask): Promise<ContributedTask | undefined>;
52
+ }
53
+
54
+ export interface ITaskResolver {
55
+ resolve(uri: URI, identifier: string | KeyedTaskIdentifier | undefined): Task | undefined;
56
+ }
57
+
58
+ export interface ITaskSummary {
59
+ /**
60
+ * Exit code of the process.
61
+ */
62
+ exitCode?: number;
63
+ }
64
+
65
+ export interface IActivateTaskExecutorData {
66
+ executor: ITaskExecutor;
67
+ task: Task;
68
+ promise: Promise<ITaskSummary>;
69
+ }
70
+
71
+ export const enum TaskExecuteKind {
72
+ Started = 1,
73
+ Active = 2,
74
+ }
75
+
76
+ export interface ITaskExecuteResult {
77
+ kind: TaskExecuteKind;
78
+ promise: Promise<ITaskSummary>;
79
+ task: Task;
80
+ started?: {
81
+ restartOnFileChanges?: string;
82
+ };
83
+ active?: {
84
+ same: boolean;
85
+ background: boolean;
86
+ };
87
+ }
88
+
89
+ export interface TerminateResponse {
90
+ success: boolean;
91
+ code?: TerminateResponseCode;
92
+ error?: any;
93
+ }
94
+
95
+ export const enum TerminateResponseCode {
96
+ Success = 0,
97
+ Unknown = 1,
98
+ AccessDenied = 2,
99
+ ProcessNotFound = 3,
100
+ }
101
+
102
+ // eslint-disable-next-line @typescript-eslint/no-empty-interface
103
+ export interface ExecutorOptions {}
104
+
105
+ export const ITaskExecutor = Symbol('ITaskExecutor');
106
+
107
+ export interface ITaskExecutor {
108
+ executorId: number;
109
+ processId: number | undefined;
110
+ widgetId: string | undefined;
111
+ processReady: Deferred<void>;
112
+ execute(task: Task, reuse?: boolean): Promise<{ exitCode?: number }>;
113
+ reset(): void;
114
+ terminate(): Promise<{ success: boolean }>;
115
+ updateLaunchConfig(launchConfig: IShellLaunchConfig): void;
116
+ updateProblemCollector(collector: ProblemCollector): void;
117
+ onDidTerminalWidgetRemove: Event<void>;
118
+ onDidTaskProcessExit: Event<number | undefined>;
119
+ }
120
+
121
+ export interface TaskTerminateResponse extends TerminateResponse {
122
+ task: Task | undefined;
123
+ }
124
+
125
+ export const ITaskSystem = Symbol('ITaskSystem');
126
+ export interface ITaskSystem {
127
+ onDidStateChange: Event<TaskEvent>;
128
+ onDidBackgroundTaskBegin: Event<TaskEvent>;
129
+ onDidBackgroundTaskEnded: Event<TaskEvent>;
130
+ onDidProblemMatched: Event<TaskEvent>;
131
+ attach(task: Task | ConfiguringTask, terminalClient: ITerminalClient): Promise<ITaskExecuteResult>;
132
+ run(task: Task | ConfiguringTask): Promise<ITaskExecuteResult>;
133
+ rerun(): Promise<ITaskExecuteResult | undefined>;
134
+ isActive(): Promise<boolean>;
135
+ isActiveSync(): boolean;
136
+ getActiveTasks(): Task[];
137
+ getBusyTasks(): Task[];
138
+ canAutoTerminate(): boolean;
139
+ terminate(task: Task): Promise<TaskTerminateResponse>;
140
+ terminateAll(): Promise<TaskTerminateResponse[]>;
141
+ revealTask(task: Task): boolean;
142
+ customExecutionComplete(task: Task, result: number): Promise<void>;
143
+ }
144
+
145
+ export interface ITaskService {
146
+ attach(taskId: string, terminal: ITerminalClient): Promise<void>;
147
+
148
+ run(task: Task | ConfiguringTask): Promise<ITaskSummary>;
149
+
150
+ runTaskCommand(): void;
151
+ rerunLastTask(): void;
152
+
153
+ updateWorkspaceTasks(tasks: TaskMap): void;
154
+
155
+ registerTaskProvider(provider: ITaskProvider, type: string): IDisposable;
156
+
157
+ tasks(filter?: TaskFilter): Promise<Task[]>;
158
+
159
+ getTask(workspaceFolder: Uri, identifier: string | TaskIdentifier, compareId?: boolean): Promise<Task | undefined>;
160
+
161
+ terminateTask(key: string): Promise<void>;
162
+
163
+ onDidStateChange: Event<TaskEvent>;
164
+ onDidRegisterTaskProvider: Event<string>;
165
+ }