✨ feat(vscode): 注释里的仓库路径链接能点开并跳到符号

This commit is contained in:
cruldra committed 2026-10-03 03:22:20 +08:00
1 parent 5528a405cc
commit c4113748e1
13 files changed
+662 -18

No files matched your search

@@ -0,0 +1,93 @@
import { useEffect, useState } from 'react'
import { RepoLinks } from './repoLink'
import { onMessage, postMessage } from './vscode'
/** 坏链接悬停上的字,走页面上统一的 data-tooltip,不用原生 title。 */
const MISSING = '找不到'
/** 文件或符号没有的仓库链接:红色波浪线,悬停「找不到」。问到之前先是普通链接。 */
function paintLink(anchor: HTMLAnchorElement, broken: boolean): void {
const tip = anchor.getAttribute('data-tooltip')
if (broken) {
anchor.classList.add('cr-link-broken')
if (tip !== MISSING) {
if (tip)
anchor.dataset.linkTooltip = tip
anchor.setAttribute('data-tooltip', MISSING)
}
return
}
if (!anchor.classList.contains('cr-link-broken'))
return
anchor.classList.remove('cr-link-broken')
if (tip === MISSING) {
const prev = anchor.dataset.linkTooltip
if (prev)
anchor.setAttribute('data-tooltip', prev)
else
anchor.removeAttribute('data-tooltip')
}
delete anchor.dataset.linkTooltip
}
function paint(links: RepoLinks): void {
for (const anchor of document.querySelectorAll<HTMLAnchorElement>('.cr-markdown a'))
paintLink(anchor, links.isBroken(anchor.getAttribute('href') ?? ''))
}
/**
* 注释、说明、问 AI 的回答里的仓库路径链接:点了发给扩展端打开(不走 webview 把 http 链接交给外部打开的那条),
* 画出来之后把路径发去问哪些文件没有。
*/
export function useRepoLinks(): void {
const [links] = useState(() => new RepoLinks(postMessage))
useEffect(() => {
const off = onMessage((msg) => {
if (msg.type === 'repoLinksChecked') {
if (links.checked(msg.id, msg.missing))
paint(links)
return
}
if (msg.type === 'repoLinkBroken') {
links.broken(msg.path, msg.symbol)
paint(links)
return
}
if (msg.type === 'repoLinkOpened') {
links.opened(msg.path, msg.symbol)
paint(links)
}
})
// 捕获阶段:赶在 webview 把 <a> 交给外部打开之前拦下仓库链接
const onClick = (event: MouseEvent): void => {
const anchor = event.target instanceof Element ? event.target.closest('a') : null
if (!anchor || !links.click(anchor.getAttribute('href')))
return
event.preventDefault()
event.stopPropagation()
}
let timer = 0
const scan = (): void => {
window.clearTimeout(timer)
timer = window.setTimeout(() => {
const anchors = [...document.querySelectorAll<HTMLAnchorElement>('.cr-markdown a')]
for (const anchor of anchors)
paintLink(anchor, links.isBroken(anchor.getAttribute('href') ?? ''))
links.scan(anchors.map(anchor => anchor.getAttribute('href') ?? ''))
}, 50)
}
const observer = new MutationObserver(scan)
observer.observe(document.body, { childList: true, subtree: true })
document.addEventListener('click', onClick, true)
document.addEventListener('auxclick', onClick, true)
scan()
return () => {
off()
observer.disconnect()
document.removeEventListener('click', onClick, true)
document.removeEventListener('auxclick', onClick, true)
window.clearTimeout(timer)
}
}, [links])
}