Files
superwork/vscode/src/codeReader/qa/linkRule.ts
T

70 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 问 AI 时要模型怎么写回答里的代码位置:写成和注释里一样的仓库链接,代码阅读预览里点了能跳过去。
* 扩展自己写进回答里的代码位置也按这个写法(见 [codeLink])。
*/
import type { NamedFolder } from '../editor/repoPath'
import { namesFolder } from '../editor/repoPath'
import { escapeMarkdown } from '../explorer/rowDecoration'
/** 链接打不开时(问的代码不在 git 仓库里)的写法:行内代码的绝对路径加行号。 */
const OUTSIDE_RULE = '问的代码不在 git 仓库里,提到代码位置时写行内代码:绝对路径加行号,如 `/path/to/file.py:12-20`。'
/**
* 要模型把代码位置写成 Markdown 链接的那段话,带一个例子。链接的写法同注释里的仓库链接([parseRepoLink](vscode/webview-ui/src/codeReader/repoLink.ts#parseRepoLink) 认的):
* 优先 `#符号`,几行写 `#L起-L止`。预览按 `base` 找链接里的路径,所以把它告诉模型。
*
* @param base 链接路径从哪儿算:字符串是被问的代码所在的 git 仓库根(绝对路径);数组是「了解一块代码」找代码用的工作区文件夹,
* 几个文件夹时路径以文件夹的名字开头(见 [namesFolder])。被问的代码不在 git 仓库里时没有,链接打不开,改要行内代码的绝对路径
*/
export function codeLinkRule(base: string | readonly NamedFolder[] | undefined): string {
if (base === undefined)
return OUTSIDE_RULE
const { from, prefix, outside } = linkFrom(base)
return [
`提到代码位置时写成 Markdown 链接,点了能跳过去。${from},开头不带 / 或 ./:`,
'- 指到函数、类、方法:[显示名](路径#符号),符号写函数名或「类名.方法名」。优先用这种,代码改了行号也不失效。',
'- 指到函数里的几行:[文件名:起-止](路径#L起-L止),只有一行时写 #L行号。',
`例如:[OrderService.cancel](${prefix}backend/app/orders/service.py#OrderService.cancel)、[service.py:357-364](${prefix}backend/app/orders/service.py#L357-L364)。`,
`${outside}的代码(比如依赖库)不写链接,写行内代码:绝对路径加行号,如 \`/path/to/file.py:12-20\`。`,
].join('\n')
}
/** [codeLinkRule] 里随 `base` 变的几处:路径从哪儿算起、例子里路径的开头、哪些代码算在外面。 */
function linkFrom(base: string | readonly NamedFolder[]): { from: string, prefix: string, outside: string } {
if (typeof base === 'string')
return { from: `路径从仓库根 ${base} 算起`, prefix: '', outside: '仓库外' }
if (!namesFolder(base))
return { from: `路径从工作区文件夹 ${base[0].path} 算起`, prefix: '', outside: '这个文件夹外' }
const names = base.map(folder => folder.name)
return { from: `路径的第一段是工作区文件夹的名字(${names.join('、')}),后面接相对这个文件夹的路径`, prefix: `${names[0]}/`, outside: '这几个文件夹外' }
}
/** 链接地址里 CommonMark 会断开、或页面解码时会认错的字:空白、尖括号、百分号(页面按 `decodeURIComponent` 解回来)。成对的括号不算,原样留着。 */
const UNSAFE_IN_HREF = /[%\s<>]/g
/** 括号是不是成对:成对的 CommonMark 认作链接地址的一部分,不成对的链接就断了。 */
function balanced(text: string): boolean {
let depth = 0
for (const char of text) {
depth += char === '(' ? 1 : char === ')' ? -1 : 0
if (depth < 0)
return false
}
return depth === 0
}
/**
* 一处代码写成的仓库链接,和 [codeLinkRule] 要模型写的一样:有符号时是 `[显示名](路径#符号)`,没有时只到文件。
* 显示名里的 Markdown 记号转义掉;路径里 [UNSAFE_IN_HREF] 的字和不成对的括号按百分号编码,页面照样认成链接。
*
* @param label 显示名
* @param target 链接路径(见 [codeLinkRule] 的 `base`)
* @param symbol 文件里的符号,函数名或「类名.方法名」
*/
export function codeLink(label: string, target: string, symbol?: string): string {
const safe = target.replace(UNSAFE_IN_HREF, encodeURIComponent)
const href = balanced(safe) ? safe : safe.replaceAll('(', '%28').replaceAll(')', '%29')
return `[${escapeMarkdown(label)}](${symbol === undefined ? href : `${href}#${symbol}`})`
}