From e222912acf98c428c53096c84b4eb540126d6a96 Mon Sep 17 00:00:00 2001 From: Youwen Wu Date: Mon, 16 Jun 2025 10:36:27 -0700 Subject: [PATCH 1/9] feat: add regex option for username_display --- src/config.rs | 21 +++++++++++++++++++++ src/tests.rs | 1 + 2 files changed, 22 insertions(+) diff --git a/src/config.rs b/src/config.rs index f22bd05..3da111f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -399,6 +399,9 @@ pub enum UserDisplayStyle { // it can wind up being the Matrix username if there are display name collisions in the room, // in order to avoid any confusion. DisplayName, + + // Acts like Username, except when the username matches given regex, then acts like DisplayName + Regex, } #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -568,6 +571,7 @@ pub struct TunableValues { pub typing_notice_display: bool, pub users: UserOverrides, pub username_display: UserDisplayStyle, + pub username_display_regex: Option, pub message_user_color: bool, pub default_room: Option, pub open_command: Option>, @@ -595,6 +599,7 @@ pub struct Tunables { pub typing_notice_display: Option, pub users: Option, pub username_display: Option, + pub username_display_regex: Option, pub message_user_color: Option, pub default_room: Option, pub open_command: Option>, @@ -626,6 +631,7 @@ impl Tunables { typing_notice_display: self.typing_notice_display.or(other.typing_notice_display), users: merge_maps(self.users, other.users), username_display: self.username_display.or(other.username_display), + username_display_regex: self.username_display_regex.or(other.username_display_regex), message_user_color: self.message_user_color.or(other.message_user_color), default_room: self.default_room.or(other.default_room), open_command: self.open_command.or(other.open_command), @@ -655,6 +661,7 @@ impl Tunables { typing_notice_display: self.typing_notice_display.unwrap_or(true), users: self.users.unwrap_or_default(), username_display: self.username_display.unwrap_or_default(), + username_display_regex: self.username_display_regex, message_user_color: self.message_user_color.unwrap_or(false), default_room: self.default_room, open_command: self.open_command, @@ -1048,6 +1055,20 @@ impl ApplicationSettings { Cow::Borrowed(user_id.as_str()) } }, + (None, UserDisplayStyle::Regex) => { + let re = regex::Regex::new( + &self.tunables.username_display_regex.clone().unwrap_or("*".into()), + ) + .unwrap(); + + if !re.is_match(user_id.as_str()) { + Cow::Borrowed(user_id.as_str()) + } else 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) diff --git a/src/tests.rs b/src/tests.rs index 3e3f825..7e957e1 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -189,6 +189,7 @@ pub fn mock_tunables() -> TunableValues { open_command: None, external_edit_file_suffix: String::from(".md"), username_display: UserDisplayStyle::Username, + username_display_regex: Some(String::from(".*")), message_user_color: false, mouse: Default::default(), notifications: Notifications { From 1f88ef6e02bc5e5a4b71f9e27b21980bd0eb5ca6 Mon Sep 17 00:00:00 2001 From: Youwen Wu Date: Mon, 16 Jun 2025 10:42:50 -0700 Subject: [PATCH 2/9] feat: add test for new username_display config options --- src/config.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/config.rs b/src/config.rs index 3da111f..4eac881 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1185,6 +1185,13 @@ mod tests { let res: Tunables = serde_json::from_str("{\"username_display\": \"displayname\"}").unwrap(); assert_eq!(res.username_display, Some(UserDisplayStyle::DisplayName)); + + let res: Tunables = serde_json::from_str( + "{\"username_display\": \"regex\",\n\"username_display_regex\": \"foo\"}", + ) + .unwrap(); + assert_eq!(res.username_display, Some(UserDisplayStyle::Regex)); + assert_eq!(res.username_display_regex.unwrap_or("FAILED".into()), "foo".to_string()); } #[test] From ec88f4441ec0cf24a30bc1a310d6f398b3bb4ac6 Mon Sep 17 00:00:00 2001 From: vaw Date: Wed, 23 Jul 2025 00:05:23 +0000 Subject: [PATCH 3/9] Recognise URLs in plain text message bodies (#476) --- Cargo.lock | 10 ++++++++++ Cargo.toml | 1 + src/message/html.rs | 4 ++-- src/message/mod.rs | 1 + src/windows/room/chat.rs | 19 +++++++++++++++---- 5 files changed, 29 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 630b764..0166516 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2274,6 +2274,7 @@ dependencies = [ "image", "lazy_static 1.5.0", "libc", + "linkify", "markup5ever_rcdom", "matrix-sdk", "mime", @@ -2831,6 +2832,15 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "linkify" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dfa36d52c581e9ec783a7ce2a5e0143da6237be5811a0b3153fedfdbe9f780" +dependencies = [ + "memchr", +] + [[package]] name = "linux-raw-sys" version = "0.3.8" diff --git a/Cargo.toml b/Cargo.toml index 556fcc2..4530498 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,6 +64,7 @@ unicode-width = "0.1.10" url = {version = "^2.2.2", features = ["serde"]} edit = "0.1.4" humansize = "2.0.0" +linkify = "0.10.0" [dependencies.comrak] version = "0.22.0" diff --git a/src/message/html.rs b/src/message/html.rs index 8c36df7..c3a8009 100644 --- a/src/message/html.rs +++ b/src/message/html.rs @@ -524,11 +524,11 @@ impl StyleTree { } pub struct TreeGenState { - link_num: u8, + pub link_num: u8, } impl TreeGenState { - fn next_link_char(&mut self) -> Option { + pub fn next_link_char(&mut self) -> Option { let num = self.link_num; if num < 62 { diff --git a/src/message/mod.rs b/src/message/mod.rs index e1dbedc..34f80b1 100644 --- a/src/message/mod.rs +++ b/src/message/mod.rs @@ -72,6 +72,7 @@ mod state; pub use self::compose::text_to_message; use self::state::{body_cow_state, html_state}; +pub use html::TreeGenState; type ProtocolPreview<'a> = (&'a Protocol, u16, u16); diff --git a/src/windows/room/chat.rs b/src/windows/room/chat.rs index 426a66c..8a01a0a 100644 --- a/src/windows/room/chat.rs +++ b/src/windows/room/chat.rs @@ -86,7 +86,14 @@ use crate::base::{ SendAction, }; -use crate::message::{text_to_message, Message, MessageEvent, MessageKey, MessageTimeStamp}; +use crate::message::{ + text_to_message, + Message, + MessageEvent, + MessageKey, + MessageTimeStamp, + TreeGenState, +}; use crate::worker::Requester; use super::scrollback::{Scrollback, ScrollbackState}; @@ -226,10 +233,14 @@ impl ChatState { let links = if let Some(html) = &msg.html { html.get_links() - } else if let Ok(url) = Url::parse(&msg.event.body()) { - vec![('0', url)] } else { - vec![] + linkify::LinkFinder::new() + .links(&msg.event.body()) + .filter_map(|u| Url::parse(u.as_str()).ok()) + .scan(TreeGenState { link_num: 0 }, |state, u| { + state.next_link_char().map(|c| (c, u)) + }) + .collect() }; if links.is_empty() { From 963ce3c7c28fc13c15dcde7ffb0dc626123bdabe Mon Sep 17 00:00:00 2001 From: Thierry Delafontaine Date: Wed, 23 Jul 2025 02:19:18 +0200 Subject: [PATCH 4/9] Support `XDG_CONFIG_HOME` on macOS for config directory resolution (#478) --- src/config.rs | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/config.rs b/src/config.rs index f22bd05..99e9490 100644 --- a/src/config.rs +++ b/src/config.rs @@ -2,6 +2,7 @@ use std::borrow::Cow; use std::collections::hash_map::DefaultHasher; use std::collections::{BTreeMap, HashMap}; +use std::env; use std::fmt; use std::fs::File; use std::hash::{Hash, Hasher}; @@ -837,14 +838,22 @@ pub struct ApplicationSettings { } impl ApplicationSettings { + fn get_xdg_config_home() -> Option { + env::var("XDG_CONFIG_HOME").ok().map(PathBuf::from) + } + pub fn load(cli: Iamb) -> Result> { - let mut config_dir = cli.config_directory.or_else(dirs::config_dir).unwrap_or_else(|| { - usage!( - "No user configuration directory found;\ - please specify one via -C.\n\n - For more information try '--help'" - ); - }); + let mut config_dir = cli + .config_directory + .or_else(Self::get_xdg_config_home) + .or_else(dirs::config_dir) + .unwrap_or_else(|| { + usage!( + "No user configuration directory found;\ + please specify one via -C.\n\n + For more information try '--help'" + ); + }); config_dir.push("iamb"); let config_json = config_dir.join("config.json"); From 331a6bca896a51844197f96f02f7288d04ea2191 Mon Sep 17 00:00:00 2001 From: vaw Date: Wed, 23 Jul 2025 00:26:29 +0000 Subject: [PATCH 5/9] Make blockquotes in message visually distict (#466) --- src/message/html.rs | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/message/html.rs b/src/message/html.rs index c3a8009..d2b40ae 100644 --- a/src/message/html.rs +++ b/src/message/html.rs @@ -41,6 +41,8 @@ use crate::{ util::{join_cell_text, space_text}, }; +const QUOTE_COLOR: Color = Color::Indexed(236); + /// Generate bullet points from a [ListStyle]. pub struct BulletIterator { style: ListStyle, @@ -351,11 +353,14 @@ impl StyleTreeNode { printer.push_span_nobreak(span); }, StyleTreeNode::Blockquote(child) => { - let mut subp = printer.sub(4); + let mut subp = printer.sub(3); child.print(&mut subp, style); for mut line in subp.finish() { - line.spans.insert(0, Span::styled(" ", style)); + line.spans.insert(0, Span::styled(" ", style)); + line.spans + .insert(0, Span::styled(line::THICK_VERTICAL, style.fg(QUOTE_COLOR))); + line.spans.insert(0, Span::styled(" ", style)); printer.push_line(line); } }, @@ -1075,14 +1080,29 @@ pub mod tests { let s = "
Hello world!
"; let tree = parse_matrix_html(s); let text = tree.to_text(10, Style::default(), false, &settings); + let style = Style::new().fg(QUOTE_COLOR); assert_eq!(text.lines.len(), 2); assert_eq!( text.lines[0], - Line::from(vec![Span::raw(" "), Span::raw("Hello"), Span::raw(" ")]) + Line::from(vec![ + Span::raw(" "), + Span::styled(line::THICK_VERTICAL, style), + Span::raw(" "), + Span::raw("Hello"), + Span::raw(" "), + Span::raw(" "), + ]) ); assert_eq!( text.lines[1], - Line::from(vec![Span::raw(" "), Span::raw("world"), Span::raw("!")]) + Line::from(vec![ + Span::raw(" "), + Span::styled(line::THICK_VERTICAL, style), + Span::raw(" "), + Span::raw("world"), + Span::raw("!"), + Span::raw(" "), + ]) ); } From 0ff8828a1c6dd944edcaa68bdab859e89d626e6b Mon Sep 17 00:00:00 2001 From: vaw Date: Wed, 23 Jul 2025 04:05:40 +0000 Subject: [PATCH 6/9] Add config option to allow resetting mode after sending a message (#459) Co-authored-by: Ulyssa --- docs/iamb.5 | 3 +++ src/config.rs | 4 ++++ src/main.rs | 3 +++ src/tests.rs | 1 + 4 files changed, 11 insertions(+) diff --git a/docs/iamb.5 b/docs/iamb.5 index 0fdab5f..8357f96 100644 --- a/docs/iamb.5 +++ b/docs/iamb.5 @@ -173,6 +173,9 @@ respective shortcodes. .It Sy message_user_color Defines whether or not the message body is colored like the username. +.It Sy normal_after_send +Defines whether to reset input to Normal mode after sending a message. + .It Sy notifications When this subsection is present, you can enable and configure push notifications. See diff --git a/src/config.rs b/src/config.rs index 99e9490..d998e20 100644 --- a/src/config.rs +++ b/src/config.rs @@ -558,6 +558,7 @@ impl SortOverrides { pub struct TunableValues { pub log_level: Level, pub message_shortcode_display: bool, + pub normal_after_send: bool, pub reaction_display: bool, pub reaction_shortcode_display: bool, pub read_receipt_send: bool, @@ -584,6 +585,7 @@ pub struct TunableValues { pub struct Tunables { pub log_level: Option, pub message_shortcode_display: Option, + pub normal_after_send: Option, pub reaction_display: Option, pub reaction_shortcode_display: Option, pub read_receipt_send: Option, @@ -614,6 +616,7 @@ impl Tunables { message_shortcode_display: self .message_shortcode_display .or(other.message_shortcode_display), + normal_after_send: self.normal_after_send.or(other.normal_after_send), reaction_display: self.reaction_display.or(other.reaction_display), reaction_shortcode_display: self .reaction_shortcode_display @@ -645,6 +648,7 @@ impl Tunables { TunableValues { log_level: self.log_level.map(Level::from).unwrap_or(Level::INFO), message_shortcode_display: self.message_shortcode_display.unwrap_or(false), + normal_after_send: self.normal_after_send.unwrap_or(false), reaction_display: self.reaction_display.unwrap_or(true), reaction_shortcode_display: self.reaction_shortcode_display.unwrap_or(false), read_receipt_send: self.read_receipt_send.unwrap_or(true), diff --git a/src/main.rs b/src/main.rs index c38b239..b0a0eec 100644 --- a/src/main.rs +++ b/src/main.rs @@ -596,6 +596,9 @@ impl Application { None }, IambAction::Send(act) => { + if store.application.settings.tunables.normal_after_send { + self.bindings.reset_mode(); + } self.screen.current_window_mut()?.send_command(act, ctx, store).await? }, diff --git a/src/tests.rs b/src/tests.rs index 3e3f825..73d3e84 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -171,6 +171,7 @@ pub fn mock_tunables() -> TunableValues { default_room: None, log_level: Level::INFO, message_shortcode_display: false, + normal_after_send: true, reaction_display: true, reaction_shortcode_display: false, read_receipt_send: true, From e9cdb3371abb60d74e2a3949b6e0adcd073165ce Mon Sep 17 00:00:00 2001 From: vaw Date: Wed, 23 Jul 2025 05:19:23 +0000 Subject: [PATCH 7/9] Clear desktop notification when message is read (#427) Co-authored-by: Ulyssa --- src/base.rs | 5 ++++ src/main.rs | 3 +++ src/notifications.rs | 54 ++++++++++++++++++++++++++++++++++++++------ src/worker.rs | 10 +++++--- 4 files changed, 62 insertions(+), 10 deletions(-) diff --git a/src/base.rs b/src/base.rs index 56d4c2d..bb4f491 100644 --- a/src/base.rs +++ b/src/base.rs @@ -92,6 +92,7 @@ use modalkit::{ use crate::config::ImagePreviewProtocolValues; use crate::message::ImageStatus; +use crate::notifications::NotificationHandle; use crate::preview::{source_from_event, spawn_insert_preview}; use crate::{ message::{Message, MessageEvent, MessageKey, MessageTimeStamp, Messages}, @@ -1558,6 +1559,9 @@ pub struct ChatStore { /// Collator for locale-aware text sorting. pub collator: feruca::Collator, + + /// Notifications that should be dismissed when the user opens the room. + pub open_notifications: HashMap>, } impl ChatStore { @@ -1582,6 +1586,7 @@ impl ChatStore { draw_curr: None, ring_bell: false, focused: true, + open_notifications: Default::default(), } } diff --git a/src/main.rs b/src/main.rs index b0a0eec..cee2247 100644 --- a/src/main.rs +++ b/src/main.rs @@ -561,6 +561,9 @@ impl Application { IambAction::ClearUnreads => { let user_id = &store.application.settings.profile.user_id; + // Clear any notifications we displayed: + store.application.open_notifications.clear(); + for room_id in store.application.sync_info.chats() { if let Some(room) = store.application.rooms.get_mut(room_id) { room.fully_read(user_id); diff --git a/src/notifications.rs b/src/notifications.rs index c120222..0462101 100644 --- a/src/notifications.rs +++ b/src/notifications.rs @@ -8,6 +8,7 @@ use matrix_sdk::{ events::{room::message::MessageType, AnyMessageLikeEventContent, AnySyncTimelineEvent}, serde::Raw, MilliSecondsSinceUnixEpoch, + OwnedRoomId, RoomId, }, Client, @@ -24,6 +25,21 @@ const IAMB_XDG_NAME: &str = match option_env!("IAMB_XDG_NAME") { Some(iamb) => iamb, }; +/// Handle for an open notification that should be closed when the user views it. +pub struct NotificationHandle( + #[cfg(all(feature = "desktop", unix, not(target_os = "macos")))] + Option, +); + +impl Drop for NotificationHandle { + fn drop(&mut self) { + #[cfg(all(feature = "desktop", unix, not(target_os = "macos")))] + if let Some(handle) = self.0.take() { + handle.close(); + } + } +} + pub async fn register_notifications( client: &Client, settings: &ApplicationSettings, @@ -54,6 +70,7 @@ pub async fn register_notifications( return; } + let room_id = room.room_id().to_owned(); match notification.event { RawAnySyncOrStrippedTimelineEvent::Sync(e) => { match parse_full_notification(e, room, show_message).await { @@ -66,8 +83,14 @@ pub async fn register_notifications( return; } - send_notification(¬ify_via, &store, &summary, body.as_deref()) - .await; + send_notification( + ¬ify_via, + &summary, + body.as_deref(), + room_id, + &store, + ) + .await; }, Err(err) => { tracing::error!("Failed to extract notification data: {err}") @@ -86,13 +109,14 @@ pub async fn register_notifications( async fn send_notification( via: &NotifyVia, - store: &AsyncProgramStore, summary: &str, body: Option<&str>, + room_id: OwnedRoomId, + store: &AsyncProgramStore, ) { #[cfg(feature = "desktop")] if via.desktop { - send_notification_desktop(summary, body); + send_notification_desktop(summary, body, room_id, store).await; } #[cfg(not(feature = "desktop"))] { @@ -110,7 +134,12 @@ async fn send_notification_bell(store: &AsyncProgramStore) { } #[cfg(feature = "desktop")] -fn send_notification_desktop(summary: &str, body: Option<&str>) { +async fn send_notification_desktop( + summary: &str, + body: Option<&str>, + room_id: OwnedRoomId, + _store: &AsyncProgramStore, +) { let mut desktop_notification = notify_rust::Notification::new(); desktop_notification .summary(summary) @@ -125,8 +154,19 @@ fn send_notification_desktop(summary: &str, body: Option<&str>) { desktop_notification.body(body); } - if let Err(err) = desktop_notification.show() { - tracing::error!("Failed to send notification: {err}") + match desktop_notification.show() { + Err(err) => tracing::error!("Failed to send notification: {err}"), + Ok(handle) => { + #[cfg(all(unix, not(target_os = "macos")))] + _store + .lock() + .await + .application + .open_notifications + .entry(room_id) + .or_default() + .push(NotificationHandle(Some(handle))); + }, } } diff --git a/src/worker.rs b/src/worker.rs index 144c34c..d0392d1 100644 --- a/src/worker.rs +++ b/src/worker.rs @@ -502,19 +502,23 @@ async fn send_receipts_forever(client: &Client, store: &AsyncProgramStore) { loop { interval.tick().await; - let locked = store.lock().await; - let user_id = &locked.application.settings.profile.user_id; + let mut locked = store.lock().await; + let ChatStore { settings, open_notifications, rooms, .. } = &mut locked.application; + let user_id = &settings.profile.user_id; let mut updates = Vec::new(); for room in client.joined_rooms() { let room_id = room.room_id(); - let Some(info) = locked.application.rooms.get(&room_id) else { + let Some(info) = rooms.get(room_id) else { continue; }; let changed = info.receipts(user_id).filter_map(|(thread, new_receipt)| { let old_receipt = sent.get(room_id).and_then(|ts| ts.get(thread)); let changed = Some(new_receipt) != old_receipt; + if changed { + open_notifications.remove(room_id); + } changed.then(|| (room_id.to_owned(), thread.to_owned(), new_receipt.to_owned())) }); From 5029c6ee3b903878dedea1027d0965a207c7ccbb Mon Sep 17 00:00:00 2001 From: Youwen Wu Date: Mon, 16 Jun 2025 10:36:27 -0700 Subject: [PATCH 8/9] feat: add regex option for username_display --- src/config.rs | 21 +++++++++++++++++++++ src/tests.rs | 1 + 2 files changed, 22 insertions(+) diff --git a/src/config.rs b/src/config.rs index d998e20..820df3b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -400,6 +400,9 @@ pub enum UserDisplayStyle { // it can wind up being the Matrix username if there are display name collisions in the room, // in order to avoid any confusion. DisplayName, + + // Acts like Username, except when the username matches given regex, then acts like DisplayName + Regex, } #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -570,6 +573,7 @@ pub struct TunableValues { pub typing_notice_display: bool, pub users: UserOverrides, pub username_display: UserDisplayStyle, + pub username_display_regex: Option, pub message_user_color: bool, pub default_room: Option, pub open_command: Option>, @@ -598,6 +602,7 @@ pub struct Tunables { pub typing_notice_display: Option, pub users: Option, pub username_display: Option, + pub username_display_regex: Option, pub message_user_color: Option, pub default_room: Option, pub open_command: Option>, @@ -630,6 +635,7 @@ impl Tunables { typing_notice_display: self.typing_notice_display.or(other.typing_notice_display), users: merge_maps(self.users, other.users), username_display: self.username_display.or(other.username_display), + username_display_regex: self.username_display_regex.or(other.username_display_regex), message_user_color: self.message_user_color.or(other.message_user_color), default_room: self.default_room.or(other.default_room), open_command: self.open_command.or(other.open_command), @@ -660,6 +666,7 @@ impl Tunables { typing_notice_display: self.typing_notice_display.unwrap_or(true), users: self.users.unwrap_or_default(), username_display: self.username_display.unwrap_or_default(), + username_display_regex: self.username_display_regex, message_user_color: self.message_user_color.unwrap_or(false), default_room: self.default_room, open_command: self.open_command, @@ -1061,6 +1068,20 @@ impl ApplicationSettings { Cow::Borrowed(user_id.as_str()) } }, + (None, UserDisplayStyle::Regex) => { + let re = regex::Regex::new( + &self.tunables.username_display_regex.clone().unwrap_or("*".into()), + ) + .unwrap(); + + if !re.is_match(user_id.as_str()) { + Cow::Borrowed(user_id.as_str()) + } else 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) diff --git a/src/tests.rs b/src/tests.rs index 73d3e84..e4f67a9 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -190,6 +190,7 @@ pub fn mock_tunables() -> TunableValues { open_command: None, external_edit_file_suffix: String::from(".md"), username_display: UserDisplayStyle::Username, + username_display_regex: Some(String::from(".*")), message_user_color: false, mouse: Default::default(), notifications: Notifications { From 1475d9ce50a8ceaef020f1eed6eed43521690762 Mon Sep 17 00:00:00 2001 From: Youwen Wu Date: Mon, 16 Jun 2025 10:42:50 -0700 Subject: [PATCH 9/9] feat: add test for new username_display config options --- src/config.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/config.rs b/src/config.rs index 820df3b..5084dcb 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1198,6 +1198,13 @@ mod tests { let res: Tunables = serde_json::from_str("{\"username_display\": \"displayname\"}").unwrap(); assert_eq!(res.username_display, Some(UserDisplayStyle::DisplayName)); + + let res: Tunables = serde_json::from_str( + "{\"username_display\": \"regex\",\n\"username_display_regex\": \"foo\"}", + ) + .unwrap(); + assert_eq!(res.username_display, Some(UserDisplayStyle::Regex)); + assert_eq!(res.username_display_regex.unwrap_or("FAILED".into()), "foo".to_string()); } #[test]