1#!/usr/bin/env node
2
3const HELP = `
4USAGE
5 zed-local [options] [zed args]
6
7SUMMARY
8 Runs 1-4 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 2, 3, or 4 Zed instances, with their windows tiled.
16 --top Arrange the Zed windows so they take up the top half of the screen.
17`.trim();
18
19const { spawn, execFileSync } = require("child_process");
20const assert = require("assert");
21
22const defaultUsers = require("../crates/collab/.admins.default.json");
23let users = defaultUsers;
24try {
25 const customUsers = require("../crates/collab/.admins.json");
26 assert(customUsers.length > 0);
27 assert(customUsers.every((user) => typeof user === "string"));
28 users.splice(0, 0, ...customUsers);
29} catch (_) {}
30
31const RESOLUTION_REGEX = /(\d+) x (\d+)/;
32const DIGIT_FLAG_REGEX = /^--?(\d+)$/;
33
34let instanceCount = 1;
35let isReleaseMode = false;
36let isTop = false;
37
38const args = process.argv.slice(2);
39while (args.length > 0) {
40 const arg = args[0];
41
42 const digitMatch = arg.match(DIGIT_FLAG_REGEX);
43 if (digitMatch) {
44 instanceCount = parseInt(digitMatch[1]);
45 } else if (arg === "--release") {
46 isReleaseMode = true;
47 } else if (arg === "--top") {
48 isTop = true;
49 } else if (arg === "--help") {
50 console.log(HELP);
51 process.exit(0);
52 } else {
53 break;
54 }
55
56 args.shift();
57}
58
59// Parse the resolution of the main screen
60const displayInfo = JSON.parse(
61 execFileSync("system_profiler", ["SPDisplaysDataType", "-json"], {
62 encoding: "utf8",
63 }),
64);
65const mainDisplayResolution =
66 displayInfo?.SPDisplaysDataType[0]?.spdisplays_ndrvs
67 ?.find((entry) => entry.spdisplays_main === "spdisplays_yes")
68 ?._spdisplays_resolution?.match(RESOLUTION_REGEX);
69if (!mainDisplayResolution) {
70 throw new Error("Could not parse screen resolution");
71}
72const screenWidth = parseInt(mainDisplayResolution[1]);
73let screenHeight = parseInt(mainDisplayResolution[2]);
74
75if (isTop) {
76 screenHeight = Math.floor(screenHeight / 2);
77}
78
79// Determine the window size for each instance
80let instanceWidth = screenWidth;
81let instanceHeight = screenHeight;
82if (instanceCount > 1) {
83 instanceWidth = Math.floor(screenWidth / 2);
84 if (instanceCount > 2) {
85 instanceHeight = Math.floor(screenHeight / 2);
86 }
87}
88
89// If a user is specified, make sure it's first in the list
90const user = process.env.ZED_IMPERSONATE;
91if (user) {
92 users = [user].concat(users.filter((u) => u !== user));
93}
94
95const positions = [
96 "0,0",
97 `${instanceWidth},0`,
98 `0,${instanceHeight}`,
99 `${instanceWidth},${instanceHeight}`,
100];
101
102let buildArgs = ["build"];
103let zedBinary = "target/debug/Zed";
104if (isReleaseMode) {
105 buildArgs.push("--release");
106 zedBinary = "target/release/Zed";
107}
108
109execFileSync("cargo", buildArgs, { stdio: "inherit" });
110setTimeout(() => {
111 for (let i = 0; i < instanceCount; i++) {
112 spawn(zedBinary, i == 0 ? args : [], {
113 stdio: "inherit",
114 env: {
115 ZED_IMPERSONATE: users[i],
116 ZED_WINDOW_POSITION: positions[i],
117 ZED_STATELESS: "1",
118 ZED_ALWAYS_ACTIVE: "1",
119 ZED_SERVER_URL: "http://localhost:8080",
120 ZED_ADMIN_API_TOKEN: "secret",
121 ZED_WINDOW_SIZE: `${instanceWidth},${instanceHeight}`,
122 PATH: process.env.PATH,
123 RUST_LOG: process.env.RUST_LOG || "info",
124 },
125 });
126 }
127}, 0.1);