⚡ perf(vscode): 预览切走标签页时页面留着,切回来不再重新生成、重发文档和整份重画
This commit is contained in:
1 parent
53283808a0
commit
5426c50cd9
8 files changed
+112
-53
No files matched your search
@@ -2,7 +2,7 @@
|
|||||||
* 文档发出去之后,在后台数每个能看调用树的块(函数、类、接口卡片)落在几棵调用树里,问到的都记下(没人调用的也记),数完把棵数发给页面。
|
* 文档发出去之后,在后台数每个能看调用树的块(函数、类、接口卡片)落在几棵调用树里,问到的都记下(没人调用的也记),数完把棵数发给页面。
|
||||||
* 页面点开某一块时,问到过有调用者就把树发回去;没问过、或上回没人调用就当场问这一块,问到了补发棵数再发树,没问到发一棵空的;
|
* 页面点开某一块时,问到过有调用者就把树发回去;没问过、或上回没人调用就当场问这一块,问到了补发棵数再发树,没问到发一棵空的;
|
||||||
* 接口在契约文件里没对上时,发出去的树带着 contractMiss,页面照它说明,不当成没人调用。
|
* 接口在契约文件里没对上时,发出去的树带着 contractMiss,页面照它说明,不当成没人调用。
|
||||||
* 问到的结果跟着文档版本走:同一版文档又发一遍(标签页切回来页面重新加载、按新规则重发)时接着用,已知的棵数马上补发,
|
* 问到的结果跟着文档版本走:同一版文档又发一遍(页面重新加载、按新规则重发)时接着用,已知的棵数马上补发,
|
||||||
* 只问还没问过(或问法变了)的块;换了一版、预览关掉才丢掉。问的时候文档版本变了,结果也丢掉。
|
* 只问还没问过(或问法变了)的块;换了一版、预览关掉才丢掉。问的时候文档版本变了,结果也丢掉。
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export const OCCURRENCES_CONTEXT = 'superpowers.codeReader.hasOccurrences'
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 上下文键跟着各预览报来的状态走:有一个预览能跳就是真(同一时刻只有一个页面有焦点),变了才设。
|
* 上下文键跟着各预览报来的状态走:有一个预览能跳就是真(同一时刻只有一个页面有焦点),变了才设。
|
||||||
* 预览关掉、页面被销毁(标签页切走)时当它不能跳。
|
* 预览关掉时当它不能跳;标签页切走时页面失了焦点,自己会报不能跳。
|
||||||
*/
|
*/
|
||||||
export class NavigationContext<Owner> {
|
export class NavigationContext<Owner> {
|
||||||
private readonly available = new Set<Owner>()
|
private readonly available = new Set<Owner>()
|
||||||
|
|||||||
@@ -6,16 +6,17 @@ import type { SymbolNode } from '../symbolLinks'
|
|||||||
import type { CodeColors } from '../theme/codeColors'
|
import type { CodeColors } from '../theme/codeColors'
|
||||||
import type { ExtensionToWebview, WebviewToExtension } from './messages'
|
import type { ExtensionToWebview, WebviewToExtension } from './messages'
|
||||||
import { describe, expect, it, vi } from 'vitest'
|
import { describe, expect, it, vi } from 'vitest'
|
||||||
import { window, workspace } from 'vscode'
|
import { commands, window, workspace } from 'vscode'
|
||||||
import { documentMessage } from './documentMessage'
|
import { documentMessage } from './documentMessage'
|
||||||
import { documentSymbolNodes } from './documentSymbols'
|
import { documentSymbolNodes } from './documentSymbols'
|
||||||
import { PreviewEditorProvider } from './previewEditor'
|
import { OCCURRENCES_CONTEXT } from './occurrenceNavigation'
|
||||||
|
import { PREVIEW_VIEW_TYPE, PreviewEditorProvider } from './previewEditor'
|
||||||
|
|
||||||
vi.mock('vscode', () => ({
|
vi.mock('vscode', () => ({
|
||||||
commands: { executeCommand: vi.fn() },
|
commands: { executeCommand: vi.fn() },
|
||||||
window: { showErrorMessage: vi.fn() },
|
window: { showErrorMessage: vi.fn(), registerCustomEditorProvider: vi.fn() },
|
||||||
workspace: {
|
workspace: {
|
||||||
onDidChangeTextDocument: () => ({ dispose: () => {} }),
|
onDidChangeTextDocument: vi.fn(() => ({ dispose: () => {} })),
|
||||||
onDidChangeConfiguration: () => ({ dispose: () => {} }),
|
onDidChangeConfiguration: () => ({ dispose: () => {} }),
|
||||||
getConfiguration: () => ({ get: (_key: string, fallback: unknown) => fallback }),
|
getConfiguration: () => ({ get: (_key: string, fallback: unknown) => fallback }),
|
||||||
openTextDocument: vi.fn(),
|
openTextDocument: vi.fn(),
|
||||||
@@ -214,10 +215,10 @@ function opening(profiles = { ask: profileSet(''), rewrite: profileSet('') }, co
|
|||||||
return { provider, document, diffBase }
|
return { provider, document, diffBase }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 一个标签页:收发消息、切走(页面被销毁,切回来重新加载、再发 ready) */
|
/** 一个标签页:收发消息;切走、切回来时页面留着,VS Code 只报一声可见性变了 */
|
||||||
function tab() {
|
function tab() {
|
||||||
let receive: ((message: WebviewToExtension) => Promise<void>) | undefined
|
let receive: ((message: WebviewToExtension) => Promise<void>) | undefined
|
||||||
let viewStateChanged: (() => void) | undefined
|
const viewStateListeners: (() => void)[] = []
|
||||||
const post = vi.fn<(message: ExtensionToWebview) => void>()
|
const post = vi.fn<(message: ExtensionToWebview) => void>()
|
||||||
const panel = {
|
const panel = {
|
||||||
active: true,
|
active: true,
|
||||||
@@ -231,20 +232,22 @@ function tab() {
|
|||||||
return { dispose: () => {} }
|
return { dispose: () => {} }
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
onDidChangeViewState: (listener: typeof viewStateChanged) => {
|
onDidChangeViewState: (listener: () => void) => {
|
||||||
viewStateChanged = listener
|
viewStateListeners.push(listener)
|
||||||
return { dispose: () => {} }
|
return { dispose: () => {} }
|
||||||
},
|
},
|
||||||
onDidDispose: () => ({ dispose: () => {} }),
|
onDidDispose: () => ({ dispose: () => {} }),
|
||||||
}
|
}
|
||||||
|
const setVisible = (visible: boolean): void => {
|
||||||
|
panel.visible = visible
|
||||||
|
panel.active = visible
|
||||||
|
viewStateListeners.forEach(listener => listener())
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
panel: panel as unknown as Parameters<PreviewEditorProvider['resolveCustomTextEditor']>[1],
|
panel: panel as unknown as Parameters<PreviewEditorProvider['resolveCustomTextEditor']>[1],
|
||||||
send: (message: WebviewToExtension) => receive!(message),
|
send: (message: WebviewToExtension) => receive!(message),
|
||||||
hide: () => {
|
hide: () => setVisible(false),
|
||||||
panel.visible = false
|
show: () => setVisible(true),
|
||||||
viewStateChanged!()
|
|
||||||
panel.visible = true
|
|
||||||
},
|
|
||||||
posted: () => post.mock.calls.map(([message]) => message),
|
posted: () => post.mock.calls.map(([message]) => message),
|
||||||
clear: () => post.mockClear(),
|
clear: () => post.mockClear(),
|
||||||
}
|
}
|
||||||
@@ -284,7 +287,7 @@ describe('预览对比什么看这个标签页是从哪儿打开的', () => {
|
|||||||
expect(diffBase).not.toHaveBeenCalledWith(document.uri, 'uncommitted')
|
expect(diffBase).not.toHaveBeenCalledWith(document.uri, 'uncommitted')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('开着的预览再从主栏点开就不比,再从「未提交的更改」点开又比;只切标签页不变', async () => {
|
it('开着的预览再从主栏点开就不比,再从「未提交的更改」点开又比;页面重新加载后按它记着的照旧比', async () => {
|
||||||
const { provider, document } = opening()
|
const { provider, document } = opening()
|
||||||
const page = tab()
|
const page = tab()
|
||||||
await provider.open(document.uri as never, { from: 'uncommitted' }, async () => provider.resolveCustomTextEditor(document as never, page.panel))
|
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' })
|
expect(page.posted()).toContainEqual({ type: 'openedFrom', from: 'uncommitted' })
|
||||||
await vi.waitFor(() => expect(page.posted()).toContainEqual(comparedWithHead))
|
await vi.waitFor(() => expect(page.posted()).toContainEqual(comparedWithHead))
|
||||||
|
|
||||||
page.hide()
|
|
||||||
page.clear()
|
page.clear()
|
||||||
await page.send({ type: 'ready', openedFrom: 'uncommitted' })
|
await page.send({ type: 'ready', openedFrom: 'uncommitted' })
|
||||||
expect(page.posted()).toContainEqual({ type: 'openedFrom', from: '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('代码配色跟着当前颜色主题', () => {
|
describe('代码配色跟着当前颜色主题', () => {
|
||||||
it('ready 后先于第一份文档发;换了主题发给 ready 了的预览,还没 ready 的等 ready 时发眼下的', async () => {
|
it('ready 后先于第一份文档发;换了主题发给 ready 了的预览,还没 ready 的等 ready 时发眼下的', async () => {
|
||||||
const colors = themeColors(COLORS)
|
const colors = themeColors(COLORS)
|
||||||
|
|||||||
@@ -103,6 +103,14 @@ export class PreviewEditorProvider implements CustomTextEditorProvider {
|
|||||||
readonly askFeature: (subject: Uri, request: TopRequest) => Promise<string | undefined>,
|
readonly askFeature: (subject: Uri, request: TopRequest) => Promise<string | undefined>,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册成预览编辑器。标签页切走时页面留着(`retainContextWhenHidden`):切回来不用重新生成、重发文档,页面也不用整份重画,
|
||||||
|
* 代价是每个开着的预览切走后仍占着它那个页面的内存。
|
||||||
|
*/
|
||||||
|
register(): Disposable {
|
||||||
|
return window.registerCustomEditorProvider(PREVIEW_VIEW_TYPE, this, { webviewOptions: { retainContextWhenHidden: true } })
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用 `show` 打开 `uri` 的预览,把 `request` 交给它:新建的预览解析时带走;`show` 只是切到了已经开着的预览(没有新建)时,交给切到的那个。
|
* 用 `show` 打开 `uri` 的预览,把 `request` 交给它:新建的预览解析时带走;`show` 只是切到了已经开着的预览(没有新建)时,交给切到的那个。
|
||||||
*
|
*
|
||||||
@@ -174,7 +182,10 @@ export class PreviewEditorProvider implements CustomTextEditorProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Preview {
|
class Preview {
|
||||||
/** webview 发过 ready 之后才发消息;标签页切走时页面被销毁,切回来重新加载、再发 ready,这期间也不发 */
|
/**
|
||||||
|
* 页面发过 ready(装好了、能收消息)之后才发消息。标签页切走时页面留着,照常收消息,切回来什么都不用补;
|
||||||
|
* 页面真的重新加载了(webview 进程崩了重建、开发者重载 webview)会再发 ready,那时整份重发
|
||||||
|
*/
|
||||||
private ready = false
|
private ready = false
|
||||||
/** 侧栏「问 AI」打开的:页面准备好后把光标放进顶部输入框 */
|
/** 侧栏「问 AI」打开的:页面准备好后把光标放进顶部输入框 */
|
||||||
private pendingPathFocus = false
|
private pendingPathFocus = false
|
||||||
@@ -215,8 +226,8 @@ class Preview {
|
|||||||
/** 这份文件相比对比基准改了什么:发完文档再算、补发 */
|
/** 这份文件相比对比基准改了什么:发完文档再算、补发 */
|
||||||
private readonly diff: PreviewDiff
|
private readonly diff: PreviewDiff
|
||||||
/**
|
/**
|
||||||
* 随下一份真正发出去的文档走、只走一次:新预览第一份文档要滚到的位置,或页面正在重新加载时收不到的 reveal。
|
* 随下一份真正发出去的文档走、只走一次:新预览第一份文档要滚到的位置,或页面 ready 之前又来的 reveal。
|
||||||
* 之后页面因标签页切走重新加载,按 webview 自己记的位置恢复
|
* 之后页面真的重新加载了,按 webview 自己记的位置恢复
|
||||||
*/
|
*/
|
||||||
private pendingReveal: Reveal | undefined
|
private pendingReveal: Reveal | undefined
|
||||||
/** 这个标签页是从哪儿打开的,定对比什么;打开请求没说的(跳定义新开的、VS Code 重启后恢复的)先空着,页面 ready 时按它记着的定 */
|
/** 这个标签页是从哪儿打开的,定对比什么;打开请求没说的(跳定义新开的、VS Code 重启后恢复的)先空着,页面 ready 时按它记着的定 */
|
||||||
@@ -243,7 +254,7 @@ class Preview {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
this.names = new DocumentNames((version, names) => {
|
this.names = new DocumentNames((version, names) => {
|
||||||
if (this.ready && version === document.version)
|
if (version === document.version)
|
||||||
this.post({ type: 'displayNames', version, names })
|
this.post({ type: 'displayNames', version, names })
|
||||||
})
|
})
|
||||||
this.lookups = new DocumentLookups(() => this.schedulePush())
|
this.lookups = new DocumentLookups(() => this.schedulePush())
|
||||||
@@ -251,25 +262,15 @@ class Preview {
|
|||||||
this.diff = new PreviewDiff(
|
this.diff = new PreviewDiff(
|
||||||
() => this.from === undefined ? undefined : owner.diffBases.diffBase(document.uri, this.from),
|
() => this.from === undefined ? undefined : owner.diffBases.diffBase(document.uri, this.from),
|
||||||
source => this.readOld(source),
|
source => this.readOld(source),
|
||||||
(diff) => {
|
diff => this.post({ type: 'diff', diff }),
|
||||||
if (this.ready)
|
|
||||||
this.post({ type: 'diff', diff })
|
|
||||||
},
|
|
||||||
text => logger.add({ level: 'warn', source: 'codeReader', message: `${document.uri.fsPath}:${text}` }),
|
text => logger.add({ level: 'warn', source: 'codeReader', message: `${document.uri.fsPath}:${text}` }),
|
||||||
)
|
)
|
||||||
this.disposables = [
|
this.disposables = [
|
||||||
owner.diffBases.onDidChange(() => {
|
owner.diffBases.onDidChange(() => {
|
||||||
if (this.ready && this.sent)
|
if (this.sent)
|
||||||
void this.diff.update(this.sent)
|
void this.diff.update(this.sent)
|
||||||
}),
|
}),
|
||||||
panel.webview.onDidReceiveMessage((msg: WebviewToExtension) => this.handleMessage(msg)),
|
panel.webview.onDidReceiveMessage((msg: WebviewToExtension) => this.handleMessage(msg)),
|
||||||
panel.onDidChangeViewState(() => {
|
|
||||||
if (panel.visible)
|
|
||||||
return
|
|
||||||
// 页面被销毁了,高亮跟着没了
|
|
||||||
this.ready = false
|
|
||||||
owner.navigation.update(this, false)
|
|
||||||
}),
|
|
||||||
workspace.onDidChangeTextDocument((e) => {
|
workspace.onDidChangeTextDocument((e) => {
|
||||||
if (e.document !== document || e.contentChanges.length === 0)
|
if (e.document !== document || e.contentChanges.length === 0)
|
||||||
return
|
return
|
||||||
@@ -391,7 +392,7 @@ class Preview {
|
|||||||
const { message, displayNames, symbols, imports } = await documentMessage({ languageId, version, getText: () => source, fileName: this.document.fileName, entryNames: this.owner.entries.namesOf(this.document.uri) }, this.registry, this.runtime, (text) => {
|
const { message, displayNames, symbols, imports } = await documentMessage({ languageId, version, getText: () => source, fileName: this.document.fileName, entryNames: this.owner.entries.namesOf(this.document.uri) }, this.registry, this.runtime, (text) => {
|
||||||
logger.add({ level: 'warn', source: 'codeReader', message: `${this.document.uri.fsPath}:${text}` })
|
logger.add({ level: 'warn', source: 'codeReader', message: `${this.document.uri.fsPath}:${text}` })
|
||||||
}, session.lookup, callees.calleeKind)
|
}, session.lookup, callees.calleeKind)
|
||||||
if (request !== this.latest || !this.ready)
|
if (request !== this.latest)
|
||||||
return
|
return
|
||||||
this.definitions.remember(message.version, message.blocks)
|
this.definitions.remember(message.version, message.blocks)
|
||||||
// 解析失败的文档没有块:问 AI 找不到块,也不拿它比差异
|
// 解析失败的文档没有块:问 AI 找不到块,也不拿它比差异
|
||||||
@@ -490,7 +491,7 @@ class Preview {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 跳到高亮着的名字的下一处 / 上一处出现;页面不在就算了(页面重新加载时高亮已经没了)。 */
|
/** 跳到高亮着的名字的下一处 / 上一处出现;页面还没 ready 就算了(还没有高亮)。 */
|
||||||
moveOccurrence(step: 1 | -1): void {
|
moveOccurrence(step: 1 | -1): void {
|
||||||
if (this.ready)
|
if (this.ready)
|
||||||
this.post({ type: 'moveOccurrence', step })
|
this.post({ type: 'moveOccurrence', step })
|
||||||
@@ -512,7 +513,7 @@ class Preview {
|
|||||||
this.focusPathAsk()
|
this.focusPathAsk()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 滚到 reveal:页面在就直接发,正在重新加载就等它发 ready 后随文档带过去。 */
|
/** 滚到 reveal:页面 ready 了就直接发,还没 ready 就等它发 ready 后随文档带过去。 */
|
||||||
private reveal(reveal: Reveal): void {
|
private reveal(reveal: Reveal): void {
|
||||||
if (this.ready)
|
if (this.ready)
|
||||||
this.post({ type: 'reveal', ...reveal })
|
this.post({ type: 'reveal', ...reveal })
|
||||||
@@ -556,10 +557,9 @@ class Preview {
|
|||||||
this.post({ type: 'focusPathAsk' })
|
this.post({ type: 'focusPathAsk' })
|
||||||
}
|
}
|
||||||
|
|
||||||
/** kind 那种对话的 profile 列表每次现读(同看板),读完页面还在才发。 */
|
/** kind 那种对话的 profile 列表每次现读(同看板)。 */
|
||||||
private async postProfiles(kind: ChatKind): Promise<void> {
|
private async postProfiles(kind: ChatKind): Promise<void> {
|
||||||
const choice = await this.owner.qa.profiles[kind].choice()
|
const choice = await this.owner.qa.profiles[kind].choice()
|
||||||
if (this.ready)
|
|
||||||
this.post({ type: 'profiles', kind, ...choice })
|
this.post({ type: 'profiles', kind, ...choice })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -602,6 +602,7 @@ class Preview {
|
|||||||
private async handleMessage(msg: WebviewToExtension): Promise<void> {
|
private async handleMessage(msg: WebviewToExtension): Promise<void> {
|
||||||
switch (msg.type) {
|
switch (msg.type) {
|
||||||
case 'ready':
|
case 'ready':
|
||||||
|
// 页面刚装好(新开的、VS Code 重启后恢复的、真的重新加载了的):眼下的状态和文档整份发一遍
|
||||||
this.ready = true
|
this.ready = true
|
||||||
// 打开请求说了从哪儿打开的就按它;没说的按页面记着的(VS Code 重启后恢复的),页面也没记过的(新开的)不比
|
// 打开请求说了从哪儿打开的就按它;没说的按页面记着的(VS Code 重启后恢复的),页面也没记过的(新开的)不比
|
||||||
this.from ??= msg.openedFrom ?? 'other'
|
this.from ??= msg.openedFrom ?? 'other'
|
||||||
@@ -709,7 +710,7 @@ class Preview {
|
|||||||
if (version !== document.version)
|
if (version !== document.version)
|
||||||
return
|
return
|
||||||
const occurrences = await this.occurrences(offset)
|
const occurrences = await this.occurrences(offset)
|
||||||
if (version === document.version && this.ready)
|
if (version === document.version)
|
||||||
this.post({ type: 'occurrencesFound', id, occurrences })
|
this.post({ type: 'occurrencesFound', id, occurrences })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -719,7 +720,7 @@ class Preview {
|
|||||||
if (version !== document.version)
|
if (version !== document.version)
|
||||||
return
|
return
|
||||||
const found = (await this.definitions.locate(version, offset)).length > 0
|
const found = (await this.definitions.locate(version, offset)).length > 0
|
||||||
if (version === document.version && this.ready)
|
if (version === document.version)
|
||||||
this.post({ type: 'definitionProbed', id, found })
|
this.post({ type: 'definitionProbed', id, found })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -774,7 +775,7 @@ class Preview {
|
|||||||
const [definition] = await findDefinitions(await this.definitions.locate(version, offset))
|
const [definition] = await findDefinitions(await this.definitions.locate(version, offset))
|
||||||
const own = definition?.uri === document.uri.toString() && definition.offset === offset
|
const own = definition?.uri === document.uri.toString() && definition.offset === offset
|
||||||
const peek = definition && !own ? await this.peekAt(definition.location.uri, definition.offset) : undefined
|
const peek = definition && !own ? await this.peekAt(definition.location.uri, definition.offset) : undefined
|
||||||
if (version === document.version && this.ready)
|
if (version === document.version)
|
||||||
this.post({ type: 'definitionPeeked', id, ...(peek && { peek }) })
|
this.post({ type: 'definitionPeeked', id, ...(peek && { peek }) })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -789,7 +790,6 @@ class Preview {
|
|||||||
const link = rel === undefined ? `[${symbol}]` : `${rel}#${symbol}`
|
const link = rel === undefined ? `[${symbol}]` : `${rel}#${symbol}`
|
||||||
logger.add({ level: 'warn', source: 'codeReader', message: `没能找 ${link} 指的符号:${err instanceof Error ? err.message : String(err)}` })
|
logger.add({ level: 'warn', source: 'codeReader', message: `没能找 ${link} 指的符号:${err instanceof Error ? err.message : String(err)}` })
|
||||||
}
|
}
|
||||||
if (this.ready)
|
|
||||||
this.post({ type: 'definitionPeeked', id, ...(peek && { peek }) })
|
this.post({ type: 'definitionPeeked', id, ...(peek && { peek }) })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -869,7 +869,6 @@ class Preview {
|
|||||||
})),
|
})),
|
||||||
Promise.all(references.map(async reference => await this.resolveSymbolLink(reference, symbolsOf) ? undefined : reference)),
|
Promise.all(references.map(async reference => await this.resolveSymbolLink(reference, symbolsOf) ? undefined : reference)),
|
||||||
])
|
])
|
||||||
if (this.ready)
|
|
||||||
this.post({ type: 'repoLinksChecked', id, missing: missing.filter(rel => rel !== undefined), unresolved: unresolved.filter(reference => reference !== undefined) })
|
this.post({ type: 'repoLinksChecked', id, missing: missing.filter(rel => rel !== undefined), unresolved: unresolved.filter(reference => reference !== undefined) })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -908,7 +907,6 @@ class Preview {
|
|||||||
const src = this.panel.webview.asWebviewUri(Uri.file(entry.fsPath)).toString()
|
const src = this.panel.webview.asWebviewUri(Uri.file(entry.fsPath)).toString()
|
||||||
return { path: rel, src }
|
return { path: rel, src }
|
||||||
}))
|
}))
|
||||||
if (this.ready)
|
|
||||||
this.post({ type: 'repoImagesChecked', id, images })
|
this.post({ type: 'repoImagesChecked', id, images })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1010,12 +1008,10 @@ class Preview {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private markRepoLinkBroken(path: string | undefined, symbol?: string, block?: string): void {
|
private markRepoLinkBroken(path: string | undefined, symbol?: string, block?: string): void {
|
||||||
if (this.ready)
|
|
||||||
this.post({ type: 'repoLinkBroken', ...(path !== undefined && { path }), ...(symbol !== undefined && { symbol }), ...(block !== undefined && { block }) })
|
this.post({ type: 'repoLinkBroken', ...(path !== undefined && { path }), ...(symbol !== undefined && { symbol }), ...(block !== undefined && { block }) })
|
||||||
}
|
}
|
||||||
|
|
||||||
private markRepoLinkOpened(path: string | undefined, symbol?: string, block?: string): void {
|
private markRepoLinkOpened(path: string | undefined, symbol?: string, block?: string): void {
|
||||||
if (this.ready)
|
|
||||||
this.post({ type: 'repoLinkOpened', ...(path !== undefined && { path }), ...(symbol !== undefined && { symbol }), ...(block !== undefined && { block }) })
|
this.post({ type: 'repoLinkOpened', ...(path !== undefined && { path }), ...(symbol !== undefined && { symbol }), ...(block !== undefined && { block }) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { BlockFilter } from './editor/blockFilter'
|
|||||||
import { NEXT_OCCURRENCE, PREVIOUS_OCCURRENCE } from './editor/occurrenceNavigation'
|
import { NEXT_OCCURRENCE, PREVIOUS_OCCURRENCE } from './editor/occurrenceNavigation'
|
||||||
import { languageOf, openingFor } from './editor/openTarget'
|
import { languageOf, openingFor } from './editor/openTarget'
|
||||||
import { PATH_PREVIEW_SCHEME } from './editor/pathPreview'
|
import { PATH_PREVIEW_SCHEME } from './editor/pathPreview'
|
||||||
import { PREVIEW_VIEW_TYPE, PreviewEditorProvider } from './editor/previewEditor'
|
import { PreviewEditorProvider } from './editor/previewEditor'
|
||||||
import { EditorSwitch } from './editor/switchEditor'
|
import { EditorSwitch } from './editor/switchEditor'
|
||||||
import { WorkspaceEntries } from './entries/workspaceEntries'
|
import { WorkspaceEntries } from './entries/workspaceEntries'
|
||||||
import { registerCopyCommands } from './explorer/copyCommands'
|
import { registerCopyCommands } from './explorer/copyCommands'
|
||||||
@@ -168,7 +168,7 @@ export function registerCodeReader(context: ExtensionContext): CodeReaderApi {
|
|||||||
void commands.executeCommand('setContext', LANGUAGE_IDS_CONTEXT, registry.languageIds())
|
void commands.executeCommand('setContext', LANGUAGE_IDS_CONTEXT, registry.languageIds())
|
||||||
previews.refresh()
|
previews.refresh()
|
||||||
}),
|
}),
|
||||||
window.registerCustomEditorProvider(PREVIEW_VIEW_TYPE, previews),
|
previews.register(),
|
||||||
files,
|
files,
|
||||||
view,
|
view,
|
||||||
locator,
|
locator,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|||||||
import { App } from './App'
|
import { App } from './App'
|
||||||
import { createViewStateStore } from './viewState'
|
import { createViewStateStore } from './viewState'
|
||||||
|
|
||||||
/** 扩展端那头:页面发出去的消息、页面挂着的收消息回调,以及 webview 自己存的状态(标签页切回来页面重新加载时还在) */
|
/** 扩展端那头:页面发出去的消息、页面挂着的收消息回调,以及 webview 自己存的状态(页面重新加载时还在) */
|
||||||
const host = vi.hoisted(() => ({
|
const host = vi.hoisted(() => ({
|
||||||
posted: [] as unknown[],
|
posted: [] as unknown[],
|
||||||
listeners: new Set<(msg: unknown) => void>(),
|
listeners: new Set<(msg: unknown) => void>(),
|
||||||
@@ -99,7 +99,7 @@ afterEach(async () => {
|
|||||||
vi.unstubAllGlobals()
|
vi.unstubAllGlobals()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('标签页切走再切回来,调用树弹框照旧开着', () => {
|
describe('页面重新加载后,调用树弹框照旧开着', () => {
|
||||||
it('弹框开着、选了第二个入口:页面重新加载拿到文档后重新打开那块、要它的树,停回第二个入口;关掉后再加载就不开了', async () => {
|
it('弹框开着、选了第二个入口:页面重新加载拿到文档后重新打开那块、要它的树,停回第二个入口;关掉后再加载就不开了', async () => {
|
||||||
await load()
|
await load()
|
||||||
await deliver(documentOf(3, [block('0', 'mask_phone')]))
|
await deliver(documentOf(3, [block('0', 'mask_phone')]))
|
||||||
@@ -127,7 +127,7 @@ describe('标签页切走再切回来,调用树弹框照旧开着', () => {
|
|||||||
expect(host.posted).not.toContainEqual(expect.objectContaining({ type: 'openCallTree' }))
|
expect(host.posted).not.toContainEqual(expect.objectContaining({ type: 'openCallTree' }))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('页面重新加载前切走、文档还没到又切走一次,记着的弹框还在', async () => {
|
it('重新加载后文档还没到,页面又重新加载了一次:记着的弹框还在', async () => {
|
||||||
saved = { editing: [], folds: {}, callTree: { anchor: 'mask_phone', entry: 1 } }
|
saved = { editing: [], folds: {}, callTree: { anchor: 'mask_phone', entry: 1 } }
|
||||||
await load()
|
await load()
|
||||||
await unload()
|
await unload()
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ function record(reportedLine: { current: number | undefined }): void {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 记住并恢复预览看到的位置:
|
* 记住并恢复预览看到的位置:
|
||||||
* ① 滚动时把视口顶部那块记进 webview 状态,页面因标签页切走被重新加载后照着滚回去;
|
* ① 滚动时把视口顶部那块记进 webview 状态,页面重新加载后(VS Code 重启后恢复标签页、webview 进程崩了重建)照着滚回去;
|
||||||
* ② 同时把那块的起始行报给扩展端,切回文本编辑器时把这行放到顶部。
|
* ② 同时把那块的起始行报给扩展端,切回文本编辑器时把这行放到顶部。
|
||||||
* 第一份文档带 reveal(刚从文本编辑器切过来、跳到定义)时不恢复,交给 useReveal 滚。
|
* 第一份文档带 reveal(刚从文本编辑器切过来、跳到定义)时不恢复,交给 useReveal 滚。
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { ScrollAnchor } from './scrollPosition'
|
|||||||
import type { BlockView, OpenedFrom } from './types'
|
import type { BlockView, OpenedFrom } from './types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 标签页切走时 VS Code 会销毁 webview、切回来重新加载,靠它恢复:哪些块停在「编辑」、
|
* 页面重新加载(webview 进程崩了重建、开发者重载 webview)时靠它恢复(标签页切走时页面一直留着,用不上它):哪些块停在「编辑」、
|
||||||
* 用户把哪些块、属性行和子行折叠 / 展开了(块 id 是它在树里的位置,文档刷新后照旧对得上;属性行、子行 id 见 [propertyRowId]、[propertyItemId]),滚到了哪,
|
* 用户把哪些块、属性行和子行折叠 / 展开了(块 id 是它在树里的位置,文档刷新后照旧对得上;属性行、子行 id 见 [propertyRowId]、[propertyItemId]),滚到了哪,
|
||||||
* 以及开着哪一块的调用树弹框、选中第几个入口。
|
* 以及开着哪一块的调用树弹框、选中第几个入口。
|
||||||
* VS Code 重启后恢复标签页时也带着它,这个标签页是从哪儿打开的靠它报回扩展端。
|
* VS Code 重启后恢复标签页时也带着它,这个标签页是从哪儿打开的靠它报回扩展端。
|
||||||
|
|||||||
Reference in new issue
Block a user