mirror of
https://github.com/microsoft/vscode.git
synced 2026-02-15 07:28:05 +00:00
update
This commit is contained in:
committed by
Oleg Solomko
parent
4097e0fdf7
commit
e2f4bbb62c
@@ -22,6 +22,18 @@
|
||||
"copilot-instructions.md"
|
||||
],
|
||||
"configuration": "./language-configuration.json"
|
||||
},
|
||||
{
|
||||
"id": "instructions",
|
||||
"aliases": [
|
||||
"Instructions",
|
||||
"instructions"
|
||||
],
|
||||
"extensions": [
|
||||
".instructions.md",
|
||||
"copilot-instructions.md"
|
||||
],
|
||||
"configuration": "./language-configuration.json"
|
||||
}
|
||||
],
|
||||
"grammars": [
|
||||
@@ -33,6 +45,15 @@
|
||||
"markup.underline.link.markdown",
|
||||
"punctuation.definition.list.begin.markdown"
|
||||
]
|
||||
},
|
||||
{
|
||||
"language": "instructions",
|
||||
"path": "./syntaxes/prompt.tmLanguage.json",
|
||||
"scopeName": "text.html.markdown.prompt",
|
||||
"unbalancedBracketScopes": [
|
||||
"markup.underline.link.markdown",
|
||||
"punctuation.definition.list.begin.markdown"
|
||||
]
|
||||
}
|
||||
],
|
||||
"configurationDefaults": {
|
||||
@@ -40,6 +61,11 @@
|
||||
"editor.unicodeHighlight.ambiguousCharacters": false,
|
||||
"editor.unicodeHighlight.invisibleCharacters": false,
|
||||
"diffEditor.ignoreTrimWhitespace": false
|
||||
},
|
||||
"[instructions]": {
|
||||
"editor.unicodeHighlight.ambiguousCharacters": false,
|
||||
"editor.unicodeHighlight.invisibleCharacters": false,
|
||||
"diffEditor.ignoreTrimWhitespace": false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"displayName": "Prompt Language Basics",
|
||||
"description": "Syntax highlighting for Prompt documents."
|
||||
"description": "Syntax highlighting for Prompt and Instructions documents."
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ import { convertBufferToScreenshotVariable, ScreenshotVariableId } from '../cont
|
||||
import { resizeImage } from '../imageUtils.js';
|
||||
import { INSTRUCTIONS_COMMAND_ID } from '../promptSyntax/contributions/usePromptCommand.js';
|
||||
import { CHAT_CATEGORY } from './chatActions.js';
|
||||
import { runAttachPromptAction, registerReusablePromptActions } from './reusablePromptActions/index.js';
|
||||
import { runAttachInstructionsAction, registerReusablePromptActions } from './reusablePromptActions/index.js';
|
||||
|
||||
export function registerChatContextActions() {
|
||||
registerAction2(AttachContextAction);
|
||||
@@ -78,7 +78,7 @@ export function registerChatContextActions() {
|
||||
*/
|
||||
type IAttachmentQuickPickItem = ICommandVariableQuickPickItem | IQuickAccessQuickPickItem | IToolQuickPickItem |
|
||||
IImageQuickPickItem | IOpenEditorsQuickPickItem | ISearchResultsQuickPickItem |
|
||||
IScreenShotQuickPickItem | IRelatedFilesQuickPickItem | IReusablePromptQuickPickItem | IFolderQuickPickItem | IDiagnosticsQuickPickItem;
|
||||
IScreenShotQuickPickItem | IRelatedFilesQuickPickItem | IInstructionsQuickPickItem | IFolderQuickPickItem | IDiagnosticsQuickPickItem;
|
||||
|
||||
/**
|
||||
* These are the types that we can get out of the quick pick
|
||||
@@ -147,12 +147,12 @@ function isRelatedFileQuickPickItem(obj: unknown): obj is IRelatedFilesQuickPick
|
||||
/**
|
||||
* Checks is a provided object is a prompt instructions quick pick item.
|
||||
*/
|
||||
function isPromptInstructionsQuickPickItem(obj: unknown): obj is IReusablePromptQuickPickItem {
|
||||
function isPromptInstructionsQuickPickItem(obj: unknown): obj is IInstructionsQuickPickItem {
|
||||
if (!obj || typeof obj !== 'object') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ('kind' in obj && obj.kind === 'reusable-prompt');
|
||||
return ('kind' in obj && obj.kind === INSTRUCTION_PICK_ID);
|
||||
}
|
||||
|
||||
interface IRelatedFilesQuickPickItem extends IQuickPickItem {
|
||||
@@ -235,17 +235,17 @@ interface IDiagnosticsQuickPickItemWithFilter extends IQuickPickItem {
|
||||
/**
|
||||
* Quick pick item for reusable prompt attachment.
|
||||
*/
|
||||
const REUSABLE_PROMPT_PICK_ID = 'reusable-prompt';
|
||||
interface IReusablePromptQuickPickItem extends IQuickPickItem {
|
||||
const INSTRUCTION_PICK_ID = 'instructions';
|
||||
interface IInstructionsQuickPickItem extends IQuickPickItem {
|
||||
/**
|
||||
* The ID of the quick pick item.
|
||||
*/
|
||||
id: typeof REUSABLE_PROMPT_PICK_ID;
|
||||
id: typeof INSTRUCTION_PICK_ID;
|
||||
|
||||
/**
|
||||
* Unique kind identifier of the reusable prompt attachment.
|
||||
* Unique kind identifier of the instructions attachment.
|
||||
*/
|
||||
kind: typeof REUSABLE_PROMPT_PICK_ID;
|
||||
kind: typeof INSTRUCTION_PICK_ID;
|
||||
|
||||
/**
|
||||
* Keybinding of the command.
|
||||
@@ -628,7 +628,7 @@ export class AttachContextAction extends Action2 {
|
||||
toAttach.push(convertBufferToScreenshotVariable(blob));
|
||||
}
|
||||
} else if (isPromptInstructionsQuickPickItem(pick)) {
|
||||
await runAttachPromptAction({ widget }, commandService);
|
||||
await runAttachInstructionsAction({ widget }, commandService);
|
||||
} else {
|
||||
// Anything else is an attachment
|
||||
const attachmentPick = pick as IAttachmentQuickPickItem;
|
||||
@@ -836,9 +836,9 @@ export class AttachContextAction extends Action2 {
|
||||
const keybinding = keybindingService.lookupKeybinding(INSTRUCTIONS_COMMAND_ID, contextKeyService);
|
||||
|
||||
quickPickItems.push({
|
||||
id: REUSABLE_PROMPT_PICK_ID,
|
||||
kind: REUSABLE_PROMPT_PICK_ID,
|
||||
label: localize('chatContext.attach.prompt.label', 'Prompt...'),
|
||||
id: INSTRUCTION_PICK_ID,
|
||||
kind: INSTRUCTION_PICK_ID,
|
||||
label: localize('chatContext.attach.instructions.label', 'Instructions...'),
|
||||
iconClass: ThemeIcon.asClassName(Codicon.bookmark),
|
||||
keybinding,
|
||||
});
|
||||
|
||||
@@ -19,8 +19,8 @@ import { ICommandService } from '../../../../../../platform/commands/common/comm
|
||||
import { ContextKeyExpr } from '../../../../../../platform/contextkey/common/contextkey.js';
|
||||
import { Action2, registerAction2 } from '../../../../../../platform/actions/common/actions.js';
|
||||
import { IQuickInputService } from '../../../../../../platform/quickinput/common/quickInput.js';
|
||||
import { attachInstructionsFile, IAttachOptions } from './dialogs/askToSelectPrompt/utils/attachPrompt.js';
|
||||
import { ISelectPromptOptions, askToSelectPrompt } from './dialogs/askToSelectPrompt/askToSelectPrompt.js';
|
||||
import { attachInstructionsFiles, IAttachOptions } from './dialogs/askToSelectPrompt/utils/attachPrompt.js';
|
||||
import { ISelectInstructionsOptions, askToSelectInstructions } from './dialogs/askToSelectPrompt/askToSelectPrompt.js';
|
||||
|
||||
/**
|
||||
* Action ID for the `Attach Instruction` action.
|
||||
@@ -30,8 +30,8 @@ const ATTACH_INSTRUCTIONS_ACTION_ID = 'workbench.action.chat.attach.instructions
|
||||
/**
|
||||
* Options for the {@link AttachInstructionsAction} action.
|
||||
*/
|
||||
export interface IChatAttachPromptActionOptions extends Pick<
|
||||
ISelectPromptOptions, 'resource' | 'widget'
|
||||
export interface IChatAttachInstructionsActionOptions extends Pick<
|
||||
ISelectInstructionsOptions, 'resource' | 'widget'
|
||||
> {
|
||||
/**
|
||||
* Whether to create a new chat panel or open
|
||||
@@ -40,7 +40,7 @@ export interface IChatAttachPromptActionOptions extends Pick<
|
||||
inNewChat?: boolean;
|
||||
|
||||
/**
|
||||
* Whether to skip the prompt files selection dialog.
|
||||
* Whether to skip the instructions files selection dialog.
|
||||
*
|
||||
* Note! if this option is set to `true`, the {@link resource}
|
||||
* option `must be defined`.
|
||||
@@ -64,7 +64,7 @@ class AttachInstructionsAction extends Action2 {
|
||||
|
||||
public override async run(
|
||||
accessor: ServicesAccessor,
|
||||
options: IChatAttachPromptActionOptions,
|
||||
options: IChatAttachInstructionsActionOptions,
|
||||
): Promise<void> {
|
||||
const fileService = accessor.get(IFileService);
|
||||
const labelService = accessor.get(ILabelService);
|
||||
@@ -89,8 +89,8 @@ class AttachInstructionsAction extends Action2 {
|
||||
commandService,
|
||||
};
|
||||
|
||||
const { widget } = await attachInstructionsFile(
|
||||
resource,
|
||||
const { widget } = await attachInstructionsFiles(
|
||||
[resource],
|
||||
attachOptions,
|
||||
);
|
||||
|
||||
@@ -102,9 +102,8 @@ class AttachInstructionsAction extends Action2 {
|
||||
// find all prompt files in the user workspace
|
||||
const promptFiles = await promptsService.listPromptFiles('instructions');
|
||||
|
||||
await askToSelectPrompt({
|
||||
await askToSelectInstructions({
|
||||
...options,
|
||||
type: 'instructions',
|
||||
promptFiles,
|
||||
fileService,
|
||||
viewsService,
|
||||
@@ -118,12 +117,12 @@ class AttachInstructionsAction extends Action2 {
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the `Attach Prompt` action with provided options. We export this
|
||||
* Runs the `Attach Instructions` action with provided options. We export this
|
||||
* function instead of {@link ATTACH_INSTRUCTIONS_ACTION_ID} directly to
|
||||
* encapsulate/enforce the correct options to be passed to the action.
|
||||
*/
|
||||
export const runAttachPromptAction = async (
|
||||
options: IChatAttachPromptActionOptions,
|
||||
export const runAttachInstructionsAction = async (
|
||||
options: IChatAttachInstructionsActionOptions,
|
||||
commandService: ICommandService,
|
||||
): Promise<void> => {
|
||||
return await commandService.executeCommand(
|
||||
|
||||
@@ -14,18 +14,17 @@ import { ThemeIcon } from '../../../../../../base/common/themables.js';
|
||||
import { ResourceContextKey } from '../../../../../common/contextkeys.js';
|
||||
import { KeyCode, KeyMod } from '../../../../../../base/common/keyCodes.js';
|
||||
import { PROMPT_LANGUAGE_ID } from '../../../common/promptSyntax/constants.js';
|
||||
import { attachInstructionsFile } from './dialogs/askToSelectPrompt/utils/attachPrompt.js';
|
||||
import { detachPrompt } from './dialogs/askToSelectPrompt/utils/detachPrompt.js';
|
||||
import { runPromptFile } from './dialogs/askToSelectPrompt/utils/attachPrompt.js';
|
||||
import { PromptsConfig } from '../../../../../../platform/prompts/common/config.js';
|
||||
import { ICommandAction } from '../../../../../../platform/action/common/action.js';
|
||||
import { IViewsService } from '../../../../../services/views/common/viewsService.js';
|
||||
import { ServicesAccessor } from '../../../../../../editor/browser/editorExtensions.js';
|
||||
import { EditorContextKeys } from '../../../../../../editor/common/editorContextKeys.js';
|
||||
import { ICommandService } from '../../../../../../platform/commands/common/commands.js';
|
||||
import { getActivePromptUri } from '../../promptSyntax/contributions/usePromptCommand.js';
|
||||
import { ContextKeyExpr } from '../../../../../../platform/contextkey/common/contextkey.js';
|
||||
import { KeybindingWeight } from '../../../../../../platform/keybinding/common/keybindingsRegistry.js';
|
||||
import { Action2, MenuId, registerAction2 } from '../../../../../../platform/actions/common/actions.js';
|
||||
import { ICodeEditorService } from '../../../../../../editor/browser/services/codeEditorService.js';
|
||||
|
||||
/**
|
||||
* Condition for the `Run Current Prompt` action.
|
||||
@@ -121,31 +120,21 @@ abstract class RunPromptBaseAction extends Action2 {
|
||||
const viewsService = accessor.get(IViewsService);
|
||||
const commandService = accessor.get(ICommandService);
|
||||
|
||||
resource ||= getActivePromptUri(accessor);
|
||||
resource ||= getActivePromptFileUri(accessor);
|
||||
assertDefined(
|
||||
resource,
|
||||
'Cannot find URI resource for an active text editor.',
|
||||
);
|
||||
|
||||
const { widget, wasAlreadyAttached } = await attachInstructionsFile(
|
||||
const { widget } = await runPromptFile(
|
||||
resource,
|
||||
{
|
||||
inNewChat,
|
||||
skipIfImplicitlyAttached: true,
|
||||
commandService,
|
||||
viewsService,
|
||||
},
|
||||
);
|
||||
|
||||
// submit the prompt immediately
|
||||
await widget.acceptInput();
|
||||
|
||||
// detach the prompt immediately, unless was attached
|
||||
// before the action was executed
|
||||
if (wasAlreadyAttached === false) {
|
||||
await detachPrompt(resource, { widget });
|
||||
}
|
||||
|
||||
return widget;
|
||||
}
|
||||
}
|
||||
@@ -182,6 +171,20 @@ class RunCurrentPromptAction extends RunPromptBaseAction {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets `URI` of a prompt file open in an active editor instance, if any.
|
||||
*/
|
||||
export const getActivePromptFileUri = (
|
||||
accessor: ServicesAccessor,
|
||||
): URI | undefined => {
|
||||
const codeEditorService = accessor.get(ICodeEditorService);
|
||||
const model = codeEditorService.getActiveCodeEditor()?.getModel();
|
||||
if (model?.getLanguageId() === PROMPT_LANGUAGE_ID) {
|
||||
return model.uri;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Action ID for the `Run Current Prompt In New Chat` action.
|
||||
*/
|
||||
|
||||
@@ -3,17 +3,15 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { PROMPT_DOCS_OPTION } from './constants.js';
|
||||
import { IChatWidget } from '../../../../chat.js';
|
||||
import { attachInstructionsFile } from './utils/attachPrompt.js';
|
||||
import { attachInstructionsFiles } from './utils/attachPrompt.js';
|
||||
import { handleButtonClick } from './utils/handleButtonClick.js';
|
||||
import { URI } from '../../../../../../../../base/common/uri.js';
|
||||
import { assert } from '../../../../../../../../base/common/assert.js';
|
||||
import { createPromptPickItem } from './utils/createPromptPickItem.js';
|
||||
import { createPlaceholderText } from './utils/createPlaceholderText.js';
|
||||
import { extUri } from '../../../../../../../../base/common/resources.js';
|
||||
import { WithUriValue } from '../../../../../../../../base/common/types.js';
|
||||
import { IPromptPath, TPromptsType } from '../../../../../common/promptSyntax/service/types.js';
|
||||
import { IPromptPath } from '../../../../../common/promptSyntax/service/types.js';
|
||||
import { DisposableStore } from '../../../../../../../../base/common/lifecycle.js';
|
||||
import { IFileService } from '../../../../../../../../platform/files/common/files.js';
|
||||
import { ILabelService } from '../../../../../../../../platform/label/common/label.js';
|
||||
@@ -24,9 +22,9 @@ import { ICommandService } from '../../../../../../../../platform/commands/commo
|
||||
import { IQuickInputService, IQuickPickItem } from '../../../../../../../../platform/quickinput/common/quickInput.js';
|
||||
|
||||
/**
|
||||
* Options for the {@link askToSelectPrompt} function.
|
||||
* Options for the {@link askToSelectInstructions} function.
|
||||
*/
|
||||
export interface ISelectPromptOptions {
|
||||
export interface ISelectInstructionsOptions {
|
||||
/**
|
||||
* Prompt resource `URI` to attach to the chat input, if any.
|
||||
* If provided the resource will be pre-selected in the prompt picker dialog,
|
||||
@@ -48,11 +46,6 @@ export interface ISelectPromptOptions {
|
||||
*/
|
||||
readonly promptFiles: readonly IPromptPath[];
|
||||
|
||||
/**
|
||||
* Type of the prompt files to select.
|
||||
*/
|
||||
readonly type: TPromptsType;
|
||||
|
||||
readonly fileService: IFileService;
|
||||
readonly labelService: ILabelService;
|
||||
readonly viewsService: IViewsService;
|
||||
@@ -63,25 +56,20 @@ export interface ISelectPromptOptions {
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the prompt selection dialog to the user that allows to select a prompt file(s).
|
||||
* Shows the instructions selection dialog to the user that allows to select a instructions file(s).
|
||||
*
|
||||
* If {@link ISelectPromptOptions.resource resource} is provided, the dialog will have
|
||||
* If {@link ISelectInstructionsOptions.resource resource} is provided, the dialog will have
|
||||
* the resource pre-selected in the prompts list.
|
||||
*/
|
||||
export const askToSelectPrompt = async (
|
||||
options: ISelectPromptOptions,
|
||||
export const askToSelectInstructions = async (
|
||||
options: ISelectInstructionsOptions,
|
||||
): Promise<void> => {
|
||||
const { promptFiles, resource, quickInputService, labelService, type } = options;
|
||||
const { promptFiles, resource, quickInputService, labelService } = options;
|
||||
|
||||
const fileOptions = promptFiles.map((promptFile) => {
|
||||
return createPromptPickItem(promptFile, labelService);
|
||||
});
|
||||
|
||||
/**
|
||||
* Add a link to the documentation to the end of prompts list.
|
||||
*/
|
||||
fileOptions.push(PROMPT_DOCS_OPTION);
|
||||
|
||||
// if a resource is provided, create an `activeItem` for it to pre-select
|
||||
// it in the UI, and sort the list so the active item appears at the top
|
||||
let activeItem: WithUriValue<IQuickPickItem> | undefined;
|
||||
@@ -100,7 +88,7 @@ export const askToSelectPrompt = async (
|
||||
// "user" prompts are always registered in the prompts list, hence it
|
||||
// should be safe to assume that `resource` is not "user" prompt here
|
||||
storage: 'local',
|
||||
type,
|
||||
type: 'instructions',
|
||||
}, labelService);
|
||||
fileOptions.push(activeItem);
|
||||
}
|
||||
@@ -138,8 +126,8 @@ export const askToSelectPrompt = async (
|
||||
quickPick.canAcceptInBackground = true;
|
||||
quickPick.matchOnDescription = true;
|
||||
quickPick.items = fileOptions;
|
||||
quickPick.canSelectMany = true;
|
||||
|
||||
const { openerService } = options;
|
||||
return await new Promise<void>(resolve => {
|
||||
const disposables = new DisposableStore();
|
||||
|
||||
@@ -161,27 +149,9 @@ export const askToSelectPrompt = async (
|
||||
const { selectedItems } = quickPick;
|
||||
const { keyMods } = quickPick;
|
||||
|
||||
// sanity check to confirm our expectations
|
||||
assert(
|
||||
selectedItems.length === 1,
|
||||
`Only one item can be accepted, got '${selectedItems.length}'.`,
|
||||
);
|
||||
|
||||
const selectedOption = selectedItems[0];
|
||||
|
||||
// whether user selected the docs link option
|
||||
const docsSelected = (selectedOption === PROMPT_DOCS_OPTION);
|
||||
|
||||
// if documentation item was selected, open its link in a browser
|
||||
if (docsSelected) {
|
||||
// note that opening a file in editor also hides(disposes) the dialog
|
||||
await openerService.open(selectedOption.value);
|
||||
return;
|
||||
}
|
||||
|
||||
// otherwise attach the selected prompt to a chat input
|
||||
const attachResult = await attachInstructionsFile(
|
||||
selectedOption.value,
|
||||
// otherwise attach the selected instructions file to a chat input
|
||||
const attachResult = await attachInstructionsFiles(
|
||||
selectedItems.map(item => item.value),
|
||||
{
|
||||
...options,
|
||||
inNewChat: keyMods.ctrlCmd,
|
||||
|
||||
@@ -8,12 +8,13 @@ import { URI } from '../../../../../../../../../base/common/uri.js';
|
||||
import { ACTION_ID_NEW_CHAT } from '../../../../chatClearActions.js';
|
||||
import { extUri } from '../../../../../../../../../base/common/resources.js';
|
||||
import { assertDefined } from '../../../../../../../../../base/common/types.js';
|
||||
import { IChatAttachPromptActionOptions } from '../../../chatAttachPromptAction.js';
|
||||
import { IChatAttachInstructionsActionOptions } from '../../../chatAttachPromptAction.js';
|
||||
import { IViewsService } from '../../../../../../../../services/views/common/viewsService.js';
|
||||
import { ICommandService } from '../../../../../../../../../platform/commands/common/commands.js';
|
||||
import { detachPrompt } from './detachPrompt.js';
|
||||
|
||||
/**
|
||||
* Options for the {@link attachInstructionsFile} function.
|
||||
* Options for the {@link attachInstructionsFiles} function.
|
||||
*/
|
||||
export interface IAttachOptions {
|
||||
/**
|
||||
@@ -26,22 +27,43 @@ export interface IAttachOptions {
|
||||
*/
|
||||
readonly inNewChat?: boolean;
|
||||
|
||||
/**
|
||||
* Whether to skip attaching provided prompt if it is
|
||||
* already attached as an implicit "current file" context.
|
||||
*/
|
||||
readonly skipIfImplicitlyAttached?: boolean;
|
||||
|
||||
readonly viewsService: IViewsService;
|
||||
readonly commandService: ICommandService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return value of the {@link attachInstructionsFile} function.
|
||||
* Return value of the {@link attachInstructionsFiles} function.
|
||||
*/
|
||||
interface IAttachResult {
|
||||
readonly widget: IChatWidget;
|
||||
readonly wasAlreadyAttached: boolean;
|
||||
readonly wasAlreadyAttached: URI[];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Options for the {@link attachInstructionsFiles} function.
|
||||
*/
|
||||
export interface IRunPromptOptions {
|
||||
/**
|
||||
* Chat widget instance to attach the prompt to.
|
||||
*/
|
||||
readonly widget?: IChatWidget;
|
||||
/**
|
||||
* Whether to create a new chat session and
|
||||
* attach the prompt to it.
|
||||
*/
|
||||
readonly inNewChat?: boolean;
|
||||
|
||||
readonly viewsService: IViewsService;
|
||||
readonly commandService: ICommandService;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return value of the {@link attachInstructionsFiles} function.
|
||||
*/
|
||||
interface IRunPromptResult {
|
||||
readonly widget: IChatWidget;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,7 +79,7 @@ const isAttachedAsCurrentPrompt = (
|
||||
return false;
|
||||
}
|
||||
|
||||
if (implicitContext.isPrompt === false) {
|
||||
if (implicitContext.isInstructions === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -78,30 +100,61 @@ const isAttachedAsCurrentPrompt = (
|
||||
};
|
||||
|
||||
/**
|
||||
* Attaches provided prompts to a chat input.
|
||||
* Attaches provided instructions to a chat input.
|
||||
*/
|
||||
export const attachInstructionsFile = async (
|
||||
file: URI,
|
||||
export const attachInstructionsFiles = async (
|
||||
files: URI[],
|
||||
options: IAttachOptions,
|
||||
): Promise<IAttachResult> => {
|
||||
const { skipIfImplicitlyAttached } = options;
|
||||
|
||||
const widget = await getChatWidgetObject(options);
|
||||
|
||||
if (skipIfImplicitlyAttached && isAttachedAsCurrentPrompt(file, widget)) {
|
||||
return { widget, wasAlreadyAttached: true };
|
||||
}
|
||||
const wasAlreadyAttached: URI[] = [];
|
||||
|
||||
const wasAlreadyAttached = widget
|
||||
.attachmentModel
|
||||
.promptInstructions
|
||||
.add(file);
|
||||
for (const file of files) {
|
||||
if (widget.attachmentModel.promptInstructions.add(file)) {
|
||||
wasAlreadyAttached.push(file);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return { widget, wasAlreadyAttached };
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets a chat widget based on the provided {@link IChatAttachPromptActionOptions.widget widget}
|
||||
* Runs the prompt file
|
||||
*/
|
||||
export const runPromptFile = async (
|
||||
file: URI,
|
||||
options: IRunPromptOptions,
|
||||
): Promise<IRunPromptResult> => {
|
||||
|
||||
const widget = await getChatWidgetObject(options);
|
||||
|
||||
let wasAlreadyAttached: boolean = false;
|
||||
|
||||
if (isAttachedAsCurrentPrompt(file, widget)) {
|
||||
wasAlreadyAttached = true;
|
||||
} else {
|
||||
if (widget.attachmentModel.promptInstructions.add(file)) {
|
||||
wasAlreadyAttached = true;
|
||||
}
|
||||
}
|
||||
|
||||
// submit the prompt immediately
|
||||
await widget.acceptInput();
|
||||
|
||||
// detach the prompt immediately, unless was attached
|
||||
// before the action was executed
|
||||
if (wasAlreadyAttached === false) {
|
||||
await detachPrompt(file, { widget });
|
||||
}
|
||||
|
||||
return { widget };
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets a chat widget based on the provided {@link IChatAttachInstructionsActionOptions.widget widget}
|
||||
* reference and the `inNewChat` flag.
|
||||
*
|
||||
* @throws if failed to reveal a chat widget.
|
||||
|
||||
@@ -5,32 +5,32 @@
|
||||
|
||||
import { SUPER_KEY_NAME } from '../constants.js';
|
||||
import { localize } from '../../../../../../../../../nls.js';
|
||||
import { ISelectPromptOptions } from '../askToSelectPrompt.js';
|
||||
import { ISelectInstructionsOptions } from '../askToSelectPrompt.js';
|
||||
|
||||
/**
|
||||
* Creates a placeholder text to show in the prompt selection dialog.
|
||||
* Creates a placeholder text to show in the attach instructions selection dialog.
|
||||
*/
|
||||
export const createPlaceholderText = (
|
||||
options: ISelectPromptOptions,
|
||||
options: ISelectInstructionsOptions,
|
||||
): string => {
|
||||
const { widget } = options;
|
||||
|
||||
let text = localize(
|
||||
'commands.prompts.use.select-dialog.placeholder',
|
||||
'Select a prompt to use',
|
||||
'commands.select-dialog.instructions.placeholder',
|
||||
'Select instructions files to attach',
|
||||
);
|
||||
|
||||
// if no widget reference is provided, add the note about the `ctrl`/`cmd`
|
||||
// modifier that can be leveraged by users to alter the command behavior
|
||||
if (widget === undefined) {
|
||||
const superModifierNote = localize(
|
||||
'commands.prompts.use.select-dialog.super-modifier-note',
|
||||
'commands.select-dialog.super-modifier-note',
|
||||
'{0}-key to use in new chat',
|
||||
SUPER_KEY_NAME,
|
||||
);
|
||||
|
||||
text += localize(
|
||||
'commands.prompts.use.select-dialog.modifier-notes',
|
||||
'commands.select-dialog.modifier-notes',
|
||||
' (hold {0})',
|
||||
superModifierNote,
|
||||
);
|
||||
|
||||
@@ -14,4 +14,4 @@ export const registerReusablePromptActions = () => {
|
||||
registerAttachPromptActions();
|
||||
};
|
||||
|
||||
export { runAttachPromptAction } from './chatAttachPromptAction.js';
|
||||
export { runAttachInstructionsAction } from './chatAttachPromptAction.js';
|
||||
|
||||
@@ -53,7 +53,7 @@ export class ImplicitContextAttachmentWidget extends Disposable {
|
||||
dom.clearNode(this.domNode);
|
||||
this.renderDisposables.clear();
|
||||
|
||||
const attachmentTypeName = (this.attachment.isPrompt === false)
|
||||
const attachmentTypeName = (this.attachment.isInstructions === false)
|
||||
? localize('file.lowercase', "file")
|
||||
: localize('prompt.lowercase', "prompt");
|
||||
|
||||
@@ -73,7 +73,7 @@ export class ImplicitContextAttachmentWidget extends Disposable {
|
||||
const currentFileHint = currentFile + (this.attachment.enabled ? '' : ` (${inactive})`);
|
||||
const title = `${currentFileHint}\n${uriLabel}`;
|
||||
|
||||
const icon = (this.attachment.isPrompt)
|
||||
const icon = this.attachment.isInstructions
|
||||
? ThemeIcon.fromId(Codicon.bookmark.id)
|
||||
: undefined;
|
||||
|
||||
|
||||
@@ -106,12 +106,12 @@ export class PromptAttachmentWidget extends Disposable {
|
||||
const fileBasename = basename(file);
|
||||
const fileDirname = dirname(file);
|
||||
const friendlyName = `${fileBasename} ${fileDirname}`;
|
||||
const ariaLabel = localize('chat.promptAttachment', "Prompt attachment, {0}", friendlyName);
|
||||
const ariaLabel = localize('chat.instructionsAttachment', "Instructions attachment, {0}", friendlyName);
|
||||
|
||||
const uriLabel = this.labelService.getUriLabel(file, { relative: true });
|
||||
const promptLabel = localize('prompt', "Prompt");
|
||||
const instructionsLabel = localize('instructions', "Instructions");
|
||||
|
||||
let title = `${promptLabel} ${uriLabel}`;
|
||||
let title = `${instructionsLabel} ${uriLabel}`;
|
||||
|
||||
// if there are some errors/warning during the process of resolving
|
||||
// attachment references (including all the nested child references),
|
||||
@@ -144,7 +144,7 @@ export class PromptAttachmentWidget extends Disposable {
|
||||
this.domNode.ariaLabel = ariaLabel;
|
||||
this.domNode.tabIndex = 0;
|
||||
|
||||
const hintElement = dom.append(this.domNode, dom.$('span.chat-implicit-hint', undefined, promptLabel));
|
||||
const hintElement = dom.append(this.domNode, dom.$('span.chat-implicit-hint', undefined, instructionsLabel));
|
||||
this._register(this.hoverService.setupManagedHover(getDefaultHoverDelegate('element'), hintElement, title));
|
||||
|
||||
// create the `remove` button
|
||||
|
||||
@@ -204,7 +204,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
|
||||
}
|
||||
|
||||
// if prompt attached as an implicit "current file" context
|
||||
return (this.implicitContext.isPrompt && this.implicitContext.enabled);
|
||||
return (this.implicitContext.isInstructions && this.implicitContext.enabled);
|
||||
}
|
||||
|
||||
private _indexOfLastAttachedContextDeletedWithKeyboard: number = -1;
|
||||
|
||||
@@ -24,7 +24,7 @@ import { IChatRequestFileEntry, IChatRequestImplicitVariableEntry } from '../../
|
||||
import { IChatService } from '../../common/chatService.js';
|
||||
import { ChatAgentLocation } from '../../common/constants.js';
|
||||
import { ILanguageModelIgnoredFilesService } from '../../common/ignoredFiles.js';
|
||||
import { PROMPT_LANGUAGE_ID } from '../../common/promptSyntax/constants.js';
|
||||
import { INSTRUCTIONS_LANGUAGE_ID } from '../../common/promptSyntax/constants.js';
|
||||
import { IChatWidget, IChatWidgetService } from '../chat.js';
|
||||
import { createPromptVariableId } from '../chatAttachmentModel/chatPromptAttachmentsCollection.js';
|
||||
|
||||
@@ -238,7 +238,7 @@ export class ChatImplicitContext extends Disposable implements IChatRequestImpli
|
||||
get id() {
|
||||
// IDs for prompt files need to start with a special prefix
|
||||
// that is used by the copilot extension to identify them
|
||||
if (this.isPrompt) {
|
||||
if (this.isInstructions) {
|
||||
assertDefined(
|
||||
this.value,
|
||||
'Implicit prompt attachments must have a value.',
|
||||
@@ -265,7 +265,7 @@ export class ChatImplicitContext extends Disposable implements IChatRequestImpli
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
const fileType = this.isPrompt ? 'prompt' : 'file';
|
||||
const fileType = this.isInstructions ? 'prompt' : 'file';
|
||||
|
||||
if (URI.isUri(this.value)) {
|
||||
return `${fileType}:${basename(this.value)}`;
|
||||
@@ -279,7 +279,7 @@ export class ChatImplicitContext extends Disposable implements IChatRequestImpli
|
||||
readonly kind = 'implicit';
|
||||
|
||||
get modelDescription(): string {
|
||||
if (this.isPrompt) {
|
||||
if (this.isInstructions) {
|
||||
if (URI.isUri(this.value)) {
|
||||
return `User's active prompt instructions file`;
|
||||
} else if (this._isSelection) {
|
||||
@@ -314,8 +314,8 @@ export class ChatImplicitContext extends Disposable implements IChatRequestImpli
|
||||
}
|
||||
|
||||
private _languageId: string | undefined;
|
||||
get isPrompt() {
|
||||
return (this._languageId === PROMPT_LANGUAGE_ID);
|
||||
get isInstructions() {
|
||||
return (this._languageId === INSTRUCTIONS_LANGUAGE_ID);
|
||||
}
|
||||
|
||||
private _enabled = true;
|
||||
|
||||
@@ -9,28 +9,21 @@ import { CHAT_CATEGORY } from '../../actions/chatActions.js';
|
||||
import { IChatWidget, IChatWidgetService } from '../../chat.js';
|
||||
import { ChatContextKeys } from '../../../common/chatContextKeys.js';
|
||||
import { KeyMod, KeyCode } from '../../../../../../base/common/keyCodes.js';
|
||||
import { PROMPT_LANGUAGE_ID } from '../../../common/promptSyntax/constants.js';
|
||||
import { PromptsConfig } from '../../../../../../platform/prompts/common/config.js';
|
||||
import { isPromptFile } from '../../../../../../platform/prompts/common/constants.js';
|
||||
import { runAttachPromptAction } from '../../actions/reusablePromptActions/index.js';
|
||||
import { IEditorService } from '../../../../../services/editor/common/editorService.js';
|
||||
import { runAttachInstructionsAction } from '../../actions/reusablePromptActions/index.js';
|
||||
import { ICommandService } from '../../../../../../platform/commands/common/commands.js';
|
||||
import { ContextKeyExpr } from '../../../../../../platform/contextkey/common/contextkey.js';
|
||||
import { MenuId, MenuRegistry } from '../../../../../../platform/actions/common/actions.js';
|
||||
import { ServicesAccessor } from '../../../../../../platform/instantiation/common/instantiation.js';
|
||||
import { IActiveCodeEditor, isCodeEditor, isDiffEditor } from '../../../../../../editor/browser/editorBrowser.js';
|
||||
import { KeybindingsRegistry, KeybindingWeight } from '../../../../../../platform/keybinding/common/keybindingsRegistry.js';
|
||||
import { ICodeEditorService } from '../../../../../../editor/browser/services/codeEditorService.js';
|
||||
import { INSTRUCTIONS_LANGUAGE_ID } from '../../../common/promptSyntax/constants.js';
|
||||
|
||||
/**
|
||||
* Command ID of the "Attach Instructions" command.
|
||||
*/
|
||||
export const INSTRUCTIONS_COMMAND_ID = 'workbench.command.instructions.use';
|
||||
|
||||
/**
|
||||
* Command ID of the "Run Prompt" command.
|
||||
*/
|
||||
export const RUN_PROMPT_COMMAND_ID = 'workbench.command.prompt.run';
|
||||
|
||||
/**
|
||||
* Keybinding of the "Use Instructions" command.
|
||||
* The `cmd + /` is the current keybinding for 'attachment', so we use
|
||||
@@ -61,14 +54,14 @@ const command = async (
|
||||
): Promise<void> => {
|
||||
const commandService = accessor.get(ICommandService);
|
||||
|
||||
await runAttachPromptAction({
|
||||
resource: getActivePromptUri(accessor),
|
||||
await runAttachInstructionsAction({
|
||||
resource: getActiveInstructionsFileUri(accessor),
|
||||
widget: getFocusedChatWidget(accessor),
|
||||
}, commandService);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get chat widget reference to attach prompt to.
|
||||
* Get chat widget reference to attach instructions to.
|
||||
*/
|
||||
export function getFocusedChatWidget(accessor: ServicesAccessor): IChatWidget | undefined {
|
||||
const chatWidgetService = accessor.get(IChatWidgetService);
|
||||
@@ -87,53 +80,21 @@ export function getFocusedChatWidget(accessor: ServicesAccessor): IChatWidget |
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets active editor instance, if any.
|
||||
* Gets `URI` of a instructions file open in an active editor instance, if any.
|
||||
*/
|
||||
export function getActiveCodeEditor(accessor: ServicesAccessor): IActiveCodeEditor | undefined {
|
||||
const editorService = accessor.get(IEditorService);
|
||||
const { activeTextEditorControl } = editorService;
|
||||
|
||||
if (isCodeEditor(activeTextEditorControl) && activeTextEditorControl.hasModel()) {
|
||||
return activeTextEditorControl;
|
||||
}
|
||||
|
||||
if (isDiffEditor(activeTextEditorControl)) {
|
||||
const originalEditor = activeTextEditorControl.getOriginalEditor();
|
||||
if (!originalEditor.hasModel()) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return originalEditor;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets `URI` of a prompt file open in an active editor instance, if any.
|
||||
*/
|
||||
export const getActivePromptUri = (
|
||||
export const getActiveInstructionsFileUri = (
|
||||
accessor: ServicesAccessor,
|
||||
): URI | undefined => {
|
||||
const activeEditor = getActiveCodeEditor(accessor);
|
||||
if (!activeEditor) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const model = activeEditor.getModel();
|
||||
if (model.getLanguageId() === PROMPT_LANGUAGE_ID) {
|
||||
const codeEditorService = accessor.get(ICodeEditorService);
|
||||
const model = codeEditorService.getActiveCodeEditor()?.getModel();
|
||||
if (model?.getLanguageId() === INSTRUCTIONS_LANGUAGE_ID) {
|
||||
return model.uri;
|
||||
}
|
||||
|
||||
if (isPromptFile(model.uri)) {
|
||||
return model.uri;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Register the "Use Prompt" command with its keybinding.
|
||||
* Register the "Attach Instructions" command with its keybinding.
|
||||
*/
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: INSTRUCTIONS_COMMAND_ID,
|
||||
@@ -149,7 +110,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
|
||||
command: {
|
||||
id: INSTRUCTIONS_COMMAND_ID,
|
||||
title: localize('commands.prompts.use.title', "Use Instructions"),
|
||||
title: localize('commands.prompts.use.title', "Attach Instructions"),
|
||||
category: CHAT_CATEGORY
|
||||
},
|
||||
when: ContextKeyExpr.and(PromptsConfig.enabledCtx, ChatContextKeys.enabled)
|
||||
|
||||
@@ -76,7 +76,7 @@ export interface IChatRequestImplicitVariableEntry extends IBaseChatRequestVaria
|
||||
readonly isFile: true;
|
||||
readonly value: URI | Location | undefined;
|
||||
readonly isSelection: boolean;
|
||||
readonly isPrompt: boolean;
|
||||
readonly isInstructions: boolean;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { LanguageFilter } from '../../../../../editor/common/languageSelector.js';
|
||||
import { LanguageSelector } from '../../../../../editor/common/languageSelector.js';
|
||||
|
||||
/**
|
||||
* Documentation link for the reusable prompts feature.
|
||||
@@ -17,8 +17,11 @@ export const INSTRUCTIONS_DOCUMENTATION_URL = PROMPT_DOCUMENTATION_URL; // TODO:
|
||||
export const PROMPT_LANGUAGE_ID = 'prompt';
|
||||
|
||||
/**
|
||||
* Prompt files language selector.
|
||||
* Language ID for instructions syntax.
|
||||
*/
|
||||
export const LANGUAGE_SELECTOR: LanguageFilter = Object.freeze({
|
||||
language: PROMPT_LANGUAGE_ID,
|
||||
});
|
||||
export const INSTRUCTIONS_LANGUAGE_ID = 'instructions';
|
||||
|
||||
/**
|
||||
* Prompt and instructions files language selector.
|
||||
*/
|
||||
export const PROMPT_AND_INSTRUCTIONS_LANGUAGE_SELECTOR: LanguageSelector = [PROMPT_LANGUAGE_ID, INSTRUCTIONS_LANGUAGE_ID];
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { LANGUAGE_SELECTOR } from '../../constants.js';
|
||||
import { PROMPT_AND_INSTRUCTIONS_LANGUAGE_SELECTOR } from '../../constants.js';
|
||||
import { IPromptsService } from '../../service/types.js';
|
||||
import { assert } from '../../../../../../../base/common/assert.js';
|
||||
import { ITextModel } from '../../../../../../../editor/common/model.js';
|
||||
@@ -25,7 +25,7 @@ export class PromptLinkProvider extends Disposable implements LinkProvider {
|
||||
) {
|
||||
super();
|
||||
|
||||
this._register(this.languageService.linkProvider.register(LANGUAGE_SELECTOR, this));
|
||||
this._register(this.languageService.linkProvider.register(PROMPT_AND_INSTRUCTIONS_LANGUAGE_SELECTOR, this));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* - add `Windows` support
|
||||
*/
|
||||
|
||||
import { LANGUAGE_SELECTOR } from '../../constants.js';
|
||||
import { PROMPT_AND_INSTRUCTIONS_LANGUAGE_SELECTOR } from '../../constants.js';
|
||||
import { IPromptsService } from '../../service/types.js';
|
||||
import { URI } from '../../../../../../../base/common/uri.js';
|
||||
import { extUri } from '../../../../../../../base/common/resources.js';
|
||||
@@ -102,7 +102,7 @@ export class PromptPathAutocompletion extends Disposable implements CompletionIt
|
||||
) {
|
||||
super();
|
||||
|
||||
this._register(this.languageService.completionProvider.register(LANGUAGE_SELECTOR, this));
|
||||
this._register(this.languageService.completionProvider.register(PROMPT_AND_INSTRUCTIONS_LANGUAGE_SELECTOR, this));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { PROMPT_LANGUAGE_ID } from '../../constants.js';
|
||||
import { INSTRUCTIONS_LANGUAGE_ID, PROMPT_LANGUAGE_ID } from '../../constants.js';
|
||||
import { ProviderInstanceBase } from './providerInstanceBase.js';
|
||||
import { ITextModel } from '../../../../../../../editor/common/model.js';
|
||||
import { assertDefined } from '../../../../../../../base/common/types.js';
|
||||
@@ -96,15 +96,15 @@ export abstract class ProviderInstanceManagerBase<TInstance extends ProviderInst
|
||||
modelService.onModelLanguageChanged((event) => {
|
||||
const { model, oldLanguageId } = event;
|
||||
|
||||
// if language is set to `prompt` language, handle that model
|
||||
// if language is set to `prompt` or `instructions` language, handle that model
|
||||
if (isPromptFileModel(model)) {
|
||||
this.instances.get(model);
|
||||
return;
|
||||
}
|
||||
|
||||
// if the language is changed away from `prompt`,
|
||||
// if the language is changed away from `prompt` or `instructions`,
|
||||
// remove and dispose provider for this model
|
||||
if (oldLanguageId === PROMPT_LANGUAGE_ID) {
|
||||
if (isPromptOrInstructionsFile(oldLanguageId)) {
|
||||
this.instances.remove(model, true);
|
||||
return;
|
||||
}
|
||||
@@ -133,6 +133,12 @@ export abstract class ProviderInstanceManagerBase<TInstance extends ProviderInst
|
||||
}
|
||||
}
|
||||
|
||||
const isPromptOrInstructionsFile = (
|
||||
languageId: string
|
||||
): boolean => {
|
||||
return languageId === PROMPT_LANGUAGE_ID || languageId === INSTRUCTIONS_LANGUAGE_ID;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if a provided model is used for prompt files.
|
||||
*/
|
||||
@@ -148,7 +154,7 @@ const isPromptFileModel = (
|
||||
return false;
|
||||
}
|
||||
|
||||
if (model.getLanguageId() !== PROMPT_LANGUAGE_ID) {
|
||||
if (!isPromptOrInstructionsFile(model.getLanguageId())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user