diff --git a/docs/iamb.5 b/docs/iamb.5 index 46cd8970..b3870141 100644 --- a/docs/iamb.5 +++ b/docs/iamb.5 @@ -485,7 +485,7 @@ How to sort the .Sy :rooms window. Defaults to -.Sy ["favorite",\ "lowpriority",\ "unread",\ "name"] . +.Sy ["favorite",\ "invite",\ "lowpriority",\ "mentions",\ "notifications",\ "recent",\ "name"] . .It Sy chats How to sort the .Sy :chats @@ -532,7 +532,11 @@ Sort rooms by alphabetically ascending server name from the room alias. This will fall back to the server from the room identifier if there is no canonical alias for the room. .It Sy unread -Put unread rooms before other rooms. +Put rooms with unread messages before other rooms. +.It Sy notifications +Put rooms with unread notifications before other rooms. +.It Sy mentions +Put rooms with unread mentions before other rooms. .It Sy recent Sort rooms by most recent message timestamp. .It Sy invite diff --git a/src/base.rs b/src/base.rs index 344987ae..665fcfe1 100644 --- a/src/base.rs +++ b/src/base.rs @@ -277,6 +277,12 @@ pub enum SortFieldRoom { /// Sort rooms by whether they have unread messages. Unread, + /// Sort rooms by whether they have unread notifications. + Notifications, + + /// Sort rooms by whether they have unread mentions. + Mentions, + /// Sort rooms by the timestamps of their most recent messages. Recent, @@ -345,6 +351,8 @@ impl Visitor<'_> for SortRoomVisitor { "lowpriority" => SortFieldRoom::LowPriority, "recent" => SortFieldRoom::Recent, "unread" => SortFieldRoom::Unread, + "notifications" => SortFieldRoom::Notifications, + "mentions" => SortFieldRoom::Mentions, "name" => SortFieldRoom::Name, "alias" => SortFieldRoom::Alias, "id" => SortFieldRoom::RoomId, @@ -978,14 +986,6 @@ pub struct UnreadInfo { } impl UnreadInfo { - pub fn is_unread(&self) -> bool { - self.unread_mark || self.unread_notifications > 0 || self.unread_mentions > 0 - } - - pub fn has_mention(&self) -> bool { - self.unread_mentions > 0 - } - pub fn latest(&self) -> Option<&MessageTimeStamp> { self.latest.as_ref() } diff --git a/src/config.rs b/src/config.rs index 898f68a8..6b3e1c68 100644 --- a/src/config.rs +++ b/src/config.rs @@ -56,11 +56,13 @@ const DEFAULT_MEMBERS_SORT: [SortColumn; 4] = [ SortColumn(SortFieldUser::UserId, SortOrder::Ascending), ]; -const DEFAULT_ROOM_SORT: [SortColumn; 5] = [ +const DEFAULT_ROOM_SORT: [SortColumn; 7] = [ SortColumn(SortFieldRoom::Favorite, SortOrder::Ascending), SortColumn(SortFieldRoom::Invite, SortOrder::Ascending), SortColumn(SortFieldRoom::LowPriority, SortOrder::Ascending), - SortColumn(SortFieldRoom::Unread, SortOrder::Ascending), + SortColumn(SortFieldRoom::Mentions, SortOrder::Ascending), + SortColumn(SortFieldRoom::Notifications, SortOrder::Ascending), + SortColumn(SortFieldRoom::Recent, SortOrder::Ascending), SortColumn(SortFieldRoom::Name, SortOrder::Ascending), ]; diff --git a/src/windows/mod.rs b/src/windows/mod.rs index 458d00d1..a9f6c9c4 100644 --- a/src/windows/mod.rs +++ b/src/windows/mod.rs @@ -127,19 +127,37 @@ fn selected_text(s: &str, selected: bool) -> Text<'_> { Text::from(selected_span(s, selected)) } -fn name_and_labels<'a>( +fn name_unreads_labels<'a>( name: &'a str, unread: &UnreadInfo, room: &MatrixRoom, style: Style, -) -> (Span<'a>, Vec>>) { - // TODO: use different colors for "mention", "notification", "muted room" - let name_style = if unread.is_unread() { - style.add_modifier(StyleModifier::BOLD) +) -> (Span<'static>, Span<'a>, Vec>>) { + let (value, number_style) = if unread.unread_mentions > 0 { + (unread.unread_mentions + unread.unread_notifications, Color::Red) + } else if unread.unread_notifications > 0 { + (unread.unread_notifications, Color::Yellow) } else { - style + (unread.unread_messages, Color::Gray) }; + let unreads = if unread.unread_mark { + Span::styled(" U ", Color::Green) + } else if value > 99 { + Span::styled("99+ ", number_style) + } else if value == 0 { + Span::styled(" ", number_style) + } else { + Span::styled(format!(" {:2} ", value), number_style) + }; + + let name_style = + if unread.unread_mark || unread.unread_notifications > 0 || unread.unread_mentions > 0 { + style.bold() + } else { + style + }; + let name = Span::styled(name, name_style); let mut labels = vec![]; @@ -152,13 +170,7 @@ fn name_and_labels<'a>( MatrixRoomState::Invited => labels.push(vec![Span::styled("Invited", style)]), } - if unread.unread_mentions > 0 { - labels.push(vec![Span::styled("Unread Mention", style)]); - } else if unread.is_unread() { - labels.push(vec![Span::styled("Unread", style)]); - } - - (name, labels) + (unreads, name, labels) } /// Sort `Some` to be less than `None` so that list items with values come before those without. @@ -237,6 +249,14 @@ fn room_cmp( // Sort true (unread) before false (read) b.is_unread().cmp(&a.is_unread()) }, + SortFieldRoom::Notifications => { + // Sort true (unread) before false (read) + b.has_notification().cmp(&a.has_notification()) + }, + SortFieldRoom::Mentions => { + // Sort true (unread) before false (read) + b.has_mention().cmp(&a.has_mention()) + }, SortFieldRoom::Recent => { // sort larger timestamps towards the top. some_cmp(a.recent_ts(), b.recent_ts(), |a, b| b.cmp(a)) @@ -321,6 +341,8 @@ trait RoomLikeItem { fn room_id(&self) -> &RoomId; fn has_tag(&self, tag: TagName) -> bool; fn is_unread(&self) -> bool; + fn has_notification(&self) -> bool; + fn has_mention(&self) -> bool; fn recent_ts(&self) -> Option<&MessageTimeStamp>; fn alias(&self) -> Option<&RoomAliasId>; fn name(&self) -> &str; @@ -1033,11 +1055,6 @@ impl GenericChatItem { fn tags(&self) -> &Option { &self.room_info.deref().1 } - - #[inline] - fn has_mention(&self) -> bool { - self.unread.has_mention() - } } impl RoomLikeItem for GenericChatItem { @@ -1066,7 +1083,15 @@ impl RoomLikeItem for GenericChatItem { } fn is_unread(&self) -> bool { - self.unread.is_unread() + self.unread.unread_messages > 0 + } + + fn has_notification(&self) -> bool { + self.unread.unread_notifications > 0 + } + + fn has_mention(&self) -> bool { + self.unread.unread_mentions > 0 } fn is_invite(&self) -> bool { @@ -1088,8 +1113,9 @@ impl ListItem for GenericChatItem { _: &mut ProgramStore, ) -> Text<'_> { let style = selected_style(selected); - let (name, mut labels) = name_and_labels(&self.name, &self.unread, self.room(), style); - let mut spans = vec![name]; + let (unreads, name, mut labels) = + name_unreads_labels(&self.name, &self.unread, self.room(), style); + let mut spans = vec![unreads, name]; labels.push(if self.is_dm { vec![Span::styled("DM", style)] @@ -1184,7 +1210,15 @@ impl RoomLikeItem for RoomItem { } fn is_unread(&self) -> bool { - self.unread.is_unread() + self.unread.unread_messages > 0 + } + + fn has_notification(&self) -> bool { + self.unread.unread_notifications > 0 + } + + fn has_mention(&self) -> bool { + self.unread.unread_mentions > 0 } fn is_invite(&self) -> bool { @@ -1206,8 +1240,9 @@ impl ListItem for RoomItem { _: &mut ProgramStore, ) -> Text<'_> { let style = selected_style(selected); - let (name, mut labels) = name_and_labels(&self.name, &self.unread, self.room(), style); - let mut spans = vec![name]; + let (unreads, name, mut labels) = + name_unreads_labels(&self.name, &self.unread, self.room(), style); + let mut spans = vec![unreads, name]; if let Some(tags) = &self.tags() { labels.extend(tags.keys().map(|t| tag_to_span(t, style))); @@ -1293,7 +1328,15 @@ impl RoomLikeItem for DirectItem { } fn is_unread(&self) -> bool { - self.unread.is_unread() + self.unread.unread_messages > 0 + } + + fn has_notification(&self) -> bool { + self.unread.unread_notifications > 0 + } + + fn has_mention(&self) -> bool { + self.unread.unread_mentions > 0 } fn is_invite(&self) -> bool { @@ -1315,8 +1358,9 @@ impl ListItem for DirectItem { _: &mut ProgramStore, ) -> Text<'_> { let style = selected_style(selected); - let (name, mut labels) = name_and_labels(&self.name, &self.unread, self.room(), style); - let mut spans = vec![name]; + let (unreads, name, mut labels) = + name_unreads_labels(&self.name, &self.unread, self.room(), style); + let mut spans = vec![unreads, name]; if let Some(tags) = &self.tags() { labels.extend(tags.keys().map(|t| tag_to_span(t, style))); @@ -1403,6 +1447,16 @@ impl RoomLikeItem for SpaceItem { false } + fn has_notification(&self) -> bool { + // XXX: this needs to check whether the space contains rooms with unread messages + false + } + + fn has_mention(&self) -> bool { + // XXX: this needs to check whether the space contains rooms with unread messages + false + } + fn is_invite(&self) -> bool { self.room().state() == MatrixRoomState::Invited } @@ -1621,7 +1675,15 @@ mod tests { } fn is_unread(&self) -> bool { - self.unread.is_unread() + self.unread.unread_messages > 0 + } + + fn has_notification(&self) -> bool { + self.unread.unread_notifications > 0 + } + + fn has_mention(&self) -> bool { + self.unread.unread_mentions > 0 } fn is_invite(&self) -> bool {