Files
dsh-plugin-coop-with-vs/vscode-extension/src/extension.ts
T

92 lines
3.0 KiB
TypeScript

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<void> {
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.
}