✨ feat(vscode): 只有从「未提交的更改」点开的预览才对比上一次提交

This commit is contained in:
cruldra committed 2026-10-12 15:52:35 +11:00
1 parent 4ec2b9d5d6
commit 3c7513b7cd
18 files changed
+366 -143

No files matched your search

+7 -3
View File
@@ -7,6 +7,7 @@ import type { FileAttribute } from '../attributes'
import type { BlockView } from '../blockView'
import type { ContractMiss } from '../callTree/endpoint'
import type { CallTree } from '../callTree/model'
import type { OpenedFrom } from '../diff/diffBase'
import type { DocumentDiff } from '../diff/previewDiff'
import type { ShownName } from '../nameResolution'
import type { ProfileChoice } from '../qa/askProfiles'
@@ -133,8 +134,9 @@ export interface StickyScrollSettings {
* failed 带给人看的原因,stopped 是被叫停,这两种都没记、文件没改
* moveOccurrence:跳到高亮着的名字的下一处(step 1)/ 上一处(step -1)出现,由这两条命令发给活动的预览
* displayNames:version 这份文档里显示成中文名的名字,查到一步发一步(先本文件定义的,再别的文件定义的);不是页面上这个版本的不理
* diff:这份文件相比对比基准改了什么(审查工作副本里的、工作区仓库里有改动的才有,见 DiffBase):每发一份文档之后算好了补发,
* 基准变了(提交后 HEAD 动了、换了审查的工单、文件有了改动)再发;null 是不比了(退出了审查、改动提交了)
* diff:这份文件相比对比基准改了什么(审查工作副本里的、从「未提交的更改」点开的有改动的才有,见 [对比基准](vscode/src/codeReader/diff/diffBase.ts#DiffBase)):每发一份文档之后算好了补发,
* 基准变了(提交后 HEAD 动了、换了审查的工单、文件有了改动、这个标签页换了从哪儿打开)再发;null 是不比了(退出了审查、改动提交了、从别处点开了)
* openedFrom:这个标签页是从哪儿打开的,页面记进自己的状态,VS Code 重启后恢复这个标签页时随 ready 报回来;ready 后发一次,开着时又被点开再发
* repoLinksChecked:id 那次 checkRepoLinks 的回答,missing 是仓库里没有的路径,unresolved 是找不到的不带路径的引用
* repoLinkBroken:这个仓库链接打不开。没有 symbol 是文件或目录没有;有 symbol 是文件在、这个符号没有;没有 path 是不带路径的引用找不到
* repoLinkOpened:这个仓库链接打开了。路径在;带了 symbol 时这个符号也在;没有 path 是不带路径的引用找到了
@@ -161,6 +163,7 @@ export type ExtensionToWebview
| { type: 'rewriteProgress', askId: string, question: string, text: string, activity?: string, queued?: true }
| { type: 'rewriteEnd', askId: string, status: 'done' | 'failed' | 'stopped', text?: string, error?: string, entryId?: string }
| { type: 'diff', diff: DocumentDiff | null }
| { type: 'openedFrom', from: OpenedFrom }
| { type: 'repoLinksChecked', id: number, missing: string[], unresolved: string[] }
| { type: 'repoLinkBroken', path?: string, symbol?: string }
| { type: 'repoLinkOpened', path?: string, symbol?: string }
@@ -170,6 +173,7 @@ export type ExtensionToWebview
| { type: 'callTree', version: number, anchor: string, name: string, where: string, trees: CallTree[], contractMiss?: ContractMiss }
/**
* ready:页面准备好了,可以收消息;openedFrom 是页面记着的这个标签页是从哪儿打开的,新开的页面没记过就没有
* edit:把 startLine..endLine 这几行整行替换成 text(扩展端给每行补回原来的缩进)
* visibleLine:预览视口顶部那块的起始行,切回文本编辑器时把这行放到顶部
* probeDefinition:按住 ⌘ / Ctrl 指到带下标的名字上,问源码 offset 处有没有定义;id 标出是哪一次问,回答带回去;
@@ -200,7 +204,7 @@ export type ExtensionToWebview
* goToCallNode:点了调用树上的一个节点,打开 uri 那个文件,跳到 line 行 character 列(都从 0 起,见 CallTreeNode.jump)
*/
export type WebviewToExtension
= | { type: 'ready' }
= | { type: 'ready', openedFrom?: OpenedFrom }
| { type: 'setFilter', excluded: Excluded }
| { type: 'setOnlyChanges', on: boolean }
| { type: 'edit', startLine: number, endLine: number, text: string }
@@ -1,3 +1,4 @@
import type { DiffBase, OpenedFrom } from '../diff/diffBase'
import type { RewriteRequest } from '../qa/codeQa'
import type { ExtensionToWebview, WebviewToExtension } from './messages'
import { describe, expect, it, vi } from 'vitest'
@@ -10,6 +11,7 @@ vi.mock('vscode', () => ({
workspace: {
onDidChangeTextDocument: () => ({ dispose: () => {} }),
onDidChangeConfiguration: () => ({ dispose: () => {} }),
getConfiguration: () => ({ get: (_key: string, fallback: unknown) => fallback }),
openTextDocument: vi.fn(),
applyEdit: vi.fn(async () => true),
},
@@ -28,6 +30,15 @@ vi.mock('../../webview/html', () => ({ webviewHtml: () => '', webviewUiRoot: ()
vi.mock('../folderStore', () => ({ locate: () => ({ folder: { uri: { fsPath: '/workspace' } }, key: 'web/a.ts' }) }))
vi.mock('./pathPreview', () => ({ PATH_PREVIEW_SCHEME: 'code-reader-path', topSubjectOf: (uri: unknown) => ({ uri, kind: 'file' }) }))
vi.mock('./occurrences', () => ({ occurrencesOf: vi.fn() }))
/** 排视图数据不真的解析:一份没有块的文档 */
vi.mock('./documentMessage', () => ({
documentMessage: vi.fn(async (input: { languageId: string, version: number }) => ({
message: { type: 'document', version: input.version, languageId: input.languageId, blocks: [], attributes: [], translators: [], translatorId: '', names: [] },
displayNames: [],
symbols: [],
imports: [],
})),
}))
const SOURCE = ['// 旧说明', 'export function load() {}']
@@ -125,3 +136,136 @@ describe('预览里重写注释', () => {
}
})
})
const HEAD_BASE: DiffBase = { label: '上一次提交', key: 'head:web/a.ts', read: async () => '// 改之前', missing: '' }
/** 对比基准只认从「未提交的更改」点开的:从那儿点开的对比上一次提交,别处打开的不比 */
function opening() {
const subscribe = () => ({ dispose: () => {} })
const diffBase = vi.fn((_uri: unknown, from: OpenedFrom): DiffBase | undefined => from === 'uncommitted' ? HEAD_BASE : undefined)
const qa = {
imageRoot: () => undefined,
onDidChange: subscribe,
onDidAnswer: subscribe,
onDidRewrite: subscribe,
profiles: { subscribe, choice: async () => ({ profiles: [], selected: '' }) },
view: () => ({ type: 'qa' }),
}
const provider = new PreviewEditorProvider(...[
{},
{},
{},
{ subscribe, current: { excluded: {}, onlyChanges: false } },
async () => [],
qa,
async () => [],
async () => [],
{ diffBase, onDidChange: subscribe },
{ namesOf: () => [], onDidChange: subscribe },
async () => false,
] as unknown as ConstructorParameters<typeof PreviewEditorProvider>)
const uri = { fsPath: '/workspace/web/a.ts', scheme: 'file', toString: () => 'file:///workspace/web/a.ts' }
const document = { uri, version: 1, languageId: 'typescript', fileName: '/workspace/web/a.ts', getText: () => SOURCE.join('\n') }
return { provider, document, diffBase }
}
/** 一个标签页:收发消息、切走(页面被销毁,切回来重新加载、再发 ready) */
function tab() {
let receive: ((message: WebviewToExtension) => Promise<void>) | undefined
let viewStateChanged: (() => void) | undefined
const post = vi.fn<(message: ExtensionToWebview) => void>()
const panel = {
active: true,
visible: true,
webview: {
options: {},
postMessage: post,
asWebviewUri: (file: unknown) => file,
onDidReceiveMessage: (listener: typeof receive) => {
receive = listener
return { dispose: () => {} }
},
},
onDidChangeViewState: (listener: typeof viewStateChanged) => {
viewStateChanged = listener
return { dispose: () => {} }
},
onDidDispose: () => ({ dispose: () => {} }),
}
return {
panel: panel as unknown as Parameters<PreviewEditorProvider['resolveCustomTextEditor']>[1],
send: (message: WebviewToExtension) => receive!(message),
hide: () => {
panel.visible = false
viewStateChanged!()
panel.visible = true
},
posted: () => post.mock.calls.map(([message]) => message),
clear: () => post.mockClear(),
}
}
const comparedWithHead = expect.objectContaining({ type: 'diff', diff: expect.objectContaining({ base: '上一次提交' }) })
describe('预览对比什么看这个标签页是从哪儿打开的', () => {
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' })
expect(page.posted()).toContainEqual({ type: 'openedFrom', from: 'uncommitted' })
await vi.waitFor(() => expect(page.posted()).toContainEqual(comparedWithHead))
})
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))
page.clear()
await provider.open(document.uri as never, { from: 'other' }, async () => {})
expect(page.posted()).toContainEqual({ type: 'openedFrom', from: 'other' })
await vi.waitFor(() => expect(page.posted()).toContainEqual({ type: 'diff', diff: null }))
page.clear()
await provider.open(document.uri as never, { from: 'uncommitted' }, async () => {})
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' })
await vi.waitFor(() => expect(page.posted()).toContainEqual(comparedWithHead))
})
it('没带打开请求的(VS Code 重启后恢复的)按页面记着的来,页面没记过的不比', async () => {
const { provider, document, diffBase } = opening()
const restored = tab()
provider.resolveCustomTextEditor(document as never, restored.panel)
await restored.send({ type: 'ready', openedFrom: 'uncommitted' })
await vi.waitFor(() => expect(restored.posted()).toContainEqual(comparedWithHead))
diffBase.mockClear()
const fresh = tab()
provider.resolveCustomTextEditor(document as never, fresh.panel)
await fresh.send({ type: 'ready' })
expect(fresh.posted()).toContainEqual({ type: 'openedFrom', from: 'other' })
expect(diffBase).toHaveBeenCalledWith(document.uri, 'other')
expect(diffBase).not.toHaveBeenCalledWith(document.uri, 'uncommitted')
})
it('打开请求说的从哪儿打开比页面记着的优先', async () => {
const { provider, document, diffBase } = opening()
const page = tab()
await provider.open(document.uri as never, { from: 'other' }, async () => provider.resolveCustomTextEditor(document as never, page.panel))
await page.send({ type: 'ready', openedFrom: 'uncommitted' })
expect(page.posted()).toContainEqual({ type: 'openedFrom', from: 'other' })
expect(diffBase).toHaveBeenCalledWith(document.uri, 'other')
expect(diffBase).not.toHaveBeenCalledWith(document.uri, 'uncommitted')
})
})
+67 -57
View File
@@ -3,7 +3,7 @@ import type { ClaudeImage } from '../../cc/spawnClaude'
import type { IndexFind } from '../callTree/endpoint'
import type { CommentEdit, CommentJob, WriteResult } from '../commentRewrite'
import type { BlockKind, LocalSymbol, RuleLocation, RuleReference } from '../contract'
import type { DiffBaseSource } from '../diff/diffBase'
import type { OpenedFrom, PreviewDiffBases } from '../diff/diffBase'
import type { OldDocument, SentDocument } from '../diff/previewDiff'
import type { WorkspaceEntries } from '../entries/workspaceEntries'
import type { DefinitionStart, IndexedName, NameSources } from '../nameResolution'
@@ -51,16 +51,24 @@ export const PREVIEW_VIEW_TYPE = 'superpowers.codeReader.preview'
/** 连续输入时合并成一次重新解析。 */
const REFRESH_DELAY_MS = 150
/**
* 一次打开预览带的话:这个标签页是从哪儿打开的(定对比什么);要滚到哪(从文本编辑器切过来时它顶部那一行,或跳到的定义);
* 要不要把光标放进顶部「问 AI」输入框(侧栏「问 AI」打开的)。
*/
export interface OpenRequest {
from: OpenedFrom
reveal?: Reveal
focusPath?: boolean
}
/**
* 「代码阅读预览」自定义编辑器。预览和普通编辑器共用同一个 TextDocument:
* 块内修改走 WorkspaceEdit,未保存标记、⌘S、⌘Z 都是 VS Code 原生行为。
*/
export class PreviewEditorProvider implements CustomTextEditorProvider {
private readonly previews = new Set<Preview>()
/** 侧栏「问 AI」打开的预览,解析时把光标放进顶部输入框;已经开着的由 openAsking 自己聚焦 */
private readonly pathFocus = new Set<string>()
/** 按文档 uri:等着交给马上要打开的新预览的位置(从文本编辑器切过来时它顶部那一行,或跳到的定义) */
private readonly reveals = new Map<string, Reveal>()
/** 按文档 uri:等着交给马上要打开的新预览的打开请求(见 [open]) */
private readonly requests = new Map<string, OpenRequest>()
/** 哪个预览眼下能用键盘在高亮的各处出现之间跳,跟着它设快捷键 when 子句用的上下文键 */
readonly navigation = new NavigationContext<Preview>(value => void commands.executeCommand('setContext', OCCURRENCES_CONTEXT, value))
@@ -77,8 +85,8 @@ export class PreviewEditorProvider implements CustomTextEditorProvider {
readonly indexedNames: (languageId: string, name: string) => Promise<IndexedName[]>,
/** 工作区索引里键是 key 的各项(规则查名字的定义时用,见 ruleLookups.ts;接口的调用树查契约和前端请求时用) */
readonly indexed: IndexFind,
/** 预览标差异时对比什么:按文件所在的位置定,基准变了发 onDidChange(见 locatedDiffBases) */
readonly diffBases: DiffBaseSource,
/** 预览标差异时对比什么:看文件在哪、这个标签页是从哪儿打开的,基准变了发 onDidChange */
readonly diffBases: PreviewDiffBases,
/** 哪些文件是项目写明的入口、预览里哪些名字的块标成入口 */
readonly entries: Pick<WorkspaceEntries, 'namesOf' | 'onDidChange'>,
/** 在代码阅读侧栏的树上定位一个目录(仓库链接指到目录时);树上没有它时返回 false */
@@ -87,22 +95,26 @@ export class PreviewEditorProvider implements CustomTextEditorProvider {
readonly askFeature: (subject: Uri, request: TopRequest) => Promise<string | undefined>,
) {}
/** 侧栏「问 AI」打开预览,并把光标放进顶部输入框。已经开着的就揭到前面再聚焦。 */
async openAsking(uri: Uri): Promise<void> {
/**
* 用 `show` 打开 `uri` 的预览,把 `request` 交给它:新建的预览解析时带走;`show` 只是切到了已经开着的预览(没有新建)时,交给切到的那个。
*
* `show` 默认按「预览标签」打开,同 `vscode.open`(用户关了 `workbench.editor.enablePreview` 时 VS Code 自会固定)。
*/
async open(uri: Uri, request: OpenRequest, show: () => Thenable<unknown> = () => commands.executeCommand('vscode.openWith', uri, PREVIEW_VIEW_TYPE, { preview: true })): Promise<void> {
const key = uri.toString()
this.pathFocus.add(key)
this.requests.set(key, request)
let claimed: boolean
try {
await commands.executeCommand('vscode.openWith', uri, PREVIEW_VIEW_TYPE, { preview: true })
await show()
claimed = this.requests.get(key) !== request
}
finally {
// 新开的预览在解析时已经拿走标记;还在的是本来就开着的那页,这里聚焦
if (this.pathFocus.delete(key)) {
for (const preview of this.previews) {
if (preview.uri.toString() === key)
preview.focusPathAsk()
}
}
// 打开命令等新预览解析完才返回;没打开成时也不能把这次请求留给以后别处的打开
if (this.requests.get(key) === request)
this.requests.delete(key)
}
if (!claimed)
[...this.previews].find(p => p.uri.toString() === key && p.active)?.reopen(request)
}
resolveCustomTextEditor(document: TextDocument, panel: WebviewPanel): void {
@@ -111,9 +123,9 @@ export class PreviewEditorProvider implements CustomTextEditorProvider {
panel.webview.options = { enableScripts: true, localResourceRoots: [webviewUiRoot(this.extensionUri), ...(imageRoot ? [imageRoot] : [])] }
panel.webview.html = webviewHtml(panel.webview, this.extensionUri, 'code-reader.html')
const key = document.uri.toString()
const focusPath = this.pathFocus.delete(key)
const preview = new Preview(document, panel, this, this.registry, this.runtime, this.filter, this.reveals.get(key), focusPath)
this.reveals.delete(key)
const request = this.requests.get(key)
this.requests.delete(key)
const preview = new Preview(document, panel, this, this.registry, this.runtime, this.filter, request)
this.previews.add(preview)
panel.onDidDispose(() => {
this.previews.delete(preview)
@@ -135,26 +147,6 @@ export class PreviewEditorProvider implements CustomTextEditorProvider {
}
}
/**
* 用 open 打开 uri 的预览并滚到 reveal:新建的预览随第一份文档带过去;
* open 只是切到了已经开着的预览(没有新建)时,直接发给这个预览,不重新打开。
*/
async openAt(uri: Uri, reveal: Reveal, open: () => Thenable<unknown>): Promise<void> {
const key = uri.toString()
this.reveals.set(key, reveal)
let claimed: boolean
try {
await open()
claimed = this.reveals.get(key) !== reveal
}
finally {
// 打开命令等新预览解析完才返回;没打开成时也不能把这一处留给以后别处的打开
this.reveals.delete(key)
}
if (!claimed)
[...this.previews].find(p => p.uri.toString() === key && p.active)?.reveal(reveal)
}
/** uri 的预览视口顶部那块的起始行;几个编辑器组里都开着这个文件的预览时取活动的那个。 */
visibleLine(uri: Uri): number | undefined {
const key = uri.toString()
@@ -204,6 +196,13 @@ class Preview {
private readonly callees: DocumentCallees
/** 这份文件相比对比基准改了什么:发完文档再算、补发 */
private readonly diff: PreviewDiff
/**
* 随下一份真正发出去的文档走、只走一次:新预览第一份文档要滚到的位置,或页面正在重新加载时收不到的 reveal。
* 之后页面因标签页切走重新加载,按 webview 自己记的位置恢复
*/
private pendingReveal: Reveal | undefined
/** 这个标签页是从哪儿打开的,定对比什么;没带打开请求的(VS Code 重启后恢复的)先空着,页面 ready 时按它记着的定 */
private from: OpenedFrom | undefined
constructor(
private readonly document: TextDocument,
@@ -212,14 +211,12 @@ class Preview {
private readonly registry: CodeReaderRegistry,
private readonly runtime: TreeSitterRuntime,
private readonly filter: BlockFilter,
/**
* 随下一份真正发出去的文档走、只走一次:新预览第一份文档要滚到的位置,或页面正在重新加载时收不到的 reveal。
* 之后页面因标签页切走重新加载,按 webview 自己记的位置恢复
*/
private pendingReveal: Reveal | undefined,
focusPath: boolean,
/** 打开它的那次请求;VS Code 重启后恢复的、VS Code 自己换编辑器打开的没有 */
request: OpenRequest | undefined,
) {
this.pendingPathFocus = focusPath
this.from = request?.from
this.pendingReveal = request?.reveal
this.pendingPathFocus = request?.focusPath ?? false
this.definitions = new DefinitionSources({
rule: async reference => ruleDefinitions(await owner.resolveReference(reference)),
language: async (offset) => {
@@ -234,7 +231,7 @@ class Preview {
this.lookups = new DocumentLookups(() => this.schedulePush())
this.callees = new DocumentCallees(() => this.schedulePush())
this.diff = new PreviewDiff(
() => owner.diffBases.diffBase(document.uri),
() => this.from === undefined ? undefined : owner.diffBases.diffBase(document.uri, this.from),
source => this.readOld(source),
(diff) => {
if (this.ready)
@@ -473,8 +470,22 @@ class Preview {
this.post({ type: 'moveOccurrence', step })
}
/** 开着时又被点开(见 [PreviewEditorProvider.open]):按这次是从哪儿打开的重新比,要滚到哪、要不要聚焦输入框照这次的。 */
reopen(request: OpenRequest): void {
this.from = request.from
if (this.ready) {
this.post({ type: 'openedFrom', from: request.from })
if (this.sent)
void this.diff.update(this.sent)
}
if (request.reveal)
this.reveal(request.reveal)
if (request.focusPath)
this.focusPathAsk()
}
/** 滚到 reveal:页面在就直接发,正在重新加载就等它发 ready 后随文档带过去。 */
reveal(reveal: Reveal): void {
private reveal(reveal: Reveal): void {
if (this.ready)
this.post({ type: 'reveal', ...reveal })
else
@@ -508,7 +519,7 @@ class Preview {
}
/** 把光标放进顶部输入框;页面还没准备好就等 ready。 */
focusPathAsk(): void {
private focusPathAsk(): void {
if (!this.ready) {
this.pendingPathFocus = true
return
@@ -564,6 +575,9 @@ class Preview {
switch (msg.type) {
case 'ready':
this.ready = true
// 打开请求说了从哪儿打开的就按它;没说的(VS Code 重启后恢复的)按页面记着的,页面也没记过的不算从「未提交的更改」点开的
this.from ??= msg.openedFrom ?? 'other'
this.post({ type: 'openedFrom', from: this.from })
// 先于文档发:第一份文档按筛选画,不会先整份露出来再藏
this.post({ type: 'filter', ...this.filter.current })
this.postHoverDelay()
@@ -780,7 +794,7 @@ class Preview {
}
/**
* 在预览所在的编辑器组打开别的文件,和普通编辑器里 ⌘ + 点击一样:语言有代码阅读的用预览打开,给了 spot 就滚过去、闪这处;
* 在预览所在的编辑器组打开别的文件,和普通编辑器里 ⌘ + 点击一样:语言有代码阅读的用预览打开(算从别处打开的,不对比上一次提交),给了 spot 就滚过去、闪这处;
* 其余用 VS Code 默认的打开方式,给了 spot 就选中它(见 openingFor)。目标是否按「预览标签」打开听 VS Code 的设置,
* workbench.editor.enablePreviewFromCodeNavigation 为假时先把当前这个预览标签固定住,免得被目标替掉。
*/
@@ -793,11 +807,7 @@ class Preview {
await commands.executeCommand('vscode.open', uri, { ...options, ...(opening.selection && { selection: opening.selection }) })
return
}
const open = (): Thenable<unknown> => commands.executeCommand('vscode.openWith', uri, PREVIEW_VIEW_TYPE, options)
if (opening.reveal)
await this.owner.openAt(uri, opening.reveal, open)
else
await open()
await this.owner.open(uri, { from: 'other', ...(opening.reveal && { reveal: opening.reveal }) }, () => commands.executeCommand('vscode.openWith', uri, PREVIEW_VIEW_TYPE, options))
}
/** 点了调用树上的一个节点:打开它在的文件,滚到这一处(同 ⌘ + 点击跳到别的文件)。 */
+3 -3
View File
@@ -51,13 +51,13 @@ function waitForTextEditor(uri: Uri): Promise<TextEditor | undefined> {
}).finally(() => disposables.forEach(d => d.dispose()))
}
/** 文本编辑器 → 预览(同一标签页原位替换):新预览先滚到文本编辑器顶部那一行所在的块。 */
/** 文本编辑器 → 预览(同一标签页原位替换,算从别处打开的):新预览先滚到文本编辑器顶部那一行所在的块。 */
export async function showPreview(previews: PreviewEditorProvider, args: unknown[]): Promise<void> {
const uri = resourceArg(args) ?? window.activeTextEditor?.document.uri
const topLine = uri ? textEditorOf(uri)?.visibleRanges[0]?.start.line : undefined
const reopen = (): Thenable<unknown> => commands.executeCommand(REOPEN_WITH, PREVIEW_VIEW_TYPE, ...args)
if (uri && topLine !== undefined)
await previews.openAt(uri, { line: topLine }, reopen)
if (uri)
await previews.open(uri, { from: 'other', ...(topLine !== undefined && { reveal: { line: topLine } }) }, reopen)
else
await reopen()
}