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
23const users = require(
24 process.env.SEED_PATH || "../crates/collab/seed.default.json",
25).admins;
26
27const RESOLUTION_REGEX = /(\d+) x (\d+)/;
28const DIGIT_FLAG_REGEX = /^--?(\d+)$/;
29
30let instanceCount = 1;
31let isReleaseMode = false;
32let isTop = false;
33let othersOnStable = false;
34
35const args = process.argv.slice(2);
36while (args.length > 0) {
37 const arg = args[0];
38
39 const digitMatch = arg.match(DIGIT_FLAG_REGEX);
40 if (digitMatch) {
41 instanceCount = parseInt(digitMatch[1]);
42 } else if (arg === "--release") {
43 isReleaseMode = true;
44 } else if (arg === "--top") {
45 isTop = true;
46 } else if (arg === "--help") {
47 console.log(HELP);
48 process.exit(0);
49 } else if (arg === "--stable") {
50 othersOnStable = true;
51 } else {
52 break;
53 }
54
55 args.shift();
56}
57
58// Parse the resolution of the main screen
59const displayInfo = JSON.parse(
60 execFileSync("system_profiler", ["SPDisplaysDataType", "-json"], {
61 encoding: "utf8",
62 }),
63);
64const mainDisplayResolution =
65 displayInfo?.SPDisplaysDataType[0]?.spdisplays_ndrvs
66 ?.find((entry) => entry.spdisplays_main === "spdisplays_yes")
67 ?._spdisplays_resolution?.match(RESOLUTION_REGEX);
68if (!mainDisplayResolution) {
69 throw new Error("Could not parse screen resolution");
70}
71const titleBarHeight = 24;
72const screenWidth = parseInt(mainDisplayResolution[1]);
73let screenHeight = parseInt(mainDisplayResolution[2]) - titleBarHeight;
74
75if (isTop) {
76 screenHeight = Math.floor(screenHeight / 2);
77}
78
79// Determine the window size for each instance
80let rows;
81let columns;
82switch (instanceCount) {
83 case 1:
84 [rows, columns] = [1, 1];
85 break;
86 case 2:
87 [rows, columns] = [1, 2];
88 break;
89 case 3:
90 case 4:
91 [rows, columns] = [2, 2];
92 break;
93 case 5:
94 case 6:
95 [rows, columns] = [2, 3];
96 break;
97}
98
99const instanceWidth = Math.floor(screenWidth / columns);
100const instanceHeight = Math.floor(screenHeight / rows);
101
102// If a user is specified, make sure it's first in the list
103const user = process.env.ZED_IMPERSONATE;
104if (user) {
105 users = [user].concat(users.filter((u) => u !== user));
106}
107
108let buildArgs = ["build"];
109let zedBinary = "target/debug/Zed";
110if (isReleaseMode) {
111 buildArgs.push("--release");
112 zedBinary = "target/release/Zed";
113}
114
115try {
116 execFileSync("cargo", buildArgs, { stdio: "inherit" });
117} catch (e) {
118 process.exit(0);
119}
120
121setTimeout(() => {
122 for (let i = 0; i < instanceCount; i++) {
123 const row = Math.floor(i / columns);
124 const column = i % columns;
125 const position = [
126 column * instanceWidth,
127 row * instanceHeight + titleBarHeight,
128 ].join(",");
129 const size = [instanceWidth, instanceHeight].join(",");
130 let binaryPath = zedBinary;
131 if (i != 0 && othersOnStable) {
132 binaryPath = "/Applications/Zed.app/Contents/MacOS/zed";
133 }
134 spawn(binaryPath, i == 0 ? args : [], {
135 stdio: "inherit",
136 env: {
137 ZED_IMPERSONATE: users[i],
138 ZED_WINDOW_POSITION: position,
139 ZED_STATELESS: "1",
140 ZED_ALWAYS_ACTIVE: "1",
141 ZED_SERVER_URL: "http://localhost:3000",
142 ZED_RPC_URL: "http://localhost:8080/rpc",
143 ZED_ADMIN_API_TOKEN: "secret",
144 ZED_WINDOW_SIZE: size,
145 ZED_CLIENT_CHECKSUM_SEED: "development-checksum-seed",
146 PATH: process.env.PATH,
147 RUST_LOG: process.env.RUST_LOG || "info",
148 },
149 });
150 }
151}, 0.1);