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/53880/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. ---
Hey Greg - Looks great! I think at some point some styling to prefixes would be good but I have no clue on what they should look like (I don't really pay attention to them at all so I'm not sure what they mean nor how important each one is). Applied: https://git.sr.ht/~rockorager/comlink/commit/dc0a09286c0c7d2c177ba0cd4d9c79a445c1b33c
v2: - Use std.mem.asBytes instead of the horrific @ptrCast business src/App.zig | 70 ++++++++++++++++++++++++++++++++++------------------- src/irc.zig | 59 ++++++++++++++++++++++++++++++++------------ 2 files changed, 88 insertions(+), 41 deletions(-) diff --git a/src/App.zig b/src/App.zig index 80e613f..d5bcba7 100644 --- a/src/App.zig +++ b/src/App.zig @@ -514,7 +514,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> @@ -527,7 +534,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 @@ -548,17 +562,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 @@ -648,8 +668,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; @@ -677,7 +697,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(); @@ -1194,17 +1214,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); } } @@ -1367,8 +1387,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 @@ -1615,20 +1635,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 = std.mem.asBytes(&member.prefix), }, .{ - .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 7acd545..e7bad8a 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 @@ -709,7 +736,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