128 lines · 4.8 KB
import * as vscode from "vscode";
import type { RoomClient } from "./room-client";

type Selection = { anchor: number; head: number };
type PeerState = {
  user?: { name?: string; color?: string; kind?: string };
  path?: string | null;
  selections?: Selection[];
};

const PALETTE = ["#f778ba", "#3fb950", "#d29922", "#58a6ff", "#a371f7", "#ff7b72", "#39c5cf", "#e3b341"];

function colorFor(clientId: number, preferred?: string): string {
  if (preferred && /^#[0-9a-f]{6}$/i.test(preferred)) return preferred;
  return PALETTE[clientId % PALETTE.length];
}

/** Publishes the local cursor and paints every peer's cursor, selection and name in visible editors. */
export class Cursors implements vscode.Disposable {
  private readonly decorations = new Map<
    number,
    { caret: vscode.TextEditorDecorationType; range: vscode.TextEditorDecorationType }
  >();
  private readonly disposables: vscode.Disposable[] = [];

  constructor(
    private readonly room: RoomClient,
    private readonly pathOf: (uri: vscode.Uri) => string | null,
    name: string,
  ) {
    room.awareness.setLocalStateField("user", { name, color: colorFor(room.doc.clientID) });
    room.awareness.on("change", () => this.render());
    this.disposables.push(
      vscode.window.onDidChangeTextEditorSelection((event) => this.publish(event.textEditor)),
      vscode.window.onDidChangeActiveTextEditor((editor) => (editor ? this.publish(editor) : this.clearLocal())),
      vscode.window.onDidChangeVisibleTextEditors(() => this.render()),
      vscode.workspace.onDidChangeTextDocument(() => this.render()),
    );
    if (vscode.window.activeTextEditor) this.publish(vscode.window.activeTextEditor);
  }