111 lines
5.6 KiB
TypeScript
111 lines
5.6 KiB
TypeScript
/**
|
||
* 文件夹、以及代码阅读不认识的文件,用这个协议打开代码阅读预览:
|
||
* 文档是空的,页面上没有代码块,只在顶部聊天。源码文件直接开它自己的预览。
|
||
* 「了解一块代码」找的功能的对话页也用这个协议,只是对的不是路径,是功能。
|
||
*/
|
||
|
||
import type { WorkspaceFolder } from 'vscode'
|
||
import type { QaLocation } from '../folderStore'
|
||
import type { NamedFolder, RepoPlace } from './repoPath'
|
||
import * as path from 'node:path'
|
||
import { Uri, workspace } from 'vscode'
|
||
import { locateQa } from '../folderStore'
|
||
import { featureQaKey, pathQaKey, relativeQaKey } from '../qa/pathQa'
|
||
import { folderLinkPlace, repoRootOf } from './repoPath'
|
||
|
||
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
|
||
}
|
||
|
||
/** 「了解一块代码」找代码用的工作区文件夹:眼下打开的全部,claude 在第一个里面跑,别的加进来读。 */
|
||
export function featureFolders(): NamedFolder[] {
|
||
return (workspace.workspaceFolders ?? []).map(folder => ({ name: folder.name, path: folder.uri.fsPath }))
|
||
}
|
||
|
||
/** 一页上的仓库链接从哪儿算:`place` 把一条链接的路径换成磁盘上的位置,算不出来的没有;算出来的位置都在 `roots` 这几个根下面。 */
|
||
export interface LinkBase {
|
||
place: (rel: string) => RepoPlace | undefined
|
||
roots: string[]
|
||
}
|
||
|
||
/**
|
||
* 预览这一页上仓库链接的路径从哪儿算:功能对话页从这个功能找代码用的工作区文件夹算(见 [featureFolders]、[folderLinkPlace]),
|
||
* 别的页从文档所在的 git 仓库根算,不在 git 仓库里时一条也算不出来。
|
||
*/
|
||
export async function linkBaseOf(uri: Uri): Promise<LinkBase> {
|
||
if (featureIdOf(uri) !== undefined) {
|
||
const folders = featureFolders()
|
||
return { place: rel => folderLinkPlace(folders, rel), roots: folders.map(folder => folder.path) }
|
||
}
|
||
const root = await repoRootOf(uri.fsPath)
|
||
return root === undefined ? { place: () => undefined, roots: [] } : { place: rel => ({ root, rel }), roots: [root] }
|
||
}
|
||
|
||
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' }
|
||
}
|
||
|
||
/**
|
||
* 这条路径的顶部问答记在哪、发给模型时怎么写它:同 [locateQa](工作区外的库文件也能问),
|
||
* 另外认工作区根本身,键和发给模型的路径都是空字符串(locate 不认根,顶部问答要认)。
|
||
*/
|
||
export function pathQaLocation(uri: Uri): QaLocation | undefined {
|
||
const folder = workspace.getWorkspaceFolder(uri)
|
||
if (folder && relativeQaKey(path.posix.normalize(folder.uri.path), path.posix.normalize(uri.path)) === '')
|
||
return { folder, key: '', promptPath: '' }
|
||
return locateQa(uri)
|
||
}
|
||
|
||
/**
|
||
* 顶部问答记在哪:在哪个工作区文件夹(claude 在它里面跑,带的图片存在它的 `.spx/code-qa/`)、记录表里的键(见 pathQa.ts)。
|
||
* 文件、文件夹按「工作区文件夹 + 键」(见 [pathQaLocation],工作区外的库文件记在第一个工作区文件夹);功能对话页按功能的 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) }
|
||
}
|