diff --git a/src/vs/editor/common/codecs/markdownCodec/markdownDecoder.ts b/src/vs/editor/common/codecs/markdownCodec/markdownDecoder.ts index 9da1394cf82..bec961719c9 100644 --- a/src/vs/editor/common/codecs/markdownCodec/markdownDecoder.ts +++ b/src/vs/editor/common/codecs/markdownCodec/markdownDecoder.ts @@ -6,14 +6,14 @@ import { MarkdownToken } from './tokens/markdownToken.js'; import { VSBuffer } from '../../../../base/common/buffer.js'; import { LeftBracket } from '../simpleCodec/tokens/brackets.js'; +import { PartialMarkdownImage } from './parsers/markdownImage.js'; import { ReadableStream } from '../../../../base/common/stream.js'; import { LeftAngleBracket } from '../simpleCodec/tokens/angleBrackets.js'; +import { ExclamationMark } from '../simpleCodec/tokens/exclamationMark.js'; import { BaseDecoder } from '../../../../base/common/codecs/baseDecoder.js'; import { SimpleDecoder, TSimpleToken } from '../simpleCodec/simpleDecoder.js'; import { MarkdownCommentStart, PartialMarkdownCommentStart } from './parsers/markdownComment.js'; import { MarkdownLinkCaption, PartialMarkdownLink, PartialMarkdownLinkCaption } from './parsers/markdownLink.js'; -import { ExclamationMark } from '../simpleCodec/tokens/exclamationMark.js'; -import { PartialMarkdownImage } from './parsers/markdownImage.js'; /** * Tokens handled by this decoder. diff --git a/src/vs/platform/prompts/common/constants.ts b/src/vs/platform/prompts/common/constants.ts index a7ea766894b..835233a0f7d 100644 --- a/src/vs/platform/prompts/common/constants.ts +++ b/src/vs/platform/prompts/common/constants.ts @@ -13,14 +13,22 @@ import { basename } from '../../../base/common/path.js'; export const PROMPT_FILE_EXTENSION = '.prompt.md'; /** - * Check if provided path is a prompt file. + * Copilot custom instructions file name. + */ +const COPILOT_CUSTOM_INSTRUCTIONS_FILENAME = 'copilot-instructions.md'; + +/** + * Check if provided path is a reusable prompt file. */ export const isPromptFile = ( fileUri: URI, ): boolean => { - return fileUri - .path - .endsWith(PROMPT_FILE_EXTENSION); + const filename = basename(fileUri.path); + + const hasPromptFileExtension = filename.endsWith(PROMPT_FILE_EXTENSION); + const isCustomInstructionsFile = (filename === COPILOT_CUSTOM_INSTRUCTIONS_FILENAME); + + return hasPromptFileExtension || isCustomInstructionsFile; }; /** @@ -37,5 +45,12 @@ export const getCleanPromptName = ( `Provided path '${fileUri.fsPath}' is not a prompt file.`, ); - return basename(fileUri.path, PROMPT_FILE_EXTENSION); + // if a Copilot custom instructions file, remove `markdown` file extension + // otherwise, remove the `prompt` file extension + const fileExtension = (fileUri.path.endsWith(COPILOT_CUSTOM_INSTRUCTIONS_FILENAME)) + ? '.md' + : PROMPT_FILE_EXTENSION; + + // otherwise, remove the prompt file extension + return basename(fileUri.path, fileExtension); }; diff --git a/src/vs/platform/prompts/test/common/constants.test.ts b/src/vs/platform/prompts/test/common/constants.test.ts index 376f8318c40..547cbab9087 100644 --- a/src/vs/platform/prompts/test/common/constants.test.ts +++ b/src/vs/platform/prompts/test/common/constants.test.ts @@ -5,8 +5,8 @@ import assert from 'assert'; import { URI } from '../../../../base/common/uri.js'; -import { getCleanPromptName, isPromptFile } from '../../common/constants.js'; import { randomInt } from '../../../../base/common/numbers.js'; +import { getCleanPromptName, isPromptFile } from '../../common/constants.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js'; @@ -30,6 +30,11 @@ suite('Prompt Constants', () => { getCleanPromptName(URI.file(`./${expectedPromptName}.prompt.md`)), expectedPromptName, ); + + assert.strictEqual( + getCleanPromptName(URI.file('.github/copilot-instructions.md')), + 'copilot-instructions', + ); }); test('• throws if not a prompt file URI provided', () => { @@ -65,6 +70,10 @@ suite('Prompt Constants', () => { assert( isPromptFile(URI.file(`./some-${randomInt(1000)}.prompt.md`)), ); + + assert( + isPromptFile(URI.file('.github/copilot-instructions.md')), + ); }); test('• returns `false` for non-prompt files', () => { diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/contentProviders/filePromptContentsProvider.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/contentProviders/filePromptContentsProvider.ts index 4f35897df0a..6d9249cb65c 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/contentProviders/filePromptContentsProvider.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/contentProviders/filePromptContentsProvider.ts @@ -11,6 +11,7 @@ import { CancellationError } from '../../../../../../base/common/errors.js'; import { PromptContentsProviderBase } from './promptContentsProviderBase.js'; import { VSBufferReadableStream } from '../../../../../../base/common/buffer.js'; import { CancellationToken } from '../../../../../../base/common/cancellation.js'; +import { isPromptFile } from '../../../../../../platform/prompts/common/constants.js'; import { OpenFailed, NotPromptFile, ResolveError, FolderReference } from '../../promptFileReferenceErrors.js'; import { FileChangesEvent, FileChangeType, IFileService } from '../../../../../../platform/files/common/files.js'; @@ -102,7 +103,7 @@ export class FilePromptContentProvider extends PromptContentsProviderBase