@@ -15,7 +15,9 @@ import {
15
15
QuickPickItem ,
16
16
Event ,
17
17
window ,
18
+ QuickPickItemButtonEvent ,
18
19
} from 'vscode' ;
20
+ import { createDeferred } from './utils/async' ;
19
21
20
22
// Borrowed from https://github.com/Microsoft/vscode-extension-samples/blob/master/quickinput-sample/src/multiStepInput.ts
21
23
// Why re-invent the wheel :)
@@ -37,7 +39,7 @@ export type InputStep<T extends any> = (input: MultiStepInput<T>, state: T) => P
37
39
38
40
type buttonCallbackType < T extends QuickPickItem > = ( quickPick : QuickPick < T > ) => void ;
39
41
40
- type QuickInputButtonSetup = {
42
+ export type QuickInputButtonSetup = {
41
43
/**
42
44
* Button for an action in a QuickPick.
43
45
*/
@@ -54,13 +56,12 @@ export interface IQuickPickParameters<T extends QuickPickItem, E = any> {
54
56
totalSteps ?: number ;
55
57
canGoBack ?: boolean ;
56
58
items : T [ ] ;
57
- activeItem ?: T | Promise < T > ;
59
+ activeItem ?: T | ( ( quickPick : QuickPick < T > ) => Promise < T > ) ;
58
60
placeholder : string | undefined ;
59
61
customButtonSetups ?: QuickInputButtonSetup [ ] ;
60
62
matchOnDescription ?: boolean ;
61
63
matchOnDetail ?: boolean ;
62
64
keepScrollPosition ?: boolean ;
63
- sortByLabel ?: boolean ;
64
65
acceptFilterBoxTextAsSelection ?: boolean ;
65
66
/**
66
67
* A method called only after quickpick has been created and all handlers are registered.
@@ -70,6 +71,7 @@ export interface IQuickPickParameters<T extends QuickPickItem, E = any> {
70
71
callback : ( event : E , quickPick : QuickPick < T > ) => void ;
71
72
event : Event < E > ;
72
73
} ;
74
+ onDidTriggerItemButton ?: ( e : QuickPickItemButtonEvent < T > ) => void ;
73
75
}
74
76
75
77
interface InputBoxParameters {
@@ -83,7 +85,7 @@ interface InputBoxParameters {
83
85
validate ( value : string ) : Promise < string | undefined > ;
84
86
}
85
87
86
- type MultiStepInputQuickPicResponseType < T , P > = T | ( P extends { buttons : ( infer I ) [ ] } ? I : never ) | undefined ;
88
+ type MultiStepInputQuickPickResponseType < T , P > = T | ( P extends { buttons : ( infer I ) [ ] } ? I : never ) | undefined ;
87
89
type MultiStepInputInputBoxResponseType < P > = string | ( P extends { buttons : ( infer I ) [ ] } ? I : never ) | undefined ;
88
90
export interface IMultiStepInput < S > {
89
91
run ( start : InputStep < S > , state : S ) : Promise < void > ;
@@ -95,7 +97,7 @@ export interface IMultiStepInput<S> {
95
97
activeItem,
96
98
placeholder,
97
99
customButtonSetups,
98
- } : P ) : Promise < MultiStepInputQuickPicResponseType < T , P > > ;
100
+ } : P ) : Promise < MultiStepInputQuickPickResponseType < T , P > > ;
99
101
showInputBox < P extends InputBoxParameters > ( {
100
102
title,
101
103
step,
@@ -131,8 +133,9 @@ export class MultiStepInput<S> implements IMultiStepInput<S> {
131
133
acceptFilterBoxTextAsSelection,
132
134
onChangeItem,
133
135
keepScrollPosition,
136
+ onDidTriggerItemButton,
134
137
initialize,
135
- } : P ) : Promise < MultiStepInputQuickPicResponseType < T , P > > {
138
+ } : P ) : Promise < MultiStepInputQuickPickResponseType < T , P > > {
136
139
const disposables : Disposable [ ] = [ ] ;
137
140
const input = window . createQuickPick < T > ( ) ;
138
141
input . title = title ;
@@ -161,7 +164,13 @@ export class MultiStepInput<S> implements IMultiStepInput<S> {
161
164
initialize ( input ) ;
162
165
}
163
166
if ( activeItem ) {
164
- input . activeItems = [ await activeItem ] ;
167
+ if ( typeof activeItem === 'function' ) {
168
+ activeItem ( input ) . then ( ( item ) => {
169
+ if ( input . activeItems . length === 0 ) {
170
+ input . activeItems = [ item ] ;
171
+ }
172
+ } ) ;
173
+ }
165
174
} else {
166
175
input . activeItems = [ ] ;
167
176
}
@@ -170,35 +179,46 @@ export class MultiStepInput<S> implements IMultiStepInput<S> {
170
179
// so do it after initialization. This ensures quickpick starts with the active
171
180
// item in focus when this is true, instead of having scroll position at top.
172
181
input . keepScrollPosition = keepScrollPosition ;
173
- try {
174
- return await new Promise < MultiStepInputQuickPicResponseType < T , P > > ( ( resolve , reject ) => {
175
- disposables . push (
176
- input . onDidTriggerButton ( async ( item ) => {
177
- if ( item === QuickInputButtons . Back ) {
178
- reject ( InputFlowAction . back ) ;
179
- }
180
- if ( customButtonSetups ) {
181
- for ( const customButtonSetup of customButtonSetups ) {
182
- if ( JSON . stringify ( item ) === JSON . stringify ( customButtonSetup ?. button ) ) {
183
- await customButtonSetup ?. callback ( input ) ;
184
- }
185
- }
182
+
183
+ const deferred = createDeferred < T > ( ) ;
184
+
185
+ disposables . push (
186
+ input . onDidTriggerButton ( async ( item ) => {
187
+ if ( item === QuickInputButtons . Back ) {
188
+ deferred . reject ( InputFlowAction . back ) ;
189
+ input . hide ( ) ;
190
+ }
191
+ if ( customButtonSetups ) {
192
+ for ( const customButtonSetup of customButtonSetups ) {
193
+ if ( JSON . stringify ( item ) === JSON . stringify ( customButtonSetup ?. button ) ) {
194
+ await customButtonSetup ?. callback ( input ) ;
186
195
}
187
- } ) ,
188
- input . onDidChangeSelection ( ( selectedItems ) => resolve ( selectedItems [ 0 ] ) ) ,
189
- input . onDidHide ( ( ) => {
190
- resolve ( undefined ) ;
191
- } ) ,
192
- ) ;
193
- if ( acceptFilterBoxTextAsSelection ) {
194
- disposables . push (
195
- input . onDidAccept ( ( ) => {
196
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
197
- resolve ( input . value as any ) ;
198
- } ) ,
199
- ) ;
196
+ }
200
197
}
201
- } ) ;
198
+ } ) ,
199
+ input . onDidChangeSelection ( ( selectedItems ) => deferred . resolve ( selectedItems [ 0 ] ) ) ,
200
+ input . onDidHide ( ( ) => {
201
+ if ( ! deferred . completed ) {
202
+ deferred . resolve ( undefined ) ;
203
+ }
204
+ } ) ,
205
+ input . onDidTriggerItemButton ( async ( item ) => {
206
+ if ( onDidTriggerItemButton ) {
207
+ await onDidTriggerItemButton ( item ) ;
208
+ }
209
+ } ) ,
210
+ ) ;
211
+ if ( acceptFilterBoxTextAsSelection ) {
212
+ disposables . push (
213
+ input . onDidAccept ( ( ) => {
214
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
215
+ deferred . resolve ( input . value as any ) ;
216
+ } ) ,
217
+ ) ;
218
+ }
219
+
220
+ try {
221
+ return await deferred . promise ;
202
222
} finally {
203
223
disposables . forEach ( ( d ) => d . dispose ( ) ) ;
204
224
}
@@ -283,6 +303,9 @@ export class MultiStepInput<S> implements IMultiStepInput<S> {
283
303
if ( err === InputFlowAction . back ) {
284
304
this . steps . pop ( ) ;
285
305
step = this . steps . pop ( ) ;
306
+ if ( step === undefined ) {
307
+ throw err ;
308
+ }
286
309
} else if ( err === InputFlowAction . resume ) {
287
310
step = this . steps . pop ( ) ;
288
311
} else if ( err === InputFlowAction . cancel ) {
@@ -297,6 +320,7 @@ export class MultiStepInput<S> implements IMultiStepInput<S> {
297
320
}
298
321
}
299
322
}
323
+
300
324
export const IMultiStepInputFactory = Symbol ( 'IMultiStepInputFactory' ) ;
301
325
export interface IMultiStepInputFactory {
302
326
create < S > ( ) : IMultiStepInput < S > ;
0 commit comments