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.
---
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