╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ GitRemote.zig ────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
7 const layout = xitui.layout; 8 const Key = xitui.input.Key; 9 const Grid = xitui.grid.Grid; 10 const Focus = xitui.focus.Focus; 11 const inp = @import("input.zig"); 12 13 const debug_ssh_prefix = "GIT_SSH_COMMAND='ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR' \\\r\n"; 14 15 pub const View = struct { 16 box: wgt.Box(ui.Widget), 17 session: *ui.Session, 18 protocols: [2]Protocol, 19 protocol_count: usize, 20 selected: usize = 0, 21 22 const Protocol = struct { 23 name: []const u8, 24 text: []const u8, 25 copyable_text: []const u8, 26 }; 27 28 pub fn initClone(allocator: std.mem.Allocator, session: *ui.Session, identity: []const u8) !View { 29 const aa = session.page_arena.allocator(); 30 var protocols: [2]Protocol = undefined; 31 var protocol_count: usize = 0; 32 if (session.data.git_http_port) |port| { 33 const url = try std.fmt.allocPrint(aa, "http://localhost:{d}/repo/{s}", .{ port, identity }); 34 protocols[protocol_count] = .{ 35 .name = "http", 36 .text = url, 37 .copyable_text = try std.fmt.allocPrint(aa, "git clone {s}", .{url}), 38 }; 39 protocol_count += 1; 40 } 41 if (session.data.git_ssh_port) |port| { 42 const url = try std.fmt.allocPrint(aa, "ssh://localhost:{d}/repo/{s}", .{ port, identity }); 43 protocols[protocol_count] = .{ 44 .name = "ssh", 45 .text = url, 46 .copyable_text = try std.fmt.allocPrint(aa, "{s}git clone {s}", .{ if (builtin.mode == .Debug) debug_ssh_prefix else "", url }), 47 }; 48 protocol_count += 1; 49 } 50 return initProtocols(allocator, session, protocols, protocol_count, " clone "); 51 } 52 53 pub fn initPush(allocator: std.mem.Allocator, session: *ui.Session, identity: []const u8, id: []const u8, branch: []const u8) !View { 54 const aa = session.page_arena.allocator(); 55 var protocols: [2]Protocol = undefined; 56 const port = session.data.git_ssh_port orelse return error.MissingGitPort; 57 const url = try std.fmt.allocPrint(aa, "ssh://localhost:{d}/repo/{s}/patch:{s}/branch:{s}", .{ port, identity, id, branch }); 58 const command = try std.fmt.allocPrint(aa, "git push {s} HEAD:patch", .{url}); 59 protocols[0] = .{ 60 .name = "ssh", 61 .text = command, 62 .copyable_text = if (builtin.mode == .Debug) try std.fmt.allocPrint(aa, "{s}{s}", .{ debug_ssh_prefix, command }) else command, 63 }; 64 return initProtocols(allocator, session, protocols, 1, " push "); 65 } 66 67 fn initProtocols(allocator: std.mem.Allocator, session: *ui.Session, protocols: [2]Protocol, protocol_count: usize, input_label: []const u8) !View { 68 if (protocol_count == 0) return error.MissingGitPort; 69 70 var max_text_len: usize = 0; 71 for (protocols[0..protocol_count]) |protocol| max_text_len = @max(max_text_len, protocol.text.len); 72 73 var box = try wgt.Box(ui.Widget).init(allocator, .{ .border_style = null, .direction = .horiz }); 74 errdefer box.deinit(allocator); 75 76 for (protocols[0..protocol_count]) |protocol| { 77 var label = try wgt.TextBox(ui.Widget).init(allocator, protocol.name, .{ .border_style = .hidden, .wrap_kind = .none }); 78 errdefer label.deinit(allocator); 79 label.getFocus().focusable = true; 80 try box.children.put(allocator, label.getFocus().id, .{ .widget = .{ .text_box = label }, .rect = null, .min_size = .{ .width = protocol.name.len + 2, .height = 3 } }); 81 } 82 83 var text_input = try wgt.TextInput(ui.Widget).init(allocator, .{ 84 .border_style = .single, 85 .label = input_label, 86 .read_only = true, 87 .render_content = session.is_terminal, 88 .visible_width = max_text_len, 89 }); 90 errdefer text_input.deinit(allocator); 91 text_input.getFocus().focusable = true; 92 try text_input.setContent(allocator, protocols[0].text); 93 box.getFocus().child_id = text_input.getFocus().id; 94 try box.children.put(allocator, text_input.getFocus().id, .{ .widget = .{ .text_input = text_input }, .rect = null, .min_size = .{ .width = max_text_len + 2, .height = 3 } }); 95 96 return .{ .box = box, .session = session, .protocols = protocols, .protocol_count = protocol_count }; 97 } 98 99 pub fn minWidth(self: *const View) usize { 100 var width: usize = 0; 101 var max_text_len: usize = 0; 102 for (self.protocols[0..self.protocol_count]) |protocol| { 103 width += protocol.name.len + 2; 104 max_text_len = @max(max_text_len, protocol.text.len); 105 } 106 return width + max_text_len + 2; 107 } 108 109 pub fn deinit(self: *View, allocator: std.mem.Allocator) void { 110 self.box.deinit(allocator); 111 } 112 113 pub fn build(self: *View, allocator: std.mem.Allocator, constraint: layout.Constraint, root_focus: *Focus) !void { 114 self.clearGrid(); 115 116 if (root_focus.grandchild_id) |id| { 117 var clicked: ?usize = null; 118 for (0..self.protocol_count) |i| { 119 if (id == self.protocolLabel(i).getFocus().id) clicked = i; 120 } 121 if (clicked) |selected| { 122 if (selected != self.selected) { 123 self.selected = selected; 124 try self.textInput().setContent(allocator, self.protocols[selected].text); 125 } 126 root_focus.setFocus(self.textInput().getFocus().id); 127 } 128 } 129 130 const focused = root_focus.grandchild_id == self.textInput().getFocus().id; 131 for (0..self.protocol_count) |i| { 132 self.protocolLabel(i).options.border_style = if (i == self.selected) 133 (if (focused) .double else .single) 134 else 135 .hidden; 136 } 137 self.textInput().options.border_style = .single; 138 self.textInput().cursor = 0; 139 self.textInput().scroll_offset = 0; 140 try self.box.build(allocator, constraint, root_focus); 141 142 if (!self.session.is_terminal) { 143 const aa = self.session.arena.allocator(); 144 const id = self.textInput().getFocus().id; 145 try self.session.text_inputs.put(aa, id, self.textInput()); 146 try self.session.input_values.put(aa, id, self.protocols[self.selected].text); 147 } 148 } 149 150 pub fn input(self: *View, allocator: std.mem.Allocator, key: Key, root_focus: *Focus) !void { 151 const next = switch (key) { 152 .arrow_left => if (self.selected > 0) self.selected - 1 else return, 153 .arrow_right => if (self.selected + 1 < self.protocol_count) self.selected + 1 else return, 154 .enter => { 155 if (self.session.is_terminal) self.session.host_request = .{ .show_copyable_text = self.protocols[self.selected].copyable_text }; 156 return; 157 }, 158 .mouse => |mouse| blk: { 159 for (0..self.protocol_count) |i| { 160 if (inp.leftClickOn(root_focus, self.protocolLabel(i).getFocus().id, mouse)) break :blk i; 161 } 162 if (self.session.is_terminal and !mouse.ctrl and inp.leftClickOn(root_focus, self.textInput().getFocus().id, mouse)) 163 self.session.host_request = .{ .show_copyable_text = self.protocols[self.selected].copyable_text }; 164 return; 165 }, 166 else => return, 167 }; 168 self.selected = next; 169 try self.textInput().setContent(allocator, self.protocols[next].text); 170 } 171 172 fn textInput(self: *View) *wgt.TextInput(ui.Widget) { 173 return &self.box.children.values()[self.protocol_count].widget.text_input; 174 } 175 176 fn protocolLabel(self: *View, index: usize) *wgt.TextBox(ui.Widget) { 177 return &self.box.children.values()[index].widget.text_box; 178 } 179 180 pub fn clearGrid(self: *View) void { 181 self.box.clearGrid(); 182 } 183 184 pub fn getGrid(self: View) ?Grid { 185 return self.box.getGrid(); 186 } 187 188 pub fn getFocus(self: *View) *Focus { 189 return self.box.getFocus(); 190 } 191 }; 192