@@ -41,19 +41,58 @@ export interface FormFieldTypeNumber {
4141 kind : 'number' ;
4242}
4343
44+ // FormEnumEntry represents a single option in an enumeration.
45+ export interface FormEnumEntry {
46+ // Value is the unique string identifier for this option.
47+ //
48+ // This is the value that will be sent back to the server in
49+ // 'FormAnswers' if the user selects this option.
50+ value : string ;
51+
52+ // Description is the human-readable label presented to the user.
53+ description : string ;
54+ }
55+
4456// FormFieldTypeEnum defines a selection from a set of values.
57+ //
58+ // Use this type when:
59+ // - The number of options is small (e.g., < 20).
60+ // - All options are known at the time the form is created.
4561export interface FormFieldTypeEnum {
4662 kind : 'enum' ;
4763
4864 // Name is an optional identifier for the enum type.
4965 name ?: string ;
5066
51- // Values is the set of allowable options.
52- values : string [ ] ;
67+ // Entries is the list of allowable options.
68+ entries : FormEnumEntry [ ] ;
69+ }
70+
71+ // FormFieldTypeLazyEnum defines a selection from a large or dynamic enum entry set.
72+ //
73+ // Use this type when:
74+ // 1. The dataset is too large to send efficiently in a single payload
75+ // (e.g., thousands of workspace symbols, file uri or cloud resources).
76+ // 2. The available options depend on the user's input (e.g., semantic search).
77+ // 3. Generating the list is expensive and should only be done if requested.
78+ //
79+ // The client is expected to render a search interface (e.g., a combo box with
80+ // a text input) and query the server via 'interactive/listEnum' as the user types.
81+ export interface FormFieldTypeLazyEnum {
82+ kind : 'lazyEnum' ;
83+
84+ // TODO(hxjiang): consider make debounce configurable since fetching
85+ // cloud resources could be expensive and slow.
5386
54- // Description provides human-readable labels for the options.
55- // This array must have the same length as values.
56- description : string [ ] ;
87+ // Source identifies the data source on the server.
88+ //
89+ // Examples: "workspace/symbol", "database/schema", "git/tags".
90+ source : string ;
91+
92+ // Config contains the static settings for the source.
93+ // The client treats this as opaque data and echoes it back in the
94+ // 'interactive/listEnum' request.
95+ config ?: any ;
5796}
5897
5998// FormFieldTypeList defines a homogenous list of items.
@@ -72,6 +111,7 @@ export type FormFieldType =
72111 | FormFieldTypeBool
73112 | FormFieldTypeNumber
74113 | FormFieldTypeEnum
114+ | FormFieldTypeLazyEnum
75115 | FormFieldTypeList ;
76116
77117// ----------------------------------------------------------------------------
@@ -253,6 +293,110 @@ export async function CollectAnswers(
253293 return answers ;
254294}
255295
296+ /**
297+ * InteractiveListEnumParams defines the parameters for the
298+ * 'interactive/listEnum' request.
299+ */
300+ interface InteractiveListEnumParams {
301+ /**
302+ * Source identifies the data source on the server.
303+ *
304+ * The client treats this as opaque data and echoes it back in the
305+ * 'interactive/listEnum' request.
306+ *
307+ * Examples: "workspace/symbol", "database/schema", "git/tags".
308+ */
309+ source : string ;
310+
311+ /**
312+ * Config contains the static settings for the specified source.
313+ *
314+ * The client treats this as opaque data and echoes it back in the
315+ * 'interactive/listEnum' request.
316+ */
317+ config ?: any ;
318+
319+ /**
320+ * A query string to filter enum entries by.
321+ *
322+ * The exact interpretation of this string (e.g., fuzzy matching, exact
323+ * match, prefix search, or regular expression) is entirely up to the
324+ * server and may vary depending on the source. This follows the similar
325+ * semantics as the standard 'workspace/symbol' request. Clients may
326+ * send an empty string here to request a default set of enum entries.
327+ */
328+ query : string ;
329+ }
330+
331+ /**
332+ * Opens a Quick Pick that dynamically fetches options from the Language Server.
333+ */
334+ export async function pickLazyEnum ( description : string , source : string , config : any = { } ) : Promise < string | undefined > {
335+ return new Promise ( ( resolve ) => {
336+ const quickPick = vscode . window . createQuickPick < vscode . QuickPickItem & { value : string } > ( ) ;
337+
338+ quickPick . title = description ;
339+ quickPick . placeholder = 'Type to search ' + source ;
340+ quickPick . matchOnDescription = true ;
341+
342+ let debounceTimeout : NodeJS . Timeout | undefined ;
343+ let isResolved = false ;
344+
345+ // Call "interactive/listEnum" and render result as entries as quick
346+ // pick items.
347+ const search = async ( query : string ) => {
348+ quickPick . busy = true ;
349+ try {
350+ const params : InteractiveListEnumParams = {
351+ source : source ,
352+ config : config ,
353+ query : query
354+ } ;
355+ const result = await vscode . commands . executeCommand < FormEnumEntry [ ] > ( 'gopls.lsp' , {
356+ method : 'interactive/listEnum' ,
357+ param : params
358+ } ) ;
359+
360+ if ( ! result ) {
361+ quickPick . items = [ ] ;
362+ return ;
363+ }
364+
365+ quickPick . items = result . map ( ( entry ) => ( {
366+ label : entry . description ,
367+ detail : entry . value !== entry . description ? entry . value : undefined ,
368+ value : entry . value
369+ } ) ) ;
370+ } catch ( e ) {
371+ console . error ( 'Error fetching enum options:' , e ) ;
372+ quickPick . items = [ ] ;
373+ } finally {
374+ quickPick . busy = false ;
375+ }
376+ } ;
377+
378+ quickPick . onDidChangeValue ( ( value ) => {
379+ if ( debounceTimeout ) clearTimeout ( debounceTimeout ) ;
380+ debounceTimeout = setTimeout ( ( ) => search ( value ) , 400 ) ;
381+ } ) ;
382+
383+ quickPick . onDidAccept ( ( ) => {
384+ const selection = quickPick . selectedItems [ 0 ] ;
385+ isResolved = true ;
386+ resolve ( selection ? selection . value : undefined ) ;
387+ quickPick . hide ( ) ;
388+ } ) ;
389+
390+ quickPick . onDidHide ( ( ) => {
391+ if ( ! isResolved ) resolve ( undefined ) ;
392+ quickPick . dispose ( ) ;
393+ } ) ;
394+
395+ quickPick . show ( ) ;
396+ search ( '' ) ; // Initial Trigger
397+ } ) ;
398+ }
399+
256400/**
257401 * Helper to prompt for a single field based on its type.
258402 */
@@ -344,17 +488,13 @@ async function promptForField(field: FormField, prevAnswer: any | undefined): Pr
344488 } as vscode . InputBoxOptions ) ;
345489
346490 case 'enum' : {
347- const descriptions = type . description || [ ] ;
348-
349- const pickItems = type . values . map ( ( value , index ) => {
350- const description = descriptions [ index ] ;
351-
491+ const pickItems = type . entries . map ( ( entry , _ ) => {
352492 return {
353493 // Use description if it exists, otherwise use value
354- label : description || value ,
494+ label : entry . description || entry . value ,
355495 // Show value in detail if description exists
356- description : description ? value : undefined ,
357- value : value
496+ description : entry . description ? entry . value : undefined ,
497+ value : entry . value
358498 } ;
359499 } ) ;
360500
@@ -366,6 +506,10 @@ async function promptForField(field: FormField, prevAnswer: any | undefined): Pr
366506 return selected ? selected . value : undefined ;
367507 }
368508
509+ case 'lazyEnum' : {
510+ return await pickLazyEnum ( field . description , type . source , type . config ) ;
511+ }
512+
369513 case 'bool' : {
370514 const boolItems = [
371515 { label : 'Yes' , value : true } ,
0 commit comments