diff --git a/Cargo.lock b/Cargo.lock index 8dd674e..33a7fba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -494,6 +494,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "482aa5695bca086022be453c700a40c02893f1ba7098a2c88351de55341ae894" dependencies = [ "clap", + "emojis", "entities", "memchr", "once_cell", diff --git a/Cargo.toml b/Cargo.toml index 8bfe922..4e8a57d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ arboard = "3.2.0" bitflags = "1.3.2" chrono = "0.4" clap = {version = "4.0", features = ["derive"]} -comrak = "0.18.0" +comrak = {version = "0.18.0", features = ["shortcodes"]} css-color-parser = "0.1.2" dirs = "4.0.0" emojis = "~0.5.2" diff --git a/src/base.rs b/src/base.rs index ff9fee1..b275866 100644 --- a/src/base.rs +++ b/src/base.rs @@ -788,9 +788,7 @@ impl ApplicationInfo for IambInfo { IambBufferId::Command(CommandType::Command) => complete_cmdbar(text, cursor, store), IambBufferId::Command(CommandType::Search) => vec![], - IambBufferId::Room(_, RoomFocus::MessageBar) => { - complete_matrix_names(text, cursor, store) - }, + IambBufferId::Room(_, RoomFocus::MessageBar) => complete_msgbar(text, cursor, store), IambBufferId::Room(_, RoomFocus::Scrollback) => vec![], IambBufferId::DirectList => vec![], @@ -822,6 +820,53 @@ fn complete_users(text: &EditRope, cursor: &mut Cursor, store: &ProgramStore) -> .collect() } +fn complete_msgbar(text: &EditRope, cursor: &mut Cursor, store: &ProgramStore) -> Vec { + let id = text + .get_prefix_word_mut(cursor, &MATRIX_ID_WORD) + .unwrap_or_else(EditRope::empty); + let id = Cow::from(&id); + + match id.chars().next() { + // Complete room aliases. + Some('#') => { + return store.application.names.complete(id.as_ref()); + }, + + // Complete room identifiers. + Some('!') => { + return store + .application + .rooms + .complete(id.as_ref()) + .into_iter() + .map(|i| i.to_string()) + .collect(); + }, + + // Complete Emoji shortcodes. + Some(':') => { + let list = store.application.emojis.complete(&id[1..]); + let iter = list.into_iter().take(200).map(|s| format!(":{}:", s)); + + return iter.collect(); + }, + + // Complete usernames for @ and empty strings. + Some('@') | None => { + return store + .application + .presences + .complete(id.as_ref()) + .into_iter() + .map(|i| i.to_string()) + .collect(); + }, + + // Unknown sigil. + Some(_) => return vec![], + } +} + fn complete_matrix_names( text: &EditRope, cursor: &mut Cursor, @@ -1011,6 +1056,29 @@ pub mod tests { ); } + #[tokio::test] + async fn test_complete_msgbar() { + let store = mock_store().await; + + let text = EditRope::from("going for a walk :walk "); + let mut cursor = Cursor::new(0, 22); + let res = complete_msgbar(&text, &mut cursor, &store); + assert_eq!(res, vec![":walking:", ":walking_man:", ":walking_woman:"]); + assert_eq!(cursor, Cursor::new(0, 17)); + + let text = EditRope::from("hello @user1 "); + let mut cursor = Cursor::new(0, 12); + let res = complete_msgbar(&text, &mut cursor, &store); + assert_eq!(res, vec!["@user1:example.com"]); + assert_eq!(cursor, Cursor::new(0, 6)); + + let text = EditRope::from("see #room "); + let mut cursor = Cursor::new(0, 9); + let res = complete_msgbar(&text, &mut cursor, &store); + assert_eq!(res, vec!["#room1:example.com"]); + assert_eq!(cursor, Cursor::new(0, 4)); + } + #[tokio::test] async fn test_complete_cmdbar() { let store = mock_store().await; diff --git a/src/message/mod.rs b/src/message/mod.rs index b80aae2..4f1746d 100644 --- a/src/message/mod.rs +++ b/src/message/mod.rs @@ -97,6 +97,7 @@ const TIME_GUTTER_EMPTY_SPAN: Span<'static> = span_static(TIME_GUTTER_EMPTY); fn text_to_message_content(input: String) -> TextMessageEventContent { let mut options = ComrakOptions::default(); + options.extension.shortcodes = true; options.render.hardbreaks = true; let html = markdown_to_html(input.as_str(), &options); @@ -1013,6 +1014,11 @@ pub mod tests { "
const A: usize = 1;\n
\n" ); + let input = ":heart:\n"; + let content = text_to_message_content(input.into()); + assert_eq!(content.body, input); + assert_eq!(content.formatted.unwrap().body, "

\u{2764}\u{FE0F}

\n"); + let input = "para 1\n\npara 2\n"; let content = text_to_message_content(input.into()); assert_eq!(content.body, input); diff --git a/src/tests.rs b/src/tests.rs index a5a8de3..322556e 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -42,6 +42,8 @@ use crate::{ worker::Requester, }; +const TEST_ROOM1_ALIAS: &str = "#room1:example.com"; + lazy_static! { pub static ref TEST_ROOM1_ID: OwnedRoomId = RoomId::new(server_name!("example.com")).to_owned(); pub static ref TEST_USER1: OwnedUserId = user_id!("@user1:example.com").to_owned(); @@ -223,7 +225,8 @@ pub async fn mock_store() -> ProgramStore { let room_id = TEST_ROOM1_ID.clone(); let info = mock_room(); - store.rooms.insert(room_id, info); + store.rooms.insert(room_id.clone(), info); + store.names.insert(TEST_ROOM1_ALIAS.to_string(), room_id); ProgramStore::new(store) }