Several people edit one model at once through a server you run yourself (archify-vscode-ext-sync); the files stay ordinary files on each disk. Sign-in through the browser, offline-first with later merge, teammates' cursors in the Source view and text editors, and a Shared section on the model page with people, versions, Compare and Restore. Without a server nothing changes: the sync code is a separate bundle that loads only when a model is shared, after a sign-in, or when a sync command or invite link is used.
111 lines
3.1 KiB
TypeScript
111 lines
3.1 KiB
TypeScript
import * as vscode from 'vscode';
|
|
|
|
// What the model page shows of a shared model. Lives in the main bundle; the
|
|
// sync bundle plugs in as the provider once it loads. Without it, the model
|
|
// page has no Live section and nothing else changes.
|
|
|
|
export interface LivePerson {
|
|
name: string;
|
|
/** Model-relative path of the file the person has open. */
|
|
file?: string;
|
|
me?: boolean;
|
|
}
|
|
|
|
export interface LiveVersion {
|
|
id: number;
|
|
label: string;
|
|
kind: 'snapshot' | 'publish' | 'restore';
|
|
name?: string;
|
|
/** ISO time. */
|
|
at: string;
|
|
restoredFrom?: string;
|
|
}
|
|
|
|
export interface LivePanel {
|
|
name: string;
|
|
host: string;
|
|
state: 'connecting' | 'online' | 'offline' | 'signed-out' | 'forbidden' | 'outdated';
|
|
people: LivePerson[];
|
|
versions?: LiveVersion[];
|
|
versionsProblem?: string;
|
|
}
|
|
|
|
/** Someone else's cursor in a file, as offsets into its "\n" text. */
|
|
export interface LiveCursor {
|
|
name: string;
|
|
color: string;
|
|
anchor: number;
|
|
head: number;
|
|
}
|
|
|
|
/** A selection as offsets into a file's "\n" text. */
|
|
export interface LiveSelection {
|
|
anchor: number;
|
|
head: number;
|
|
}
|
|
|
|
export type LiveAction =
|
|
| { kind: 'publish' }
|
|
| { kind: 'invite' }
|
|
| { kind: 'signIn' }
|
|
| { kind: 'versions' }
|
|
| { kind: 'compare'; version: number }
|
|
| { kind: 'restore'; version: number };
|
|
|
|
export interface LiveProvider {
|
|
panel(root: vscode.Uri): Promise<LivePanel | undefined>;
|
|
act(root: vscode.Uri, action: LiveAction): Promise<void>;
|
|
/** Others' cursors in a shared file; empty when the file is not shared. */
|
|
cursors(file: vscode.Uri): LiveCursor[];
|
|
/** This person's cursor in a page's Source view; undefined when it closes. */
|
|
setCursor(file: vscode.Uri, selection: LiveSelection | undefined): void;
|
|
}
|
|
|
|
class LiveBridge implements vscode.Disposable {
|
|
private provider: LiveProvider | undefined;
|
|
private readonly emitter = new vscode.EventEmitter<vscode.Uri | undefined>();
|
|
/** A model root whose Live section changed; undefined for all of them. */
|
|
readonly onDidChange = this.emitter.event;
|
|
private readonly cursorEmitter = new vscode.EventEmitter<void>();
|
|
/** Someone's cursor moved somewhere; ask `cursors` for the file you show. */
|
|
readonly onDidChangeCursors = this.cursorEmitter.event;
|
|
|
|
setProvider(provider: LiveProvider | undefined): void {
|
|
this.provider = provider;
|
|
this.emitter.fire(undefined);
|
|
this.cursorEmitter.fire();
|
|
}
|
|
|
|
changed(root?: vscode.Uri): void {
|
|
this.emitter.fire(root);
|
|
}
|
|
|
|
async panel(root: vscode.Uri): Promise<LivePanel | undefined> {
|
|
return this.provider?.panel(root);
|
|
}
|
|
|
|
async act(root: vscode.Uri, action: LiveAction): Promise<void> {
|
|
await this.provider?.act(root, action);
|
|
}
|
|
|
|
cursorsChanged(): void {
|
|
this.cursorEmitter.fire();
|
|
}
|
|
|
|
cursors(file: vscode.Uri): LiveCursor[] {
|
|
return this.provider?.cursors(file) ?? [];
|
|
}
|
|
|
|
setCursor(file: vscode.Uri, selection: LiveSelection | undefined): void {
|
|
this.provider?.setCursor(file, selection);
|
|
}
|
|
|
|
dispose(): void {
|
|
this.emitter.dispose();
|
|
this.cursorEmitter.dispose();
|
|
}
|
|
}
|
|
|
|
export const liveBridge = new LiveBridge();
|
|
export type { LiveBridge };
|