import { basename } from 'node:path' import * as vscode from 'vscode' import { createSnapshot, type OpenDocument } from './model.js' import { SnapshotPublisher } from './publisher.js' const HEARTBEAT_MS = 15_000 const DEBOUNCE_MS = 150 function localFile(uri: vscode.Uri): string | undefined { return uri.scheme === 'file' && uri.fsPath ? uri.fsPath : undefined } function tabDocuments(tab: vscode.Tab): OpenDocument[] { const input = tab.input const paths: string[] = [] if (input instanceof vscode.TabInputText) { const path = localFile(input.uri) if (path) paths.push(path) } else if (input instanceof vscode.TabInputTextDiff) { const original = localFile(input.original) const modified = localFile(input.modified) if (original) paths.push(original) if (modified) paths.push(modified) } return paths.map((path) => ({ path, label: basename(path), dirty: tab.isDirty })) } export function collectOpenDocuments(): OpenDocument[] { return vscode.window.tabGroups.all.flatMap((group) => group.tabs.flatMap(tabDocuments)) } function workspaceLabel(): string | undefined { const workspaceFile = vscode.workspace.workspaceFile if (workspaceFile?.scheme === 'file') return workspaceFile.fsPath const folders = vscode.workspace.workspaceFolders if (folders?.length === 1 && folders[0]?.uri.scheme === 'file') return folders[0].uri.fsPath return vscode.workspace.name } export async function activate(context: vscode.ExtensionContext): Promise { const output = vscode.window.createOutputChannel('DSH Open File Bridge', { log: true }) const publisher = new SnapshotPublisher() let debounce: NodeJS.Timeout | undefined let disposed = false const publish = async () => { if (disposed) return const snapshot = createSnapshot({ instanceId: publisher.instanceId, processId: process.pid, workspace: workspaceLabel(), documents: collectOpenDocuments(), }) try { await publisher.publish(snapshot) output.debug(`Published ${snapshot.documents.length} open files`) } catch (error) { output.error(`Snapshot publish failed: ${error instanceof Error ? error.message : String(error)}`) } } const schedule = () => { if (debounce) clearTimeout(debounce) debounce = setTimeout(() => void publish(), DEBOUNCE_MS) } context.subscriptions.push( output, vscode.window.tabGroups.onDidChangeTabs(schedule), vscode.window.onDidChangeActiveTextEditor(schedule), vscode.workspace.onDidChangeWorkspaceFolders(schedule), ) const heartbeat = setInterval(() => void publish(), HEARTBEAT_MS) const onExit = () => publisher.disposeSync() process.once('exit', onExit) context.subscriptions.push({ dispose() { disposed = true if (debounce) clearTimeout(debounce) clearInterval(heartbeat) process.off('exit', onExit) void publisher.dispose() }, }) await publish() } export function deactivate(): void { // ExtensionContext disposal owns cleanup. }