Skip to content

Commit 024e565

Browse files
authored
Manage prompt files: copy (microsoft#250301)
* manage prompt files: copy * fix comments
1 parent 6a39965 commit 024e565

File tree

2 files changed

+111
-63
lines changed

2 files changed

+111
-63
lines changed

src/vs/workbench/contrib/chat/browser/actions/promptActions/dialogs/askToSelectPrompt/promptFilePickers.ts

Lines changed: 87 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { IKeyMods, IQuickInputButton, IQuickInputService, IQuickPick, IQuickPick
2323
import { askForPromptFileName } from '../../../../promptSyntax/contributions/createPromptCommand/dialogs/askForPromptName.js';
2424
import { IInstantiationService } from '../../../../../../../../platform/instantiation/common/instantiation.js';
2525
import { CancellationToken } from '../../../../../../../../base/common/cancellation.js';
26+
import { askForPromptSourceFolder } from '../../../../promptSyntax/contributions/createPromptCommand/dialogs/askForPromptSourceFolder.js';
2627

2728
/**
2829
* Options for the {@link askToSelectInstructions} function.
@@ -47,6 +48,7 @@ export interface ISelectOptions {
4748
readonly optionEdit?: boolean;
4849
readonly optionDelete?: boolean;
4950
readonly optionRename?: boolean;
51+
readonly optionMove?: boolean;
5052
}
5153

5254
export interface ISelectPromptResult {
@@ -154,6 +156,14 @@ const RENAME_BUTTON: IQuickInputButton = Object.freeze({
154156
iconClass: ThemeIcon.asClassName(Codicon.pencil),
155157
});
156158

159+
/**
160+
* Button that copies a prompt file.
161+
*/
162+
const COPY_BUTTON: IQuickInputButton = Object.freeze({
163+
tooltip: localize('copy', "Copy"),
164+
iconClass: ThemeIcon.asClassName(Codicon.copy),
165+
});
166+
157167
export class PromptFilePickers {
158168
constructor(
159169
@ILabelService private readonly _labelService: ILabelService,
@@ -253,6 +263,9 @@ export class PromptFilePickers {
253263
if (options.optionRename !== false) {
254264
buttons.push(RENAME_BUTTON);
255265
}
266+
if (options.optionMove !== false) {
267+
buttons.push(COPY_BUTTON);
268+
}
256269
if (options.optionDelete !== false) {
257270
buttons.push(DELETE_BUTTON);
258271
}
@@ -343,6 +356,17 @@ export class PromptFilePickers {
343356
};
344357
}
345358

359+
private async keepQuickPickOpen(quickPick: IQuickPick<IPromptPickerQuickPickItem>, work: () => Promise<void>): Promise<void> {
360+
const previousIgnoreFocusOut = quickPick.ignoreFocusOut;
361+
quickPick.ignoreFocusOut = true;
362+
try {
363+
await work();
364+
} finally {
365+
quickPick.ignoreFocusOut = previousIgnoreFocusOut;
366+
}
367+
368+
}
369+
346370
private async _handleButtonClick(quickPick: IQuickPick<IPromptPickerQuickPickItem>, context: IQuickPickItemButtonEvent<IPromptPickerQuickPickItem>, options: ISelectOptions): Promise<void> {
347371
const { item, button } = context;
348372
const { value, } = item;
@@ -353,20 +377,32 @@ export class PromptFilePickers {
353377
return;
354378
}
355379

380+
// `copy` button was pressed, open the prompt file in editor
381+
if (button === COPY_BUTTON) {
382+
const currentFolder = dirname(value);
383+
const newFolder = await this._instaService.invokeFunction(askForPromptSourceFolder, options.type, currentFolder);
384+
if (!newFolder) {
385+
return;
386+
}
387+
const newName = await this._instaService.invokeFunction(askForPromptFileName, options.type, newFolder.uri, item.label);
388+
if (!newName) {
389+
return;
390+
}
391+
const newFile = joinPath(newFolder.uri, newName);
392+
await this._fileService.copy(value, newFile);
393+
await this._openerService.open(newFile);
394+
395+
return;
396+
}
397+
356398
// `rename` button was pressed, open a rename dialog
357399
if (button === RENAME_BUTTON) {
358-
// don't close the main prompt selection dialog by the confirmation dialog
359-
const previousIgnoreFocusOut = quickPick.ignoreFocusOut;
360-
quickPick.ignoreFocusOut = true;
361-
try {
362-
const currentFolder = dirname(value);
363-
const newName = await this._instaService.invokeFunction(askForPromptFileName, options.type, currentFolder);
364-
if (newName) {
365-
await this._fileService.move(value, joinPath(currentFolder, newName));
366-
}
367-
} finally {
368-
quickPick.ignoreFocusOut = previousIgnoreFocusOut;
369-
quickPick.items = await this._createPromptPickItems(options);
400+
const currentFolder = dirname(value);
401+
const newName = await this._instaService.invokeFunction(askForPromptFileName, options.type, currentFolder, item.label);
402+
if (newName) {
403+
const newFile = joinPath(currentFolder, newName);
404+
await this._fileService.move(value, newFile);
405+
await this._openerService.open(newFile);
370406
}
371407
return;
372408
}
@@ -389,56 +425,52 @@ export class PromptFilePickers {
389425
);
390426

391427
// don't close the main prompt selection dialog by the confirmation dialog
392-
const previousIgnoreFocusOut = quickPick.ignoreFocusOut;
393-
quickPick.ignoreFocusOut = true;
394-
395-
const filename = getCleanPromptName(value);
396-
const { confirmed } = await this._dialogService.confirm({
397-
message: localize(
398-
'commands.prompts.use.select-dialog.delete-prompt.confirm.message',
399-
"Are you sure you want to delete '{0}'?",
400-
filename,
401-
),
402-
});
403-
404-
// restore the previous value of the `ignoreFocusOut` property
405-
quickPick.ignoreFocusOut = previousIgnoreFocusOut;
406-
407-
// if prompt deletion was not confirmed, nothing to do
408-
if (!confirmed) {
409-
return;
410-
}
428+
await this.keepQuickPickOpen(quickPick, async () => {
429+
430+
const filename = getCleanPromptName(value);
431+
const { confirmed } = await this._dialogService.confirm({
432+
message: localize(
433+
'commands.prompts.use.select-dialog.delete-prompt.confirm.message',
434+
"Are you sure you want to delete '{0}'?",
435+
filename,
436+
),
437+
});
438+
439+
// if prompt deletion was not confirmed, nothing to do
440+
if (!confirmed) {
441+
return;
442+
}
411443

412-
// prompt deletion was confirmed so delete the prompt file
413-
await this._fileService.del(value);
444+
// prompt deletion was confirmed so delete the prompt file
445+
await this._fileService.del(value);
414446

415-
// remove the deleted prompt from the selection dialog list
416-
let removedIndex = -1;
417-
quickPick.items = quickPick.items.filter((option, index) => {
418-
if (option === item) {
419-
removedIndex = index;
447+
// remove the deleted prompt from the selection dialog list
448+
let removedIndex = -1;
449+
quickPick.items = quickPick.items.filter((option, index) => {
450+
if (option === item) {
451+
removedIndex = index;
420452

421-
return false;
422-
}
423-
424-
return true;
425-
});
453+
return false;
454+
}
426455

427-
// if the deleted item was active item, find a new item to set as active
428-
if (activeItem && (activeItem === item)) {
429-
assert(
430-
removedIndex >= 0,
431-
'Removed item index must be a valid index.',
432-
);
456+
return true;
457+
});
433458

434-
// we set the previous item as new active, or the next item
435-
// if removed prompt item was in the beginning of the list
436-
const newActiveItemIndex = Math.max(removedIndex - 1, 0);
437-
const newActiveItem: IPromptPickerQuickPickItem | undefined = quickPick.items[newActiveItemIndex];
459+
// if the deleted item was active item, find a new item to set as active
460+
if (activeItem && (activeItem === item)) {
461+
assert(
462+
removedIndex >= 0,
463+
'Removed item index must be a valid index.',
464+
);
438465

439-
quickPick.activeItems = newActiveItem ? [newActiveItem] : [];
440-
}
466+
// we set the previous item as new active, or the next item
467+
// if removed prompt item was in the beginning of the list
468+
const newActiveItemIndex = Math.max(removedIndex - 1, 0);
469+
const newActiveItem: IPromptPickerQuickPickItem | undefined = quickPick.items[newActiveItemIndex];
441470

471+
quickPick.activeItems = newActiveItem ? [newActiveItem] : [];
472+
}
473+
});
442474
return;
443475
}
444476

src/vs/workbench/contrib/chat/browser/promptSyntax/contributions/createPromptCommand/dialogs/askForPromptSourceFolder.ts

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import { localize } from '../../../../../../../../nls.js';
77
import { URI } from '../../../../../../../../base/common/uri.js';
88
import { WithUriValue } from '../../../../../../../../base/common/types.js';
9-
import { basename, extUri } from '../../../../../../../../base/common/resources.js';
9+
import { basename, extUri, isEqual } from '../../../../../../../../base/common/resources.js';
1010
import { ILabelService } from '../../../../../../../../platform/label/common/label.js';
1111
import { IOpenerService } from '../../../../../../../../platform/opener/common/opener.js';
1212
import { PROMPT_DOCUMENTATION_URL } from '../../../../../common/promptSyntax/constants.js';
@@ -22,11 +22,11 @@ interface IFolderQuickPickItem extends IQuickPickItem {
2222

2323
/**
2424
* Asks the user for a specific prompt folder, if multiple folders provided.
25-
* Returns immediately if only one folder available.
2625
*/
2726
export async function askForPromptSourceFolder(
2827
accessor: ServicesAccessor,
29-
type: PromptsType
28+
type: PromptsType,
29+
existingFolder?: URI | undefined,
3030
): Promise<IPromptPath | undefined> {
3131
const quickInputService = accessor.get(IQuickInputService);
3232
const promptsService = accessor.get(IPromptsService);
@@ -44,28 +44,29 @@ export async function askForPromptSourceFolder(
4444
return;
4545
}
4646

47-
// if there is only one folder, no need to ask
48-
// note! when we add more actions to the dialog, this will have to go
49-
if (folders.length === 1) {
47+
// if there is only one folder and it's for new, no need to ask
48+
if (!existingFolder && folders.length === 1) {
5049
return folders[0];
5150
}
5251

5352
const pickOptions: IPickOptions<IFolderQuickPickItem> = {
54-
placeHolder: getPlaceholderString(type),
53+
placeHolder: existingFolder ? getPlaceholderStringforMove(type) : getPlaceholderStringforNew(type),
5554
canPickMany: false,
5655
matchOnDescription: true,
5756
};
5857

5958
// create list of source folder locations
6059
const foldersList = folders.map<IFolderQuickPickItem>(folder => {
6160
const uri = folder.uri;
61+
const detail = (existingFolder && isEqual(uri, existingFolder)) ? localize('current.folder', "Current Location") : undefined;
6262
if (folder.storage === 'user') {
6363
return {
6464
type: 'item',
6565
label: localize(
6666
'commands.prompts.create.source-folder.user',
6767
"User Data Folder",
6868
),
69+
detail,
6970
description: labelService.getUriLabel(uri),
7071
tooltip: uri.fsPath,
7172
folder
@@ -83,6 +84,7 @@ export async function askForPromptSourceFolder(
8384
return {
8485
type: 'item',
8586
label: basename(uri),
87+
detail,
8688
description: labelService.getUriLabel(uri, { relative: true }),
8789
tooltip: uri.fsPath,
8890
folder,
@@ -97,6 +99,7 @@ export async function askForPromptSourceFolder(
9799
'commands.prompts.create.source-folder.current-workspace',
98100
"Current Workspace",
99101
),
102+
detail,
100103
// use absolute path as the description
101104
description: labelService.getUriLabel(uri, { relative: false }),
102105
tooltip: uri.fsPath,
@@ -112,7 +115,7 @@ export async function askForPromptSourceFolder(
112115
return answer.folder;
113116
}
114117

115-
function getPlaceholderString(type: PromptsType): string {
118+
function getPlaceholderStringforNew(type: PromptsType): string {
116119
switch (type) {
117120
case PromptsType.instructions:
118121
return localize('workbench.command.instructions.create.location.placeholder', "Select a location to create the instructions file in...");
@@ -125,6 +128,19 @@ function getPlaceholderString(type: PromptsType): string {
125128
}
126129
}
127130

131+
function getPlaceholderStringforMove(type: PromptsType): string {
132+
switch (type) {
133+
case PromptsType.instructions:
134+
return localize('workbench.command.instructions.move.location.placeholder', "Select a location to move the instructions file to...");
135+
case PromptsType.prompt:
136+
return localize('workbench.command.prompt.move.location.placeholder', "Select a location to move the prompt file to...");
137+
case PromptsType.mode:
138+
return localize('workbench.command.mode.move.location.placeholder', "Select a location to move the mode file to...");
139+
default:
140+
throw new Error('Unknown prompt type');
141+
}
142+
}
143+
128144
/**
129145
* Shows a dialog to the user when no prompt source folders are found.
130146
*

0 commit comments

Comments
 (0)