mirror of
https://github.com/youwen5/iamb.git
synced 2025-06-20 05:39:52 -07:00
Support hiding server part of username in message scrollback (#71)
This commit is contained in:
parent
61aba80be1
commit
64891ec68f
8 changed files with 164 additions and 37 deletions
13
src/base.rs
13
src/base.rs
|
@ -445,6 +445,9 @@ pub struct RoomInfo {
|
|||
|
||||
/// Users currently typing in this room, and when we received notification of them doing so.
|
||||
pub users_typing: Option<(Instant, Vec<OwnedUserId>)>,
|
||||
|
||||
/// The display names for users in this room.
|
||||
pub display_names: HashMap<OwnedUserId, String>,
|
||||
}
|
||||
|
||||
impl RoomInfo {
|
||||
|
@ -583,13 +586,13 @@ impl RoomInfo {
|
|||
match n {
|
||||
0 => Spans(vec![]),
|
||||
1 => {
|
||||
let user = settings.get_user_span(typers[0].as_ref());
|
||||
let user = settings.get_user_span(typers[0].as_ref(), self);
|
||||
|
||||
Spans(vec![user, Span::from(" is typing...")])
|
||||
},
|
||||
2 => {
|
||||
let user1 = settings.get_user_span(typers[0].as_ref());
|
||||
let user2 = settings.get_user_span(typers[1].as_ref());
|
||||
let user1 = settings.get_user_span(typers[0].as_ref(), self);
|
||||
let user2 = settings.get_user_span(typers[1].as_ref(), self);
|
||||
|
||||
Spans(vec![
|
||||
user1,
|
||||
|
@ -835,11 +838,11 @@ impl<'de> Visitor<'de> for IambIdVisitor {
|
|||
},
|
||||
Some("members") => {
|
||||
let Some(path) = url.path_segments() else {
|
||||
return Err(E::custom( "Invalid members window URL"));
|
||||
return Err(E::custom("Invalid members window URL"));
|
||||
};
|
||||
|
||||
let &[room_id] = path.collect::<Vec<_>>().as_slice() else {
|
||||
return Err(E::custom( "Invalid members window URL"));
|
||||
return Err(E::custom("Invalid members window URL"));
|
||||
};
|
||||
|
||||
let Ok(room_id) = OwnedRoomId::try_from(room_id) else {
|
||||
|
|
|
@ -19,7 +19,7 @@ use modalkit::tui::{
|
|||
text::Span,
|
||||
};
|
||||
|
||||
use super::base::IambId;
|
||||
use super::base::{IambId, RoomInfo};
|
||||
|
||||
macro_rules! usage {
|
||||
( $($args: tt)* ) => {
|
||||
|
@ -227,6 +227,24 @@ fn merge_users(a: Option<UserOverrides>, b: Option<UserOverrides>) -> Option<Use
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum UserDisplayStyle {
|
||||
// The Matrix username for the sender (e.g., "@user:example.com").
|
||||
#[default]
|
||||
Username,
|
||||
|
||||
// The localpart of the Matrix username (e.g., "@user").
|
||||
LocalPart,
|
||||
|
||||
// The display name for the Matrix user, calculated according to the rules from the spec.
|
||||
//
|
||||
// This is usually something like "Ada Lovelace" if the user has configured a display name, but
|
||||
// it can wind up being the Matrix username if there are display name collisions in the room,
|
||||
// in order to avoid any confusion.
|
||||
DisplayName,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TunableValues {
|
||||
pub log_level: Level,
|
||||
|
@ -238,6 +256,7 @@ pub struct TunableValues {
|
|||
pub typing_notice_send: bool,
|
||||
pub typing_notice_display: bool,
|
||||
pub users: UserOverrides,
|
||||
pub username_display: UserDisplayStyle,
|
||||
pub default_room: Option<String>,
|
||||
pub open_command: Option<Vec<String>>,
|
||||
}
|
||||
|
@ -253,6 +272,7 @@ pub struct Tunables {
|
|||
pub typing_notice_send: Option<bool>,
|
||||
pub typing_notice_display: Option<bool>,
|
||||
pub users: Option<UserOverrides>,
|
||||
pub username_display: Option<UserDisplayStyle>,
|
||||
pub default_room: Option<String>,
|
||||
pub open_command: Option<Vec<String>>,
|
||||
}
|
||||
|
@ -271,6 +291,7 @@ impl Tunables {
|
|||
typing_notice_send: self.typing_notice_send.or(other.typing_notice_send),
|
||||
typing_notice_display: self.typing_notice_display.or(other.typing_notice_display),
|
||||
users: merge_users(self.users, other.users),
|
||||
username_display: self.username_display.or(other.username_display),
|
||||
default_room: self.default_room.or(other.default_room),
|
||||
open_command: self.open_command.or(other.open_command),
|
||||
}
|
||||
|
@ -287,6 +308,7 @@ impl Tunables {
|
|||
typing_notice_send: self.typing_notice_send.unwrap_or(true),
|
||||
typing_notice_display: self.typing_notice_display.unwrap_or(true),
|
||||
users: self.users.unwrap_or_default(),
|
||||
username_display: self.username_display.unwrap_or_default(),
|
||||
default_room: self.default_room,
|
||||
open_command: self.open_command,
|
||||
}
|
||||
|
@ -525,18 +547,45 @@ impl ApplicationSettings {
|
|||
Span::styled(String::from(c), style)
|
||||
}
|
||||
|
||||
pub fn get_user_span<'a>(&self, user_id: &'a UserId) -> Span<'a> {
|
||||
let (color, name) = self
|
||||
.tunables
|
||||
pub fn get_user_overrides(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
) -> (Option<Color>, Option<Cow<'static, str>>) {
|
||||
self.tunables
|
||||
.users
|
||||
.get(user_id)
|
||||
.map(|user| (user.color.as_ref().map(|c| c.0), user.name.clone().map(Cow::Owned)))
|
||||
.unwrap_or_default();
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
let user_id = user_id.as_str();
|
||||
let color = color.unwrap_or_else(|| user_color(user_id));
|
||||
pub fn get_user_style(&self, user_id: &UserId) -> Style {
|
||||
let color = self
|
||||
.tunables
|
||||
.users
|
||||
.get(user_id)
|
||||
.and_then(|user| user.color.as_ref().map(|c| c.0))
|
||||
.unwrap_or_else(|| user_color(user_id.as_str()));
|
||||
|
||||
user_style_from_color(color)
|
||||
}
|
||||
|
||||
pub fn get_user_span<'a>(&self, user_id: &'a UserId, info: &'a RoomInfo) -> Span<'a> {
|
||||
let (color, name) = self.get_user_overrides(user_id);
|
||||
|
||||
let color = color.unwrap_or_else(|| user_color(user_id.as_str()));
|
||||
let style = user_style_from_color(color);
|
||||
let name = name.unwrap_or(Cow::Borrowed(user_id));
|
||||
let name = match (name, &self.tunables.username_display) {
|
||||
(Some(name), _) => name,
|
||||
(None, UserDisplayStyle::Username) => Cow::Borrowed(user_id.as_str()),
|
||||
(None, UserDisplayStyle::LocalPart) => Cow::Borrowed(user_id.localpart()),
|
||||
(None, UserDisplayStyle::DisplayName) => {
|
||||
if let Some(display) = info.display_names.get(user_id) {
|
||||
Cow::Borrowed(display.as_str())
|
||||
} else {
|
||||
Cow::Borrowed(user_id.as_str())
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Span::styled(name, style)
|
||||
}
|
||||
|
@ -641,6 +690,19 @@ mod tests {
|
|||
assert_eq!(res.users, Some(users.into_iter().collect()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_tunables_username_display() {
|
||||
let res: Tunables = serde_json::from_str("{\"username_display\": \"username\"}").unwrap();
|
||||
assert_eq!(res.username_display, Some(UserDisplayStyle::Username));
|
||||
|
||||
let res: Tunables = serde_json::from_str("{\"username_display\": \"localpart\"}").unwrap();
|
||||
assert_eq!(res.username_display, Some(UserDisplayStyle::LocalPart));
|
||||
|
||||
let res: Tunables =
|
||||
serde_json::from_str("{\"username_display\": \"displayname\"}").unwrap();
|
||||
assert_eq!(res.username_display, Some(UserDisplayStyle::DisplayName));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_layout() {
|
||||
let user = WindowPath::UserId(user_id!("@user:example.com").to_owned());
|
||||
|
|
|
@ -644,7 +644,7 @@ impl Message {
|
|||
{
|
||||
let cols = MessageColumns::Four;
|
||||
let fill = width - USER_GUTTER - TIME_GUTTER - READ_GUTTER;
|
||||
let user = self.show_sender(prev, true, settings);
|
||||
let user = self.show_sender(prev, true, info, settings);
|
||||
let time = self.timestamp.show_time();
|
||||
let read = match info.receipts.get(self.event.event_id()) {
|
||||
Some(read) => read.iter(),
|
||||
|
@ -655,7 +655,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, settings);
|
||||
let user = self.show_sender(prev, true, info, settings);
|
||||
let time = self.timestamp.show_time();
|
||||
let read = [].iter();
|
||||
|
||||
|
@ -663,7 +663,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, settings);
|
||||
let user = self.show_sender(prev, true, info, settings);
|
||||
let time = None;
|
||||
let read = [].iter();
|
||||
|
||||
|
@ -671,7 +671,7 @@ impl Message {
|
|||
} else {
|
||||
let cols = MessageColumns::One;
|
||||
let fill = width.saturating_sub(2);
|
||||
let user = self.show_sender(prev, false, settings);
|
||||
let user = self.show_sender(prev, false, info, settings);
|
||||
let time = None;
|
||||
let read = [].iter();
|
||||
|
||||
|
@ -700,7 +700,7 @@ impl Message {
|
|||
if let Some(r) = &reply {
|
||||
let w = width.saturating_sub(2);
|
||||
let mut replied = r.show_msg(w, style, true);
|
||||
let mut sender = r.sender_span(settings);
|
||||
let mut sender = r.sender_span(info, settings);
|
||||
let sender_width = UnicodeWidthStr::width(sender.content.as_ref());
|
||||
let trailing = w.saturating_sub(sender_width + 1);
|
||||
|
||||
|
@ -793,16 +793,21 @@ impl Message {
|
|||
}
|
||||
}
|
||||
|
||||
fn sender_span(&self, settings: &ApplicationSettings) -> Span {
|
||||
settings.get_user_span(self.sender.as_ref())
|
||||
fn sender_span<'a>(
|
||||
&'a self,
|
||||
info: &'a RoomInfo,
|
||||
settings: &'a ApplicationSettings,
|
||||
) -> Span<'a> {
|
||||
settings.get_user_span(self.sender.as_ref(), info)
|
||||
}
|
||||
|
||||
fn show_sender(
|
||||
&self,
|
||||
fn show_sender<'a>(
|
||||
&'a self,
|
||||
prev: Option<&Message>,
|
||||
align_right: bool,
|
||||
settings: &ApplicationSettings,
|
||||
) -> Option<Span> {
|
||||
info: &'a RoomInfo,
|
||||
settings: &'a ApplicationSettings,
|
||||
) -> Option<Span<'a>> {
|
||||
if let Some(prev) = prev {
|
||||
if self.sender == prev.sender &&
|
||||
self.timestamp.same_day(&prev.timestamp) &&
|
||||
|
@ -812,7 +817,7 @@ impl Message {
|
|||
}
|
||||
}
|
||||
|
||||
let Span { content, style } = self.sender_span(settings);
|
||||
let Span { content, style } = self.sender_span(info, settings);
|
||||
let stop = content.len().min(28);
|
||||
let s = &content[..stop];
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ use crate::{
|
|||
ProfileConfig,
|
||||
TunableValues,
|
||||
UserColor,
|
||||
UserDisplayStyle,
|
||||
UserDisplayTunables,
|
||||
},
|
||||
message::{
|
||||
|
@ -160,6 +161,7 @@ pub fn mock_room() -> RoomInfo {
|
|||
fetch_id: RoomFetchStatus::NotStarted,
|
||||
fetch_last: None,
|
||||
users_typing: None,
|
||||
display_names: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -189,6 +191,7 @@ pub fn mock_tunables() -> TunableValues {
|
|||
.into_iter()
|
||||
.collect::<HashMap<_, _>>(),
|
||||
open_command: None,
|
||||
username_display: UserDisplayStyle::Username,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -409,7 +409,7 @@ impl WindowOps<IambInfo> for IambWindow {
|
|||
|
||||
if need_fetch {
|
||||
if let Ok(mems) = store.application.worker.members(room_id.clone()) {
|
||||
let items = mems.into_iter().map(MemberItem::new);
|
||||
let items = mems.into_iter().map(|m| MemberItem::new(m, room_id.clone()));
|
||||
state.set(items.collect());
|
||||
*last_fetch = Some(Instant::now());
|
||||
}
|
||||
|
@ -1100,11 +1100,12 @@ impl Promptable<ProgramContext, ProgramStore, IambInfo> for VerifyItem {
|
|||
#[derive(Clone)]
|
||||
pub struct MemberItem {
|
||||
member: RoomMember,
|
||||
room_id: OwnedRoomId,
|
||||
}
|
||||
|
||||
impl MemberItem {
|
||||
fn new(member: RoomMember) -> Self {
|
||||
Self { member }
|
||||
fn new(member: RoomMember, room_id: OwnedRoomId) -> Self {
|
||||
Self { member, room_id }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1121,12 +1122,32 @@ impl ListItem<IambInfo> for MemberItem {
|
|||
_: &ViewportContext<ListCursor>,
|
||||
store: &mut ProgramStore,
|
||||
) -> Text {
|
||||
let mut user = store.application.settings.get_user_span(self.member.user_id());
|
||||
let info = store.application.rooms.get_or_default(self.room_id.clone());
|
||||
let user_id = self.member.user_id();
|
||||
|
||||
let (color, name) = store.application.settings.get_user_overrides(self.member.user_id());
|
||||
let color = color.unwrap_or_else(|| super::config::user_color(user_id.as_str()));
|
||||
let mut style = super::config::user_style_from_color(color);
|
||||
|
||||
if selected {
|
||||
user.style = user.style.add_modifier(StyleModifier::REVERSED);
|
||||
style = style.add_modifier(StyleModifier::REVERSED);
|
||||
}
|
||||
|
||||
let mut spans = vec![];
|
||||
let mut parens = false;
|
||||
|
||||
if let Some(name) = name {
|
||||
spans.push(Span::styled(name, style));
|
||||
parens = true;
|
||||
} else if let Some(display) = info.display_names.get(user_id) {
|
||||
spans.push(Span::styled(display.clone(), style));
|
||||
parens = true;
|
||||
}
|
||||
|
||||
spans.extend(parens.then_some(Span::styled(" (", style)));
|
||||
spans.push(Span::styled(user_id.as_str(), style));
|
||||
spans.extend(parens.then_some(Span::styled(")", style)));
|
||||
|
||||
let state = match self.member.membership() {
|
||||
MembershipState::Ban => Span::raw(" (banned)").into(),
|
||||
MembershipState::Invite => Span::raw(" (invited)").into(),
|
||||
|
@ -1136,11 +1157,9 @@ impl ListItem<IambInfo> for MemberItem {
|
|||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(state) = state {
|
||||
Spans(vec![user, state]).into()
|
||||
} else {
|
||||
user.into()
|
||||
}
|
||||
spans.extend(state);
|
||||
|
||||
return Spans(spans).into();
|
||||
}
|
||||
|
||||
fn get_word(&self) -> Option<String> {
|
||||
|
|
|
@ -808,7 +808,8 @@ impl<'a> StatefulWidget for Chat<'a> {
|
|||
state.reply_to.as_ref().and_then(|k| {
|
||||
let room = self.store.application.rooms.get(state.id())?;
|
||||
let msg = room.messages.get(k)?;
|
||||
let user = self.store.application.settings.get_user_span(msg.sender.as_ref());
|
||||
let user =
|
||||
self.store.application.settings.get_user_span(msg.sender.as_ref(), room);
|
||||
let prefix = if editing.is_some() {
|
||||
Span::from("Editing reply to ")
|
||||
} else {
|
||||
|
|
|
@ -139,8 +139,9 @@ impl RoomState {
|
|||
let mut invited = vec![Span::from(format!("You have been invited to join {name}"))];
|
||||
|
||||
if let Ok(Some(inviter)) = &inviter {
|
||||
let info = store.application.rooms.get_or_default(self.id().to_owned());
|
||||
invited.push(Span::from(" by "));
|
||||
invited.push(store.application.settings.get_user_span(inviter.user_id()));
|
||||
invited.push(store.application.settings.get_user_span(inviter.user_id(), info));
|
||||
}
|
||||
|
||||
let l1 = Spans(invited);
|
||||
|
|
|
@ -40,6 +40,7 @@ use matrix_sdk::{
|
|||
reaction::ReactionEventContent,
|
||||
room::{
|
||||
encryption::RoomEncryptionEventContent,
|
||||
member::OriginalSyncRoomMemberEvent,
|
||||
message::{MessageType, RoomMessageEventContent},
|
||||
name::RoomNameEventContent,
|
||||
redaction::{OriginalSyncRoomRedactionEvent, SyncRoomRedactionEvent},
|
||||
|
@ -838,6 +839,38 @@ impl ClientWorker {
|
|||
},
|
||||
);
|
||||
|
||||
let _ = self.client.add_event_handler(
|
||||
|ev: OriginalSyncRoomMemberEvent,
|
||||
room: MatrixRoom,
|
||||
client: Client,
|
||||
store: Ctx<AsyncProgramStore>| {
|
||||
async move {
|
||||
let room_id = room.room_id();
|
||||
let user_id = ev.state_key;
|
||||
|
||||
let ambiguous_name =
|
||||
ev.content.displayname.as_deref().unwrap_or_else(|| user_id.localpart());
|
||||
let ambiguous = client
|
||||
.store()
|
||||
.get_users_with_display_name(room_id, ambiguous_name)
|
||||
.await
|
||||
.map(|users| users.len() > 1)
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut locked = store.lock().await;
|
||||
let info = locked.application.get_room_info(room_id.to_owned());
|
||||
|
||||
if ambiguous {
|
||||
info.display_names.remove(&user_id);
|
||||
} else if let Some(display) = ev.content.displayname {
|
||||
info.display_names.insert(user_id, display);
|
||||
} else {
|
||||
info.display_names.remove(&user_id);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
let _ = self.client.add_event_handler(
|
||||
|ev: OriginalSyncKeyVerificationStartEvent,
|
||||
client: Client,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue