From 08f802cb592e3a00aef869a5ec46b9ffb59bc9cc Mon Sep 17 00:00:00 2001 From: Oleg Solomko Date: Wed, 30 Apr 2025 09:26:24 -0700 Subject: [PATCH] allow for multi-select of the instruction files --- .../chatAttachInstructionsAction.ts | 13 +--- .../askToSelectPrompt/promptFilePickers.ts | 59 ++++++++++++------- .../utils/attachInstructions.ts | 1 + 3 files changed, 43 insertions(+), 30 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/promptActions/chatAttachInstructionsAction.ts b/src/vs/workbench/contrib/chat/browser/actions/promptActions/chatAttachInstructionsAction.ts index a7b27406fa0..fd7cacf06de 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/promptActions/chatAttachInstructionsAction.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/promptActions/chatAttachInstructionsAction.ts @@ -103,21 +103,14 @@ class AttachInstructionsAction extends Action2 { } // find all prompt files in the user workspace - const promptFiles = await promptsService.listPromptFiles('instructions'); + const instructionFiles = await promptsService.listPromptFiles('instructions'); const placeholder = localize( 'commands.instructions.select-dialog.placeholder', 'Select instructions files to attach', ); - const instructions = await pickers.selectInstructionsFiles({ promptFiles, placeholder }); - - if (instructions !== undefined) { - const widget = await attachInstructionsFiles( - instructions, - attachOptions, - ); - widget.focusInput(); - } + // TODO: @legomushroom - use the options.widget reference? + await pickers.selectInstructionsFiles({ promptFiles: instructionFiles, placeholder }); } } diff --git a/src/vs/workbench/contrib/chat/browser/actions/promptActions/dialogs/askToSelectPrompt/promptFilePickers.ts b/src/vs/workbench/contrib/chat/browser/actions/promptActions/dialogs/askToSelectPrompt/promptFilePickers.ts index bdd5b506f4f..84f726c6218 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/promptActions/dialogs/askToSelectPrompt/promptFilePickers.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/promptActions/dialogs/askToSelectPrompt/promptFilePickers.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// TODO: @legomushroom import { localize } from '../../../../../../../../nls.js'; import { URI } from '../../../../../../../../base/common/uri.js'; import { OS } from '../../../../../../../../base/common/platform.js'; @@ -18,11 +19,15 @@ import { ILabelService } from '../../../../../../../../platform/label/common/lab import { IOpenerService } from '../../../../../../../../platform/opener/common/opener.js'; import { UILabelProvider } from '../../../../../../../../base/common/keybindingLabels.js'; import { IDialogService } from '../../../../../../../../platform/dialogs/common/dialogs.js'; +import { ICommandService } from '../../../../../../../../platform/commands/common/commands.js'; import { getCleanPromptName } from '../../../../../../../../platform/prompts/common/constants.js'; import { INSTRUCTIONS_DOCUMENTATION_URL, PROMPT_DOCUMENTATION_URL } from '../../../../../common/promptSyntax/constants.js'; -import { IKeyMods, IQuickInputButton, IQuickInputService, IQuickPick, IQuickPickItem, IQuickPickItemButtonEvent } from '../../../../../../../../platform/quickinput/common/quickInput.js'; -import { ICommandService } from '../../../../../../../../platform/commands/common/commands.js'; import { NEW_PROMPT_COMMAND_ID, NEW_INSTRUCTIONS_COMMAND_ID } from '../../../../promptSyntax/contributions/createPromptCommand/createPromptCommand.js'; +import { IKeyMods, IQuickInputButton, IQuickInputService, IQuickPick, IQuickPickItem, IQuickPickItemButtonEvent } from '../../../../../../../../platform/quickinput/common/quickInput.js'; +import { attachInstructionsFiles } from './utils/attachInstructions.js'; +import { IChatWidget } from '../../../../chat.js'; +import { IViewsService } from '../../../../../../../services/views/common/viewsService.js'; +import { pick } from '../../../../../../../../base/common/arrays.js'; /** * Options for the {@link askToSelectInstructions} function. @@ -60,7 +65,7 @@ export interface ISelectPromptResult { } /** - * Button that opems the documentation. + * Button that opens the documentation. */ const HELP_BUTTON: IQuickInputButton = Object.freeze({ tooltip: localize('help', "help"), @@ -121,12 +126,14 @@ const DELETE_BUTTON: IQuickInputButton = Object.freeze({ export class PromptFilePickers { constructor( + // TODO: @legomushroom - cleanup the names @ILabelService private readonly _labelService: ILabelService, @IQuickInputService private readonly _quickInputService: IQuickInputService, @IOpenerService private readonly _openerService: IOpenerService, @IFileService private readonly _fileService: IFileService, @IDialogService private readonly _dialogService: IDialogService, @ICommandService private readonly _commandService: ICommandService, + @IViewsService private readonly _viewsService: IViewsService, ) { } /** @@ -135,7 +142,7 @@ export class PromptFilePickers { * If {@link ISelectOptions.resource resource} is provided, the dialog will have * the resource pre-selected in the prompts list. */ - public async selectInstructionsFiles(options: ISelectOptions): Promise { + public async selectInstructionsFiles(options: ISelectOptions): Promise { const fileOptions = this._createPromptPickItems(options); fileOptions.splice(0, 0, NEW_INSTRUCTIONS_FILE_OPTION); @@ -147,34 +154,41 @@ export class PromptFilePickers { quickPick.matchOnDescription = true; quickPick.items = fileOptions; - return new Promise(resolve => { + return new Promise((resolve) => { const disposables = new DisposableStore(); - - let isResolved = false; + let lastActiveWidget: IChatWidget | undefined; // then the dialog is hidden or disposed for other reason, // dispose everything and resolve the main promise disposables.add({ dispose() { quickPick.dispose(); - if (!isResolved) { - resolve(undefined); - isResolved = true; - } + resolve(undefined); + // if something was attached (lastActiveWidget is set), + // focus on the target chat input + lastActiveWidget?.focusInput(); }, }); // handle the prompt `accept` event disposables.add(quickPick.onDidAccept(async (event) => { - const { selectedItems } = quickPick; + const { selectedItems, keyMods } = quickPick; if (selectedItems[0] === NEW_INSTRUCTIONS_FILE_OPTION) { await this._commandService.executeCommand(NEW_INSTRUCTIONS_COMMAND_ID); return; } - resolve(selectedItems.map(item => item.value)); - isResolved = true; + // otherwise attach the selected instructions file to a chat input + const widget = await attachInstructionsFiles( + selectedItems.map(pick('value')), + { + inNewChat: keyMods.ctrlCmd, + viewsService: this._viewsService, + commandService: this._commandService, + }, + ); + lastActiveWidget = widget; // if user submitted their selection, close the dialog if (!event.inBackground) { @@ -184,10 +198,10 @@ export class PromptFilePickers { // handle the `button click` event on a list item (edit, delete, etc.) disposables.add(quickPick.onDidTriggerItemButton( - e => this._handleButtonClick(quickPick, e)) - ); + this._handleButtonClick.bind(this, quickPick), + )); - // when the dialog is hidden, dispose everything + // when the dialog is being hidden, dispose everything disposables.add(quickPick.onDidHide( disposables.dispose.bind(disposables), )); @@ -203,6 +217,7 @@ export class PromptFilePickers { * If {@link ISelectOptions.resource resource} is provided, the dialog will have * the resource pre-selected in the prompts list. */ + // TODO: @legomushroom - fix this one? public async selectPromptFile(options: ISelectOptions): Promise { const fileOptions = this._createPromptPickItems(options); @@ -248,6 +263,7 @@ export class PromptFilePickers { isResolved = true; } + // TODO: @legomushroom // if user submitted their selection, close the dialog if (!event.inBackground) { disposables.dispose(); @@ -256,8 +272,8 @@ export class PromptFilePickers { // handle the `button click` event on a list item (edit, delete, etc.) disposables.add(quickPick.onDidTriggerItemButton( - e => this._handleButtonClick(quickPick, e)) - ); + this._handleButtonClick.bind(this, quickPick), + )); // when the dialog is hidden, dispose everything disposables.add(quickPick.onDidHide( @@ -339,7 +355,10 @@ export class PromptFilePickers { }; } - private async _handleButtonClick(quickPick: IQuickPick>, context: IQuickPickItemButtonEvent>) { + private async _handleButtonClick( + quickPick: IQuickPick>, + context: IQuickPickItemButtonEvent>, + ) { const { item, button } = context; const { value } = item; diff --git a/src/vs/workbench/contrib/chat/browser/actions/promptActions/dialogs/askToSelectPrompt/utils/attachInstructions.ts b/src/vs/workbench/contrib/chat/browser/actions/promptActions/dialogs/askToSelectPrompt/utils/attachInstructions.ts index 14bd701edee..b9cd0261e2f 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/promptActions/dialogs/askToSelectPrompt/utils/attachInstructions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/promptActions/dialogs/askToSelectPrompt/utils/attachInstructions.ts @@ -77,6 +77,7 @@ const showChat = async ( const { commandService, viewsService } = options; if (createNew === true) { + // TODO: @legomushroom - prevent focus somehow? await commandService.executeCommand(ACTION_ID_NEW_CHAT); }