From 10ebfb7cee1a6678134cae02d799dbc1eaa6f119 Mon Sep 17 00:00:00 2001 From: vaw Date: Sun, 3 May 2026 16:30:23 +0200 Subject: [PATCH] Show the message sender on extra line if `user_gutter_width < 3` --- docs/iamb.5 | 2 + src/message/mod.rs | 96 ++++++++++++++++++++++++++++++++-------------- 2 files changed, 69 insertions(+), 29 deletions(-) diff --git a/docs/iamb.5 b/docs/iamb.5 index 82d4a824..399d7ac6 100644 --- a/docs/iamb.5 +++ b/docs/iamb.5 @@ -255,6 +255,8 @@ or .It Sy user_gutter_width Specify the width of the column where usernames are displayed in a room. Usernames that are too long are truncated. +If this value is less than 3, the usernames are displayed in an extra line at the top of the message +and this value is only used to indent the message. Defaults to 30. .It Sy tabstop diff --git a/src/message/mod.rs b/src/message/mod.rs index 05bc9d26..535f050e 100644 --- a/src/message/mod.rs +++ b/src/message/mod.rs @@ -599,6 +599,20 @@ impl MessageColumns { } } +#[derive(Default, Debug)] +enum SenderSpan<'a> { + /// Show the sender name in the user gutter. + /// This is truncated and padded to fit [`user_gutter_width`](`crate::config::TunableValues::user_gutter_width`). + Gutter(Span<'a>), + + /// Show the sender name in an extra line at the top of the message. + Line(Span<'a>), + + /// The sender name has already been printed. + #[default] + None, +} + struct MessageFormatter<'a> { settings: &'a ApplicationSettings, @@ -612,7 +626,7 @@ struct MessageFormatter<'a> { fill: usize, /// The formatted Span for the message sender. - user: Option>, + user: SenderSpan<'a>, /// The time the message was sent. time: Option>, @@ -629,6 +643,20 @@ impl<'a> MessageFormatter<'a> { self.fill } + fn message_start_line(&self) -> u16 { + let mut line = 0; + + if self.date.is_some() { + line += 1; + } + + if let SenderSpan::Line(_) = self.user { + line += 1; + } + + line + } + #[inline] fn push_spans(&mut self, prev_line: Line<'a>, style: Style, text: &mut Text<'a>) { if let Some(date) = self.date.take() { @@ -643,13 +671,21 @@ impl<'a> MessageFormatter<'a> { let user_gutter_empty_span = space_span(self.settings.tunables.user_gutter_width, Style::default()); + let user_gutter = match std::mem::take(&mut self.user) { + SenderSpan::Line(user) => { + text.lines.push(user.into()); + user_gutter_empty_span + }, + SenderSpan::Gutter(user) => user, + SenderSpan::None => user_gutter_empty_span, + }; + match self.cols { MessageColumns::Four => { let settings = self.settings; - let user = self.user.take().unwrap_or(user_gutter_empty_span); let time = self.time.take().unwrap_or(TIME_GUTTER_EMPTY_SPAN); - let mut line = vec![user]; + let mut line = vec![user_gutter]; line.extend(prev_line.spans); line.push(time); @@ -669,27 +705,21 @@ impl<'a> MessageFormatter<'a> { text.lines.push(Line::from(line)) }, MessageColumns::Three => { - let user = self.user.take().unwrap_or(user_gutter_empty_span); let time = self.time.take().unwrap_or_else(|| Span::from("")); - let mut line = vec![user]; + let mut line = vec![user_gutter]; line.extend(prev_line.spans); line.push(time); text.lines.push(Line::from(line)) }, MessageColumns::Two => { - let user = self.user.take().unwrap_or(user_gutter_empty_span); - let mut line = vec![user]; + let mut line = vec![user_gutter]; line.extend(prev_line.spans); text.lines.push(Line::from(line)); }, MessageColumns::One => { - if let Some(user) = self.user.take() { - text.lines.push(Line::from(vec![user])); - } - let leading = space_span(2, style); let mut line = vec![leading]; line.extend(prev_line.spans); @@ -933,7 +963,7 @@ impl Message { { let cols = MessageColumns::Four; let fill = width - user_gutter - TIME_GUTTER - READ_GUTTER; - let user = self.show_sender(prev, true, info, settings); + let user = self.show_sender(prev, true, info, settings, width); let time = self.timestamp.show_time(); let read = info .event_receipts @@ -947,7 +977,7 @@ impl Message { } else if user_gutter + TIME_GUTTER + MIN_MSG_LEN <= width { let cols = MessageColumns::Three; let fill = width - user_gutter - TIME_GUTTER; - let user = self.show_sender(prev, true, info, settings); + let user = self.show_sender(prev, true, info, settings, width); let time = self.timestamp.show_time(); let read = Vec::new(); @@ -955,7 +985,7 @@ impl Message { } else if user_gutter + MIN_MSG_LEN <= width { let cols = MessageColumns::Two; let fill = width - user_gutter; - let user = self.show_sender(prev, true, info, settings); + let user = self.show_sender(prev, true, info, settings, width); let time = None; let read = Vec::new(); @@ -963,7 +993,7 @@ impl Message { } else { let cols = MessageColumns::One; let fill = width.saturating_sub(2); - let user = self.show_sender(prev, false, info, settings); + let user = self.show_sender(prev, false, info, settings, width); let time = None; let read = Vec::new(); @@ -1006,9 +1036,9 @@ impl Message { let proto_main = proto.map(|p| { let y_off = text.lines.len() as u16; let x_off = fmt.cols.user_gutter_width(settings); - // Adjust y_off by 1 if a date was printed before the message to account for - // the extra line we're going to print. - let y_off = if fmt.date.is_some() { y_off + 1 } else { y_off }; + + // Account for extra lines printed before the message; + let y_off = y_off + fmt.message_start_line(); (p, x_off, y_off) }); @@ -1093,31 +1123,39 @@ impl Message { fn show_sender<'a>( &'a self, prev: Option<&Message>, - align_right: bool, + gutter_enabled: bool, info: &'a RoomInfo, settings: &'a ApplicationSettings, - ) -> Option> { + width: usize, + ) -> SenderSpan<'a> { if let Some(prev) = prev { if self.sender == prev.sender && self.timestamp.same_day(&prev.timestamp) && !self.event.is_emote() { - return None; + return SenderSpan::None; } } let Span { content, style } = self.sender_span(info, settings); let user_gutter = settings.tunables.user_gutter_width; - let ((truncated, width), _) = take_width(content, user_gutter - 2); - let padding = user_gutter - 2 - width; - let sender = if align_right { - format!("{}{} ", space(padding), truncated) - } else { - format!("{}{} ", truncated, space(padding)) - }; + let show_in_gutter = gutter_enabled && user_gutter > 2; + + if show_in_gutter { + let ((truncated, width), _) = take_width(content, user_gutter - 2); + let padding = user_gutter - 2 - width; + + let sender = format!("{}{} ", space(padding), truncated); - Span::styled(sender, style).into() + SenderSpan::Gutter(Span::styled(sender, style)) + } else if UnicodeWidthStr::width(content.as_ref()) > width { + let ((truncated, _), _) = take_width(content, width); + + SenderSpan::Line(Span::styled(truncated, style)) + } else { + SenderSpan::Line(Span::styled(content, style)) + } } pub fn redact(&mut self, redaction: SyncRoomRedactionEvent) {