♻️ refactor(vscode): 代码阅读不再在页面里拦 ⌘↓ / ⌘↑,快捷键只在「键盘快捷方式」里绑;跳到下一处时把浏览器自己的滚动拉回来,页面不再先滚到底

This commit is contained in:
cruldra committed 2026-09-27 00:33:18 +08:00
1 parent 0cad5722bb
commit 1b1299dc1f
10 files changed
+157 -165

No files matched your search

+54 -14
View File
@@ -1,4 +1,4 @@
import type { Inline, KeyCombo, Occurrence, Segment, WebviewToExtension } from './types'
import type { Inline, Occurrence, Segment, WebviewToExtension } from './types'
/** 高亮画成哪种底色,和 VS Code 编辑器一样:写入(定义、赋值)用强高亮色,读取和按文字认出的用普通高亮色。 */
export type OccurrenceStyle = 'strong' | 'normal'
@@ -111,10 +111,58 @@ export function stepIndex(
return undefined
}
/** 按下的键是不是这个组合:键名(字母不分大小写)和四个修饰键都对上。 */
export function matchesKey(event: Pick<KeyboardEvent, 'key' | 'ctrlKey' | 'shiftKey' | 'altKey' | 'metaKey'>, combo: KeyCombo): boolean {
return event.key.toLowerCase() === combo.key.toLowerCase()
&& event.ctrlKey === combo.ctrl && event.shiftKey === combo.shift && event.altKey === combo.alt && event.metaKey === combo.meta
/** 按键之后多久内发来的跳转算这个键触发的:命令从 VS Code 绕一圈回到页面通常只要几十毫秒 */
export const KEY_TO_COMMAND_MS = 1000
/** 浏览器对一个键自己的滚动(⌘↓ 滚到底)按下后多久内会开始:它可能在命令发来之后才开始,这段时间里跳到的位置要守住 */
export const KEY_SCROLL_MS = 500
export interface ScrollPosition { x: number, y: number }
/**
* 跳到下一处 / 上一处的命令由 VS Code 的快捷键系统触发,页面不知道绑的是哪个键,浏览器对这个键自己的动作(⌘↓ 滚到底)拦不住;
* 它还可能早于命令开始,也可能晚于命令(实测命令十几毫秒就到,滚动动画再过一帧才开始)。所以不管哪个键:
* 按键那一刻记下页面滚在哪(press);命令发来时先回到那里(take)再滚到目标;跳完守住跳到的位置(hold),
* 按键后 KEY_SCROLL_MS 内浏览器自己滚走了就拉回来(scrolled)。
* 按键后点了鼠标、滚了滚轮、又按了键,或跳转隔了 KEY_TO_COMMAND_MS 以上才来(从命令面板运行),都不算这个键触发的。
*/
export class KeyScroll {
private pressed: { position: ScrollPosition, at: number } | undefined
private held: { position: ScrollPosition, until: number } | undefined
/** 按下任何键(照常交给浏览器和 VS Code 处理)时记下页面滚在哪。 */
press(position: ScrollPosition, at: number): void {
this.pressed = { position, at }
this.held = undefined
}
forget(): void {
this.pressed = undefined
this.held = undefined
}
/** 跳转发来时:刚按过键就交出按键那一刻的位置(只交一次),从这时起守住它,直到跳完换成跳到的位置。 */
take(now: number): ScrollPosition | undefined {
const pressed = this.pressed
this.pressed = undefined
this.held = pressed && now - pressed.at <= KEY_TO_COMMAND_MS ? { position: pressed.position, until: pressed.at + KEY_SCROLL_MS } : undefined
return this.held?.position
}
/** 跳完了(滚到了目标,或没处可跳留在原地):按键触发的跳转改守这个位置。 */
hold(position: ScrollPosition): void {
if (this.held)
this.held = { ...this.held, position }
}
/** 页面滚动了:还在守着、又被浏览器滚走了,给出要拉回的位置。 */
scrolled(position: ScrollPosition, now: number): ScrollPosition | undefined {
const held = this.held
if (!held || now > held.until) {
this.held = undefined
return undefined
}
return held.position.x === position.x && held.position.y === position.y ? undefined : held.position
}
}
/**
@@ -210,7 +258,7 @@ export interface NavigationFacts {
highlighted: boolean
/** 页面有焦点:按的键才会到这个页面 */
focused: boolean
/** 焦点在输入框里:⌘↑ / ⌘↓ 这些键归输入框移光标 */
/** 焦点在输入框里:按的键归输入框(打字、移光标),不拿来跳 */
typing: boolean
}
@@ -237,14 +285,6 @@ export class OccurrenceNavigation {
this.report()
}
/**
* 这个键要不要拦下浏览器的默认动作:能跳、又是那两条命令绑的键。VS Code 把页面上按的每个键都转给自己的快捷键系统,
* 但不回头告诉页面有没有命令接了它,浏览器的默认动作又只能在按下的当下拦,所以只能照命令绑的键(keys)当场判断。
*/
blocks(event: Pick<KeyboardEvent, 'key' | 'ctrlKey' | 'shiftKey' | 'altKey' | 'metaKey'>, keys: readonly KeyCombo[]): boolean {
return this.available && keys.some(combo => matchesKey(event, combo))
}
private report(): void {
const { available } = this
if (available === this.reported)