⚡ perf(vscode): 预览切走标签页时页面留着,切回来不再重新生成、重发文档和整份重画

This commit is contained in:
cruldra committed 2026-10-13 13:17:22 +11:00
1 parent 53283808a0
commit 5426c50cd9
8 files changed
+118 -59

No files matched your search

@@ -6,16 +6,17 @@ import type { SymbolNode } from '../symbolLinks'
import type { CodeColors } from '../theme/codeColors'
import type { ExtensionToWebview, WebviewToExtension } from './messages'
import { describe, expect, it, vi } from 'vitest'
import { window, workspace } from 'vscode'
import { commands, window, workspace } from 'vscode'
import { documentMessage } from './documentMessage'
import { documentSymbolNodes } from './documentSymbols'
import { PreviewEditorProvider } from './previewEditor'
import { OCCURRENCES_CONTEXT } from './occurrenceNavigation'
import { PREVIEW_VIEW_TYPE, PreviewEditorProvider } from './previewEditor'
vi.mock('vscode', () => ({
commands: { executeCommand: vi.fn() },
window: { showErrorMessage: vi.fn() },
window: { showErrorMessage: vi.fn(), registerCustomEditorProvider: vi.fn() },
workspace: {
onDidChangeTextDocument: () => ({ dispose: () => {} }),
onDidChangeTextDocument: vi.fn(() => ({ dispose: () => {} })),
onDidChangeConfiguration: () => ({ dispose: () => {} }),
getConfiguration: () => ({ get: (_key: string, fallback: unknown) => fallback }),
openTextDocument: vi.fn(),
@@ -214,10 +215,10 @@ function opening(profiles = { ask: profileSet(''), rewrite: profileSet('') }, co
return { provider, document, diffBase }
}
/** 一个标签页:收发消息、切走(页面被销毁,切回来重新加载、再发 ready) */
/** 一个标签页:收发消息;切走、切回来时页面留着,VS Code 只报一声可见性变了 */
function tab() {
let receive: ((message: WebviewToExtension) => Promise<void>) | undefined
let viewStateChanged: (() => void) | undefined
const viewStateListeners: (() => void)[] = []
const post = vi.fn<(message: ExtensionToWebview) => void>()
const panel = {
active: true,
@@ -231,20 +232,22 @@ function tab() {
return { dispose: () => {} }
},
},
onDidChangeViewState: (listener: typeof viewStateChanged) => {
viewStateChanged = listener
onDidChangeViewState: (listener: () => void) => {
viewStateListeners.push(listener)
return { dispose: () => {} }
},
onDidDispose: () => ({ dispose: () => {} }),
}
const setVisible = (visible: boolean): void => {
panel.visible = visible
panel.active = visible
viewStateListeners.forEach(listener => listener())
}
return {
panel: panel as unknown as Parameters<PreviewEditorProvider['resolveCustomTextEditor']>[1],
send: (message: WebviewToExtension) => receive!(message),
hide: () => {
panel.visible = false
viewStateChanged!()
panel.visible = true
},
hide: () => setVisible(false),
show: () => setVisible(true),
posted: () => post.mock.calls.map(([message]) => message),
clear: () => post.mockClear(),
}
@@ -284,7 +287,7 @@ describe('预览对比什么看这个标签页是从哪儿打开的', () => {
expect(diffBase).not.toHaveBeenCalledWith(document.uri, 'uncommitted')
})
it('开着的预览再从主栏点开就不比,再从「未提交的更改」点开又比;只切标签页不变', async () => {
it('开着的预览再从主栏点开就不比,再从「未提交的更改」点开又比;页面重新加载后按它记着的照旧比', async () => {
const { provider, document } = opening()
const page = tab()
await provider.open(document.uri as never, { from: 'uncommitted' }, async () => provider.resolveCustomTextEditor(document as never, page.panel))
@@ -301,7 +304,6 @@ describe('预览对比什么看这个标签页是从哪儿打开的', () => {
expect(page.posted()).toContainEqual({ type: 'openedFrom', from: 'uncommitted' })
await vi.waitFor(() => expect(page.posted()).toContainEqual(comparedWithHead))
page.hide()
page.clear()
await page.send({ type: 'ready', openedFrom: 'uncommitted' })
expect(page.posted()).toContainEqual({ type: 'openedFrom', from: 'uncommitted' })
@@ -336,6 +338,67 @@ describe('预览对比什么看这个标签页是从哪儿打开的', () => {
})
})
describe('标签页切走时页面留着', () => {
it('注册时就让页面在标签页切走后留着', () => {
const { provider } = opening()
provider.register()
expect(window.registerCustomEditorProvider).toHaveBeenCalledWith(PREVIEW_VIEW_TYPE, provider, { webviewOptions: { retainContextWhenHidden: true } })
})
it('切走再切回来:不重新生成文档、什么都不重发,高亮照旧能用键盘跳', async () => {
const { provider, document } = opening()
const page = tab()
await provider.open(document.uri as never, { from: 'uncommitted' }, async () => provider.resolveCustomTextEditor(document as never, page.panel))
await page.send({ type: 'ready' })
await vi.waitFor(() => expect(page.posted()).toContainEqual(comparedWithHead))
await page.send({ type: 'occurrenceNavigation', available: true })
page.clear()
vi.mocked(documentMessage).mockClear()
vi.mocked(commands.executeCommand).mockClear()
page.hide()
page.show()
// 比连续输入合并重新解析的那一下(150ms)等得久
await new Promise(resolve => setTimeout(resolve, 200))
expect(documentMessage).not.toHaveBeenCalled()
expect(page.posted()).toEqual([])
expect(commands.executeCommand).not.toHaveBeenCalledWith('setContext', OCCURRENCES_CONTEXT, false)
})
it('切走期间文件在别处改了:照常把新的文档发给留着的页面', async () => {
const { provider, document } = opening()
const page = tab()
provider.resolveCustomTextEditor(document as never, page.panel)
await page.send({ type: 'ready' })
page.hide()
page.clear()
document.version = 2
const edited = vi.mocked(workspace.onDidChangeTextDocument).mock.calls.at(-1)![0]
edited({ document, contentChanges: [{ rangeOffset: 0, rangeLength: 0, text: '// ' }] } as never)
await vi.waitFor(() => expect(page.posted()).toContainEqual(expect.objectContaining({ type: 'document', version: 2 })))
})
it('页面真的重新加载了(webview 进程崩了重建、开发者重载):再发 ready 时照旧整份发一遍', async () => {
const { provider, document } = opening()
const page = tab()
await provider.open(document.uri as never, { from: 'uncommitted' }, async () => provider.resolveCustomTextEditor(document as never, page.panel))
await page.send({ type: 'ready' })
await vi.waitFor(() => expect(page.posted()).toContainEqual(comparedWithHead))
page.clear()
vi.mocked(documentMessage).mockClear()
await page.send({ type: 'ready', openedFrom: 'uncommitted' })
expect(documentMessage).toHaveBeenCalled()
expect(page.posted().map(message => message.type)).toEqual(expect.arrayContaining(['openedFrom', 'filter', 'qa', 'codeColors', 'document']))
await vi.waitFor(() => {
expect(page.posted()).toContainEqual(comparedWithHead)
expect(page.posted()).toContainEqual(expect.objectContaining({ type: 'profiles', kind: 'ask' }))
expect(page.posted()).toContainEqual(expect.objectContaining({ type: 'profiles', kind: 'rewrite' }))
})
})
})
describe('代码配色跟着当前颜色主题', () => {
it('ready 后先于第一份文档发;换了主题发给 ready 了的预览,还没 ready 的等 ready 时发眼下的', async () => {
const colors = themeColors(COLORS)