/** * 文件夹、以及代码阅读不认识的文件,用这个协议打开代码阅读预览: * 文档是空的,页面上没有代码块,只在顶部聊天。源码文件直接开它自己的预览。 * 「了解一块代码」找的功能的对话页也用这个协议,只是对的不是路径,是功能。 */ import type { WorkspaceFolder } from 'vscode' import * as path from 'node:path' import { Uri, workspace } from 'vscode' import { featureQaKey, pathQaKey, relativeQaKey } from '../qa/pathQa' export const PATH_PREVIEW_SCHEME = 'code-reader-path' export type PathSubject = 'file' | 'folder' /** 预览顶部的聊天问的是什么:文件、文件夹,或「了解一块代码」找的一个功能 */ export type TopSubject = PathSubject | 'feature' /** 功能对话页的查询串:后面跟着功能的 id */ const FEATURE_QUERY = 'feature=' /** 「了解一块代码」一个功能的对话页:路径是问的那句话(标签页上显示它,`/` 换成全角的免得被当成目录),查询串带着功能的 id。 */ export function featurePageUri(id: string, question: string): Uri { return Uri.from({ scheme: PATH_PREVIEW_SCHEME, path: `/${question.replaceAll('/', '/')}`, query: `${FEATURE_QUERY}${id}` }) } /** 一个功能的问答按这个 Uri 记、按它对事件:对话页标签上叫什么都是它。 */ export function featureSubjectUri(id: string): Uri { return Uri.from({ scheme: PATH_PREVIEW_SCHEME, path: '/', query: `${FEATURE_QUERY}${id}` }) } /** 功能对话页(或 featureSubjectUri)对的功能 id;别的 Uri 没有。 */ export function featureIdOf(uri: Uri): string | undefined { return uri.scheme === PATH_PREVIEW_SCHEME && uri.query.startsWith(FEATURE_QUERY) ? uri.query.slice(FEATURE_QUERY.length) : undefined } export function pathPreviewUri(target: Uri, kind: PathSubject): Uri { return target.with({ scheme: PATH_PREVIEW_SCHEME, query: kind, fragment: '' }) } /** 预览文档是路径页时,还原成真实文件或文件夹;普通源码预览不是。 */ export function pathPreviewOf(uri: Uri): { uri: Uri, kind: PathSubject } | undefined { if (uri.scheme !== PATH_PREVIEW_SCHEME) return undefined if (uri.query !== 'file' && uri.query !== 'folder') return undefined return { uri: uri.with({ scheme: 'file', query: '', fragment: '' }), kind: uri.query } } /** 预览这一页顶部问的是什么:功能对话页是那个功能(featureSubjectUri),路径页还原成真实文件或文件夹,源码预览就是文档自己。 */ export function topSubjectOf(uri: Uri): { uri: Uri, kind: TopSubject } { const feature = featureIdOf(uri) if (feature !== undefined) return { uri: featureSubjectUri(feature), kind: 'feature' } return pathPreviewOf(uri) ?? { uri, kind: 'file' } } /** * 这条路径在哪个工作区文件夹、相对路径是什么。 * 工作区根本身的相对路径是空字符串(locate 不认根,顶部问答要认)。 */ export function pathQaLocation(uri: Uri): { folder: WorkspaceFolder, key: string } | undefined { const folder = workspace.getWorkspaceFolder(uri) if (!folder) return undefined const key = relativeQaKey(path.posix.normalize(folder.uri.path), path.posix.normalize(uri.path)) return key === undefined ? undefined : { folder, key } } /** * 顶部问答记在哪:在哪个工作区文件夹(claude 在它里面跑,带的图片存在它的 `.spx/code-qa/`)、记录表里的键(见 pathQa.ts)。 * 文件、文件夹按「工作区文件夹 + 相对路径」;功能对话页按功能的 id,记在第一个工作区文件夹。不在工作区里、没打开文件夹时没有。 */ export function topQaLocation(uri: Uri): { folder: WorkspaceFolder, key: string } | undefined { const feature = featureIdOf(uri) if (feature !== undefined) { const folder = workspace.workspaceFolders?.[0] return folder && { folder, key: featureQaKey(feature) } } const at = pathQaLocation(uri) return at && { folder: at.folder, key: pathQaKey(at.folder.uri.toString(), at.key) } }