~rockorager/comlink

ui: add highest channel membership prefix to nicklist v1 PROPOSED

Gregory Anders: 1
 ui: add highest channel membership prefix to nicklist

 2 files changed, 88 insertions(+), 41 deletions(-)
Export patchset (mbox)
How do I use this?

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 -3
Learn more about email & git

[PATCH] ui: add highest channel membership prefix to nicklist Export this patch

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