115 lines
5.1 KiB
TypeScript
115 lines
5.1 KiB
TypeScript
import type { Disposable, TextEditor, ViewColumn } from 'vscode'
|
|
import type { OpenedFrom } from '../diff/diffBase'
|
|
import type { PreviewEditorProvider } from './previewEditor'
|
|
import { commands, Range, TabInputCustom, TabInputText, TextEditorRevealType, Uri, window } from 'vscode'
|
|
import { PREVIEW_VIEW_TYPE } from './previewEditor'
|
|
|
|
/** VS Code 自带普通文本编辑器的 editor id。 */
|
|
const TEXT_EDITOR = 'default'
|
|
/**
|
|
* VS Code 自带的「重新打开编辑器的方式」:在原标签页位置换成另一种编辑器,新编辑器打开后才返回。
|
|
* 原文件有未保存改动也不弹保存框——改动在 TextDocument 里,不跟着标签页走。
|
|
*/
|
|
const REOPEN_WITH = 'reopenActiveEditorWith'
|
|
/** 切换命令返回时文本编辑器还没同步到扩展这边的话,最多再等这么久 */
|
|
const EDITOR_WAIT_MS = 1000
|
|
|
|
function sameUri(a: Uri, b: Uri): boolean {
|
|
return a.toString() === b.toString()
|
|
}
|
|
|
|
/** 标题栏按钮传来的是 (文件 Uri, { groupId }),命令面板里调用时没有参数。 */
|
|
function resourceArg(args: unknown[]): Uri | undefined {
|
|
return args[0] instanceof Uri ? args[0] : undefined
|
|
}
|
|
|
|
function activeCustomEditorUri(): Uri | undefined {
|
|
const input = window.tabGroups.activeTabGroup.activeTab?.input
|
|
return input instanceof TabInputCustom ? input.uri : undefined
|
|
}
|
|
|
|
function textEditorOf(uri: Uri): TextEditor | undefined {
|
|
const active = window.activeTextEditor
|
|
if (active && sameUri(active.document.uri, uri))
|
|
return active
|
|
return window.visibleTextEditors.find(editor => sameUri(editor.document.uri, uri))
|
|
}
|
|
|
|
/** 编辑器变化和命令结果是分开通知到扩展这边的:没看到 uri 的文本编辑器就等它出现。 */
|
|
function waitForTextEditor(uri: Uri): Promise<TextEditor | undefined> {
|
|
const found = textEditorOf(uri)
|
|
if (found)
|
|
return Promise.resolve(found)
|
|
const disposables: Disposable[] = []
|
|
return new Promise<TextEditor | undefined>((resolve) => {
|
|
disposables.push(window.onDidChangeVisibleTextEditors(() => {
|
|
const editor = textEditorOf(uri)
|
|
if (editor)
|
|
resolve(editor)
|
|
}))
|
|
const timer = setTimeout(resolve, EDITOR_WAIT_MS, undefined)
|
|
disposables.push({ dispose: () => clearTimeout(timer) })
|
|
}).finally(() => disposables.forEach(d => d.dispose()))
|
|
}
|
|
|
|
/** 一个编辑器组里的一个文件:同一个标签页在预览和文本编辑器之间换着看时按它认。 */
|
|
function tabKey(viewColumn: ViewColumn | undefined, uri: Uri): string {
|
|
return `${viewColumn ?? ''}:${uri.toString()}`
|
|
}
|
|
|
|
/**
|
|
* 编辑器右上角的「预览 / 编辑」:同一个标签页原位换成另一种编辑器,换回预览时照切走前的样子对比,不算从别处重新打开。
|
|
* 切走的预览是从哪儿打开的按编辑器组和文件记着,换上的那个文本编辑器标签页关了就忘掉。
|
|
*/
|
|
export class EditorSwitch implements Disposable {
|
|
/** 切到文本编辑器的预览原来是从哪儿打开的,按 [tabKey] 记 */
|
|
private readonly sourceFrom = new Map<string, OpenedFrom>()
|
|
private readonly closing: Disposable
|
|
|
|
constructor(private readonly previews: Pick<PreviewEditorProvider, 'open' | 'visibleLine' | 'openedFrom'>) {
|
|
this.closing = window.tabGroups.onDidChangeTabs(({ closed }) => {
|
|
for (const tab of closed) {
|
|
if (tab.input instanceof TabInputText)
|
|
this.sourceFrom.delete(tabKey(tab.group.viewColumn, tab.input.uri))
|
|
}
|
|
})
|
|
}
|
|
|
|
/** 文本编辑器 → 预览:新预览先滚到文本编辑器顶部那一行所在的块;这个文本编辑器是从预览切过来的,照原来的从哪儿打开。 */
|
|
async showPreview(args: unknown[]): Promise<void> {
|
|
const uri = resourceArg(args) ?? window.activeTextEditor?.document.uri
|
|
const reopen = (): Thenable<unknown> => commands.executeCommand(REOPEN_WITH, PREVIEW_VIEW_TYPE, ...args)
|
|
if (!uri) {
|
|
await reopen()
|
|
return
|
|
}
|
|
const editor = textEditorOf(uri)
|
|
const topLine = editor?.visibleRanges[0]?.start.line
|
|
const from = editor && this.sourceFrom.get(tabKey(editor.viewColumn, uri))
|
|
await this.previews.open(uri, { ...(from && { from }), ...(topLine !== undefined && { reveal: { line: topLine } }) }, reopen)
|
|
}
|
|
|
|
/** 预览 → 文本编辑器:把预览视口顶部那块的起始行放到文本编辑器顶部,记下这个预览是从哪儿打开的。 */
|
|
async showSource(args: unknown[]): Promise<void> {
|
|
const uri = resourceArg(args) ?? activeCustomEditorUri()
|
|
const line = uri ? this.previews.visibleLine(uri) : undefined
|
|
const from = uri ? this.previews.openedFrom(uri) : undefined
|
|
await commands.executeCommand(REOPEN_WITH, TEXT_EDITOR, ...args)
|
|
if (!uri || (line === undefined && from === undefined))
|
|
return
|
|
const editor = await waitForTextEditor(uri)
|
|
if (!editor)
|
|
return
|
|
if (from)
|
|
this.sourceFrom.set(tabKey(editor.viewColumn, uri), from)
|
|
if (line === undefined)
|
|
return
|
|
const target = Math.min(line, editor.document.lineCount - 1)
|
|
editor.revealRange(new Range(target, 0, target, 0), TextEditorRevealType.AtTop)
|
|
}
|
|
|
|
dispose(): void {
|
|
this.closing.dispose()
|
|
}
|
|
}
|