╭ try.zig ──────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
1 //! start a server with some fake data and then launch 2 //! the TUI. this provides a nice way to test things 3 //! out safely. 4 5 const std = @import("std"); 6 const builtin = @import("builtin"); 7 const hx = @import("haxy"); 8 const srv = hx.serve; 9 const evt = hx.event; 10 const xit = hx.xit; 11 const rp = xit.repo; 12 const ui = hx.ui; 13 14 // cook the terminal before a panic/segfault trace is printed, so the trace 15 // isn't mangled by raw mode and the alternate buffer 16 pub const std_options_debug_io = xit.xitui.terminal.crash_debug_io; 17 18 pub fn main(init: std.process.Init) !void { 19 var gpa: std.heap.DebugAllocator(.{}) = .init; 20 defer _ = gpa.deinit(); 21 const allocator = gpa.allocator(); 22 23 var threaded = std.Io.Threaded.init(allocator, .{ 24 .environ = init.minimal.environ, 25 }); 26 defer threaded.deinit(); 27 const io = threaded.io(); 28 29 const temp_dir_name = "temp-try"; 30 31 // create the temp dir 32 const cwd = std.Io.Dir.cwd(); 33 var temp_dir_or_err = cwd.openDir(io, temp_dir_name, .{}); 34 if (temp_dir_or_err) |*temp_dir| { 35 temp_dir.close(io); 36 try cwd.deleteTree(io, temp_dir_name); 37 } else |_| {} 38 var temp_dir = try cwd.createDirPathOpen(io, temp_dir_name, .{}); 39 defer cwd.deleteTree(io, temp_dir_name) catch {}; 40 defer temp_dir.close(io); 41 42 // write the dev SSH private key so pushes can authenticate against the 43 // matching public key seeded on the admin account below 44 { 45 const priv_key_file = try temp_dir.createFile(io, "key", .{}); 46 defer priv_key_file.close(io); 47 try priv_key_file.writeStreamingAll(io, 48 \\-----BEGIN OPENSSH PRIVATE KEY----- 49 \\b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW 50 \\QyNTUxOQAAACCniLPJiaooAWecvOCeAjoJwCSeWxzysvpTNkpYjF22JgAAAJA+7hikPu4Y 51 \\pAAAAAtzc2gtZWQyNTUxOQAAACCniLPJiaooAWecvOCeAjoJwCSeWxzysvpTNkpYjF22Jg 52 \\AAAEDVlopOMnKt/7by/IA8VZvQXUS/O6VLkixOqnnahUdPCKeIs8mJqigBZ5y84J4COgnA 53 \\JJ5bHPKy+lM2SliMXbYmAAAAC3JhZGFyQHJvYXJrAQI= 54 \\-----END OPENSSH PRIVATE KEY----- 55 \\ 56 ); 57 if (.windows != builtin.os.tag) { 58 try priv_key_file.setPermissions(io, @enumFromInt(0o600)); 59 } 60 } 61 62 const cwd_path = try std.process.currentPathAlloc(io, allocator); 63 defer allocator.free(cwd_path); 64 65 const work_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "server", "admin" }); 66 defer allocator.free(work_path); 67 68 const Repo = rp.Repo(.xit, evt.admin_repo_opts); 69 var repo = try Repo.init(io, allocator, .{ .path = work_path }); 70 defer repo.deinit(io, allocator); 71 72 try repo.addConfig(io, allocator, .{ .name = "user.name", .value = "haxy" }); 73 try repo.addConfig(io, allocator, .{ .name = "user.email", .value = "admin@haxy" }); 74 75 var session_arena = std.heap.ArenaAllocator.init(allocator); 76 defer session_arena.deinit(); 77 78 var session: ui.Session = blk: { 79 var arena = std.heap.ArenaAllocator.init(allocator); 80 defer arena.deinit(); 81 82 // define test events 83 84 var prng = std.Random.DefaultPrng.init(std.testing.random_seed); 85 86 const user_data = [_]struct { 87 name: []const u8, 88 display_name: []const u8, 89 email: []const u8, 90 }{ 91 .{ .name = "admin", .display_name = "Admin", .email = "admin@example.test" }, 92 .{ .name = "alice", .display_name = "Alice Tulley", .email = "alice@example.test" }, 93 .{ .name = "bob", .display_name = "Bob Smith", .email = "bob@example.test" }, 94 .{ .name = "carol", .display_name = "Carol Johnson", .email = "carol@example.test" }, 95 .{ .name = "dave", .display_name = "Dave Wilson", .email = "dave@example.test" }, 96 .{ .name = "eve", .display_name = "Eve Anderson", .email = "eve@example.test" }, 97 .{ .name = "frank", .display_name = "Frank Miller", .email = "frank@example.test" }, 98 .{ .name = "grace", .display_name = "Grace Lee", .email = "grace@example.test" }, 99 .{ .name = "henry", .display_name = "Henry Davis", .email = "henry@example.test" }, 100 .{ .name = "ivy", .display_name = "Ivy Martinez", .email = "ivy@example.test" }, 101 .{ .name = "jack", .display_name = "Jack Thompson", .email = "jack@example.test" }, 102 .{ .name = "kate", .display_name = "Kate Robinson", .email = "kate@example.test" }, 103 .{ .name = "liam", .display_name = "Liam Walker", .email = "liam@example.test" }, 104 .{ .name = "mona", .display_name = "Mona Patel", .email = "mona@example.test" }, 105 .{ .name = "noah", .display_name = "Noah Garcia", .email = "noah@example.test" }, 106 .{ .name = "olivia", .display_name = "Olivia Hernandez", .email = "olivia@example.test" }, 107 .{ .name = "peter", .display_name = "Peter Wright", .email = "peter@example.test" }, 108 .{ .name = "quinn", .display_name = "Quinn Foster", .email = "quinn@example.test" }, 109 .{ .name = "rachel", .display_name = "Rachel Bennett", .email = "rachel@example.test" }, 110 .{ .name = "sam", .display_name = "Sam Brooks", .email = "sam@example.test" }, 111 .{ .name = "tina", .display_name = "Tina Cooper", .email = "tina@example.test" }, 112 }; 113 114 const repo_data = [_]struct { 115 user_index: usize, 116 name: []const u8, 117 description: []const u8, 118 }{ 119 .{ .user_index = 1, .name = "ziglings", .description = "Learn the Zig programming language by fixing tiny broken programs" }, 120 .{ .user_index = 2, .name = "linux", .description = "Linux kernel source tree" }, 121 .{ .user_index = 3, .name = "kubernetes", .description = "Production-grade container orchestration" }, 122 .{ .user_index = 4, .name = "react", .description = "A declarative, efficient, and flexible JavaScript library for building user interfaces" }, 123 .{ .user_index = 5, .name = "typescript", .description = "TypeScript is a superset of JavaScript that compiles to clean JavaScript output" }, 124 .{ .user_index = 6, .name = "rust", .description = "Empowering everyone to build reliable and efficient software" }, 125 .{ .user_index = 7, .name = "go", .description = "The Go programming language" }, 126 .{ .user_index = 8, .name = "nodejs", .description = "Node.js JavaScript runtime" }, 127 .{ .user_index = 9, .name = "cpython", .description = "The Python programming language" }, 128 .{ .user_index = 10, .name = "docker", .description = "Container platform for developing, shipping, and running applications" }, 129 .{ .user_index = 1, .name = "vim", .description = "The ubiquitous text editor" }, 130 .{ .user_index = 2, .name = "neovim", .description = "Hyperextensible Vim-based text editor" }, 131 .{ .user_index = 3, .name = "emacs", .description = "GNU Emacs source code mirror" }, 132 .{ .user_index = 4, .name = "tmux", .description = "Terminal multiplexer" }, 133 .{ .user_index = 5, .name = "zsh", .description = "Mirror of the Z shell source code repository" }, 134 .{ .user_index = 6, .name = "git", .description = "Distributed version control system" }, 135 .{ .user_index = 7, .name = "mercurial", .description = "Source-control management tool" }, 136 .{ .user_index = 8, .name = "tensorflow", .description = "An end-to-end open source machine learning platform" }, 137 .{ .user_index = 9, .name = "pytorch", .description = "Tensors and dynamic neural networks in Python with strong GPU acceleration" }, 138 .{ .user_index = 10, .name = "numpy", .description = "The fundamental package for scientific computing with Python" }, 139 .{ .user_index = 1, .name = "pandas", .description = "Flexible and powerful data analysis and manipulation library for Python" }, 140 .{ .user_index = 2, .name = "scikit-learn", .description = "Machine learning in Python" }, 141 .{ .user_index = 3, .name = "nginx", .description = "High performance HTTP server and reverse proxy" }, 142 .{ .user_index = 4, .name = "redis", .description = "In-memory data structure store, used as a database, cache, and message broker" }, 143 .{ .user_index = 5, .name = "postgres", .description = "The world's most advanced open source relational database" }, 144 .{ .user_index = 6, .name = "sqlite", .description = "Self-contained, serverless, zero-configuration SQL database engine" }, 145 .{ .user_index = 7, .name = "mongodb", .description = "The MongoDB Database" }, 146 .{ .user_index = 8, .name = "elasticsearch", .description = "Free and open, distributed, RESTful search engine" }, 147 .{ .user_index = 9, .name = "kafka", .description = "Distributed event streaming platform" }, 148 .{ .user_index = 10, .name = "terraform", .description = "Infrastructure as code tool" }, 149 .{ .user_index = 11, .name = "svelte", .description = "Cybernetically enhanced web apps" }, 150 .{ .user_index = 12, .name = "vue", .description = "The progressive JavaScript framework" }, 151 .{ .user_index = 13, .name = "flask", .description = "The Python micro framework for building web applications" }, 152 .{ .user_index = 14, .name = "django", .description = "The web framework for perfectionists with deadlines" }, 153 .{ .user_index = 15, .name = "rails", .description = "Ruby on Rails web framework" }, 154 .{ .user_index = 16, .name = "phoenix", .description = "Peace of mind from prototype to production for Elixir web apps" }, 155 .{ .user_index = 17, .name = "laravel", .description = "The PHP framework for web artisans" }, 156 .{ .user_index = 18, .name = "prometheus", .description = "The Prometheus monitoring system and time series database" }, 157 .{ .user_index = 19, .name = "grafana", .description = "The open and composable observability and data visualization platform" }, 158 .{ .user_index = 20, .name = "ansible", .description = "Simple, agentless IT automation" }, 159 }; 160 161 var user_ids: [user_data.len][evt.event_id_size]u8 = undefined; 162 for (&user_ids) |*id| id.* = evt.EventWithId.randomId(prng.random()); 163 164 var repo_event_ids: [repo_data.len][evt.event_id_size]u8 = undefined; 165 for (&repo_event_ids) |*id| id.* = evt.EventWithId.randomId(prng.random()); 166 167 var password_hash_buf: [evt.User.password_hash_max_len]u8 = undefined; 168 const password_hash = try evt.User.hashPassword("password", &password_hash_buf, io); 169 170 // public key matching temp-try/key, given to admin so we can push as admin 171 const admin_ssh_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKeIs8mJqigBZ5y84J4COgnAJJ5bHPKy+lM2SliMXbYm radar@roark"; 172 173 var events_to_consume: [user_data.len + repo_data.len]evt.EventWithId = undefined; 174 for (user_data, 0..) |u, i| { 175 events_to_consume[i] = .{ 176 .id = std.fmt.bytesToHex(user_ids[i], .lower), 177 .event = .{ 178 .user = .{ 179 .name = u.name, 180 .display_name = u.display_name, 181 .email = u.email, 182 .password_hash = password_hash, 183 .ssh_keys = if (i == 0) admin_ssh_key else "", 184 }, 185 }, 186 }; 187 } 188 for (repo_data, 0..) |r, i| { 189 events_to_consume[user_data.len + i] = .{ 190 .id = std.fmt.bytesToHex(repo_event_ids[i], .lower), 191 .event = .{ 192 .repo = .{ 193 .user_id = &user_ids[r.user_index], 194 .name = r.name, 195 .description = r.description, 196 .enable_issue = true, 197 }, 198 }, 199 }; 200 } 201 202 // commit the seed events and consume them into the database 203 try evt.commitAndConsume(evt.admin_repo_opts, io, allocator, &repo, evt.events_ref, &events_to_consume); 204 205 // create the actual repos on disk, named by their hex-encoded event id 206 for (repo_data, 0..) |r, i| { 207 const repo_id = std.fmt.bytesToHex(repo_event_ids[i], .lower); 208 const repo_path = try std.fs.path.join(arena.allocator(), &.{ cwd_path, temp_dir_name, "server", "repos", &repo_id }); 209 210 var repo_i = try rp.Repo(.xit, .{}).init(io, allocator, .{ .path = repo_path }); 211 defer repo_i.deinit(io, allocator); 212 213 try repo_i.addConfig(io, allocator, .{ .name = "user.name", .value = "haxy" }); 214 try repo_i.addConfig(io, allocator, .{ .name = "user.email", .value = "admin@haxy" }); 215 216 // write the repo's name and description into a README, plus a 217 // nested doc so the file tree has a directory to descend into 218 { 219 var repo_dir = try cwd.openDir(io, repo_path, .{}); 220 defer repo_dir.close(io); 221 222 const readme = try repo_dir.createFile(io, "README.md", .{}); 223 defer readme.close(io); 224 const readme_content = try std.fmt.allocPrint(arena.allocator(), "# {s}\n\n{s}\n", .{ r.name, r.description }); 225 try readme.writeStreamingAll(io, readme_content); 226 227 try repo_dir.createDirPath(io, "docs/dev"); 228 const doc = try repo_dir.createFile(io, "docs/dev/contribute.md", .{}); 229 defer doc.close(io); 230 try doc.writeStreamingAll(io, "To contribute, please make a pull request"); 231 } 232 233 try repo_i.add(io, allocator, &.{ "README.md", "docs/dev/contribute.md" }); 234 _ = try repo_i.commit(io, allocator, .{ .message = "let there be light" }); 235 236 // a batch of commits so the commits tab has more than one page to 237 // paginate through. each rewrites a few files with scattered line 238 // edits, so every commit is a multi-file diff with several separate 239 // hunks to look at. stepped timestamps vary the date column. 240 const base_ts: u64 = 1_700_000_000; // 2023-11-14 241 const edit_files = [_][]const u8{ "src/alpha.txt", "src/beta.txt", "src/gamma.txt" }; 242 // cycled through to pad each line into prose-like content. 243 const words = [_][]const u8{ 244 "lorem", "ipsum", "dolor", "sit", "amet", "consectetur", 245 "quantum", "vector", "matrix", "buffer", "kernel", "socket", 246 "falcon", "otter", "badger", "walrus", "ferret", "marmot", 247 "scatter", "gather", "encode", "decode", "render", "commit", 248 }; 249 // commit subjects, cycled then padded to a varying length. 250 const subjects = [_][]const u8{ 251 "fix off-by-one in scatter loop", 252 "encode and decode buffers", 253 "tune kernel socket timeouts", 254 "render matrix vector product", 255 "gather falcon and otter stats", 256 "refactor badger walrus module", 257 "drop dead ferret marmot branch", 258 }; 259 var c: usize = 0; 260 while (c < 30) : (c += 1) { 261 { 262 var repo_dir = try cwd.openDir(io, repo_path, .{}); 263 defer repo_dir.close(io); 264 try repo_dir.createDirPath(io, "src"); 265 for (edit_files, 0..) |path, fi| { 266 const file = try repo_dir.createFile(io, path, .{}); 267 defer file.close(io); 268 var writer = std.Io.Writer.Allocating.init(allocator); 269 defer writer.deinit(); 270 // every 8th line is edited by this commit; the stride 271 // offset shifts with c so consecutive commits touch 272 // different lines, and the edited text carries a 273 // c-dependent word, so each commit's diff is a distinct 274 // scatter of hunks. untouched lines stay byte-identical 275 // across commits (their padding ignores c) so they don't 276 // all change every commit. each line is padded with 277 // cycled words to a per-line length, capped at 120. 278 for (0..40) |line| { 279 const start = writer.written().len; 280 if ((line + fi + c) % 8 == 0) { 281 const tag = words[(line + fi + c * 3) % words.len]; 282 try writer.writer.print("rev {d} {s}", .{ c, tag }); 283 } 284 const target = 40 + (line * 17 + fi * 23) % 70; 285 var w = line + fi; 286 while (true) : (w += 1) { 287 const word = words[w % words.len]; 288 const len = writer.written().len - start; 289 if (len >= target or len + 1 + word.len > 120) break; 290 if (len == 0) 291 try writer.writer.print("{s}", .{word}) 292 else 293 try writer.writer.print(" {s}", .{word}); 294 } 295 try writer.writer.writeByte('\n'); 296 } 297 try file.writeStreamingAll(io, writer.written()); 298 } 299 } 300 try repo_i.add(io, allocator, &edit_files); 301 // a cycling subject padded to a c-varying length so the commit 302 // list shows messages of different widths, capped at 120. 303 var msg_writer = std.Io.Writer.Allocating.init(allocator); 304 defer msg_writer.deinit(); 305 try msg_writer.writer.print("{s}", .{subjects[c % subjects.len]}); 306 const msg_target = 16 + (c * 41) % 96; 307 var mw = c; 308 while (true) : (mw += 1) { 309 const word = words[mw % words.len]; 310 const len = msg_writer.written().len; 311 if (len >= msg_target or len + 1 + word.len > 120) break; 312 try msg_writer.writer.print(" {s}", .{word}); 313 } 314 const message = try arena.allocator().dupe(u8, msg_writer.written()); 315 _ = try repo_i.commit(io, allocator, .{ .message = message, .timestamp = base_ts + c * std.time.s_per_day }); 316 } 317 } 318 319 break :blk try ui.Session.init(&session_arena, &repo, .{}); 320 }; 321 session.is_terminal = true; 322 // try uses the default serve options below, so the footer's url points at 323 // the default web UI port. 324 session.web_port = try (srv.Options{}).wuiPort(); 325 326 // start the server 327 328 var cli = false; 329 330 var arg_it = try init.minimal.args.iterateAllocator(allocator); 331 defer arg_it.deinit(); 332 _ = arg_it.skip(); 333 while (arg_it.next()) |arg| { 334 if (std.mem.eql(u8, "--cli", arg)) { 335 cli = true; 336 } 337 } 338 339 const server_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "server" }); 340 defer allocator.free(server_path); 341 342 // let the native TUI's page builders open the on-disk repos for the file tree 343 session.io = io; 344 session.repos_dir = try std.fs.path.join(session_arena.allocator(), &.{ server_path, "repos" }); 345 346 if (cli) { 347 var stdout_writer = std.Io.File.stdout().writer(io, &.{}); 348 var stderr_writer = std.Io.File.stderr().writer(io, &.{}); 349 const run_opts = hx.main.RunOpts{ .out = &stdout_writer.interface, .err = &stderr_writer.interface }; 350 351 const key_path = try std.fs.path.join(allocator, &.{ cwd_path, temp_dir_name, "key" }); 352 defer allocator.free(key_path); 353 354 const Runnable = struct { 355 io: std.Io, 356 key_path: []const u8, 357 358 pub fn run(self: @This()) !void { 359 std.debug.print( 360 \\ 361 \\connect to the TUI with: 362 \\ ssh -p 8022 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null localhost 363 \\ 364 \\create a git repo and push over SSH: 365 \\ mkdir -p temp-try/client/test 366 \\ cd temp-try/client/test 367 \\ git init 368 \\ echo "hello" > hello.txt 369 \\ git add hello.txt 370 \\ git commit -m "let there be light" 371 \\ GIT_SSH_COMMAND='ssh -p 8022 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes -i {s}' git push localhost:admin/test HEAD:master 372 \\ 373 \\to quit, press enter. 374 \\ 375 , .{self.key_path}); 376 // portable stdin read via std.Io — equivalent to the 377 // std.posix.read(STDIN_FILENO, ...) that doesn't compile 378 // on windows. 379 var buf: [1]u8 = undefined; 380 var stdin_reader = std.Io.File.stdin().reader(self.io, &buf); 381 _ = stdin_reader.interface.takeByte() catch {}; 382 } 383 }; 384 385 try srv.run(.xit, .{}, io, allocator, cwd_path, .{ 386 .data_dir = server_path, 387 }, run_opts.err, Runnable{ .io = io, .key_path = key_path }); 388 } else { 389 var null_writer = std.Io.Writer.Discarding.init(&.{}); 390 const run_opts = hx.main.RunOpts{ .out = &null_writer.writer, .err = &null_writer.writer }; 391 392 const Runnable = struct { 393 io: std.Io, 394 allocator: std.mem.Allocator, 395 session: *ui.Session, 396 397 pub fn run(self: @This()) !void { 398 // launch the TUI 399 try hx.ui.run(self.io, self.allocator, self.session); 400 } 401 }; 402 403 try srv.run(.xit, .{}, io, allocator, cwd_path, .{ 404 .data_dir = server_path, 405 }, run_opts.err, Runnable{ .io = io, .allocator = allocator, .session = &session }); 406 } 407 } 408