/** * 代码阅读预览与扩展之间的消息,与 webview 端 `webview-ui/src/codeReader/types.ts` 同构,两边手动保持一致。 */ import type { ClaudeImage } from '../../cc/spawnClaude' import type { FileAttribute } from '../attributes' import type { BlockView } from '../blockView' import type { ProfileChoice } from '../qa/askProfiles' import type { RunningAsk } from '../qa/asks' import type { Excluded } from './blockFilter' export type { Excluded } export interface TranslatorOption { id: string, label: string } /** * 让预览滚到包含 line 的块。definition 只在跳到定义时有:定义处那个名字在源码里的下标(Segment.offset), * 预览展开把它折叠住的块和属性行,再让就是这处定义的那一块或属性表里那一行闪几下。 */ export interface Reveal { line: number definition?: number } /** * 点中的名字在这份文档里的一处出现:源码下标 [start, end)。kind 同 VS Code 的 DocumentHighlightKind: * write 是写入(定义、赋值),read 是读取,text 是语言扩展按文字认出来的。 */ export interface Occurrence { start: number end: number kind: 'text' | 'read' | 'write' } export interface DocumentMessage { type: 'document' /** TextDocument.version:webview 靠它判断是不是新一份文档 */ version: number languageId: string blocks: BlockView[] /** 这份文件里出现过的筛选属性和值(带显示名、块数),按筛选栏的先后排,见 fileAttributes */ attributes: FileAttribute[] translators: TranslatorOption[] translatorId: string error?: string /** 新预览只在第一份 document 里带:从文本编辑器切过来时是它顶部那一行,跳到定义时是定义处 */ reveal?: Reveal } /** 问过的一次(给预览看的):问题、回答(Markdown)、什么时候问的(ISO 时间)、用哪个 profile 答的。 */ export interface QaEntryView { id: string question: string answer: string createdAt: string /** 用哪个 profile 答的(profile 名);选「默认」时、以前存的没有 */ profile?: string /** 问题带的图片,预览里能直接用的地址(webview.asWebviewUri);没带图片时没有 */ images?: string[] } /** 挂在一块上的问答;记号对不上这份文件里任何一块的不显示。 */ export interface QaThreadView { anchor: string entries: QaEntryView[] } /** * 这份文件的问答:available 为假时文件不在工作区文件夹里、问答没地方存,不给「问 AI」; * running 是正在答的问题(页面重新加载后靠它接上)。 */ export interface QaMessage { type: 'qa' available: boolean threads: QaThreadView[] running: RunningAsk[] } /** * reveal:已经开着的预览滚到某处(同 DocumentMessage.reveal) * definitionProbed:id 那次 probeDefinition 的回答,found 表示 VS Code 查得到定义 * occurrencesFound:id 那次 findOccurrences 的回答,语言扩展没答(或没装)就是空的 * filter:整个工作区共用的块筛选;ready 后先于第一份文档发一次,之后任何一个预览改了都发给所有预览 * qa:这份文件的问答;ready 后发一次,问答、正在答的问题变了再发 * askProfiles:「问 AI」输入框旁边的 profile 下拉:选项和选中的那项;ready 后发一次,任何一个预览改了都发给所有预览 * answer:askId 那个问题的回答长了(text 是到现在为止的全文)、或换了在做的事 * answerEnd:askId 那个问题答完了:done 带最终的回答和存下的那条的 id,failed 带给人看的原因,stopped 是被叫停 * moveOccurrence:跳到高亮着的名字的下一处(step 1)/ 上一处(step -1)出现,由这两条命令发给活动的预览 */ export type ExtensionToWebview = | DocumentMessage | ({ type: 'reveal' } & Reveal) | { type: 'definitionProbed', id: number, found: boolean } | { type: 'occurrencesFound', id: number, occurrences: Occurrence[] } | { type: 'moveOccurrence', step: 1 | -1 } | { type: 'filter', excluded: Excluded } | QaMessage | ({ type: 'askProfiles' } & ProfileChoice) | { type: 'answer', askId: string, text: string, activity?: string } | { type: 'answerEnd', askId: string, status: 'done' | 'failed' | 'stopped', text?: string, error?: string, entryId?: string } /** * edit:把 startLine..endLine 这几行整行替换成 text(扩展端给每行补回原来的缩进) * visibleLine:预览视口顶部那块的起始行,切回文本编辑器时把这行放到顶部 * probeDefinition:按住 ⌘ / Ctrl 指到带下标的名字上,问源码 offset 处有没有定义;id 标出是哪一次问,回答带回去; * version 是这份下标所属的文档版本 * goToDefinition:⌘ / Ctrl + 点击,跳到 offset 处名字的定义 * findOccurrences:单击带下标的名字,问它在这份文档里出现在哪几处;id、version 同 probeDefinition * setFilter:改块筛选(整份替换),扩展端记下后发 filter 给所有预览 * ask:问 anchor 那块代码;askId 由 webview 起,回答的消息带回去;扩展端接着这块最近一次问答的会话问,用眼下选的 profile; * images 是贴进输入框的图片(没贴是空的),随问题发给 Claude Code,答成了跟这条一起存 * stopAsk:叫停 askId 那个问题,这次不存 * deleteQa:删掉 anchor 那块下面的一次问答(已经理解了) * selectAskProfile:「问 AI」换 profile(id 是 profile 名,「默认」是 ''),扩展端记下后发 askProfiles 给所有预览 * occurrenceNavigation:眼下能不能用键盘在高亮着的名字的各处出现之间跳(有高亮、页面有焦点、焦点不在输入框里),变了才发; * 扩展端按它设 when 子句用的上下文键 */ export type WebviewToExtension = | { type: 'ready' } | { type: 'setFilter', excluded: Excluded } | { type: 'edit', startLine: number, endLine: number, text: string } | { type: 'selectTranslator', id: string } | { type: 'visibleLine', line: number } | { type: 'probeDefinition', id: number, version: number, offset: number } | { type: 'goToDefinition', version: number, offset: number } | { type: 'findOccurrences', id: number, version: number, offset: number } | { type: 'ask', askId: string, anchor: string, question: string, images: ClaudeImage[] } | { type: 'stopAsk', askId: string } | { type: 'deleteQa', anchor: string, entryId: string } | { type: 'selectAskProfile', id: string } | { type: 'occurrenceNavigation', available: boolean }