219 lines · 8.6 KB
import * as vscode from "vscode";
import type * as Y from "yjs";
import type { FileMeta } from "../lib/editor-protocol";
import type { ForgeApi } from "./forge";
import type { RoomClient } from "./room-client";

const encoder = new TextEncoder();
const decoder = new TextDecoder();
const READ_ONLY_KINDS = new Set(["lfs", "symlink", "binary", "too-large", "gitlink"]);

/**
 * `gitfor:` file system over the branch tree plus the live room. Directory listings come from the tree at
 * the session head, overlaid with files created or deleted in the room; file contents are the shared
 * Y.Text, so every reader sees co-editors' edits. Writes go into the room, never straight to git.
 */
/** Rewrites `text` to `next` touching only the differing middle, so remote cursors stay put. */
function replaceText(text: Y.Text, next: string): void {
  const current = text.toString();
  let start = 0;
  while (start < current.length && start < next.length && current[start] === next[start]) start++;
  let end = 0;
  while (
    end < current.length - start &&
    end < next.length - start &&
    current[current.length - 1 - end] === next[next.length - 1 - end]
  )
    end++;
  text.delete(start, current.length - start - end);
  text.insert(start, next.slice(start, next.length - end));
}

export class GitforFileSystem implements vscode.FileSystemProvider {
  private readonly emitter = new vscode.EventEmitter<vscode.FileChangeEvent[]>();
  readonly onDidChangeFile = this.emitter.event;
  private headFiles = new Set<string>();
  private headDirs = new Set<string>([""]);
  private loaded: Promise<void> | null = null;

  /** Set by the extension: paths whose open editor is already bound to the room. */
  isBound: (path: string, content: string) => boolean = () => false;