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..93da1b1e --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/fileService.ts @@ -0,0 +1,78 @@ +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 { messageOfSelection } from '../common/prompt/prompts.js'; +import { IVoidModelService } from '../common/voidModelService.js'; + + + +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 voidModelService = accessor.get(IVoidModelService) + + const stat = await fileService.stat(uri) + + const folderOpts = { + maxChildren: 1000, + maxCharsPerFile: 2_000_000, + } as const + + let m: string = 'No contents detected' + if (stat.isFile) { + m = await messageOfSelection({ + type: 'File', + uri, + language: (await voidModelService.getModelSafe(uri)).model?.getLanguageId() || '', + state: { wasAddedAsCurrentFile: false, }, + }, { + folderOpts, + directoryStrService, + fileService, + }) + } + + if (stat.isDirectory) { + m = await messageOfSelection({ + type: 'Folder', + uri, + }, { + folderOpts, + fileService, + directoryStrService, + }) + } + + await clipboardService.writeText(m) + + } catch (error) { + const notificationService = accessor.get(INotificationService) + notificationService.error(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 b83c1a0f..5746067e 100644 --- a/src/vs/workbench/contrib/void/common/prompt/prompts.ts +++ b/src/vs/workbench/contrib/void/common/prompt/prompts.ts @@ -529,7 +529,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, @@ -553,46 +555,70 @@ const readFile = async (fileService: IFileService, uri: URI, fileSizeLimit: numb +export const messageOfSelection = async ( + s: StagingSelectionItem, + opts: { + directoryStrService: IDirectoryStrService, + fileService: IFileService, + folderOpts: { + maxChildren: number, + maxCharsPerFile: number, + } + } +) => { + const lineNumAddition = (range: [number, number]) => ` (lines ${range[0]}:${range[1]})` + + if (s.type === 'File' || s.type === 'CodeSelection') { + 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}` + return str + } + else if (s.type === 'Folder') { + const dirStr: string = await opts.directoryStrService.getDirectoryStrTool(s.uri) + const folderStructure = `${s.uri.fsPath} folder structure:${tripleTick[0]}\n${dirStr}\n${tripleTick[1]}` + + const uris = await opts.directoryStrService.getAllURIsInDirectory(s.uri, { maxResults: opts.folderOpts.maxChildren }) + const strOfFiles = await Promise.all(uris.map(async uri => { + const { val, truncated } = await readFile(opts.fileService, uri, opts.folderOpts.maxCharsPerFile) + const truncationStr = truncated ? `\n... file truncated ...` : '' + const content = val === null ? 'null' : `${tripleTick[0]}\n${val}${truncationStr}\n${tripleTick[1]}` + const str = `${uri.fsPath}:\n${content}` + return str + })) + const contentStr = [folderStructure, ...strOfFiles].join('\n\n') + return contentStr + } + else + return '' + +} - -export const chat_userMessageContent = async (instructions: string, currSelns: StagingSelectionItem[] | null, - opts: { directoryStrService: IDirectoryStrService, fileService: IFileService } +export const chat_userMessageContent = async ( + instructions: string, + currSelns: StagingSelectionItem[] | null, + opts: { + directoryStrService: IDirectoryStrService, + fileService: IFileService + }, ) => { - const lineNumAddition = (range: [number, number]) => ` (lines ${range[0]}:${range[1]})` - let selnsStrs: string[] = [] - selnsStrs = await Promise.all(currSelns?.map(async (s) => { + const selnsStrs = await Promise.all( + (currSelns ?? []).map(async (s) => + messageOfSelection(s, { + ...opts, + folderOpts: { maxChildren: 100, maxCharsPerFile: 100_000, } + }) + ) + ) - if (s.type === 'File' || s.type === 'CodeSelection') { - const { val } = await readFile(opts.fileService, s.uri, 2_000_000) - 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}` - return str - } - else if (s.type === 'Folder') { - const dirStr: string = await opts.directoryStrService.getDirectoryStrTool(s.uri) - const folderStructure = `${s.uri.fsPath} folder structure:${tripleTick[0]}\n${dirStr}\n${tripleTick[1]}` - const uris = await opts.directoryStrService.getAllURIsInDirectory(s.uri, { maxResults: 100 }) - const strOfFiles = await Promise.all(uris.map(async uri => { - const { val, truncated } = await readFile(opts.fileService, uri, 100_000) - const truncationStr = truncated ? `\n... file truncated ...` : '' - const content = val === null ? 'null' : `${tripleTick[0]}\n${val}${truncationStr}\n${tripleTick[1]}` - const str = `${uri.fsPath}:\n${content}` - return str - })) - const contentStr = [folderStructure, ...strOfFiles].join('\n\n') - return contentStr - } - else - return '' - }) ?? []) - - const selnsStr = selnsStrs.join('\n') ?? '' let str = '' str += `${instructions}` + + const selnsStr = selnsStrs.join('\n\n') ?? '' if (selnsStr) str += `\n---\nSELECTIONS\n${selnsStr}` return str; }