Gregory Anders: 1 ui: add highest channel membership prefix to nicklist 2 files changed, 88 insertions(+), 41 deletions(-)
Copy & paste the following snippet into your terminal to import this patchset into git:
curl -s https://lists.sr.ht/~rockorager/comlink/patches/53773/mbox | git am -3Learn more about email & git
Adds the highest channel membership prefix flag (e.g. @ or +) next to each user's nick in the nicklist. Since prefixes are specific to user+channel combination, we add a Member struct that contains a pointer to a User and a prefix character, and each Channel then contains a list of Members. For now, the prefix is only a single character (u8) which contains the highest prefix for that user (as determined from the WHOX or NAMES reply). If comlink ever enables the multi-prefix extension we may want to change this to be a string, but since we only display a single character I think this is ok for now. --- src/App.zig | 74 ++++++++++++++++++++++++++++++++++------------------- src/irc.zig | 59 ++++++++++++++++++++++++++++++------------ 2 files changed, 90 insertions(+), 43 deletions(-) diff --git a/src/App.zig b/src/App.zig index c33c977..9ee2ac5 100644 --- a/src/App.zig +++ b/src/App.zig @@ -511,7 +511,14 @@ pub fn run(self: *App) !void { const user_ptr = try msg.client.getOrCreateUser(nick); if (mem.indexOfScalar(u8, flags, 'G')) |_| user_ptr.away = true; var channel = try msg.client.getOrCreateChannel(channel_name); - try channel.addMember(user_ptr); + + const prefix = for (flags) |c| { + if (std.mem.indexOfScalar(u8, msg.client.supports.prefix, c)) |_| { + break c; + } + } else ' '; + + try channel.addMember(user_ptr, .{ .prefix = prefix }); }, .RPL_WHOSPCRPL => { // syntax: <client> <channel> <nick> <flags> @@ -524,7 +531,14 @@ pub fn run(self: *App) !void { const user_ptr = try msg.client.getOrCreateUser(nick); if (mem.indexOfScalar(u8, flags, 'G')) |_| user_ptr.away = true; var channel = try msg.client.getOrCreateChannel(channel_name); - try channel.addMember(user_ptr); + + const prefix = for (flags) |c| { + if (std.mem.indexOfScalar(u8, msg.client.supports.prefix, c)) |_| { + break c; + } + } else ' '; + + try channel.addMember(user_ptr, .{ .prefix = prefix }); }, .RPL_ENDOFWHO => { // syntax: <client> <mask> :End of WHO list @@ -545,17 +559,23 @@ pub fn run(self: *App) !void { var channel = try msg.client.getOrCreateChannel(channel_name); var name_iter = std.mem.splitScalar(u8, names, ' '); while (name_iter.next()) |name| { - const has_prefix = for (msg.client.supports.prefix) |ch| { - if (name[0] == ch) break true; - } else false; + const nick, const prefix = for (msg.client.supports.prefix) |ch| { + if (name[0] == ch) { + break .{ name[1..], name[0] }; + } + } else + .{ name, ' ' }; - if (has_prefix) log.debug("HAS PREFIX {s}", .{name}); - const user_ptr = if (has_prefix) - try msg.client.getOrCreateUser(name[1..]) - else - try msg.client.getOrCreateUser(name); - try channel.addMember(user_ptr); + if (prefix != ' ') { + log.debug("HAS PREFIX {s}", .{name}); + } + + const user_ptr = try msg.client.getOrCreateUser(nick); + + try channel.addMember(user_ptr, .{ .prefix = prefix, .sort = false }); } + + channel.sortMembers(); }, .RPL_ENDOFNAMES => { // syntax: <client> <channel> :End of /NAMES list @@ -645,8 +665,8 @@ pub fn run(self: *App) !void { var channel = try msg.client.getOrCreateChannel(target); const user_ptr = try msg.client.getOrCreateUser(target); const me_ptr = try msg.client.getOrCreateUser(msg.client.config.nick); - try channel.addMember(user_ptr); - try channel.addMember(me_ptr); + try channel.addMember(user_ptr, .{}); + try channel.addMember(me_ptr, .{}); // we set who_requested so we don't try to request // who on DMs channel.who_requested = true; @@ -674,7 +694,7 @@ pub fn run(self: *App) !void { if (mem.eql(u8, user.nick, msg.client.config.nick)) try self.requestHistory(msg.client, .after, channel) else - try channel.addMember(user); + try channel.addMember(user, .{}); }, .MARKREAD => { var iter = msg.paramIterator(); @@ -1191,17 +1211,17 @@ const Completer = struct { pub fn findMatches(self: *Completer, chan: *irc.Channel) !void { if (self.options.items.len > 0) return; const alloc = self.options.allocator; - var members = std.ArrayList(*irc.User).init(alloc); + var members = std.ArrayList(irc.Channel.Member).init(alloc); defer members.deinit(); for (chan.members.items) |member| { - if (std.ascii.startsWithIgnoreCase(member.nick, self.word)) { + if (std.ascii.startsWithIgnoreCase(member.user.nick, self.word)) { try members.append(member); } } - std.sort.insertion(*irc.User, members.items, chan, irc.Channel.compareRecentMessages); + std.sort.insertion(irc.Channel.Member, members.items, chan, irc.Channel.compareRecentMessages); self.options = try std.ArrayList([]const u8).initCapacity(alloc, members.items.len); for (members.items) |member| { - try self.options.append(member.nick); + try self.options.append(member.user.nick); } } @@ -1364,8 +1384,8 @@ pub fn whox(self: *App, client: *Client, channel: *irc.Channel) !void { { const other = try client.getOrCreateUser(channel.name); const me = try client.getOrCreateUser(client.config.nick); - try channel.addMember(other); - try channel.addMember(me); + try channel.addMember(other, .{}); + try channel.addMember(me, .{}); return; } // Only use WHO if we have WHOX and away-notify. Without @@ -1612,20 +1632,20 @@ fn draw(self: *App) !void { self.state.members.scroll_offset = @min(self.state.members.scroll_offset, channel.members.items.len -| member_list_win.height); var member_row: usize = 0; - for (channel.members.items) |member| { + for (channel.members.items) |*member| { defer member_row += 1; if (member_row < self.state.members.scroll_offset) continue; var member_seg = [_]vaxis.Segment{ .{ - .text = " ", + .text = @as([*]const u8, @ptrCast(&member.prefix))[0..1], }, .{ - .text = member.nick, + .text = member.user.nick, .style = .{ - .fg = if (member.away) + .fg = if (member.user.away) .{ .index = 8 } else - member.color, + member.user.color, }, }, }; diff --git a/src/irc.zig b/src/irc.zig index b4e6292..c6c0d25 100644 --- a/src/irc.zig +++ b/src/irc.zig @@ -84,7 +84,7 @@ pub const Channel = struct { client: *Client, name: []const u8, topic: ?[]const u8 = null, - members: std.ArrayList(*User), + members: std.ArrayList(Member), in_flight: struct { who: bool = false, names: bool = false, @@ -98,6 +98,22 @@ pub const Channel = struct { has_unread: bool = false, has_unread_highlight: bool = false, + pub const Member = struct { + user: *User, + + /// Highest channel membership prefix (or empty space if no prefix) + prefix: u8, + + pub fn compare(_: void, lhs: Member, rhs: Member) bool { + return if (lhs.prefix != ' ' and rhs.prefix == ' ') + true + else if (lhs.prefix == ' ' and rhs.prefix != ' ') + false + else + std.ascii.orderIgnoreCase(lhs.user.nick, rhs.user.nick).compare(.lt); + } + }; + pub fn deinit(self: *const Channel, alloc: std.mem.Allocator) void { alloc.free(self.name); self.members.deinit(); @@ -114,7 +130,7 @@ pub const Channel = struct { return std.ascii.orderIgnoreCase(lhs.name, rhs.name).compare(std.math.CompareOperator.lt); } - pub fn compareRecentMessages(self: *Channel, lhs: *User, rhs: *User) bool { + pub fn compareRecentMessages(self: *Channel, lhs: Member, rhs: Member) bool { var l: i64 = 0; var r: i64 = 0; var iter = std.mem.reverseIterator(self.messages.items); @@ -123,10 +139,10 @@ pub const Channel = struct { const bang = std.mem.indexOfScalar(u8, source, '!') orelse source.len; const nick = source[0..bang]; - if (l == 0 and msg.time != null and std.mem.eql(u8, lhs.nick, nick)) { + if (l == 0 and msg.time != null and std.mem.eql(u8, lhs.user.nick, nick)) { log.debug("L!!", .{}); l = msg.time.?.instant().unixTimestamp(); - } else if (r == 0 and msg.time != null and std.mem.eql(u8, rhs.nick, nick)) + } else if (r == 0 and msg.time != null and std.mem.eql(u8, rhs.user.nick, nick)) r = msg.time.?.instant().unixTimestamp(); } if (l > 0 and r > 0) break; @@ -135,20 +151,35 @@ pub const Channel = struct { } pub fn sortMembers(self: *Channel) void { - std.sort.insertion(*User, self.members.items, {}, User.compare); + std.sort.insertion(Member, self.members.items, {}, Member.compare); } - pub fn addMember(self: *Channel, user: *User) !void { - for (self.members.items) |member| { - if (user == member) return; + pub fn addMember(self: *Channel, user: *User, args: struct { + prefix: ?u8 = null, + sort: bool = true, + }) !void { + if (args.prefix) |p| { + log.debug("adding member: nick={s}, prefix={c}", .{user.nick, p}); + } + for (self.members.items) |*member| { + if (user == member.user) { + // Update the prefix for an existing member if the prefix is + // known + if (args.prefix) |p| member.prefix = p; + return; + } + } + + try self.members.append(.{ .user = user, .prefix = args.prefix orelse ' ' }); + + if (args.sort) { + self.sortMembers(); } - try self.members.append(user); - self.sortMembers(); } pub fn removeMember(self: *Channel, user: *User) void { for (self.members.items, 0..) |member, i| { - if (user == member) { + if (user == member.user) { _ = self.members.orderedRemove(i); return; } @@ -164,10 +195,6 @@ pub const User = struct { pub fn deinit(self: *const User, alloc: std.mem.Allocator) void { alloc.free(self.nick); } - - pub fn compare(_: void, lhs: *User, rhs: *User) bool { - return std.ascii.orderIgnoreCase(lhs.nick, rhs.nick).compare(std.math.CompareOperator.lt); - } }; /// an irc message @@ -703,7 +730,7 @@ pub const Client = struct { } const channel: Channel = .{ .name = try self.alloc.dupe(u8, name), - .members = std.ArrayList(*User).init(self.alloc), + .members = std.ArrayList(Channel.Member).init(self.alloc), .messages = std.ArrayList(Message).init(self.alloc), .client = self, }; -- 2.45.2