✨ feat(vscode): 代码阅读鼠标停在名字和注释链接上弹出定义所在那块的卡片,别的文件的也行
This commit is contained in:
1 parent
bc7380c716
commit
a48b016d73
20 files changed
+1145
-89
No files matched your search
@@ -0,0 +1,86 @@
|
||||
import type { PeekTarget, ShownPeek } from './definitionPeek'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { DefinitionPeeks, documentNameAt, inPeek, linkPeekSource } from './definitionPeek'
|
||||
import { onMessage, postMessage } from './vscode'
|
||||
|
||||
/** 指针下能弹定义卡片的一处:注释里指到符号的链接,或页面上这份文档带下标的名字;浮窗里的不算。 */
|
||||
function peekTargetAt(target: EventTarget | null): PeekTarget<HTMLElement> | null {
|
||||
if (!(target instanceof Element) || inPeek(target))
|
||||
return null
|
||||
const link = target.closest<HTMLAnchorElement>('.cr-markdown a')
|
||||
if (link) {
|
||||
const source = linkPeekSource(link.getAttribute('href') ?? '')
|
||||
return source ? { anchor: link, source } : null
|
||||
}
|
||||
const name = documentNameAt(target)
|
||||
return name?.dataset.offset === undefined ? null : { anchor: name, source: { offset: Number(name.dataset.offset) } }
|
||||
}
|
||||
|
||||
function isInPeek(target: EventTarget | null): boolean {
|
||||
return target instanceof Element && inPeek(target)
|
||||
}
|
||||
|
||||
/**
|
||||
* 和 VS Code 编辑器里的悬停一样:鼠标停在带下标的名字、注释里指到符号的链接上,过一会儿(editor.hover.delay)弹出定义所在那块的卡片,
|
||||
* 移开名字和浮窗过一会儿(editor.hover.hidingDelay)收起,Esc、滚动、按下鼠标立刻收(见 DefinitionPeeks)。
|
||||
* version 是当前显示的文档版本,名字的下标属于它。返回弹着的卡片和点标题时的跳转。
|
||||
*/
|
||||
export function useDefinitionPeek(version: number | undefined): { shown: ShownPeek<HTMLElement> | undefined, jump: () => void } {
|
||||
const [shown, setShown] = useState<ShownPeek<HTMLElement> | undefined>()
|
||||
const [peeks] = useState(() => new DefinitionPeeks<HTMLElement>(postMessage, setShown))
|
||||
|
||||
useEffect(() => {
|
||||
peeks.setVersion(version)
|
||||
}, [peeks, version])
|
||||
|
||||
useEffect(() => {
|
||||
const onOver = (event: PointerEvent): void => {
|
||||
if (isInPeek(event.target))
|
||||
peeks.enterPopup()
|
||||
else
|
||||
peeks.point(peekTargetAt(event.target))
|
||||
}
|
||||
const onOut = (event: PointerEvent): void => {
|
||||
if (event.relatedTarget === null)
|
||||
peeks.point(null)
|
||||
}
|
||||
const onDown = (event: PointerEvent): void => {
|
||||
if (!isInPeek(event.target))
|
||||
peeks.dismiss()
|
||||
}
|
||||
const onKey = (event: KeyboardEvent): void => {
|
||||
if (event.key === 'Escape')
|
||||
peeks.dismiss()
|
||||
}
|
||||
// 捕获阶段:页面和页面里能滚的框滚动都收;浮窗自己里面滚不收
|
||||
const onScroll = (event: Event): void => {
|
||||
if (!isInPeek(event.target))
|
||||
peeks.dismiss()
|
||||
}
|
||||
// hoverDelay 在页面 ready 后、第一份文档之前就来,挂在页面根上的 App 一开始就收
|
||||
const off = onMessage((msg) => {
|
||||
if (msg.type === 'hoverDelay')
|
||||
peeks.setDelays(msg.delay, msg.hidingDelay)
|
||||
else if (msg.type === 'definitionPeeked')
|
||||
peeks.answer(msg.id, msg.peek)
|
||||
else if (msg.type === 'reveal')
|
||||
peeks.dismiss()
|
||||
})
|
||||
document.addEventListener('pointerover', onOver)
|
||||
document.addEventListener('pointerout', onOut)
|
||||
document.addEventListener('pointerdown', onDown, true)
|
||||
window.addEventListener('keydown', onKey)
|
||||
window.addEventListener('scroll', onScroll, true)
|
||||
return () => {
|
||||
off()
|
||||
document.removeEventListener('pointerover', onOver)
|
||||
document.removeEventListener('pointerout', onOut)
|
||||
document.removeEventListener('pointerdown', onDown, true)
|
||||
window.removeEventListener('keydown', onKey)
|
||||
window.removeEventListener('scroll', onScroll, true)
|
||||
peeks.dispose()
|
||||
}
|
||||
}, [peeks])
|
||||
|
||||
return { shown, jump: () => peeks.jump() }
|
||||
}
|
||||
Reference in new issue
Block a user