✨ feat(vscode): 代码阅读预览按住 ⌘ / Ctrl 点名字跳到定义
This commit is contained in:
1 parent
e5db1a2e65
commit
4e146eb48f
20 files changed
+870
-60
No files matched your search
@@ -0,0 +1,124 @@
|
||||
import type { WebviewToExtension } from './types'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { onMessage, postMessage } from './vscode'
|
||||
|
||||
/** 跳到定义用的修饰键,按平台习惯:macOS 是 ⌘,其余是 Ctrl。 */
|
||||
const IS_MAC = /Mac|iPhone|iPad/.test(navigator.userAgent)
|
||||
|
||||
function modifierHeld(event: MouseEvent | KeyboardEvent): boolean {
|
||||
return IS_MAC ? event.metaKey : event.ctrlKey
|
||||
}
|
||||
|
||||
/** 事件落在的那个带源码下标的名字(SegmentText 渲染的 data-offset)。 */
|
||||
function nameAt(target: EventTarget | null): HTMLElement | null {
|
||||
return target instanceof Element ? target.closest<HTMLElement>('[data-offset]') : null
|
||||
}
|
||||
|
||||
function offsetOf(el: HTMLElement | null): number | undefined {
|
||||
return el?.dataset.offset === undefined ? undefined : Number(el.dataset.offset)
|
||||
}
|
||||
|
||||
/**
|
||||
* 和 VS Code 编辑器里一样:按住 ⌘ / Ctrl、鼠标停在查得到定义的名字上时,名字显示成链接(data-link),点一下跳到定义。
|
||||
* 有没有定义问扩展端,回答按文档版本缓存:同一版本里每个名字只问一次,换了版本清空,迟到的旧版本回答丢掉。
|
||||
*/
|
||||
class DefinitionLinks {
|
||||
private version: number | undefined
|
||||
/** 这个版本里问过的下标:true / false 是回答,undefined 是问了还没回 */
|
||||
private readonly answers = new Map<number, boolean | undefined>()
|
||||
private hovered: HTMLElement | null = null
|
||||
private held = false
|
||||
private linked: HTMLElement | null = null
|
||||
|
||||
constructor(private readonly post: (msg: WebviewToExtension) => void) {}
|
||||
|
||||
setVersion(version: number | undefined): void {
|
||||
if (version === this.version)
|
||||
return
|
||||
this.version = version
|
||||
this.answers.clear()
|
||||
this.update()
|
||||
}
|
||||
|
||||
point(target: EventTarget | null, held: boolean): void {
|
||||
this.hovered = nameAt(target)
|
||||
this.held = held
|
||||
this.update()
|
||||
}
|
||||
|
||||
hold(held: boolean): void {
|
||||
this.held = held
|
||||
this.update()
|
||||
}
|
||||
|
||||
answer(version: number, offset: number, found: boolean): void {
|
||||
if (version !== this.version)
|
||||
return
|
||||
this.answers.set(offset, found)
|
||||
this.update()
|
||||
}
|
||||
|
||||
/** ⌘ / Ctrl + 点击名字:跳到定义,并拦下这次点击,不让它再触发块上的切换。已知没有定义的名字照常点。 */
|
||||
click(event: MouseEvent): void {
|
||||
const offset = modifierHeld(event) ? offsetOf(nameAt(event.target)) : undefined
|
||||
if (offset === undefined || this.version === undefined || this.answers.get(offset) === false)
|
||||
return
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
this.post({ type: 'goToDefinition', version: this.version, offset })
|
||||
}
|
||||
|
||||
private update(): void {
|
||||
const offset = this.held ? offsetOf(this.hovered) : undefined
|
||||
if (offset !== undefined && this.version !== undefined && !this.answers.has(offset)) {
|
||||
this.answers.set(offset, undefined)
|
||||
this.post({ type: 'probeDefinition', version: this.version, offset })
|
||||
}
|
||||
const link = offset !== undefined && this.answers.get(offset) === true ? this.hovered : null
|
||||
if (link === this.linked)
|
||||
return
|
||||
this.linked?.removeAttribute('data-link')
|
||||
link?.setAttribute('data-link', '')
|
||||
this.linked = link
|
||||
}
|
||||
}
|
||||
|
||||
/** 预览里的名字能 ⌘ / Ctrl + 点击跳到定义;version 是当前显示的文档版本,名字的下标属于它。 */
|
||||
export function useDefinitionLinks(version: number | undefined): void {
|
||||
const [links] = useState(() => new DefinitionLinks(postMessage))
|
||||
|
||||
useEffect(() => {
|
||||
links.setVersion(version)
|
||||
}, [links, version])
|
||||
|
||||
useEffect(() => {
|
||||
const onMove = (event: MouseEvent): void => links.point(event.target, modifierHeld(event))
|
||||
const onOut = (event: MouseEvent): void => {
|
||||
if (event.relatedTarget === null)
|
||||
links.point(null, false)
|
||||
}
|
||||
const onKey = (event: KeyboardEvent): void => links.hold(modifierHeld(event))
|
||||
const onBlur = (): void => links.hold(false)
|
||||
const onClick = (event: MouseEvent): void => links.click(event)
|
||||
const off = onMessage((msg) => {
|
||||
if (msg.type === 'definitionProbed')
|
||||
links.answer(msg.version, msg.offset, msg.found)
|
||||
})
|
||||
document.addEventListener('mousemove', onMove)
|
||||
document.addEventListener('mouseout', onOut)
|
||||
// 捕获阶段:先于块里的按钮、折叠箭头拿到点击
|
||||
document.addEventListener('click', onClick, true)
|
||||
window.addEventListener('keydown', onKey)
|
||||
window.addEventListener('keyup', onKey)
|
||||
window.addEventListener('blur', onBlur)
|
||||
return () => {
|
||||
off()
|
||||
document.removeEventListener('mousemove', onMove)
|
||||
document.removeEventListener('mouseout', onOut)
|
||||
document.removeEventListener('click', onClick, true)
|
||||
window.removeEventListener('keydown', onKey)
|
||||
window.removeEventListener('keyup', onKey)
|
||||
window.removeEventListener('blur', onBlur)
|
||||
}
|
||||
}, [links])
|
||||
}
|
||||
Reference in new issue
Block a user