✨ feat(vscode): 代码阅读的名字高亮更显眼(当前那处描框、别处画细线),⌘↓ / ⌘↑ 在各处之间跳,跳到折叠处自动展开,筛选栏写第几处
This commit is contained in:
1 parent
c5c5d2b517
commit
49f253b80f
18 files changed
+731
-53
No files matched your search
@@ -1,12 +1,12 @@
|
||||
import type { Click, Highlight } from './occurrences'
|
||||
import type { BlockView, Inline, Occurrence, PreviewText, PropertyItem, Segment, WebviewToExtension } from './types'
|
||||
import type { BlockView, Inline, KeyCombo, Occurrence, PreviewText, PropertyItem, Segment, WebviewToExtension } from './types'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { toBlockViews } from '../../../src/codeReader/blockView'
|
||||
import { createPythonAdapter } from '../../../src/codeReader/languages/python'
|
||||
import { WASM_DIR, withBlocks } from '../../../src/codeReader/testSupport'
|
||||
import { pythonZhTranslator } from '../../../src/codeReader/translators/pythonZh'
|
||||
import { DefinitionLinks } from './definitionLinks'
|
||||
import { clickAction, OccurrenceHighlight, occurrenceStyle, segmentOccurrence } from './occurrences'
|
||||
import { clickAction, isCurrent, matchesKey, occurrenceCount, OccurrenceHighlight, OccurrenceNavigation, occurrenceStyle, segmentOccurrence, stepIndex } from './occurrences'
|
||||
|
||||
function name(text: string, offset: number): Segment {
|
||||
return { text, role: 'variable', offset }
|
||||
@@ -121,8 +121,8 @@ describe('occurrences:高亮的状态', () => {
|
||||
expect(asked()).toEqual([{ id: asked()[0].id, version: 1, offset: 20 }])
|
||||
const occurrences = [at(20, 28, 'write'), at(60, 68)]
|
||||
occ.answer(asked()[0].id, occurrences)
|
||||
expect(occ.highlight).toEqual({ name: 'ALPHABET', occurrences })
|
||||
expect(changes).toEqual([{ name: 'ALPHABET', occurrences }])
|
||||
expect(occ.highlight).toEqual({ name: 'ALPHABET', occurrences, current: 0 })
|
||||
expect(changes).toEqual([{ name: 'ALPHABET', occurrences, current: 0 }])
|
||||
|
||||
find(occ, 5, 'secrets')
|
||||
occ.answer(asked()[1].id, [])
|
||||
@@ -136,7 +136,7 @@ describe('occurrences:高亮的状态', () => {
|
||||
find(occ, 5, 'secrets')
|
||||
expect(occ.highlight?.name).toBe('ALPHABET')
|
||||
occ.answer(asked()[1].id, [at(5, 12), at(40, 47)])
|
||||
expect(occ.highlight).toEqual({ name: 'secrets', occurrences: [at(5, 12), at(40, 47)] })
|
||||
expect(occ.highlight).toEqual({ name: 'secrets', occurrences: [at(5, 12), at(40, 47)], current: 0 })
|
||||
})
|
||||
|
||||
it('只认最近一次询问的回答,迟到的丢掉', () => {
|
||||
@@ -146,9 +146,9 @@ describe('occurrences:高亮的状态', () => {
|
||||
const [first, second] = asked()
|
||||
occ.answer(second.id, [at(5, 12)])
|
||||
occ.answer(first.id, [at(20, 28)])
|
||||
expect(occ.highlight).toEqual({ name: 'secrets', occurrences: [at(5, 12)] })
|
||||
expect(occ.highlight).toEqual({ name: 'secrets', occurrences: [at(5, 12)], current: 0 })
|
||||
occ.answer(second.id, [at(99, 100)])
|
||||
expect(occ.highlight).toEqual({ name: 'secrets', occurrences: [at(5, 12)] })
|
||||
expect(occ.highlight).toEqual({ name: 'secrets', occurrences: [at(5, 12)], current: 0 })
|
||||
})
|
||||
|
||||
it('回答不留到下次用:答过「没有」后再点同一个名字照样再问', () => {
|
||||
@@ -227,3 +227,103 @@ function allSegments(views: BlockView[]): Segment[] {
|
||||
...allSegments(view.children),
|
||||
])
|
||||
}
|
||||
|
||||
const ANYWHERE = (): boolean => true
|
||||
|
||||
describe('occurrences:用键盘在各处出现之间跳', () => {
|
||||
it('回答照源码先后排好,点中的那处是当前那一处;筛选栏写「第几处/共几处」,点中的不在回答里时写「N 处」', () => {
|
||||
const { occ, asked } = setup()
|
||||
find(occ, 40, 'ALPHABET')
|
||||
occ.answer(asked()[0].id, [at(90, 98), at(40, 48), at(10, 18, 'write')])
|
||||
expect(occ.highlight).toEqual({ name: 'ALPHABET', occurrences: [at(10, 18, 'write'), at(40, 48), at(90, 98)], current: 1 })
|
||||
expect(occurrenceCount(occ.highlight!)).toBe('2/3')
|
||||
|
||||
find(occ, 60, 'ALPHABET')
|
||||
occ.answer(asked()[1].id, [at(10, 18), at(40, 48), at(90, 98)])
|
||||
expect(occ.highlight?.current).toBeUndefined()
|
||||
expect(occurrenceCount(occ.highlight!)).toBe('3 处')
|
||||
})
|
||||
|
||||
it('往后、往前按源码先后跳,到头了从另一头接着跳;当前那一处跟着换', () => {
|
||||
const { occ, asked, changes } = setup()
|
||||
find(occ, 40, 'ALPHABET')
|
||||
occ.answer(asked()[0].id, [at(10, 18), at(40, 48), at(90, 98)])
|
||||
expect(occ.move(1, ANYWHERE)).toEqual(at(90, 98))
|
||||
expect(occ.highlight?.current).toBe(2)
|
||||
expect(occ.move(1, ANYWHERE)).toEqual(at(10, 18))
|
||||
expect(occ.highlight?.current).toBe(0)
|
||||
expect(occ.move(-1, ANYWHERE)).toEqual(at(90, 98))
|
||||
expect(occ.move(-1, ANYWHERE)).toEqual(at(40, 48))
|
||||
expect(occurrenceCount(occ.highlight!)).toBe('2/3')
|
||||
expect(changes.at(-1)).toEqual({ name: 'ALPHABET', occurrences: [at(10, 18), at(40, 48), at(90, 98)], current: 1 })
|
||||
})
|
||||
|
||||
it('页面上找不到的(在被筛掉的块里)跳过,总数照旧;一处都找不到、没有高亮时不动', () => {
|
||||
const { occ, asked, changes } = setup()
|
||||
expect(occ.move(1, ANYWHERE)).toBeUndefined()
|
||||
find(occ, 10, 'ALPHABET')
|
||||
occ.answer(asked()[0].id, [at(10, 18), at(40, 48), at(90, 98)])
|
||||
const shown = (occurrence: Occurrence): boolean => occurrence.start !== 40
|
||||
expect(occ.move(1, shown)).toEqual(at(90, 98))
|
||||
expect(occ.move(1, shown)).toEqual(at(10, 18))
|
||||
expect(occ.move(-1, shown)).toEqual(at(90, 98))
|
||||
expect(occurrenceCount(occ.highlight!)).toBe('3/3')
|
||||
const before = changes.length
|
||||
expect(occ.move(1, () => false)).toBeUndefined()
|
||||
expect(changes).toHaveLength(before)
|
||||
expect(occ.highlight?.current).toBe(2)
|
||||
})
|
||||
|
||||
it('点中的名字不在回答里:从点中的地方往后是它后面第一处,往前是它前面最后一处', () => {
|
||||
const occurrences = [at(10, 18), at(40, 48), at(90, 98)]
|
||||
expect(stepIndex(occurrences, undefined, 60, 1, ANYWHERE)).toBe(2)
|
||||
expect(stepIndex(occurrences, undefined, 60, -1, ANYWHERE)).toBe(1)
|
||||
expect(stepIndex(occurrences, undefined, 5, -1, ANYWHERE)).toBe(2)
|
||||
expect(stepIndex(occurrences, undefined, 95, 1, ANYWHERE)).toBe(0)
|
||||
expect(stepIndex(occurrences, 0, 0, -1, ANYWHERE)).toBe(2)
|
||||
expect(stepIndex(occurrences, 1, 0, 1, occurrence => occurrence.start === 40)).toBe(1)
|
||||
expect(stepIndex([], undefined, 0, 1, ANYWHERE)).toBeUndefined()
|
||||
})
|
||||
|
||||
it('当前那一处:和它重叠的段描边,别的出现、没下标的段不描', () => {
|
||||
const highlight = { name: 'ALPHABET', occurrences: [at(10, 18), at(40, 48)], current: 1 }
|
||||
expect(isCurrent(name('ALPHABET', 40), highlight)).toBe(true)
|
||||
expect(isCurrent(name('ALPHABET', 10), highlight)).toBe(false)
|
||||
expect(isCurrent({ text: 'ALPHABET', role: 'plain' }, highlight)).toBe(false)
|
||||
expect(isCurrent(name('ALPHABET', 40), { ...highlight, current: undefined })).toBe(false)
|
||||
})
|
||||
|
||||
it('能不能用键盘跳:有高亮、页面有焦点、焦点不在输入框里;第一次都告诉扩展端,之后变了才告诉', () => {
|
||||
const sent: WebviewToExtension[] = []
|
||||
const navigation = new OccurrenceNavigation(msg => sent.push(msg), { focused: true, typing: false })
|
||||
const reports = (): boolean[] => sent.flatMap(msg => msg.type === 'occurrenceNavigation' ? [msg.available] : [])
|
||||
navigation.update({})
|
||||
expect(reports()).toEqual([false])
|
||||
navigation.update({ highlighted: true })
|
||||
expect(navigation.available).toBe(true)
|
||||
navigation.update({ highlighted: true })
|
||||
navigation.update({ typing: true })
|
||||
navigation.update({ typing: false })
|
||||
navigation.update({ focused: false })
|
||||
navigation.update({ highlighted: false })
|
||||
navigation.update({ focused: true })
|
||||
expect(reports()).toEqual([false, true, false, true, false])
|
||||
})
|
||||
|
||||
it('命令绑的键:能跳时拦下浏览器的默认动作(⌘↓ 滚到底),不能跳、别的键、修饰键对不上都不拦', () => {
|
||||
const keys: KeyCombo[] = [{ key: 'ArrowDown', ctrl: false, shift: false, alt: false, meta: true }, { key: 'j', ctrl: true, shift: false, alt: false, meta: false }]
|
||||
const press = (key: string, modifiers: Partial<Record<'ctrlKey' | 'shiftKey' | 'altKey' | 'metaKey', boolean>> = {}): Pick<KeyboardEvent, 'key' | 'ctrlKey' | 'shiftKey' | 'altKey' | 'metaKey'> =>
|
||||
({ key, ctrlKey: false, shiftKey: false, altKey: false, metaKey: false, ...modifiers })
|
||||
const navigation = new OccurrenceNavigation(() => {}, { focused: true, typing: false })
|
||||
expect(navigation.blocks(press('ArrowDown', { metaKey: true }), keys)).toBe(false)
|
||||
navigation.update({ highlighted: true })
|
||||
expect(navigation.blocks(press('ArrowDown', { metaKey: true }), keys)).toBe(true)
|
||||
expect(navigation.blocks(press('J', { ctrlKey: true }), keys)).toBe(true)
|
||||
expect(navigation.blocks(press('ArrowDown', { metaKey: true, shiftKey: true }), keys)).toBe(false)
|
||||
expect(navigation.blocks(press('ArrowDown'), keys)).toBe(false)
|
||||
expect(navigation.blocks(press('ArrowUp', { metaKey: true }), keys)).toBe(false)
|
||||
navigation.update({ typing: true })
|
||||
expect(navigation.blocks(press('ArrowDown', { metaKey: true }), keys)).toBe(false)
|
||||
expect(matchesKey(press('ArrowDown', { metaKey: true }), keys[0])).toBe(true)
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user