···526526527527 fn drawMessageView(self: *Channel, ctx: vxfw.DrawContext) Allocator.Error!vxfw.Surface {
528528 const max = ctx.max.size();
529529- if (max.width == 0 or max.height == 0) {
529529+ if (max.width == 0 or
530530+ max.height == 0 or
531531+ self.messages.items.len == 0)
532532+ {
530533 return .{
531534 .size = max,
532535 .widget = self.messageViewWidget(),
···539542540543 // Row is the row we are printing on. We add the offset to achieve our scroll location
541544 var row: i17 = max.height + self.scroll.offset;
545545+ // Message offset
546546+ const offset = self.scroll.msg_offset orelse self.messages.items.len;
547547+ // Timezone ref
548548+ // Gutter (left side where time is printed) width
549549+ const gutter_width = 6;
542550543543- const offset = self.scroll.msg_offset orelse self.messages.items.len;
551551+ const messages = self.messages.items[0..offset];
552552+ var iter = std.mem.reverseIterator(messages);
553553+554554+ var sender: []const u8 = "";
555555+ var maybe_instant: ?zeit.Instant = null;
556556+557557+ {
558558+ assert(messages.len > 0);
559559+ // Initialize sender and maybe_instant to the last message values
560560+ const last_msg = iter.next() orelse unreachable;
561561+ // Reset iter index
562562+ iter.index += 1;
563563+ sender = last_msg.senderNick() orelse "";
564564+ maybe_instant = last_msg.localTime(&self.client.app.tz);
565565+ }
544566545545- var iter = std.mem.reverseIterator(self.messages.items[0..offset]);
546546- const gutter_width = 6;
547567 while (iter.next()) |msg| {
548568 // Break if we have gone past the top of the screen
549569 if (row < 0) break;
550570571571+ // Get the sender nickname of the *next* message. Next meaning next message in the
572572+ // iterator, which is chronologically the previous message since we are printing in
573573+ // reverse
574574+ const next_sender: []const u8 = blk: {
575575+ const next_msg = iter.next() orelse break :blk "";
576576+ // Fix the index of the iterator
577577+ iter.index += 1;
578578+ break :blk next_msg.senderNick() orelse "";
579579+ };
580580+581581+ // Get the server time for the *next* message. We'll use this to decide printing of
582582+ // username and time
583583+ const maybe_next_instant: ?zeit.Instant = blk: {
584584+ const next_msg = iter.next() orelse break :blk null;
585585+ // Fix the index of the iterator
586586+ iter.index += 1;
587587+ break :blk next_msg.localTime(&self.client.app.tz);
588588+ };
589589+590590+ defer {
591591+ // After this loop, we want to save these values for the next iteration
592592+ maybe_instant = maybe_next_instant;
593593+ sender = next_sender;
594594+ }
595595+596596+ // Message content
597597+ const content: []const u8 = blk: {
598598+ var param_iter = msg.paramIterator();
599599+ // First param is the target, we don't need it
600600+ _ = param_iter.next() orelse unreachable;
601601+ break :blk param_iter.next() orelse "";
602602+ };
603603+551604 // Draw the message so we have it's wrapped height
552552- const text: vxfw.Text = .{ .text = msg.bytes };
605605+ const text: vxfw.Text = .{ .text = content };
553606 const child_ctx = ctx.withConstraints(
554607 .{ .height = 0, .width = 0 },
555608 .{ .width = max.width -| gutter_width, .height = null },
···564617 });
565618566619 // If we have a time, print it in the gutter
567567- if (msg.localTime(&self.client.app.tz)) |instant| {
568568- const time = instant.time();
569569- const buf = try std.fmt.allocPrint(
570570- ctx.arena,
571571- "{d:0>2}:{d:0>2}",
572572- .{ time.hour, time.minute },
573573- );
620620+ if (maybe_instant) |instant| {
621621+ var style: vaxis.Style = .{ .dim = true };
622622+623623+ // The time text we will print
624624+ const buf: []const u8 = blk: {
625625+ const time = instant.time();
626626+ // Check our next time. If *this* message occurs on a different day, we want to
627627+ // print the date
628628+ if (maybe_next_instant) |next_instant| {
629629+ const next_time = next_instant.time();
630630+ if (time.day != next_time.day) {
631631+ style = .{};
632632+ break :blk try std.fmt.allocPrint(
633633+ ctx.arena,
634634+ "{d:0>2}/{d:0>2}",
635635+ .{ @intFromEnum(time.month), time.day },
636636+ );
637637+ }
638638+ }
639639+640640+ // if it is the first message, we also want to print the date
641641+ if (iter.index == 0) {
642642+ style = .{};
643643+ break :blk try std.fmt.allocPrint(
644644+ ctx.arena,
645645+ "{d:0>2}/{d:0>2}",
646646+ .{ @intFromEnum(time.month), time.day },
647647+ );
648648+ }
649649+650650+ // Otherwise, we print clock time
651651+ break :blk try std.fmt.allocPrint(
652652+ ctx.arena,
653653+ "{d:0>2}:{d:0>2}",
654654+ .{ time.hour, time.minute },
655655+ );
656656+ };
657657+574658 const time_text: vxfw.Text = .{
575659 .text = buf,
576576- .style = .{ .dim = true },
660660+ .style = style,
577661 .softwrap = false,
578662 };
579663 try children.append(.{
580664 .origin = .{ .row = row, .col = 0 },
581665 .surface = try time_text.draw(child_ctx),
582666 });
667667+668668+ // Check if we need to print the sender of this message. We do this when the timegap
669669+ // between this message and next message is > 5 minutes, or if the sender is
670670+ // different
671671+ if (sender.len > 0 and
672672+ printSender(sender, next_sender, maybe_instant, maybe_next_instant))
673673+ {
674674+ // Back up one row to print
675675+ row -= 1;
676676+ // If we need to print the sender, it will be *this* messages sender
677677+ const user = try self.client.getOrCreateUser(sender);
678678+ const sender_text: vxfw.Text = .{
679679+ .text = user.nick,
680680+ .style = .{ .fg = user.color, .bold = true },
681681+ };
682682+ try children.append(.{
683683+ .origin = .{ .row = row, .col = gutter_width },
684684+ .surface = try sender_text.draw(child_ctx),
685685+ });
686686+687687+ // Back up 1 more row for spacing
688688+ row -= 1;
689689+ }
583690 }
584691 }
585692···597704 return self.members.items[idx].widget();
598705 }
599706 return null;
707707+ }
708708+709709+ // Helper function which tells us if we should print the sender of a message, based on he
710710+ // current message sender and time, and the (chronologically) previous message sent
711711+ fn printSender(
712712+ a_sender: []const u8,
713713+ b_sender: []const u8,
714714+ a_instant: ?zeit.Instant,
715715+ b_instant: ?zeit.Instant,
716716+ ) bool {
717717+ // If sender is different, we always print the sender
718718+ if (!std.mem.eql(u8, a_sender, b_sender)) return true;
719719+720720+ if (a_instant != null and b_instant != null) {
721721+ const a_ts = a_instant.?.timestamp_ns;
722722+ const b_ts = b_instant.?.timestamp_ns;
723723+ const delta: i64 = @intCast(a_ts - b_ts);
724724+ return @abs(delta) > (5 * std.time.ns_per_min);
725725+ }
726726+727727+ // In any other case, we
728728+ return false;
600729 }
601730};
602731···815944 const rhs_time = rhs.time() orelse return false;
816945817946 return lhs_time.timestamp_ns < rhs_time.timestamp_ns;
947947+ }
948948+949949+ /// Returns the NICK of the sender of the message
950950+ pub fn senderNick(self: Message) ?[]const u8 {
951951+ const src = self.source() orelse return null;
952952+ if (std.mem.indexOfScalar(u8, src, '!')) |idx| return src[0..idx];
953953+ if (std.mem.indexOfScalar(u8, src, '@')) |idx| return src[0..idx];
954954+ return src;
818955 }
819956};
820957