buflist: Use different color when only non-messages are unread
Some checks failed
CI / lint (push) Failing after 2m30s
CI / Build and test (, 1.73.0) (push) Successful in 5m1s
CI / Build and test (, beta) (push) Successful in 6m8s
CI / Build and test (, nightly) (push) Successful in 5m24s

This commit is contained in:
2023-11-19 18:24:26 +01:00
parent b60834ebb3
commit 30b1e4282a
6 changed files with 127 additions and 66 deletions

View File

@ -25,7 +25,7 @@ use tokio::sync::mpsc::UnboundedReceiver;
use tracing_error::ErrorLayer;
use tracing_subscriber::prelude::*;
use super::{Buffer, BufferId, BufferItem, BufferItemContent};
use super::{Buffer, BufferId, BufferItem, BufferItemContent, FullyReadStatus};
use crate::widgets::Prerender;
/// Maximum number of log lines to be stored in memory
@ -103,8 +103,8 @@ impl Buffer for LogBuffer {
Default::default()
}
fn fully_read(&self) -> bool {
fn fully_read(&self) -> FullyReadStatus {
// TODO
true
FullyReadStatus::All
}
}

View File

@ -31,6 +31,18 @@ pub use log::LogBuffer;
mod room;
pub use room::RoomBuffer;
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum FullyReadStatus {
/// Nothing is read
None,
/// There are some unread messages
Not,
/// All messages are read, but some non-messages events are not
OnlyMessages,
/// All events are read
All,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum BufferId {
/// The main/home buffer
@ -120,7 +132,7 @@ pub trait Buffer: Send + Sync + memuse::DynamicUsage {
fn request_back_pagination(&self, num: u16) {}
fn unread_notification_counts(&self) -> matrix_sdk::sync::UnreadNotificationsCount;
fn fully_read(&self) -> bool;
fn fully_read(&self) -> FullyReadStatus;
fn dynamic_usage(&self) -> usize {
memuse::DynamicUsage::dynamic_usage(self)

View File

@ -46,13 +46,17 @@ use smallvec::SmallVec;
use sorted_vec::SortedVec;
use tokio::sync::oneshot;
use super::{Buffer, BufferId, BufferItem, BufferItemContent, BufferSortKey};
use super::{Buffer, BufferId, BufferItem, BufferItemContent, BufferSortKey, FullyReadStatus};
use crate::widgets::Prerender;
/// Like [`BufferItemContent`] but owned.
#[derive(Debug, Clone)]
pub enum OwnedBufferItemContent {
Text(String),
Text {
event_id: Option<OwnedEventId>,
is_message: bool,
text: String,
},
Divider(String),
Empty,
}
@ -61,13 +65,15 @@ impl DynamicUsage for OwnedBufferItemContent {
fn dynamic_usage(&self) -> usize {
std::mem::size_of::<Self>()
+ match self {
OwnedBufferItemContent::Text(s) | OwnedBufferItemContent::Divider(s) => s.dynamic_usage(),
OwnedBufferItemContent::Text { text: s, .. } | OwnedBufferItemContent::Divider(s) => {
s.dynamic_usage()
},
OwnedBufferItemContent::Empty => 0,
}
}
fn dynamic_usage_bounds(&self) -> (usize, Option<usize>) {
let (min, max) = match self {
OwnedBufferItemContent::Text(s) | OwnedBufferItemContent::Divider(s) => {
OwnedBufferItemContent::Text { text: s, .. } | OwnedBufferItemContent::Divider(s) => {
s.dynamic_usage_bounds()
},
OwnedBufferItemContent::Empty => (0, Some(0)),
@ -82,18 +88,11 @@ impl DynamicUsage for OwnedBufferItemContent {
}
}
/// Like `format!()` but returns OwnedBufferItemContent::Text
macro_rules! text {
($($tokens:tt)*) => {
OwnedBufferItemContent::Text(format!($($tokens)*))
}
}
pub struct SingleClientRoomBuffer {
room_id: OwnedRoomId,
client: Client,
/// Oldest first
/// Newest first
items: imbl::vector::Vector<(Option<u64>, OwnedBufferItemContent, Prerender)>,
latest_event_id: Option<OwnedEventId>,
// TODO: get rid of this trait object, we know it's matrix_sdk_ui::timeline::TimelineStream
@ -163,15 +162,38 @@ impl SingleClientRoomBuffer {
match tl_item.as_ref().kind() {
TimelineItemKind::Event(event) => {
use matrix_sdk_ui::timeline::TimelineItemContent::*;
// Like `format!()` but returns OwnedBufferItemContent::Text, with is_message=false
macro_rules! text {
($($tokens:tt)*) => {
OwnedBufferItemContent::Text {
event_id: event.event_id().map(ToOwned::to_owned),
is_message: false,
text: format!($($tokens)*)
}
}
}
// Like `format!()` but returns OwnedBufferItemContent::Text, with is_message=true
macro_rules! msg {
($($tokens:tt)*) => {
OwnedBufferItemContent::Text {
event_id: event.event_id().map(ToOwned::to_owned),
is_message: true,
text: format!($($tokens)*)
}
}
}
let sender = event
.sender()
.as_str()
.strip_prefix('@')
.expect("missing @ prefix");
match event.content() {
Message(message) => text!(" <{}> {}", sender, message.body().replace('\n', "\n ")),
RedactedMessage => text!("xx <{}> [redacted]", sender),
Sticker(sticker) => text!("st <{}> {}", sender, sticker.content().body),
Message(message) => msg!(" <{}> {}", sender, message.body().replace('\n', "\n ")),
RedactedMessage => msg!("xx <{}> [redacted]", sender),
Sticker(sticker) => msg!("st <{}> {}", sender, sticker.content().body),
UnableToDecrypt(_) => text!("xx <{}> [unable to decrypt]", sender),
MembershipChange(change) => {
use matrix_sdk_ui::timeline::MembershipChange::*;
@ -412,7 +434,17 @@ impl RoomBuffer {
timeline: Arc::new(timeline),
items: items // FIXME: it's always empty. why?
.into_iter()
.map(|item| (None, text!("Initial item: {:#?}", item), Prerender::new()))
.map(|item| {
(
None,
OwnedBufferItemContent::Text {
event_id: None,
is_message: false,
text: format!("Initial item: {:#?}", item),
},
Prerender::new(),
)
})
.collect(),
latest_event_id: None,
timeline_stream: Box::new(timeline_stream),
@ -551,7 +583,7 @@ impl Buffer for RoomBuffer {
.rev()
.map(|(line_id, line, prerender)| BufferItem {
content: match line {
OwnedBufferItemContent::Text(text) => BufferItemContent::Text(Text::raw(text)),
OwnedBufferItemContent::Text { text, .. } => BufferItemContent::Text(Text::raw(text)),
OwnedBufferItemContent::Divider(text) => BufferItemContent::Divider(Text::raw(text)),
OwnedBufferItemContent::Empty => BufferItemContent::Empty,
},
@ -587,14 +619,14 @@ impl Buffer for RoomBuffer {
acc
}
fn fully_read(&self) -> bool {
fn fully_read(&self) -> FullyReadStatus {
match self
.computed_roominfo
.as_ref()
.and_then(|ri| ri.fully_read_at.as_ref())
{
None => true, // Unknown, assume it's read for now, we'll probably find out later
Some(None) => false, // Nothing is read
None => FullyReadStatus::All, // Unknown, assume it's read for now, we'll probably find out later
Some(None) => FullyReadStatus::None, // Not read at all
Some(Some(fully_read_at)) => {
// Iterate through all buffers, and if any buffer's last event is not the one where
// the m.fully_read marker is at, assume the buffer is not read.
@ -606,19 +638,48 @@ impl Buffer for RoomBuffer {
// such a desync usually means the message is very recent, hence unread. (Or it's
// a federation desync, and we should probably tell the user about it somehow. But
// this is out of scope for this function.)
let mut status = FullyReadStatus::All;
for buf in &self.buffers {
let Some(latest_event_id) = &buf.latest_event_id else {
// This buffer contains no event at all, request a small backfill and assume
// it is read for now.
buf.back_pagination_request.fetch_max(1, Ordering::Relaxed);
continue;
};
if latest_event_id != fully_read_at {
return false;
let mut reached_fully_read = false;
for item in buf.items.iter().rev() {
match item {
(
_id,
OwnedBufferItemContent::Text {
event_id: Some(event_id),
is_message,
..
},
_prerender,
) => {
if event_id == fully_read_at {
reached_fully_read = true;
break;
}
if *is_message {
status = FullyReadStatus::min(status, FullyReadStatus::Not);
// We found an unread message, no point in looking further
break;
} else {
status = FullyReadStatus::min(status, FullyReadStatus::OnlyMessages);
}
},
_ => {}, // Ignore everything else, they can't be marked read or unread
}
}
if !reached_fully_read && status != FullyReadStatus::Not {
// The latest read event is not in the buffer, request a small backfill
// to try to reach it, unless:
// * we already backfilled a lot so we don't exhaust the available memory
// * we already reached an unread message, in which case it's pointless to
// backfill as we already know there are unread messages.
if buf.items.len() < 1000 {
buf.back_pagination_request.fetch_max(50, Ordering::Relaxed);
}
}
}
true
status
},
}
}

View File

@ -23,7 +23,7 @@ use tokio::sync::OnceCell;
use super::Component;
use crate::{
action::Action,
buffers::{Buffer, Buffers},
buffers::{Buffer, Buffers, FullyReadStatus},
config::{Config, KeyBindings},
};
@ -61,35 +61,23 @@ impl Buflist {
pub fn get_room_name_style(&self, buf: &dyn Buffer) -> Style {
use matrix_sdk::sync::UnreadNotificationsCount;
let config = &self.config.style.buflist;
match (buf.fully_read(), buf.unread_notification_counts()) {
(
true,
UnreadNotificationsCount {
highlight_count: 0,
notification_count: 0,
},
) => *config.uneventful,
(
_,
UnreadNotificationsCount {
highlight_count: 0,
notification_count: 0,
},
) => *config.unread_message,
(
_,
UnreadNotificationsCount {
highlight_count: 0,
notification_count: _,
},
) => *config.notification,
(
_,
UnreadNotificationsCount {
highlight_count: _,
notification_count: _,
},
) => *config.highlight,
match buf.unread_notification_counts() {
UnreadNotificationsCount {
highlight_count: 0,
notification_count: 0,
} => match buf.fully_read() {
FullyReadStatus::Not | FullyReadStatus::None => *config.unread_message,
FullyReadStatus::OnlyMessages => *config.unread_event,
FullyReadStatus::All => *config.uneventful,
},
UnreadNotificationsCount {
highlight_count: 0,
notification_count: _,
} => *config.notification,
UnreadNotificationsCount {
highlight_count: _,
notification_count: _,
} => *config.highlight,
}
}
}

View File

@ -68,7 +68,7 @@ pub struct BuflistStylesConfig {
pub highlight: StyleConfig,
pub notification: StyleConfig,
pub unread_message: StyleConfig,
// TODO: pub unread_event: StyleConfig,
pub unread_event: StyleConfig,
pub uneventful: StyleConfig,
}

View File

@ -33,7 +33,7 @@ highlight = "bold yellow"
notification = "bold green"
# When no notification, but there is a new message
unread_message = "red"
# TODO: When no new message, but something changed (someone joined, left, topic changed, ...)
# unread_event = "bold"
# When no new message, but something changed (someone joined, left, topic changed, ...)
unread_event = "bold"
# If nothing happened since you last looked at the room
uneventful = ""