77import * as vscode from 'vscode' ;
88import { InitializeParams } from 'vscode-languageserver-protocol' ;
99import {
10+ ExecuteCommandSignature ,
1011 LanguageClient ,
1112 RequestType ,
1213 ServerOptions ,
@@ -305,7 +306,53 @@ export interface InteractiveLanguageClientOptions extends LanguageClientOptions
305306 * interactive dialog, including the command resolution phase, the execution of
306307 * the validated command, and dynamic enumeration requests for lazy-loaded options.
307308 */
308- export type InteractiveMiddleware = Middleware & InteractiveListEnumMiddleware ;
309+ export type InteractiveMiddleware = Middleware &
310+ InteractiveListEnumMiddleware &
311+ InteractiveExecuteCommandMiddleware &
312+ InteractiveResolveCommandMiddleware ;
313+
314+ export interface InteractiveResolveCommandSignature {
315+ ( this : void , param : InteractiveExecuteCommandParams ) : vscode . ProviderResult < InteractiveExecuteCommandParams > ;
316+ }
317+
318+ export interface InteractiveResolveCommandMiddleware {
319+ interactiveResolveCommand ?: (
320+ this : void ,
321+ param : InteractiveExecuteCommandParams ,
322+ next : InteractiveResolveCommandSignature
323+ ) => vscode . ProviderResult < InteractiveExecuteCommandParams > ;
324+ }
325+
326+ /**
327+ * Signature for the command execution handler with user form answers.
328+ *
329+ * This signature is distinct from standard `ExecuteCommandSignature` because it
330+ * accepts an additional `formAnswers` argument containing user-provided,
331+ * server-validated inputs.
332+ */
333+ export interface InteractiveExecuteCommandSignature {
334+ ( this : void , command : string , args : any [ ] , formAnswers : any [ ] ) : vscode . ProviderResult < any > ;
335+ }
336+
337+ /**
338+ * InteractiveExecuteCommandMiddleware allows middleware implementations to
339+ * intercept the execution of commands that require user-provided form answers.
340+ *
341+ * Note: This middleware is defined as a new, separate hook rather than reusing
342+ * the standard `executeCommand` middleware. Reusing the standard middleware
343+ * would require modifying its signature to accept the additional `formAnswers`
344+ * parameter, which would break backward compatibility for existing extension
345+ * middleware configurations.
346+ */
347+ export interface InteractiveExecuteCommandMiddleware {
348+ interactiveExecuteCommand ?: (
349+ this : void ,
350+ command : string ,
351+ args : any [ ] ,
352+ formAnswers : any [ ] ,
353+ next : InteractiveExecuteCommandSignature
354+ ) => vscode . ProviderResult < any > ;
355+ }
309356
310357export interface InteractiveListEnumSignature {
311358 ( this : void , param : InteractiveListEnumParams ) : vscode . ProviderResult < FormEnumEntry [ ] > ;
@@ -328,6 +375,54 @@ export class InteractiveLanguageClient extends LanguageClient {
328375 forceDebug ?: boolean
329376 ) {
330377 super ( id , name , serverOptions , clientOptions , forceDebug ) ;
378+
379+ const interactiveOptions = this . clientOptions as InteractiveLanguageClientOptions ;
380+ const middleware = interactiveOptions . middleware ;
381+
382+ // Memorize the language client author defined "executeCommand" middleware.
383+ const original = middleware ?. executeCommand ;
384+
385+ // Intercept standard command execution to resolve required inputs interactively.
386+ // Once resolved, route the execution to the appropriate middleware:
387+ // - If the command required user answers, execute it using the
388+ // "interactiveExecuteCommand" middleware.
389+ // - If no user answers were collected, execute it using the standard
390+ // "executeCommand" middleware.
391+ const overwrite = async ( cmd : string , args : any [ ] , next : ExecuteCommandSignature ) => {
392+ const supported = this . initializeResult ?. capabilities ?. experimental ?. interactiveResolveProvider ;
393+
394+ // Language server does not support interactive command execution.
395+ if ( ! Array . isArray ( supported ) || ! supported . includes ( 'command' ) ) {
396+ return original ? original ( cmd , args , next ) : next ( cmd , args ) ;
397+ }
398+
399+ const resolved = await this . resolveCommandInteractively ( {
400+ command : cmd ,
401+ arguments : args
402+ } as InteractiveExecuteCommandParams ) ;
403+ if ( ! resolved ) {
404+ return undefined ;
405+ }
406+
407+ cmd = resolved . command ;
408+ args = resolved . arguments || [ ] ;
409+ const formAnswers = resolved . formAnswers ;
410+
411+ if ( formAnswers === undefined || formAnswers . length === 0 ) {
412+ // Execute the vscode language client provided "workspace/executeCommand"
413+ // method if the user does not provide any answers.
414+ return original ? original ( cmd , args , next ) : next ( cmd , args ) ;
415+ } else {
416+ // Execute the interactive language client provided "workspace/executeCommand"
417+ // method if the user does provide answers.
418+ return this . interactiveExecuteCommand ( cmd , args , formAnswers ) ;
419+ }
420+ } ;
421+
422+ interactiveOptions . middleware = {
423+ ...middleware ,
424+ executeCommand : overwrite
425+ } ;
331426 }
332427
333428 /**
@@ -358,15 +453,10 @@ export class InteractiveLanguageClient extends LanguageClient {
358453 async resolveCommandInteractively (
359454 param : InteractiveExecuteCommandParams
360455 ) : Promise < InteractiveExecuteCommandParams | undefined > {
361- // Avoid resolving for frequently triggered commands for performance.
362- if ( param . command === 'gopls.package_symbols' ) {
363- return param ;
364- }
365-
366456 // Invoke "command/resolve" at least once to ensure the command
367457 // is fully specified, as the initial input may lack necessary parameters.
368458 for ( let i = 0 ; i < InteractiveLanguageClient . MAX_RETRY ; i ++ ) {
369- const result = await this . ResolveCommand ( param ) ;
459+ const result = await this . interactiveResolveCommand ( param ) ;
370460 if ( ! result ) {
371461 return undefined ;
372462 }
@@ -403,52 +493,86 @@ export class InteractiveLanguageClient extends LanguageClient {
403493 return param ;
404494 }
405495
406- // ResolveCommand handles the interactive resolution of a command prior to its
407- // execution.
408- //
409- // It processes an [InteractiveExecuteCommandParams] to determine if the command
410- // requires interactive input, or to validate user-provided answers submitted
411- // via the embedded [InteractiveParams].
412- //
413- // If the command requires user input (e.g., the initial probe) or if the
414- // provided answers are invalid, it returns a modified [InteractiveExecuteCommandParams]
415- // populated with FormFields to prompt the user. If the input is valid and
416- // complete, or if the command requires no interaction at all, it returns an
417- // [InteractiveExecuteCommandParams] with an empty form, signaling the client to
418- // proceed with execution.
419- //
420- // See [InteractiveParams] for the complete multi-step client-server handshake
421- // and the architectural reasoning behind dedicated ResolveXXX methods.
422- async ResolveCommand ( param : InteractiveExecuteCommandParams ) : Promise < InteractiveExecuteCommandParams | undefined > {
423- const requestType = new RequestType < InteractiveExecuteCommandParams , InteractiveExecuteCommandParams , void > (
424- 'command/resolve'
425- ) ;
426- return this . sendRequest < InteractiveExecuteCommandParams > ( 'command/resolve' , param ) . then ( undefined , ( error ) => {
427- return this . handleFailedRequest ( requestType , undefined , error , undefined ) ;
428- } ) ;
429- }
496+ /**
497+ * Executes a command on the language server with the validated form answers.
498+ *
499+ * It routes the execution through the `interactiveExecuteCommand` middleware
500+ * hook if registered, falling back to sending a `'workspace/executeCommand'`
501+ * LSP request with the user's answered form.
502+ *
503+ * @param command The identifier of the actual command handler to execute.
504+ * @param args Arguments that the command should be invoked with.
505+ * @param formAnswers The finalized, server-validated answers collected from the user.
506+ * @returns A provider result resolving to the command execution result.
507+ */
508+ private interactiveExecuteCommand = (
509+ command : string ,
510+ args : any [ ] ,
511+ formAnswers : any [ ]
512+ ) : vscode . ProviderResult < any > => {
513+ const _interactiveExecuteCommand : InteractiveExecuteCommandSignature = ( command , args , formAnswers ) => {
514+ const requestType = new RequestType < InteractiveExecuteCommandParams , any , void > ( 'workspace/executeCommand' ) ;
515+ return this . sendRequest < FormEnumEntry [ ] > ( 'workspace/executeCommand' , {
516+ command : command ,
517+ arguments : args ,
518+ formAnswers : formAnswers
519+ } as InteractiveExecuteCommandParams ) . then ( undefined , ( error ) => {
520+ return this . handleFailedRequest ( requestType , undefined , error , undefined ) ;
521+ } ) ;
522+ } ;
430523
431- // Executes an LSP command with an extended payload containing interactive form
432- // answers.
433- async InteractiveExecuteCommand ( command : string , args : any [ ] , formAnswers : any [ ] ) : Promise < any > {
434- const requestType = new RequestType < InteractiveExecuteCommandParams , any , void > ( 'workspace/executeCommand' ) ;
435- return this . sendRequest ( 'workspace/executeCommand' , {
436- command : command ,
437- arguments : args ,
438- formAnswers : formAnswers
439- } as InteractiveExecuteCommandParams ) . then ( undefined , ( error ) => {
440- return this . handleFailedRequest ( requestType , undefined , error , undefined ) ;
441- } ) ;
442- }
524+ const middleware = this . clientOptions . middleware as InteractiveMiddleware | undefined ;
525+ return middleware ?. interactiveExecuteCommand
526+ ? middleware . interactiveExecuteCommand ( command , args , formAnswers , _interactiveExecuteCommand )
527+ : _interactiveExecuteCommand ( command , args , formAnswers ) ;
528+ } ;
529+
530+ /**
531+ * Handles the interactive resolution of a command prior to its execution.
532+ *
533+ * It processes an [InteractiveExecuteCommandParams] to determine if the command
534+ * requires interactive input, or to validate user-provided answers submitted
535+ * via the embedded [InteractiveParams].
536+ *
537+ * If the command requires user input (e.g., the initial probe) or if the
538+ * provided answers are invalid, it returns a modified [InteractiveExecuteCommandParams]
539+ * populated with FormFields to prompt the user. If the input is valid and
540+ * complete, or if the command requires no interaction at all, it returns an
541+ * [InteractiveExecuteCommandParams] with an empty form, signaling the client to
542+ * proceed with execution.
543+ *
544+ * See [InteractiveParams] for the complete multi-step client-server handshake
545+ * and the architectural reasoning behind dedicated ResolveXXX methods.
546+ *
547+ * It routes the resolution through the `interactiveResolveCommand`
548+ * middleware hook if registered, falling back to sending a `'command/resolve'`
549+ * LSP request.
550+ *
551+ * @param param The command parameters and previous answers to resolve/validate.
552+ * @returns A provider result resolving to the updated command execution parameters.
553+ */
554+ private interactiveResolveCommand = (
555+ param : InteractiveExecuteCommandParams
556+ ) : vscode . ProviderResult < InteractiveExecuteCommandParams > => {
557+ const _interactiveResolveCommand : InteractiveResolveCommandSignature = ( param ) => {
558+ const requestType = new RequestType < InteractiveExecuteCommandParams , any , void > ( 'command/resolve' ) ;
559+ return this . sendRequest < FormEnumEntry [ ] > ( 'command/resolve' , param ) . then ( undefined , ( error ) => {
560+ return this . handleFailedRequest ( requestType , undefined , error , undefined ) ;
561+ } ) ;
562+ } ;
563+
564+ const middleware = this . clientOptions . middleware as InteractiveMiddleware | undefined ;
565+ return middleware ?. interactiveResolveCommand
566+ ? middleware . interactiveResolveCommand ( param , _interactiveResolveCommand )
567+ : _interactiveResolveCommand ( param ) ;
568+ } ;
443569
444570 /**
445571 * Queries the language server to dynamically retrieve enumeration entries for
446- * interactive form fields of type 'lazyEnum'.
572+ * interactive form fields of type ` 'lazyEnum'` .
447573 *
448- * This field uses an arrow function to preserve the lexical `this` context of
449- * the client. It routes the query through the `interactiveListEnum`
450- * middleware hook if registered, falling back to the default
451- * `'interactive/listEnum'` LSP request.
574+ * It routes the query through the `interactiveListEnum` middleware hook if
575+ * registered, falling back to sending an `'interactive/listEnum'` LSP request.
452576 *
453577 * @param param The query parameters, including the data source name, static
454578 * config, and filter string.
0 commit comments