an experimental irc client

remove unused code

+16 -712
+16 -712
src/app.zig
··· 344 self.markSelectedChannelRead(); 345 if (self.ctx) |ctx| { 346 self.buffer_list.nextItem(ctx); 347 } 348 } 349 ··· 353 self.markSelectedChannelRead(); 354 if (self.ctx) |ctx| { 355 self.buffer_list.prevItem(ctx); 356 } 357 } 358 ··· 575 } 576 } 577 }, 578 - } 579 - } 580 - 581 - fn draw(self: *App, input: *TextInput) !void { 582 - var arena = std.heap.ArenaAllocator.init(self.alloc); 583 - defer arena.deinit(); 584 - const allocator = arena.allocator(); 585 - 586 - // reset window state 587 - const win = self.vx.window(); 588 - win.clear(); 589 - self.vx.setMouseShape(.default); 590 - 591 - // Handle resize of sidebars 592 - if (self.state.mouse) |mouse| { 593 - if (self.state.buffers.resizing) { 594 - self.state.buffers.width = @min(mouse.col, win.width -| self.state.members.width); 595 - } else if (self.state.members.resizing) { 596 - self.state.members.width = win.width -| mouse.col + 1; 597 - } 598 - 599 - if (mouse.col == self.state.buffers.width) { 600 - self.vx.setMouseShape(.@"ew-resize"); 601 - switch (mouse.type) { 602 - .press => { 603 - if (mouse.button == .left) self.state.buffers.resizing = true; 604 - }, 605 - .release => self.state.buffers.resizing = false, 606 - else => {}, 607 - } 608 - } else if (mouse.col == win.width -| self.state.members.width + 1) { 609 - self.vx.setMouseShape(.@"ew-resize"); 610 - switch (mouse.type) { 611 - .press => { 612 - if (mouse.button == .left) self.state.members.resizing = true; 613 - }, 614 - .release => self.state.members.resizing = false, 615 - else => {}, 616 - } 617 - } 618 - } 619 - 620 - // Define the layout 621 - const buf_list_w = self.state.buffers.width; 622 - const mbr_list_w = self.state.members.width; 623 - const message_list_width = win.width -| buf_list_w -| mbr_list_w; 624 - 625 - const channel_list_win = win.child(.{ 626 - .width = .{ .limit = self.state.buffers.width + 1 }, 627 - .border = .{ .where = .right }, 628 - }); 629 - 630 - const member_list_win = win.child(.{ 631 - .x_off = buf_list_w + message_list_width + 1, 632 - .border = .{ .where = .left }, 633 - }); 634 - 635 - const middle_win = win.child(.{ 636 - .x_off = buf_list_w + 1, 637 - .width = .{ .limit = message_list_width }, 638 - }); 639 - 640 - const topic_win = middle_win.child(.{ 641 - .height = .{ .limit = 2 }, 642 - .border = .{ .where = .bottom }, 643 - }); 644 - 645 - const message_list_win = middle_win.child(.{ 646 - .y_off = 2, 647 - .height = .{ .limit = middle_win.height -| 4 }, 648 - .width = .{ .limit = middle_win.width -| 1 }, 649 - }); 650 - 651 - // Draw the buffer list 652 - try self.drawBufferList(self.clients.items, channel_list_win); 653 - 654 - // Get our currently selected buffer and draw it 655 - const buffer = self.selectedBuffer() orelse return; 656 - switch (buffer) { 657 - .client => {}, // nothing to do 658 - 659 - .channel => |channel| { 660 - // Request WHO if we don't already have it 661 - if (!channel.who_requested) try channel.client.whox(channel); 662 - 663 - // Set the title of the terminal 664 - { 665 - var buf: [64]u8 = undefined; 666 - const title = std.fmt.bufPrint(&buf, "{s} - comlink", .{channel.name}) catch title: { 667 - // If the channel name is too long to fit in our buffer just truncate 668 - const len = @min(buf.len, channel.name.len); 669 - @memcpy(buf[0..len], channel.name[0..len]); 670 - break :title buf[0..len]; 671 - }; 672 - try self.vx.setTitle(self.tty.anyWriter(), title); 673 - } 674 - 675 - // Draw the topic 676 - try self.drawTopic(topic_win, channel.topic orelse ""); 677 - 678 - // Draw the member list 679 - try self.drawMemberList(member_list_win, channel); 680 - 681 - // Draw the message list 682 - try self.drawMessageList(allocator, message_list_win, channel); 683 - 684 - // draw a scrollbar 685 - { 686 - const scrollbar: Scrollbar = .{ 687 - .total = channel.messages.items.len, 688 - .view_size = message_list_win.height / 3, // ~3 lines per message 689 - .bottom = self.state.messages.scroll_offset, 690 - }; 691 - const scrollbar_win = middle_win.child(.{ 692 - .x_off = message_list_win.width, 693 - .y_off = 2, 694 - .height = .{ .limit = middle_win.height -| 4 }, 695 - }); 696 - scrollbar.draw(scrollbar_win); 697 - } 698 - 699 - // draw the completion list 700 - if (self.completer) |*completer| { 701 - try completer.findMatches(channel); 702 - 703 - var completion_style: vaxis.Style = .{ .bg = .{ .index = 8 } }; 704 - const completion_win = middle_win.child(.{ 705 - .width = .{ .limit = completer.widestMatch(win) + 1 }, 706 - .height = .{ .limit = @min(completer.numMatches(), middle_win.height -| 1) }, 707 - .x_off = completer.start_idx, 708 - .y_off = middle_win.height -| completer.numMatches() -| 1, 709 - }); 710 - completion_win.fill(.{ 711 - .char = .{ .grapheme = " ", .width = 1 }, 712 - .style = completion_style, 713 - }); 714 - var completion_row: usize = 0; 715 - while (completion_row < completion_win.height) : (completion_row += 1) { 716 - log.debug("COMPLETION ROW {d}, selected_idx {d}", .{ completion_row, completer.selected_idx orelse 0 }); 717 - if (completer.selected_idx) |idx| { 718 - if (completion_row == idx) 719 - completion_style.reverse = true 720 - else { 721 - completion_style = .{ .bg = .{ .index = 8 } }; 722 - } 723 - } 724 - var seg = [_]vaxis.Segment{ 725 - .{ 726 - .text = completer.options.items[completer.options.items.len - 1 - completion_row], 727 - .style = completion_style, 728 - }, 729 - .{ 730 - .text = " ", 731 - .style = completion_style, 732 - }, 733 - }; 734 - _ = try completion_win.print(&seg, .{ 735 - .row_offset = completion_win.height -| completion_row -| 1, 736 - }); 737 - } 738 - } 739 - }, 740 - } 741 - 742 - const input_win = middle_win.child(.{ 743 - .y_off = win.height -| 1, 744 - .width = .{ .limit = middle_win.width -| 7 }, 745 - .height = .{ .limit = 1 }, 746 - }); 747 - const len_win = middle_win.child(.{ 748 - .x_off = input_win.width, 749 - .y_off = win.height -| 1, 750 - .width = .{ .limit = 7 }, 751 - .height = .{ .limit = 1 }, 752 - }); 753 - const buf_name_len = blk: { 754 - const sel_buf = self.selectedBuffer() orelse @panic("no buffer"); 755 - switch (sel_buf) { 756 - .channel => |chan| break :blk chan.name.len, 757 - else => break :blk 0, 758 - } 759 - }; 760 - // PRIVMSG <channel_name> :<message>\r\n = 12 bytes of overhead 761 - const max_len = irc.maximum_message_size - buf_name_len - 12; 762 - var len_buf: [7]u8 = undefined; 763 - const msg_len = input.buf.realLength(); 764 - _ = try std.fmt.bufPrint(&len_buf, "{d: >3}/{d}", .{ msg_len, max_len }); 765 - 766 - var len_segs = [_]vaxis.Segment{ 767 - .{ 768 - .text = len_buf[0..3], 769 - .style = .{ .fg = if (msg_len > max_len) 770 - .{ .index = 1 } 771 - else 772 - .{ .index = 8 } }, 773 - }, 774 - .{ 775 - .text = len_buf[3..], 776 - .style = .{ .fg = .{ .index = 8 } }, 777 - }, 778 - }; 779 - 780 - _ = try len_win.print(&len_segs, .{}); 781 - input.draw(input_win); 782 - 783 - if (self.state.paste.showDialog()) { 784 - // Draw a modal dialog for how to handle multi-line paste 785 - const multiline_paste_win = vaxis.widgets.alignment.center(win, win.width - 10, win.height - 10); 786 - const bordered = vaxis.widgets.border.all(multiline_paste_win, .{}); 787 - bordered.clear(); 788 - const warning_width: usize = 37; 789 - const title_win = multiline_paste_win.child(.{ 790 - .height = .{ .limit = 2 }, 791 - .y_off = 1, 792 - .x_off = multiline_paste_win.width / 2 - warning_width / 2, 793 - }); 794 - const title_seg = [_]vaxis.Segment{ 795 - .{ 796 - .text = "/!\\ Warning: Multiline paste detected", 797 - .style = .{ 798 - .fg = .{ .index = 3 }, 799 - .bold = true, 800 - }, 801 - }, 802 - }; 803 - _ = try title_win.print(&title_seg, .{ .wrap = .none }); 804 - var segs = [_]vaxis.Segment{ 805 - .{ .text = self.paste_buffer.items }, 806 - }; 807 - _ = try bordered.print(&segs, .{ .wrap = .grapheme, .row_offset = 2 }); 808 - // const button: Button = .{ 809 - // .label = "Accept", 810 - // .style = .{ .bg = .{ .index = 7 } }, 811 - // }; 812 - // try button.draw(bordered.child(.{ 813 - // .x_off = 3, 814 - // .y_off = bordered.height - 4, 815 - // .height = .{ .limit = 3 }, 816 - // .width = .{ .limit = 10 }, 817 - // })); 818 - } 819 - 820 - var buffered = self.tty.bufferedWriter(); 821 - try self.vx.render(buffered.writer().any()); 822 - try buffered.flush(); 823 - } 824 - 825 - fn drawMessageList( 826 - self: *App, 827 - arena: std.mem.Allocator, 828 - win: vaxis.Window, 829 - channel: *irc.Channel, 830 - ) !void { 831 - if (channel.messages.items.len == 0) return; 832 - const client = channel.client; 833 - const last_msg_idx = channel.messages.items.len -| self.state.messages.scroll_offset; 834 - const messages = channel.messages.items[0..@max(1, last_msg_idx)]; 835 - // We draw a gutter for time information 836 - const gutter_width: usize = 6; 837 - 838 - // Our message list is offset by the gutter width 839 - const message_offset_win = win.child(.{ .x_off = gutter_width }); 840 - 841 - // Handle mouse 842 - if (win.hasMouse(self.state.mouse)) |mouse| { 843 - switch (mouse.button) { 844 - .wheel_up => { 845 - self.state.messages.scroll_offset +|= 1; 846 - self.state.mouse.?.button = .none; 847 - self.state.messages.pending_scroll += 2; 848 - }, 849 - .wheel_down => { 850 - self.state.messages.scroll_offset -|= 1; 851 - self.state.mouse.?.button = .none; 852 - self.state.messages.pending_scroll -= 2; 853 - }, 854 - else => {}, 855 - } 856 - } 857 - self.state.messages.scroll_offset = @min( 858 - self.state.messages.scroll_offset, 859 - channel.messages.items.len -| 1, 860 - ); 861 - 862 - // Define a few state variables for the loop 863 - const last_msg = messages[messages.len -| 1]; 864 - 865 - // Initialize prev_time to the time of the last message, falling back to "now" 866 - var prev_time: zeit.Instant = last_msg.localTime(&self.tz) orelse 867 - try zeit.instant(.{ .source = .now, .timezone = &self.tz }); 868 - 869 - // Initialize prev_sender to the sender of the last message 870 - var prev_sender: []const u8 = if (last_msg.source()) |src| blk: { 871 - if (std.mem.indexOfScalar(u8, src, '!')) |idx| 872 - break :blk src[0..idx]; 873 - if (std.mem.indexOfScalar(u8, src, '@')) |idx| 874 - break :blk src[0..idx]; 875 - break :blk src; 876 - } else ""; 877 - 878 - // y_off is the row we are printing on 879 - var y_off: usize = win.height; 880 - 881 - // Formatted message segments 882 - var segments = std.ArrayList(vaxis.Segment).init(arena); 883 - 884 - var msg_iter = std.mem.reverseIterator(messages); 885 - var i: usize = messages.len; 886 - while (msg_iter.next()) |message| { 887 - i -|= 1; 888 - segments.clearRetainingCapacity(); 889 - 890 - // Get the sender nick 891 - const sender: []const u8 = if (message.source()) |src| blk: { 892 - if (std.mem.indexOfScalar(u8, src, '!')) |idx| 893 - break :blk src[0..idx]; 894 - if (std.mem.indexOfScalar(u8, src, '@')) |idx| 895 - break :blk src[0..idx]; 896 - break :blk src; 897 - } else ""; 898 - 899 - // Save sender state after this loop 900 - defer prev_sender = sender; 901 - 902 - // Before we print the message, we need to decide if we should print the sender name of 903 - // the previous message. There are two cases we do this: 904 - // 1. The previous message was sent by someone other than the current message 905 - // 2. A certain amount of time has elapsed between messages 906 - // 907 - // Each case requires that we have space in the window to print the sender (y_off > 0) 908 - const time_gap = if (message.localTime(&self.tz)) |time| blk: { 909 - // Save message state for next loop 910 - defer prev_time = time; 911 - // time_gap is true when the difference between this message and last message is 912 - // greater than 5 minutes 913 - break :blk (prev_time.timestamp_ns -| time.timestamp_ns) > (5 * std.time.ns_per_min); 914 - } else false; 915 - 916 - // Print the sender of the previous message 917 - if (y_off > 0 and (time_gap or !std.mem.eql(u8, prev_sender, sender))) { 918 - // Go up one line 919 - y_off -|= 1; 920 - 921 - // Get the user so we have the correct color 922 - const user = try client.getOrCreateUser(prev_sender); 923 - const sender_win = message_offset_win.child(.{ 924 - .y_off = y_off, 925 - .height = .{ .limit = 1 }, 926 - }); 927 - 928 - // We will use the result to see if our mouse is hovering over the nickname 929 - const sender_result = try sender_win.printSegment( 930 - .{ 931 - .text = prev_sender, 932 - .style = .{ .fg = user.color, .bold = true }, 933 - }, 934 - .{ .wrap = .none }, 935 - ); 936 - 937 - // If our mouse is over the nickname, we set it to a pointer 938 - const result_win = sender_win.child(.{ .width = .{ .limit = sender_result.col } }); 939 - if (result_win.hasMouse(self.state.mouse)) |_| { 940 - self.vx.setMouseShape(.pointer); 941 - // If we have a realname we print it 942 - if (user.real_name) |real_name| { 943 - _ = try sender_win.printSegment( 944 - .{ 945 - .text = real_name, 946 - .style = .{ .italic = true, .dim = true }, 947 - }, 948 - .{ 949 - .wrap = .none, 950 - .col_offset = sender_result.col + 1, 951 - }, 952 - ); 953 - } 954 - } 955 - 956 - // Go up one more line to print the next message 957 - y_off -|= 1; 958 - } 959 - 960 - // We are out of space 961 - if (y_off == 0) break; 962 - 963 - const user = try client.getOrCreateUser(sender); 964 - try format.message(&segments, user, message); 965 - 966 - // Get the line count for this message 967 - const content_height = lineCountForWindow(message_offset_win, segments.items); 968 - 969 - const content_win = message_offset_win.child( 970 - .{ 971 - .y_off = y_off -| content_height, 972 - .height = .{ .limit = content_height }, 973 - }, 974 - ); 975 - if (content_win.hasMouse(self.state.mouse)) |mouse| { 976 - var bg_idx: u8 = 8; 977 - if (mouse.type == .press and mouse.button == .middle) { 978 - var list = std.ArrayList(u8).init(self.alloc); 979 - defer list.deinit(); 980 - for (segments.items) |item| { 981 - try list.appendSlice(item.text); 982 - } 983 - try self.vx.copyToSystemClipboard(self.tty.anyWriter(), list.items, self.alloc); 984 - bg_idx = 3; 985 - } 986 - content_win.fill(.{ 987 - .char = .{ 988 - .grapheme = " ", 989 - .width = 1, 990 - }, 991 - .style = .{ 992 - .bg = .{ .index = bg_idx }, 993 - }, 994 - }); 995 - for (segments.items) |*item| { 996 - item.style.bg = .{ .index = bg_idx }; 997 - } 998 - } 999 - var iter = message.paramIterator(); 1000 - // target is the channel, and we already handled that 1001 - _ = iter.next() orelse continue; 1002 - 1003 - const content = iter.next() orelse continue; 1004 - if (std.mem.indexOf(u8, content, client.nickname())) |_| { 1005 - for (segments.items) |*item| { 1006 - if (item.style.fg == .default) 1007 - item.style.fg = .{ .index = 3 }; 1008 - } 1009 - } 1010 - 1011 - // Color the background of unread messages gray. 1012 - if (message.localTime(&self.tz)) |instant| { 1013 - if (instant.unixTimestamp() > channel.last_read) { 1014 - for (segments.items) |*item| { 1015 - item.style.bg = .{ .index = 8 }; 1016 - } 1017 - } 1018 - } 1019 - 1020 - _ = try content_win.print( 1021 - segments.items, 1022 - .{ 1023 - .wrap = .word, 1024 - }, 1025 - ); 1026 - if (content_height > y_off) break; 1027 - const gutter = win.child(.{ 1028 - .y_off = y_off -| content_height, 1029 - .width = .{ .limit = 6 }, 1030 - }); 1031 - 1032 - if (message.localTime(&self.tz)) |instant| { 1033 - var date: bool = false; 1034 - const time = instant.time(); 1035 - var buf = try std.fmt.allocPrint( 1036 - arena, 1037 - "{d:0>2}:{d:0>2}", 1038 - .{ time.hour, time.minute }, 1039 - ); 1040 - if (i != 0 and channel.messages.items[i - 1].time() != null) { 1041 - const prev = channel.messages.items[i - 1].localTime(&self.tz).?.time(); 1042 - if (time.day != prev.day) { 1043 - date = true; 1044 - buf = try std.fmt.allocPrint( 1045 - arena, 1046 - "{d:0>2}/{d:0>2}", 1047 - .{ @intFromEnum(time.month), time.day }, 1048 - ); 1049 - } 1050 - } 1051 - if (i == 0) { 1052 - date = true; 1053 - buf = try std.fmt.allocPrint( 1054 - arena, 1055 - "{d:0>2}/{d:0>2}", 1056 - .{ @intFromEnum(time.month), time.day }, 1057 - ); 1058 - } 1059 - const fg: vaxis.Color = if (date) 1060 - .default 1061 - else 1062 - .{ .index = 8 }; 1063 - var time_seg = [_]vaxis.Segment{ 1064 - .{ 1065 - .text = buf, 1066 - .style = .{ .fg = fg }, 1067 - }, 1068 - }; 1069 - _ = try gutter.print(&time_seg, .{}); 1070 - } 1071 - 1072 - y_off -|= content_height; 1073 - 1074 - // If we are on the first message, print the sender 1075 - if (i == 0) { 1076 - y_off -|= 1; 1077 - const sender_win = win.child(.{ 1078 - .x_off = 6, 1079 - .y_off = y_off, 1080 - .height = .{ .limit = 1 }, 1081 - }); 1082 - const sender_result = try sender_win.print( 1083 - &.{.{ 1084 - .text = sender, 1085 - .style = .{ 1086 - .fg = user.color, 1087 - .bold = true, 1088 - }, 1089 - }}, 1090 - .{ .wrap = .word }, 1091 - ); 1092 - const result_win = sender_win.child(.{ .width = .{ .limit = sender_result.col } }); 1093 - if (result_win.hasMouse(self.state.mouse)) |_| { 1094 - self.vx.setMouseShape(.pointer); 1095 - } 1096 - } 1097 - 1098 - // if we are on the oldest message, request more history 1099 - if (i == 0 and !channel.at_oldest) { 1100 - try client.requestHistory(.before, channel); 1101 - } 1102 - } 1103 - } 1104 - 1105 - fn drawMemberList(self: *App, win: vaxis.Window, channel: *irc.Channel) !void { 1106 - // Handle mouse 1107 - { 1108 - if (win.hasMouse(self.state.mouse)) |mouse| { 1109 - switch (mouse.button) { 1110 - .wheel_up => { 1111 - self.state.members.scroll_offset -|= 3; 1112 - self.state.mouse.?.button = .none; 1113 - }, 1114 - .wheel_down => { 1115 - self.state.members.scroll_offset +|= 3; 1116 - self.state.mouse.?.button = .none; 1117 - }, 1118 - else => {}, 1119 - } 1120 - } 1121 - 1122 - self.state.members.scroll_offset = @min( 1123 - self.state.members.scroll_offset, 1124 - channel.members.items.len -| win.height, 1125 - ); 1126 - } 1127 - 1128 - // Draw the list 1129 - var member_row: usize = 0; 1130 - for (channel.members.items) |*member| { 1131 - defer member_row += 1; 1132 - if (member_row < self.state.members.scroll_offset) continue; 1133 - var member_seg = [_]vaxis.Segment{ 1134 - .{ 1135 - .text = std.mem.asBytes(&member.prefix), 1136 - }, 1137 - .{ 1138 - .text = member.user.nick, 1139 - .style = .{ 1140 - .fg = if (member.user.away) 1141 - .{ .index = 8 } 1142 - else 1143 - member.user.color, 1144 - }, 1145 - }, 1146 - }; 1147 - _ = try win.print(&member_seg, .{ 1148 - .row_offset = member_row -| self.state.members.scroll_offset, 1149 - }); 1150 - } 1151 - } 1152 - 1153 - fn drawTopic(_: *App, win: vaxis.Window, topic: []const u8) !void { 1154 - _ = try win.printSegment(.{ .text = topic }, .{ .wrap = .none }); 1155 - } 1156 - 1157 - fn drawBufferList(self: *App, clients: []*irc.Client, win: vaxis.Window) !void { 1158 - // Handle mouse 1159 - { 1160 - if (win.hasMouse(self.state.mouse)) |mouse| { 1161 - switch (mouse.button) { 1162 - .wheel_up => { 1163 - self.state.buffers.scroll_offset -|= 3; 1164 - self.state.mouse.?.button = .none; 1165 - }, 1166 - .wheel_down => { 1167 - self.state.buffers.scroll_offset +|= 3; 1168 - self.state.mouse.?.button = .none; 1169 - }, 1170 - else => {}, 1171 - } 1172 - } 1173 - 1174 - self.state.buffers.scroll_offset = @min( 1175 - self.state.buffers.scroll_offset, 1176 - self.state.buffers.count -| win.height, 1177 - ); 1178 - } 1179 - const buf_list_w = self.state.buffers.width; 1180 - var row: usize = 0; 1181 - 1182 - defer self.state.buffers.count = row; 1183 - for (clients) |client| { 1184 - const scroll_offset = self.state.buffers.scroll_offset; 1185 - if (!(row < scroll_offset)) { 1186 - var style: vaxis.Style = if (row == self.state.buffers.selected_idx) 1187 - .{ 1188 - .fg = if (client.status == .disconnected) .{ .index = 8 } else .default, 1189 - .reverse = true, 1190 - } 1191 - else 1192 - .{ 1193 - .fg = if (client.status == .disconnected) .{ .index = 8 } else .default, 1194 - }; 1195 - const network_win = win.child(.{ 1196 - .y_off = row, 1197 - .height = .{ .limit = 1 }, 1198 - }); 1199 - if (network_win.hasMouse(self.state.mouse)) |_| { 1200 - self.vx.setMouseShape(.pointer); 1201 - style.bg = .{ .index = 8 }; 1202 - } 1203 - _ = try network_win.print( 1204 - &.{.{ 1205 - .text = client.config.name orelse client.config.server, 1206 - .style = style, 1207 - }}, 1208 - .{}, 1209 - ); 1210 - if (network_win.hasMouse(self.state.mouse)) |_| { 1211 - self.vx.setMouseShape(.pointer); 1212 - } 1213 - } 1214 - row += 1; 1215 - for (client.channels.items) |*channel| { 1216 - defer row += 1; 1217 - if (row < scroll_offset) continue; 1218 - const channel_win = win.child(.{ 1219 - .y_off = row -| scroll_offset, 1220 - .height = .{ .limit = 1 }, 1221 - }); 1222 - if (channel_win.hasMouse(self.state.mouse)) |mouse| { 1223 - if (mouse.type == .press and mouse.button == .left and self.state.buffers.selected_idx != row) { 1224 - // When leaving a channel we mark it as read, so we make sure that's done 1225 - // before we change to the new channel. 1226 - self.markSelectedChannelRead(); 1227 - self.state.buffers.selected_idx = row; 1228 - } 1229 - } 1230 - 1231 - const is_current = row == self.state.buffers.selected_idx; 1232 - var chan_style: vaxis.Style = if (is_current) 1233 - .{ 1234 - .fg = if (client.status == .disconnected) .{ .index = 8 } else .default, 1235 - .reverse = true, 1236 - } 1237 - else if (channel.has_unread) 1238 - .{ 1239 - .fg = .{ .index = 4 }, 1240 - .bold = true, 1241 - } 1242 - else 1243 - .{ 1244 - .fg = if (client.status == .disconnected) .{ .index = 8 } else .default, 1245 - }; 1246 - const prefix: []const u8 = if (channel.name[0] == '#') "#" else ""; 1247 - const name_offset: usize = if (prefix.len > 0) 1 else 0; 1248 - 1249 - if (channel_win.hasMouse(self.state.mouse)) |mouse| { 1250 - self.vx.setMouseShape(.pointer); 1251 - if (mouse.button == .left) 1252 - chan_style.reverse = true 1253 - else 1254 - chan_style.bg = .{ .index = 8 }; 1255 - } 1256 - 1257 - const first_seg: vaxis.Segment = if (channel.has_unread_highlight) 1258 - .{ .text = " ●︎", .style = .{ .fg = .{ .index = 1 } } } 1259 - else 1260 - .{ .text = " " }; 1261 - 1262 - var chan_seg = [_]vaxis.Segment{ 1263 - first_seg, 1264 - .{ 1265 - .text = prefix, 1266 - .style = .{ .fg = .{ .index = 8 } }, 1267 - }, 1268 - .{ 1269 - .text = channel.name[name_offset..], 1270 - .style = chan_style, 1271 - }, 1272 - }; 1273 - const result = try channel_win.print( 1274 - &chan_seg, 1275 - .{}, 1276 - ); 1277 - if (result.overflow) 1278 - win.writeCell( 1279 - buf_list_w -| 1, 1280 - row -| scroll_offset, 1281 - .{ 1282 - .char = .{ 1283 - .grapheme = "…", 1284 - .width = 1, 1285 - }, 1286 - .style = chan_style, 1287 - }, 1288 - ); 1289 - } 1290 } 1291 } 1292
··· 344 self.markSelectedChannelRead(); 345 if (self.ctx) |ctx| { 346 self.buffer_list.nextItem(ctx); 347 + if (self.selectedBuffer()) |buffer| { 348 + switch (buffer) { 349 + .client => {}, 350 + .channel => |channel| { 351 + ctx.requestFocus(channel.text_field.widget()) catch {}; 352 + }, 353 + } 354 + } 355 } 356 } 357 ··· 361 self.markSelectedChannelRead(); 362 if (self.ctx) |ctx| { 363 self.buffer_list.prevItem(ctx); 364 + if (self.selectedBuffer()) |buffer| { 365 + switch (buffer) { 366 + .client => {}, 367 + .channel => |channel| { 368 + ctx.requestFocus(channel.text_field.widget()) catch {}; 369 + }, 370 + } 371 + } 372 } 373 } 374 ··· 591 } 592 } 593 }, 594 } 595 } 596