|
6 | 6 | import * as vscode from 'vscode'
|
7 | 7 | import * as nls from 'vscode-nls'
|
8 | 8 |
|
9 |
| -import { isValidResponse, StepEstimator, WIZARD_BACK, WIZARD_EXIT } from '../wizards/wizard' |
| 9 | +import { isValidResponse, isWizardControl, StepEstimator, WIZARD_BACK, WIZARD_EXIT } from '../wizards/wizard' |
10 | 10 | import { QuickInputButton, PrompterButtons } from './buttons'
|
11 | 11 | import { Prompter, PromptResult, Transform } from './prompter'
|
12 | 12 | import { assign, isAsyncIterable } from '../utilities/collectionUtils'
|
@@ -155,6 +155,61 @@ export function createQuickPick<T>(
|
155 | 155 | return prompter
|
156 | 156 | }
|
157 | 157 |
|
| 158 | +/** |
| 159 | + * Creates a UI element that presents a list of items which allow selecting many item at same time. List of results |
| 160 | + * will be JSON.stringify into a string. Use Json.parse() to recover all selected items. Due to limitation of current |
| 161 | + * implementation, the result of this QuickPick will always be a string regardless of given type. |
| 162 | + * |
| 163 | + * If you wish to pre-select some item in the Multipick, you can add `picked: true` in the DataQuickPickItem |
| 164 | + * |
| 165 | + * @example |
| 166 | +```typescript |
| 167 | + const syncFlagItems: DataQuickPickItem<string>[] = [ |
| 168 | + { |
| 169 | + label: 'Build in source', |
| 170 | + data: '--build-in-source', |
| 171 | + description: 'Opts in to build project in the source folder. Only for node apps', |
| 172 | + }, |
| 173 | + { |
| 174 | + label: 'Code', |
| 175 | + data: '--code', |
| 176 | + description: 'Sync only code resources (Lambda Functions, API Gateway, Step Functions)', |
| 177 | + picked: true, |
| 178 | + } |
| 179 | + ] |
| 180 | + // in wizard |
| 181 | + this.form.syncFlags.bindPrompter(() => { |
| 182 | + return createMultiPick(syncFlagItems, { |
| 183 | + title: 'Specify parameters for sync', |
| 184 | + placeholder: 'Press enter to proceed with highlighted option', |
| 185 | + buttons: createCommonButtons(samSyncUrl), |
| 186 | + }) |
| 187 | + }) |
| 188 | +``` |
| 189 | + * |
| 190 | + * @param items An array or a Promise for items. |
| 191 | + * @param options Customizes the QuickPick and QuickPickPrompter. |
| 192 | + * @returns A {@link QuickPickPrompter}. This can be used directly with the `prompt` method or can be fed into a Wizard. |
| 193 | + */ |
| 194 | +export function createMultiPick<T>( |
| 195 | + items: ItemLoadTypes<T>, |
| 196 | + options?: ExtendedQuickPickOptions<T> |
| 197 | +): QuickPickPrompter<T> { |
| 198 | + const picker = vscode.window.createQuickPick<DataQuickPickItem<T>>() as DataQuickPick<T> |
| 199 | + const mergedOptions = { ...defaultQuickpickOptions, ...options } |
| 200 | + assign(mergedOptions, picker) |
| 201 | + picker.buttons = mergedOptions.buttons ?? [] |
| 202 | + picker.canSelectMany = true |
| 203 | + |
| 204 | + const prompter = new QuickPickPrompter<T>(picker, mergedOptions) |
| 205 | + |
| 206 | + prompter.loadItems(items, false, mergedOptions.recentlyUsed).catch((e) => { |
| 207 | + getLogger().error('createQuickPick: loadItems failed: %s', (e as Error).message) |
| 208 | + }) |
| 209 | + |
| 210 | + return prompter |
| 211 | +} |
| 212 | + |
158 | 213 | export async function showQuickPick<T>(
|
159 | 214 | items: ItemLoadTypes<T>,
|
160 | 215 | options?: ExtendedQuickPickOptions<T>
|
@@ -186,6 +241,10 @@ export function createLabelQuickPick<T extends string>(
|
186 | 241 |
|
187 | 242 | function acceptItems<T>(picker: DataQuickPick<T>, resolve: (items: DataQuickPickItem<T>[]) => void): void {
|
188 | 243 | if (picker.selectedItems.length === 0) {
|
| 244 | + if (picker.canSelectMany) { |
| 245 | + // Allow empty choice in multipick |
| 246 | + resolve(Array.from(picker.selectedItems)) |
| 247 | + } |
189 | 248 | return
|
190 | 249 | }
|
191 | 250 |
|
@@ -354,35 +413,46 @@ export class QuickPickPrompter<T> extends Prompter<T> {
|
354 | 413 |
|
355 | 414 | const recentlyUsedItem = mergedItems.find((item) => item.recentlyUsed)
|
356 | 415 | if (recentlyUsedItem !== undefined) {
|
357 |
| - if (items.includes(recentlyUsedItem)) { |
358 |
| - const prefix = recentlyUsedItem.description |
359 |
| - const recent = recentlyUsedDescription === recentlyUsed ? `(${recentlyUsed})` : recentlyUsedDescription |
360 |
| - recentlyUsedItem.description = prefix ? `${prefix} ${recent}` : recent |
361 |
| - } |
362 |
| - |
363 |
| - picker.items = mergedItems.sort((a, b) => { |
364 |
| - // Always prioritize specified comparator if there's any |
365 |
| - if (this.options.compare) { |
366 |
| - return this.options.compare(a, b) |
367 |
| - } |
368 |
| - if (a === recentlyUsedItem) { |
369 |
| - return -1 |
370 |
| - } else if (b === recentlyUsedItem) { |
371 |
| - return 1 |
| 416 | + if (picker.canSelectMany) { |
| 417 | + picker.items = mergedItems.sort(this.options.compare) |
| 418 | + // if has recent select, apply previous selected items |
| 419 | + picker.selectedItems = picker.items.filter((item) => item.recentlyUsed) |
| 420 | + } else { |
| 421 | + if (items.includes(recentlyUsedItem)) { |
| 422 | + const prefix = recentlyUsedItem.description |
| 423 | + const recent = |
| 424 | + recentlyUsedDescription === recentlyUsed ? `(${recentlyUsed})` : recentlyUsedDescription |
| 425 | + recentlyUsedItem.description = prefix ? `${prefix} ${recent}` : recent |
372 | 426 | }
|
373 |
| - return 0 |
374 |
| - }) |
375 | 427 |
|
376 |
| - picker.activeItems = [recentlyUsedItem] |
| 428 | + picker.items = mergedItems.sort((a, b) => { |
| 429 | + // Always prioritize specified comparator if there's any |
| 430 | + if (this.options.compare) { |
| 431 | + return this.options.compare(a, b) |
| 432 | + } |
| 433 | + if (a === recentlyUsedItem) { |
| 434 | + return -1 |
| 435 | + } else if (b === recentlyUsedItem) { |
| 436 | + return 1 |
| 437 | + } |
| 438 | + return 0 |
| 439 | + }) |
| 440 | + |
| 441 | + picker.activeItems = [recentlyUsedItem] |
| 442 | + } |
377 | 443 | } else {
|
378 | 444 | picker.items = mergedItems.sort(this.options.compare)
|
379 | 445 |
|
380 | 446 | if (picker.items.length === 0 && !this.busy) {
|
381 | 447 | this.isShowingPlaceholder = true
|
382 | 448 | picker.items = this.options.noItemsFoundItem !== undefined ? [this.options.noItemsFoundItem] : []
|
383 | 449 | }
|
384 |
| - |
385 |
| - this.selectItems(...recent.filter((i) => !i.invalidSelection)) |
| 450 | + if (picker.canSelectMany) { |
| 451 | + // if doesn't have recent select, apply selection from DataQuickPickItems.picked |
| 452 | + picker.selectedItems = picker.items.filter((item) => item.picked) |
| 453 | + } else { |
| 454 | + this.selectItems(...recent.filter((i) => !i.invalidSelection)) |
| 455 | + } |
386 | 456 | }
|
387 | 457 | }
|
388 | 458 |
|
@@ -493,6 +563,28 @@ export class QuickPickPrompter<T> extends Prompter<T> {
|
493 | 563 | return choices
|
494 | 564 | }
|
495 | 565 |
|
| 566 | + if (this.quickPick.canSelectMany) { |
| 567 | + // return if control signal |
| 568 | + if (choices.length !== 0 && isWizardControl(choices[0].data)) { |
| 569 | + return choices[0].data |
| 570 | + } |
| 571 | + // reset before setting recent again |
| 572 | + this.quickPick.items.map((item) => { |
| 573 | + ;(item.recentlyUsed = false), |
| 574 | + // picked will only work on the first choice. Once choosen, remove the picked flag. |
| 575 | + (item.picked = false) |
| 576 | + }) |
| 577 | + // note this can be empty |
| 578 | + return JSON.stringify( |
| 579 | + await Promise.all( |
| 580 | + choices.map(async (choice) => { |
| 581 | + choice.recentlyUsed = true |
| 582 | + return choice.data instanceof Function ? await choice.data() : choice.data |
| 583 | + }) |
| 584 | + ) |
| 585 | + ) as T |
| 586 | + } |
| 587 | + // else single pick |
496 | 588 | this._lastPicked = choices[0]
|
497 | 589 | const result = choices[0].data
|
498 | 590 |
|
|
0 commit comments