✨ feat(vscode): 代码阅读「了解一块代码」改成按路径列调用树:结果是几条路径(用户的一种操作一条),每条从入口一路调到后端服务、数据表,跨进程的写明怎么连过去(via),结构用扁平的 nodes 加 parent 连成树、不用递归;侧栏路径下面一层层展开节点,回答里用嵌套列表画树;回答和找的过程里的代码位置写成仓库链接,功能对话页上的链接按找代码用的工作区文件夹检查、打开、悬停;旧格式的结果当没有,栏上方提示打开对话重新找;删功能前先弹模态框确认,说清连带删掉调用树和问答记录,点「删除」才删
This commit is contained in:
1 parent
cc445e7123
commit
c6de46ad37
17 files changed
+978
-253
No files matched your search
@@ -1,24 +1,69 @@
|
||||
/**
|
||||
* 问 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止`。预览按被问的代码所在的 git 仓库根找链接里的路径,所以把这个根告诉模型。
|
||||
* 优先 `#符号`,几行写 `#L起-L止`。预览按 `base` 找链接里的路径,所以把它告诉模型。
|
||||
*
|
||||
* @param repoRoot 被问的代码所在的 git 仓库根(绝对路径);不在 git 仓库里时没有,链接打不开,改要行内代码的绝对路径
|
||||
* @param base 链接路径从哪儿算:字符串是被问的代码所在的 git 仓库根(绝对路径);数组是「了解一块代码」找代码用的工作区文件夹,
|
||||
* 几个文件夹时路径以文件夹的名字开头(见 [namesFolder])。被问的代码不在 git 仓库里时没有,链接打不开,改要行内代码的绝对路径
|
||||
*/
|
||||
export function codeLinkRule(repoRoot: string | undefined): string {
|
||||
if (repoRoot === undefined)
|
||||
export function codeLinkRule(base: string | readonly NamedFolder[] | undefined): string {
|
||||
if (base === undefined)
|
||||
return OUTSIDE_RULE
|
||||
const { from, prefix, outside } = linkFrom(base)
|
||||
return [
|
||||
`提到代码位置时写成 Markdown 链接,点了能跳过去。路径从仓库根 ${repoRoot} 算起,开头不带 / 或 ./:`,
|
||||
`提到代码位置时写成 Markdown 链接,点了能跳过去。${from},开头不带 / 或 ./:`,
|
||||
'- 指到函数、类、方法:[显示名](路径#符号),符号写函数名或「类名.方法名」。优先用这种,代码改了行号也不失效。',
|
||||
'- 指到函数里的几行:[文件名:起-止](路径#L起-L止),只有一行时写 #L行号。',
|
||||
'例如:[OrderService.cancel](backend/app/orders/service.py#OrderService.cancel)、[service.py:357-364](backend/app/orders/service.py#L357-L364)。',
|
||||
'仓库外的代码(比如依赖库)不写链接,写行内代码:绝对路径加行号,如 `/path/to/file.py:12-20`。',
|
||||
`例如:[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}`})`
|
||||
}
|
||||
Reference in new issue
Block a user