Skip to content

Commit 1a26e58

Browse files
committed
support Multipick prompter
1 parent f1a8089 commit 1a26e58

File tree

1 file changed

+104
-21
lines changed

1 file changed

+104
-21
lines changed

packages/core/src/shared/ui/pickerPrompter.ts

Lines changed: 104 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import * as vscode from 'vscode'
77
import * as nls from 'vscode-nls'
88

9-
import { isValidResponse, StepEstimator, WIZARD_BACK, WIZARD_EXIT } from '../wizards/wizard'
9+
import { isValidResponse, isWizardControl, StepEstimator, WIZARD_BACK, WIZARD_EXIT } from '../wizards/wizard'
1010
import { QuickInputButton, PrompterButtons } from './buttons'
1111
import { Prompter, PromptResult, Transform } from './prompter'
1212
import { assign, isAsyncIterable } from '../utilities/collectionUtils'
@@ -155,6 +155,61 @@ export function createQuickPick<T>(
155155
return prompter
156156
}
157157

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+
158213
export async function showQuickPick<T>(
159214
items: ItemLoadTypes<T>,
160215
options?: ExtendedQuickPickOptions<T>
@@ -354,35 +409,46 @@ export class QuickPickPrompter<T> extends Prompter<T> {
354409

355410
const recentlyUsedItem = mergedItems.find((item) => item.recentlyUsed)
356411
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
412+
if (picker.canSelectMany) {
413+
picker.items = mergedItems.sort(this.options.compare)
414+
// if has recent select, apply previous selected items
415+
picker.selectedItems = picker.items.filter((item) => item.recentlyUsed)
416+
} else {
417+
if (items.includes(recentlyUsedItem)) {
418+
const prefix = recentlyUsedItem.description
419+
const recent =
420+
recentlyUsedDescription === recentlyUsed ? `(${recentlyUsed})` : recentlyUsedDescription
421+
recentlyUsedItem.description = prefix ? `${prefix} ${recent}` : recent
372422
}
373-
return 0
374-
})
375423

376-
picker.activeItems = [recentlyUsedItem]
424+
picker.items = mergedItems.sort((a, b) => {
425+
// Always prioritize specified comparator if there's any
426+
if (this.options.compare) {
427+
return this.options.compare(a, b)
428+
}
429+
if (a === recentlyUsedItem) {
430+
return -1
431+
} else if (b === recentlyUsedItem) {
432+
return 1
433+
}
434+
return 0
435+
})
436+
437+
picker.activeItems = [recentlyUsedItem]
438+
}
377439
} else {
378440
picker.items = mergedItems.sort(this.options.compare)
379441

380442
if (picker.items.length === 0 && !this.busy) {
381443
this.isShowingPlaceholder = true
382444
picker.items = this.options.noItemsFoundItem !== undefined ? [this.options.noItemsFoundItem] : []
383445
}
384-
385-
this.selectItems(...recent.filter((i) => !i.invalidSelection))
446+
if (picker.canSelectMany) {
447+
// if doesn't have recent select, apply selection from DataQuickPickItems.picked
448+
picker.selectedItems = picker.items.filter((item) => item.picked)
449+
} else {
450+
this.selectItems(...recent.filter((i) => !i.invalidSelection))
451+
}
386452
}
387453
}
388454

@@ -496,6 +562,23 @@ export class QuickPickPrompter<T> extends Prompter<T> {
496562
this._lastPicked = choices[0]
497563
const result = choices[0].data
498564

565+
if (this.quickPick.canSelectMany) {
566+
// return if control signal
567+
if (isWizardControl(result)) {
568+
return result
569+
}
570+
// reset before setting recent again
571+
this.quickPick.items.map((item) => (item.recentlyUsed = false))
572+
return JSON.stringify(
573+
await Promise.all(
574+
choices.map(async (choice) => {
575+
choice.recentlyUsed = true
576+
return choice.data instanceof Function ? await choice.data() : choice.data
577+
})
578+
)
579+
) as T
580+
}
581+
499582
return result instanceof Function ? await result() : result
500583
}
501584

0 commit comments

Comments
 (0)