diff --git a/src/vs/workbench/contrib/void/browser/fileService.ts b/src/vs/workbench/contrib/void/browser/fileService.ts new file mode 100644 index 00000000..8857feb4 --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/fileService.ts @@ -0,0 +1,89 @@ +import { localize2 } from '../../../../nls.js'; +import { URI } from '../../../../base/common/uri.js'; +import { Action2, registerAction2, MenuId } from '../../../../platform/actions/common/actions.js'; +import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js'; +import { INotificationService } from '../../../../platform/notification/common/notification.js'; +import { IFileService } from '../../../../platform/files/common/files.js'; +import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js'; +import { IDirectoryStrService } from '../common/directoryStrService.js'; +import { readFile, DEFAULT_FILE_SIZE_LIMIT } from '../common/prompt/prompts.js'; + + + +async function filePrompt(fileService: IFileService, uri: URI, clipboardService: IClipboardService): Promise { + const { val } = await readFile(fileService, uri, DEFAULT_FILE_SIZE_LIMIT) + const fileName = uri.fsPath.split('/').pop() ?? '' + if (!val) { + throw new Error('Failed to copy prompt') + } + const prompt = ` +${fileName}: +\`\`\` +${val} +\`\`\``.trim() + return prompt +} + +/** + * Add a menu item to the explorer context menu that copies a prompt for the selected file or directory. + * + * Example file prompt: + * + * ``` + * index.js: + * \`\`\` + * console.log('Hello World!'); + * \`\`\` + * + * Example directory prompt: + * ``` + * Directory of /path/to/src: + * src/ + * ├── index.ts + * ├── src.ts + * ├── latest/ + * │ ├── index.ts + * │ └── src.ts + * ├── types.ts + * └── util.ts + * ``` + */ +class FilePromptActionService extends Action2 { + private static readonly VOID_COPY_FILE_PROMPT_ID = 'void.copyfileprompt' + + constructor() { + super({ + id: FilePromptActionService.VOID_COPY_FILE_PROMPT_ID, + title: localize2('voidCopyPrompt', "Void: Copy Prompt"), + menu: [{ + id: MenuId.ExplorerContext, + group: '8_void', + order: 1, + }] + }); + } + + async run(accessor: ServicesAccessor, uri: URI): Promise { + try { + const fileService = accessor.get(IFileService); + const clipboardService = accessor.get(IClipboardService) + const directoryStrService = accessor.get(IDirectoryStrService) + const stat = await fileService.resolve(uri) + const prompt = stat.isFile + ? await filePrompt(fileService, uri, clipboardService) + : await directoryStrService.getDirectoryStrTool(uri) + await clipboardService.writeText(prompt) + } catch (error) { + const notificationService = accessor.get(INotificationService) + FilePromptActionService._onError(notificationService, error) + } + } + + private static _onError(notificationService: INotificationService, error: Error) { + const errorMessage = localize2('voidCopyPromptError', 'Failed to copy prompt') + notificationService.error(errorMessage.value) + throw error + } +} + +registerAction2(FilePromptActionService) diff --git a/src/vs/workbench/contrib/void/browser/void.contribution.ts b/src/vs/workbench/contrib/void/browser/void.contribution.ts index c0e84606..3ab89abf 100644 --- a/src/vs/workbench/contrib/void/browser/void.contribution.ts +++ b/src/vs/workbench/contrib/void/browser/void.contribution.ts @@ -58,6 +58,9 @@ import './voidOnboardingService.js' // register misc service import './miscWokrbenchContrib.js' +// register file service (for explorer context menu) +import './fileService.js' + // ---------- common (unclear if these actually need to be imported, because they're already imported wherever they're used) ---------- // llmMessage diff --git a/src/vs/workbench/contrib/void/common/prompt/prompts.ts b/src/vs/workbench/contrib/void/common/prompt/prompts.ts index 613142b8..5f53cd07 100644 --- a/src/vs/workbench/contrib/void/common/prompt/prompts.ts +++ b/src/vs/workbench/contrib/void/common/prompt/prompts.ts @@ -525,7 +525,9 @@ ${details.map((d, i) => `${i + 1}. ${d}`).join('\n\n')}`) // chat_systemMessage({ chatMode, workspaceFolders: [], openedURIs: [], activeURI: 'pee', persistentTerminalIDs: [], directoryStr: 'lol', })) // } -const readFile = async (fileService: IFileService, uri: URI, fileSizeLimit: number): Promise<{ +export const DEFAULT_FILE_SIZE_LIMIT = 2_000_000 + +export const readFile = async (fileService: IFileService, uri: URI, fileSizeLimit: number): Promise<{ val: string, truncated: boolean, fullFileLen: number, @@ -561,7 +563,7 @@ export const chat_userMessageContent = async (instructions: string, currSelns: S selnsStrs = await Promise.all(currSelns?.map(async (s) => { if (s.type === 'File' || s.type === 'CodeSelection') { - const { val } = await readFile(opts.fileService, s.uri, 2_000_000) + const { val } = await readFile(opts.fileService, s.uri, DEFAULT_FILE_SIZE_LIMIT) const lineNumAdd = s.type === 'CodeSelection' ? lineNumAddition(s.range) : '' const content = val === null ? 'null' : `${tripleTick[0]}${s.language}\n${val}\n${tripleTick[1]}` const str = `${s.uri.fsPath}${lineNumAdd}:\n${content}`