zed-local

  1#!/usr/bin/env node
  2
  3const HELP = `
  4USAGE
  5  zed-local  [options]  [zed args]
  6
  7SUMMARY
  8  Runs 1-6 instances of Zed using a locally-running collaboration server.
  9  Each instance of Zed will be signed in as a different user specified in
 10  either \`.admins.json\` or \`.admins.default.json\`.
 11
 12OPTIONS
 13  --help           Print this help message
 14  --release        Build Zed in release mode
 15  -2, -3, -4, ...  Spawn multiple Zed instances, with their windows tiled.
 16  --top            Arrange the Zed windows so they take up the top half of the screen.
 17  --stable         Use stable Zed release installed on local machine for all instances (except for the first one).
 18`.trim();
 19
 20const { spawn, execFileSync } = require("child_process");
 21const assert = require("assert");
 22
 23let users;
 24if (process.env.SEED_PATH) {
 25  users = require(process.env.SEED_PATH).admins;
 26} else {
 27  users = require("../crates/collab/seed.default.json").admins;
 28  try {
 29    const defaultUsers = users;
 30    const customUsers = require("../crates/collab/seed.json").admins;
 31    assert(customUsers.length > 0);
 32    users = customUsers.concat(
 33      defaultUsers.filter((user) => !customUsers.includes(user)),
 34    );
 35  } catch (_) {}
 36}
 37
 38const RESOLUTION_REGEX = /(\d+) x (\d+)/;
 39const DIGIT_FLAG_REGEX = /^--?(\d+)$/;
 40
 41let instanceCount = 1;
 42let isReleaseMode = false;
 43let isTop = false;
 44let othersOnStable = false;
 45
 46const args = process.argv.slice(2);
 47while (args.length > 0) {
 48  const arg = args[0];
 49
 50  const digitMatch = arg.match(DIGIT_FLAG_REGEX);
 51  if (digitMatch) {
 52    instanceCount = parseInt(digitMatch[1]);
 53  } else if (arg === "--release") {
 54    isReleaseMode = true;
 55  } else if (arg === "--top") {
 56    isTop = true;
 57  } else if (arg === "--help") {
 58    console.log(HELP);
 59    process.exit(0);
 60  } else if (arg === "--stable") {
 61    othersOnStable = true;
 62  } else {
 63    break;
 64  }
 65
 66  args.shift();
 67}
 68
 69// Parse the resolution of the main screen
 70const displayInfo = JSON.parse(
 71  execFileSync("system_profiler", ["SPDisplaysDataType", "-json"], {
 72    encoding: "utf8",
 73  }),
 74);
 75const mainDisplayResolution =
 76  displayInfo?.SPDisplaysDataType[0]?.spdisplays_ndrvs
 77    ?.find((entry) => entry.spdisplays_main === "spdisplays_yes")
 78    ?._spdisplays_resolution?.match(RESOLUTION_REGEX);
 79if (!mainDisplayResolution) {
 80  throw new Error("Could not parse screen resolution");
 81}
 82const titleBarHeight = 24;
 83const screenWidth = parseInt(mainDisplayResolution[1]);
 84let screenHeight = parseInt(mainDisplayResolution[2]) - titleBarHeight;
 85
 86if (isTop) {
 87  screenHeight = Math.floor(screenHeight / 2);
 88}
 89
 90// Determine the window size for each instance
 91let rows;
 92let columns;
 93switch (instanceCount) {
 94  case 1:
 95    [rows, columns] = [1, 1];
 96    break;
 97  case 2:
 98    [rows, columns] = [1, 2];
 99    break;
100  case 3:
101  case 4:
102    [rows, columns] = [2, 2];
103    break;
104  case 5:
105  case 6:
106    [rows, columns] = [2, 3];
107    break;
108}
109
110const instanceWidth = Math.floor(screenWidth / columns);
111const instanceHeight = Math.floor(screenHeight / rows);
112
113// If a user is specified, make sure it's first in the list
114const user = process.env.ZED_IMPERSONATE;
115if (user) {
116  users = [user].concat(users.filter((u) => u !== user));
117}
118
119let buildArgs = ["build"];
120let zedBinary = "target/debug/Zed";
121if (isReleaseMode) {
122  buildArgs.push("--release");
123  zedBinary = "target/release/Zed";
124}
125
126try {
127  execFileSync("cargo", buildArgs, { stdio: "inherit" });
128} catch (e) {
129  process.exit(0);
130}
131
132setTimeout(() => {
133  for (let i = 0; i < instanceCount; i++) {
134    const row = Math.floor(i / columns);
135    const column = i % columns;
136    const position = [
137      column * instanceWidth,
138      row * instanceHeight + titleBarHeight,
139    ].join(",");
140    const size = [instanceWidth, instanceHeight].join(",");
141    let binaryPath = zedBinary;
142    if (i != 0 && othersOnStable) {
143      binaryPath = "/Applications/Zed.app/Contents/MacOS/zed";
144    }
145    spawn(binaryPath, i == 0 ? args : [], {
146      stdio: "inherit",
147      env: {
148        ZED_IMPERSONATE: users[i],
149        ZED_WINDOW_POSITION: position,
150        ZED_STATELESS: "1",
151        ZED_ALWAYS_ACTIVE: "1",
152        ZED_SERVER_URL: "http://localhost:3000",
153        ZED_RPC_URL: "http://localhost:8080/rpc",
154        ZED_ADMIN_API_TOKEN: "secret",
155        ZED_WINDOW_SIZE: size,
156        ZED_CLIENT_CHECKSUM_SEED: "development-checksum-seed",
157        PATH: process.env.PATH,
158        RUST_LOG: process.env.RUST_LOG || "info",
159      },
160    });
161  }
162}, 0.1);