✨ feat(vscode): 代码阅读预览单击名字高亮它在文件里的各处出现

This commit is contained in:
cruldra committed 2026-09-26 15:51:52 +08:00
1 parent 5872cf7958
commit 9c0f05362c
12 files changed
+558 -9

No files matched your search

@@ -0,0 +1,133 @@
import type { Occurrence, Segment, WebviewToExtension } from './types'
/** 高亮画成哪种底色,和 VS Code 编辑器一样:写入(定义、赋值)用强高亮色,读取和按文字认出的用普通高亮色。 */
export type OccurrenceStyle = 'strong' | 'normal'
export function occurrenceStyle(kind: Occurrence['kind']): OccurrenceStyle {
return kind === 'write' ? 'strong' : 'normal'
}
/**
* 一段文字画不画高亮、画哪种:它照抄自源码,在源码里占 [offset, offset + 字数),和哪处出现重叠就画,
* 同时碰上写入和读取的按写入画。没有下标的(译词、提示词、编辑状态的源码)不画。
*/
export function segmentOccurrence(segment: Segment, occurrences: readonly Occurrence[]): OccurrenceStyle | undefined {
const { offset } = segment
if (offset === undefined)
return undefined
const end = offset + segment.text.length
let style: OccurrenceStyle | undefined
for (const occurrence of occurrences) {
if (occurrence.start < end && offset < occurrence.end) {
style = occurrenceStyle(occurrence.kind)
if (style === 'strong')
return style
}
}
return style
}
/** 一次点击的情况,useOccurrences 从事件和页面上读出来。 */
export interface Click {
/** 按着 ⌘ / Ctrl / Alt / Shift:不是单纯的单击(⌘ / Ctrl + 点击归跳到定义) */
modified: boolean
/** 连击的第几下(MouseEvent.detail),双击的第二下是 2 */
count: number
/** 点完页面上有选中的文字:这是拖着选字,不是点 */
selecting: boolean
/** 点中的带下标的名字 */
name?: { offset: number, text: string }
/** 点在自己管点击的东西上:按钮、链接、输入框、下拉列表、筛选栏、属性名 */
control: boolean
}
/** find:问 offset 处名字的各处出现;clear:清掉高亮;ignore:不动 */
export type ClickAction
= | { kind: 'find', offset: number, name: string }
| { kind: 'clear' }
| { kind: 'ignore' }
/**
* 单击名字就高亮,名字在折叠箭头这类按钮里也算(按钮照旧折叠);点空白处清掉;
* 修饰键 + 点击、双击、拖着选字、点在按钮 / 链接 / 筛选栏这些自己管点击的东西上,高亮不动。
*/
export function clickAction(click: Click): ClickAction {
if (click.modified || click.count > 1 || click.selecting)
return { kind: 'ignore' }
if (click.name)
return { kind: 'find', offset: click.name.offset, name: click.name.text }
return click.control ? { kind: 'ignore' } : { kind: 'clear' }
}
/** 眼下高亮着的名字:点中时它的文字(筛选栏上显示)和它在文档里的各处出现。 */
export interface Highlight {
name: string
occurrences: readonly Occurrence[]
}
/**
* 单击名字高亮它在文档里的各处出现,和编辑器里一样每回点都现问扩展端,回答不留到下次用;
* 只认最近一次询问的回答。回答到之前旧高亮留着(点同一个名字的另一处不闪),回答是空的就不高亮。
*/
export class OccurrenceHighlight {
private version: number | undefined
private translatorId: string | undefined
/** 最近一次询问的 id;别的回答(点了别处、清掉之后才到的)都丢掉 */
private request = 0
/** 最近一次询问点中的名字;答过或清掉后没有 */
private asked: string | undefined
private current: Highlight | undefined
constructor(
private readonly post: (msg: WebviewToExtension) => void,
private readonly changed: (highlight: Highlight | undefined) => void,
) {}
get highlight(): Highlight | undefined {
return this.current
}
/** 换了文档版本或翻译:下标、页面上的名字都换了,高亮清掉 */
setDocument(version: number | undefined, translatorId: string | undefined): void {
if (version === this.version && translatorId === this.translatorId)
return
this.version = version
this.translatorId = translatorId
this.clear()
}
click(action: ClickAction): void {
if (action.kind === 'clear')
this.clear()
else if (action.kind === 'find')
this.find(action.offset, action.name)
}
answer(id: number, occurrences: Occurrence[]): void {
if (id !== this.request || this.asked === undefined)
return
const name = this.asked
this.asked = undefined
this.show(occurrences.length > 0 ? { name, occurrences } : undefined)
}
clear(): void {
this.request++
this.asked = undefined
this.show(undefined)
}
private find(offset: number, name: string): void {
if (this.version === undefined)
return
this.asked = name
this.post({ type: 'findOccurrences', id: ++this.request, version: this.version, offset })
}
private show(highlight: Highlight | undefined): void {
if (highlight === this.current)
return
this.current = highlight
this.changed(highlight)
}
}