╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────────────────────────────────╮ │1 const std = @import("std"); │ │2 +const builtin = @import("builtin");│ │3 const xit = @import("xit"); │ │4 const rp = xit.repo; │ │5 const rf = xit.ref; │ ╰──────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────╮ │19 pub const Title = @import("./ui/Title.zig"); │ │20 pub const SubTitle = @import("./ui/SubTitle.zig"); │ │21 pub const Quit = @import("./ui/Quit.zig"); │ │22 +pub const CloneUrl = @import("./ui/CloneUrl.zig"); │ │23 pub const Unauthorized = @import("./ui/Unauthorized.zig");│ │24 │ │25 pub const PageKind = enum { │ ╰──────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────╮ │967 // port the web UI is served on, for the TUI/SSH footer's "http://localhost:<port>..."│ │968 // url. null on the web itself (no footer there). │ │969 web_port: ?u16 = null, │ │970 + // a host operation requested by a widget. terminals present clone urls on │ │971 + // their restored screen; wasm asks the browser to copy them. │ │972 + host_request: ?HostRequest = null, │ │973 quit_requested: bool = false, │ │974 + │ │975 + pub const HostRequest = union(enum) { │ │976 + clone_url: []const u8, │ │977 + }; │ │978 │ │979 const Self = @This(); │ │980 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────╮ │988 current_page: RoutablePage = .default, │ │989 // whether to render the ANSI art backdrop │ │990 enable_ansi: bool = true, │ │991 + // the bound clone-service ports. absent in local mode, where clone urls│ │992 + // are not shown. │ │993 + clone_http_port: ?u16 = null, │ │994 + clone_ssh_port: ?u16 = null, │ │995 // true when this session views a single local repo. unlike `local` │ │996 // (the host-side filesystem source) this travels in the snapshot, so │ │997 // the wasm side also parses elided link urls and hides the multi-user │ ╰─────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────╮ │1109 defer nav.deinit(allocator); │ │1110 │ │1111 var terminal = try term.Terminal.init(io, allocator);│ │1099 - defer terminal.deinit(io); │ │1112 + var terminal_live = true; │ │1113 + defer if (terminal_live) terminal.deinit(io); │ │1114 │ │1115 // set term as active so it will be properly cooked │ │1116 // when a panic/segfault happens │ ╰───────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────╮ │1140 try inputKey(allocator, &nav.root, key, session); │ │1141 } │ │1142 │ │1143 + if (session.host_request) |request| { │ │1144 + session.host_request = null; │ │1145 + switch (request) { │ │1146 + .clone_url => |url| { │ │1147 + term.setActive(null); │ │1148 + terminal.deinit(io); │ │1149 + terminal_live = false; │ │1150 + showCloneUrl(io, url) catch |show_err| { │ │1151 + terminal = try term.Terminal.init(io, allocator); │ │1152 + terminal_live = true; │ │1153 + term.setActive(&terminal); │ │1154 + return show_err; │ │1155 + }; │ │1156 + terminal = try term.Terminal.init(io, allocator); │ │1157 + terminal_live = true; │ │1158 + term.setActive(&terminal); │ │1159 + last_size = .{ .width = 0, .height = 0 }; │ │1160 + }, │ │1161 + } │ │1162 + } │ │1163 + │ │1164 // persist queued actions when there's a repo; local mode has none,│ │1165 // so just apply in-memory │ │1166 if (repo_maybe) |repo| { │ ╰─────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────╮ │1181 .min_size = .{ .width = null, .height = null }, │ │1182 .max_size = .{ .width = last_size.width, .height = last_size.height }, │ │1183 }, nav.root.getFocus()); │ │1184 + } │ │1185 +} │ │1186 + │ │1187 +fn showCloneUrl(io: std.Io, url: []const u8) !void { │ │1188 + const tty: ?std.Io.File = if (builtin.os.tag == .windows) │ │1189 + null │ │1190 + else │ │1191 + try std.Io.Dir.cwd().openFile(io, "/dev/tty", .{ .mode = .read_write }); │ │1192 + defer if (tty) |file| file.close(io); │ │1193 + │ │1194 + const input = if (tty) |file| file else std.Io.File.stdin(); │ │1195 + const output = if (tty) |file| file else std.Io.File.stdout(); │ │1196 + │ │1197 + var write_buf: [1024]u8 = undefined; │ │1198 + var writer = output.writer(io, &write_buf); │ │1199 + try writer.interface.print("\x1b[2J\x1b[H{s}\r\n\r\npress enter to go back\r\n", .{url});│ │1200 + try writer.interface.flush(); │ │1201 + │ │1202 + var read_buf: [1]u8 = undefined; │ │1203 + var reader = input.reader(io, &read_buf); │ │1204 + while (true) { │ │1205 + const byte = try reader.interface.takeByte(); │ │1206 + if (byte == '\r' or byte == '\n') return; │ │1207 } │ │1208 } │ │1209 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────╮ │1568 repo_refs: Repo.Refs.View, │ │1569 repo_issues: Repo.Issues.View, │ │1570 repo_comment: Repo.Comment.Item, │ │1571 + clone_url: CloneUrl.View, │ │1572 home_users: Home.Users.View, │ │1573 home_repos: Home.Repos.View, │ │1574 auth_tab: Home.Header.AuthTab.View,│ ╰─────────────────────────────────────────────╯