@theia/task 1.37.0-next.8 → 1.37.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.
@@ -16,17 +16,19 @@
16
16
 
17
17
  import { inject, injectable, optional } from '@theia/core/shared/inversify';
18
18
  import { TaskService } from './task-service';
19
- import { TaskInfo, TaskConfiguration, TaskCustomization, TaskScope, TaskConfigurationScope } from '../common/task-protocol';
19
+ import { TaskInfo, TaskConfiguration, TaskCustomization, TaskScope, TaskConfigurationScope, TaskDefinition } from '../common/task-protocol';
20
20
  import { TaskDefinitionRegistry } from './task-definition-registry';
21
21
  import URI from '@theia/core/lib/common/uri';
22
- import { LabelProvider, QuickAccessProvider, QuickAccessRegistry, QuickInputService, PreferenceService } from '@theia/core/lib/browser';
22
+ import { LabelProvider, QuickAccessProvider, QuickAccessRegistry, QuickInputService, QuickPick, PreferenceService } from '@theia/core/lib/browser';
23
23
  import { WorkspaceService } from '@theia/workspace/lib/browser';
24
24
  import { TerminalService } from '@theia/terminal/lib/browser/base/terminal-service';
25
25
  import { TaskNameResolver } from './task-name-resolver';
26
26
  import { TaskSourceResolver } from './task-source-resolver';
27
27
  import { TaskConfigurationManager } from './task-configuration-manager';
28
- import { filterItems, QuickInputButton, QuickPickItem, QuickPickItemOrSeparator, QuickPicks } from '@theia/core/lib/browser/quick-input/quick-input-service';
28
+ import { filterItems, QuickInputButton, QuickPickItem, QuickPickItemOrSeparator, QuickPicks, QuickPickInput, QuickPickValue } from
29
+ '@theia/core/lib/browser/quick-input/quick-input-service';
29
30
  import { CancellationToken } from '@theia/core/lib/common';
31
+ import { nls } from '@theia/core/lib/common/nls';
30
32
  import { TriggerAction } from '@theia/monaco-editor-core/esm/vs/platform/quickinput/browser/pickerQuickAccess';
31
33
 
32
34
  export namespace ConfigureTaskAction {
@@ -34,11 +36,21 @@ export namespace ConfigureTaskAction {
34
36
  export const TEXT = 'Configure Task';
35
37
  }
36
38
 
39
+ export type TaskEntry = QuickPickItemOrSeparator | QuickPickValue<string>;
40
+ export namespace TaskEntry {
41
+ export function isQuickPickValue(item: QuickPickItemOrSeparator | QuickPickValue<String>): item is QuickPickValue<string> {
42
+ return 'value' in item && typeof item.value === 'string';
43
+ }
44
+ }
45
+
46
+ export const CHOOSE_TASK = nls.localizeByDefault('Select the task to run');
47
+ export const NO_TASK_TO_RUN = nls.localize('theia/task/noTaskToRun', 'No task to run found. Configure Tasks...');
48
+
37
49
  @injectable()
38
50
  export class QuickOpenTask implements QuickAccessProvider {
39
51
  static readonly PREFIX = 'task ';
40
52
  readonly description: string = 'Run Task';
41
- protected items: Array<QuickPickItemOrSeparator> = [];
53
+ protected items: Array<TaskEntry> = [];
42
54
 
43
55
  @inject(TaskService)
44
56
  protected readonly taskService: TaskService;
@@ -77,21 +89,61 @@ export class QuickOpenTask implements QuickAccessProvider {
77
89
  protected async doInit(token: number): Promise<void> {
78
90
  const recentTasks = this.taskService.recentTasks;
79
91
  const configuredTasks = await this.taskService.getConfiguredTasks(token);
80
- const providedTasks = await this.taskService.getProvidedTasks(token);
92
+ const providedTypes = this.taskDefinitionRegistry.getAll();
81
93
 
82
- const { filteredRecentTasks, filteredConfiguredTasks, filteredProvidedTasks } = this.getFilteredTasks(recentTasks, configuredTasks, providedTasks);
94
+ const { filteredRecentTasks, filteredConfiguredTasks } = this.getFilteredTasks(recentTasks, configuredTasks, []);
83
95
  const isMulti: boolean = this.workspaceService.isMultiRootWorkspaceOpened;
84
96
  this.items = [];
85
97
 
86
98
  const filteredRecentTasksItems = this.getItems(filteredRecentTasks, 'recently used tasks', token, isMulti);
87
99
  const filteredConfiguredTasksItems = this.getItems(filteredConfiguredTasks, 'configured tasks', token, isMulti);
88
- const filteredProvidedTasksItems = this.getItems(filteredProvidedTasks, 'detected tasks', token, isMulti);
100
+ const providedTypeItems = this.createProvidedTypeItems(providedTypes);
89
101
 
90
102
  this.items.push(
91
103
  ...filteredRecentTasksItems,
92
104
  ...filteredConfiguredTasksItems,
93
- ...filteredProvidedTasksItems
105
+ ...providedTypeItems
94
106
  );
107
+
108
+ if (!this.items.length) {
109
+ this.items.push(({
110
+ label: NO_TASK_TO_RUN,
111
+ execute: () => this.configure()
112
+ }));
113
+ }
114
+ }
115
+
116
+ protected createProvidedTypeItems(providedTypes: TaskDefinition[]): TaskEntry[] {
117
+ const result: TaskEntry[] = [];
118
+ result.push({ type: 'separator', label: nls.localizeByDefault('contributed') });
119
+
120
+ for (const definition of providedTypes) {
121
+ const type = definition.taskType;
122
+ result.push({
123
+ label: `$(folder) ${type}`,
124
+ value: type,
125
+ /**
126
+ * This function is used in the context of a QuickAccessProvider (triggered from the command palette: '?task').
127
+ * It triggers a call to QuickOpenTask#getPicks,
128
+ * the 'execute' function below is called when the user selects an entry for a task type which triggers the display of
129
+ * the second level quick pick.
130
+ *
131
+ * Due to the asynchronous resolution of second-level tasks, there may be a delay in showing the quick input widget.
132
+ *
133
+ * NOTE: The widget is not delayed in other contexts e.g. by commands (Run Tasks), see the implementation at QuickOpenTask#open
134
+ *
135
+ * To improve the performance, we may consider using a `PickerQuickAccessProvider` instead of a `QuickAccessProvider`,
136
+ * and support providing 'FastAndSlowPicks'.
137
+ *
138
+ * TODO: Consider the introduction and exposure of monaco `PickerQuickAccessProvider` and the corresponding refactoring for this and other
139
+ * users of QuickAccessProvider.
140
+ */
141
+ execute: () => {
142
+ this.doSecondLevel(type);
143
+ }
144
+ });
145
+ }
146
+ return result;
95
147
  }
96
148
 
97
149
  protected onDidTriggerGearIcon(item: QuickPickItem): void {
@@ -102,17 +154,54 @@ export class QuickOpenTask implements QuickAccessProvider {
102
154
  }
103
155
 
104
156
  async open(): Promise<void> {
157
+ this.showMultiLevelQuickPick();
158
+ }
159
+
160
+ async showMultiLevelQuickPick(): Promise<void> {
105
161
  await this.init();
106
- if (!this.items.length) {
107
- this.items.push(({
108
- label: 'No task to run found. Configure Tasks...',
162
+ const picker: QuickPick<TaskEntry> = this.quickInputService.createQuickPick();
163
+ picker.placeholder = CHOOSE_TASK;
164
+ picker.matchOnDescription = true;
165
+ picker.ignoreFocusOut = false;
166
+ picker.items = this.items;
167
+
168
+ const firstLevelTask = await this.doPickerFirstLevel(picker);
169
+
170
+ if (!!firstLevelTask && TaskEntry.isQuickPickValue(firstLevelTask)) {
171
+ // A taskType was selected
172
+ picker.busy = true;
173
+ await this.doSecondLevel(firstLevelTask.value);
174
+ } else if (!!firstLevelTask && 'execute' in firstLevelTask && typeof firstLevelTask.execute === 'function') {
175
+ firstLevelTask.execute();
176
+ }
177
+ picker.dispose();
178
+ }
179
+
180
+ protected async doPickerFirstLevel(picker: QuickPick<TaskEntry>): Promise<TaskEntry | undefined> {
181
+ picker.show();
182
+ const firstLevelPickerResult = await new Promise<TaskEntry | undefined | null>(resolve => {
183
+ picker.onDidAccept(async () => {
184
+ resolve(picker.selectedItems ? picker.selectedItems[0] : undefined);
185
+ });
186
+ });
187
+ return firstLevelPickerResult ?? undefined;
188
+ }
189
+
190
+ protected async doSecondLevel(taskType: string): Promise<void> {
191
+ // Resolve Second level tasks based on selected TaskType
192
+ const isMulti = this.workspaceService.isMultiRootWorkspaceOpened;
193
+ const token = this.taskService.startUserAction();
194
+ const providedTasks = await this.taskService.getProvidedTasks(token, taskType);
195
+ const providedTasksItems = this.getItems(providedTasks, taskType + ' tasks', token, isMulti);
196
+
197
+ if (!providedTasksItems.length) {
198
+ providedTasksItems.push(({
199
+ label: NO_TASK_TO_RUN,
109
200
  execute: () => this.configure()
110
201
  }));
111
202
  }
112
- this.quickInputService?.showQuickPick(this.items, {
113
- placeholder: 'Select the task to run',
114
- onDidTriggerItemButton: ({ item }) => this.onDidTriggerGearIcon(item)
115
- });
203
+
204
+ this.quickInputService?.showQuickPick(providedTasksItems, { placeholder: CHOOSE_TASK });
116
205
  }
117
206
 
118
207
  attach(): void {
@@ -145,17 +234,26 @@ export class QuickOpenTask implements QuickAccessProvider {
145
234
  label: 'No tasks found'
146
235
  }));
147
236
  }
148
- this.quickInputService?.showQuickPick(this.items, { placeholder: 'Choose task to open' });
237
+ this.quickInputService?.showQuickPick(this.items, { placeholder: CHOOSE_TASK });
149
238
  });
150
239
  }
151
240
 
152
241
  async configure(): Promise<void> {
153
- this.items = [];
242
+ this.quickInputService?.pick(this.resolveItemsToConfigure(), { placeHolder: nls.localizeByDefault('Select a task to configure') }).
243
+ then(async item => {
244
+ if (item && 'execute' in item && typeof item.execute === 'function') {
245
+ item.execute();
246
+ }
247
+ });
248
+ }
249
+
250
+ protected async resolveItemsToConfigure(): Promise<QuickPickInput<QuickPickItemOrSeparator>[]> {
251
+ const items: Array<QuickPickInput<QuickPickItemOrSeparator>> = [];
154
252
  const isMulti: boolean = this.workspaceService.isMultiRootWorkspaceOpened;
155
253
  const token: number = this.taskService.startUserAction();
156
254
 
157
255
  const configuredTasks = await this.taskService.getConfiguredTasks(token);
158
- const providedTasks = await this.taskService.getProvidedTasks(token);
256
+ const providedTasks = await this.taskService.getProvidedTasks(token, '*');
159
257
 
160
258
  // check if tasks.json exists. If not, display "Create tasks.json file from template"
161
259
  // If tasks.json exists and empty, display 'Open tasks.json file'
@@ -163,7 +261,7 @@ export class QuickOpenTask implements QuickAccessProvider {
163
261
  const groupedTasks = this.getGroupedTasksByWorkspaceFolder([...filteredConfiguredTasks, ...filteredProvidedTasks]);
164
262
  if (groupedTasks.has(TaskScope.Global.toString())) {
165
263
  const configs = groupedTasks.get(TaskScope.Global.toString())!;
166
- this.items.push(
264
+ items.push(
167
265
  ...configs.map(taskConfig => {
168
266
  const item = new TaskConfigureQuickOpenItem(
169
267
  token,
@@ -184,7 +282,7 @@ export class QuickOpenTask implements QuickAccessProvider {
184
282
  const folderName = new URI(rootFolder).displayName;
185
283
  if (groupedTasks.has(rootFolder)) {
186
284
  const configs = groupedTasks.get(rootFolder.toString())!;
187
- this.items.push(
285
+ items.push(
188
286
  ...configs.map((taskConfig, index) => {
189
287
  const item = new TaskConfigureQuickOpenItem(
190
288
  token,
@@ -202,28 +300,28 @@ export class QuickOpenTask implements QuickAccessProvider {
202
300
  } else {
203
301
  const { configUri } = this.preferences.resolve('tasks', [], rootFolder);
204
302
  const existTaskConfigFile = !!configUri;
205
- this.items.push(({
303
+ items.push(({
206
304
  label: existTaskConfigFile ? 'Open tasks.json file' : 'Create tasks.json file from template',
207
305
  execute: () => {
208
306
  setTimeout(() => this.taskConfigurationManager.openConfiguration(rootFolder));
209
307
  }
210
308
  }));
211
309
  }
212
- if (this.items.length > 0) {
213
- this.items.unshift({
310
+ if (items.length > 0) {
311
+ items.unshift({
214
312
  type: 'separator',
215
313
  label: isMulti ? folderName : ''
216
314
  });
217
315
  }
218
316
  }
219
317
 
220
- if (this.items.length === 0) {
221
- this.items.push(({
318
+ if (items.length === 0) {
319
+ items.push(({
222
320
  label: 'No tasks found'
223
321
  }));
224
322
  }
225
323
 
226
- this.quickInputService?.showQuickPick(this.items, { placeholder: 'Select a task to configure' });
324
+ return items;
227
325
  }
228
326
 
229
327
  protected getTaskItems(): QuickPickItem[] {
@@ -297,9 +395,7 @@ export class QuickOpenTask implements QuickAccessProvider {
297
395
  }
298
396
 
299
397
  async getPicks(filter: string, token: CancellationToken): Promise<QuickPicks> {
300
- if (this.items.length === 0) {
301
- await this.init();
302
- }
398
+ await this.init();
303
399
  return filterItems(this.items, filter);
304
400
  }
305
401
 
@@ -317,8 +413,8 @@ export class QuickOpenTask implements QuickAccessProvider {
317
413
  }
318
414
 
319
415
  private getItems(tasks: TaskConfiguration[], groupLabel: string, token: number, isMulti: boolean):
320
- QuickPickItemOrSeparator[] {
321
- const items: QuickPickItemOrSeparator[] = tasks.map(task =>
416
+ TaskEntry[] {
417
+ const items: TaskEntry[] = tasks.map(task =>
322
418
  new TaskRunQuickOpenItem(token, task, this.taskService, isMulti, this.taskDefinitionRegistry, this.taskNameResolver,
323
419
  this.taskSourceResolver, this.taskConfigurationManager, [{
324
420
  iconClass: 'codicon-gear',
@@ -81,6 +81,10 @@ export interface TaskProvider {
81
81
  provideTasks(): Promise<TaskConfiguration[]>;
82
82
  }
83
83
 
84
+ export interface WillResolveTaskProvider extends WaitUntilEvent {
85
+ taskType?: string
86
+ }
87
+
84
88
  /**
85
89
  * The {@link TaskResolverRegistry} is the common component for registration and provision of
86
90
  * {@link TaskResolver}s. Theia will collect all {@link TaskContribution}s and invoke {@link TaskContribution#registerResolvers}
@@ -89,7 +93,7 @@ export interface TaskProvider {
89
93
  @injectable()
90
94
  export class TaskResolverRegistry {
91
95
 
92
- protected readonly onWillProvideTaskResolverEmitter = new Emitter<WaitUntilEvent>();
96
+ protected readonly onWillProvideTaskResolverEmitter = new Emitter<WillResolveTaskProvider>();
93
97
  /**
94
98
  * Emit when the registry provides a registered resolver. i.e. when the {@link TaskResolverRegistry#getResolver}
95
99
  * function is called.
@@ -156,7 +160,7 @@ export class TaskResolverRegistry {
156
160
  * @returns a promise of the registered `TaskResolver` or `undefined` if no resolver is registered for the given type.
157
161
  */
158
162
  async getTaskResolver(type: string): Promise<TaskResolver | undefined> {
159
- await WaitUntilEvent.fire(this.onWillProvideTaskResolverEmitter, {});
163
+ await WaitUntilEvent.fire(this.onWillProvideTaskResolverEmitter, { taskType: type });
160
164
  return this.taskResolvers.get(type);
161
165
  }
162
166
 
@@ -200,7 +204,7 @@ export class TaskResolverRegistry {
200
204
  @injectable()
201
205
  export class TaskProviderRegistry {
202
206
 
203
- protected readonly onWillProvideTaskProviderEmitter = new Emitter<WaitUntilEvent>();
207
+ protected readonly onWillProvideTaskProviderEmitter = new Emitter<WillResolveTaskProvider>();
204
208
  /**
205
209
  * Emit when the registry provides a registered task provider. i.e. when the {@link TaskProviderRegistry#getProvider}
206
210
  * function is called.
@@ -229,6 +233,14 @@ export class TaskProviderRegistry {
229
233
  };
230
234
  }
231
235
 
236
+ /**
237
+ * Initiates activation of a TaskProvider with the given type
238
+ * @param type the task configuration type, '*' indicates, all providers.
239
+ */
240
+ async activateProvider(type: string): Promise<void> {
241
+ await WaitUntilEvent.fire(this.onWillProvideTaskProviderEmitter, { taskType: type });
242
+ }
243
+
232
244
  /**
233
245
  * Retrieves the {@link TaskProvider} registered for the given type task configuration type.
234
246
  * If there is already a `TaskProvider` registered for the specified type the registration will
@@ -238,17 +250,17 @@ export class TaskProviderRegistry {
238
250
  * @returns a promise of the registered `TaskProvider`` or `undefined` if no provider is registered for the given type.
239
251
  */
240
252
  async getProvider(type: string): Promise<TaskProvider | undefined> {
241
- await WaitUntilEvent.fire(this.onWillProvideTaskProviderEmitter, {});
253
+ await this.activateProvider(type);
242
254
  return this.providers.get(type);
243
255
  }
244
256
 
245
257
  /**
246
258
  * Retrieve all registered {@link TaskProvider}s.
247
259
  *
260
+ * Use {@link activateProvider} to control registration of providers as needed.
248
261
  * @returns a promise of all registered {@link TaskProvider}s.
249
262
  */
250
263
  async getProviders(): Promise<TaskProvider[]> {
251
- await WaitUntilEvent.fire(this.onWillProvideTaskProviderEmitter, {});
252
264
  return [...this.providers.values()];
253
265
  }
254
266
  }
@@ -403,11 +403,14 @@ export class TaskService implements TaskConfigurationClient {
403
403
  }
404
404
 
405
405
  /**
406
- * Returns an array of the task configurations which are provided by the extensions.
406
+ * Returns an array that contains the task configurations provided by the task providers for the specified task type.
407
407
  * @param token The cache token for the user interaction in progress
408
+ * @param type The task type (filter) associated to the returning TaskConfigurations
409
+ *
410
+ * '*' indicates all tasks regardless of the type
408
411
  */
409
- getProvidedTasks(token: number): Promise<TaskConfiguration[]> {
410
- return this.providedTaskConfigurations.getTasks(token);
412
+ getProvidedTasks(token: number, type?: string): Promise<TaskConfiguration[]> {
413
+ return this.providedTaskConfigurations.getTasks(token, type);
411
414
  }
412
415
 
413
416
  addRecentTasks(tasks: TaskConfiguration | TaskConfiguration[]): void {