diff --git a/Cargo.lock b/Cargo.lock index 0166516b..c822519e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -865,6 +865,15 @@ dependencies = [ "strsim 0.10.0", ] +[[package]] +name = "clap_complete" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc443334c81a804575546c5a8a79b4913b50e28d69232903604cada1de817ce" +dependencies = [ + "clap", +] + [[package]] name = "clap_derive" version = "4.3.12" @@ -2261,6 +2270,7 @@ dependencies = [ "bitflags 2.9.1", "chrono", "clap", + "clap_complete", "comrak", "css-color-parser", "dirs", diff --git a/Cargo.toml b/Cargo.toml index 4530498a..4ab84332 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ anyhow = "1.0" bitflags = "^2.3" chrono = "0.4" clap = {version = "~4.3", features = ["derive"]} +clap_complete = "~4.3" css-color-parser = "0.1.2" dirs = "4.0.0" emojis = "0.5" diff --git a/src/base.rs b/src/base.rs index bb4f491d..de305b74 100644 --- a/src/base.rs +++ b/src/base.rs @@ -1219,9 +1219,10 @@ impl RoomInfo { self.insert(ev); if let Some((event_id, source)) = source { - if let (Some(msg), Some(image_preview)) = - (self.get_event_mut(&event_id), &settings.tunables.image_preview) - { + if let (Some(msg), Some(image_preview)) = ( + self.get_event_mut(&event_id), + &settings.tunables.image_preview, + ) { msg.image_preview = ImageStatus::Downloading(image_preview.size.clone()); spawn_insert_preview( store, @@ -2249,12 +2250,18 @@ pub mod tests { // Four users typing. info.set_typing(users4); assert!(info.users_typing.is_some()); - assert_eq!(info.get_typing_spans(&settings), Line::from("Several people are typing...")); + assert_eq!( + info.get_typing_spans(&settings), + Line::from("Several people are typing...") + ); // Five users typing. info.set_typing(users5); assert!(info.users_typing.is_some()); - assert_eq!(info.get_typing_spans(&settings), Line::from("Many people are typing...")); + assert_eq!( + info.get_typing_spans(&settings), + Line::from("Many people are typing...") + ); // Test that USER5 gets rendered using the configured color and name. info.set_typing(vec![TEST_USER5.clone()]); @@ -2277,10 +2284,10 @@ pub mod tests { need_load.insert(room_id.clone(), Need::MESSAGES); need_load.insert(room_id.clone(), Need::MEMBERS); - assert_eq!(need_load.into_iter().collect::>(), vec![( - room_id, - Need::MESSAGES | Need::MEMBERS, - )],); + assert_eq!( + need_load.into_iter().collect::>(), + vec![(room_id, Need::MESSAGES | Need::MEMBERS,)], + ); } #[tokio::test] diff --git a/src/commands.rs b/src/commands.rs index c70a2aff..82725386 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1052,8 +1052,10 @@ mod tests { assert_eq!(res, vec![(act.into(), ctx.clone())]); let res = cmds.input_cmd("room tag set u.irc", ctx.clone()).unwrap(); - let act = - RoomAction::Set(RoomField::Tag(TagName::User("u.irc".parse().unwrap())), "".into()); + let act = RoomAction::Set( + RoomField::Tag(TagName::User("u.irc".parse().unwrap())), + "".into(), + ); assert_eq!(res, vec![(act.into(), ctx.clone())]); let res = cmds.input_cmd("room tag", ctx.clone()); @@ -1063,12 +1065,17 @@ mod tests { assert_eq!(res, Err(CommandError::InvalidArgument)); let res = cmds.input_cmd("room tag set unknown", ctx.clone()); - assert_eq!(res, Err(CommandError::Error("Invalid user tag name: unknown".into()))); + assert_eq!( + res, + Err(CommandError::Error("Invalid user tag name: unknown".into())) + ); let res = cmds.input_cmd("room tag set needs-leading-u-dot", ctx.clone()); assert_eq!( res, - Err(CommandError::Error("Invalid user tag name: needs-leading-u-dot".into())) + Err(CommandError::Error( + "Invalid user tag name: needs-leading-u-dot".into() + )) ); } @@ -1114,7 +1121,9 @@ mod tests { assert_eq!(res, vec![(act.into(), ctx.clone())]); let res = cmds.input_cmd("room tag unset u.custom-tag", ctx.clone()).unwrap(); - let act = RoomAction::Unset(RoomField::Tag(TagName::User("u.custom-tag".parse().unwrap()))); + let act = RoomAction::Unset(RoomField::Tag(TagName::User( + "u.custom-tag".parse().unwrap(), + ))); assert_eq!(res, vec![(act.into(), ctx.clone())]); let res = cmds.input_cmd("room tag unset u.irc", ctx.clone()).unwrap(); @@ -1128,12 +1137,17 @@ mod tests { assert_eq!(res, Err(CommandError::InvalidArgument)); let res = cmds.input_cmd("room tag unset unknown", ctx.clone()); - assert_eq!(res, Err(CommandError::Error("Invalid user tag name: unknown".into()))); + assert_eq!( + res, + Err(CommandError::Error("Invalid user tag name: unknown".into())) + ); let res = cmds.input_cmd("room tag unset needs-leading-u-dot", ctx.clone()); assert_eq!( res, - Err(CommandError::Error("Invalid user tag name: needs-leading-u-dot".into())) + Err(CommandError::Error( + "Invalid user tag name: needs-leading-u-dot".into() + )) ); } @@ -1212,12 +1226,19 @@ mod tests { let res = cmds.input_cmd(cmd, ctx.clone()); assert_eq!( res, - Err(CommandError::Error("Multiple ++order arguments are not allowed".into())) + Err(CommandError::Error( + "Multiple ++order arguments are not allowed".into() + )) ); let cmd = "space child set !roomid:example.org !otherroom:example.org"; let res = cmds.input_cmd(cmd, ctx.clone()); - assert_eq!(res, Err(CommandError::Error("Multiple room arguments are not allowed".into()))); + assert_eq!( + res, + Err(CommandError::Error( + "Multiple room arguments are not allowed".into() + )) + ); let cmd = "space child set ++foo=abcd !roomid:example.org"; let res = cmds.input_cmd(cmd, ctx.clone()); @@ -1233,11 +1254,17 @@ mod tests { let cmd = "space child set foo"; let res = cmds.input_cmd(cmd, ctx.clone()); - assert_eq!(res, Err(CommandError::Error("Invalid room id specified".into()))); + assert_eq!( + res, + Err(CommandError::Error("Invalid room id specified".into())) + ); let cmd = "space child set"; let res = cmds.input_cmd(cmd, ctx.clone()); - assert_eq!(res, Err(CommandError::Error("Must specify a room to add".into()))); + assert_eq!( + res, + Err(CommandError::Error("Must specify a room to add".into())) + ); } #[test] @@ -1269,8 +1296,9 @@ mod tests { assert_eq!(res, vec![(act.into(), ctx.clone())]); let res = cmds.input_cmd("invite send @user:example.com", ctx.clone()).unwrap(); - let act = - IambAction::Room(RoomAction::InviteSend(user_id!("@user:example.com").to_owned())); + let act = IambAction::Room(RoomAction::InviteSend( + user_id!("@user:example.com").to_owned(), + )); assert_eq!(res, vec![(act.into(), ctx.clone())]); let res = cmds.input_cmd("invite", ctx.clone()); diff --git a/src/config.rs b/src/config.rs index d998e206..c2d88147 100644 --- a/src/config.rs +++ b/src/config.rs @@ -130,6 +130,9 @@ const VERSION: &str = match option_env!("VERGEN_GIT_SHA") { #[clap(version = VERSION, about, long_about = None)] #[clap(propagate_version = true)] pub struct Iamb { + #[clap(long, value_parser)] + pub completions: Option, + #[clap(short = 'P', long, value_parser)] pub profile: Option, @@ -1029,7 +1032,12 @@ impl ApplicationSettings { self.tunables .users .get(user_id) - .map(|user| (user.color.as_ref().map(|c| c.0), user.name.clone().map(Cow::Owned))) + .map(|user| { + ( + user.color.as_ref().map(|c| c.0), + user.name.clone().map(Cow::Owned), + ) + }) .unwrap_or_default() } @@ -1294,7 +1302,10 @@ mod tests { #[test] fn test_parse_notify_via() { - assert_eq!(NotifyVia { bell: false, desktop: true }, NotifyVia::default()); + assert_eq!( + NotifyVia { bell: false, desktop: true }, + NotifyVia::default() + ); assert_eq!( NotifyVia { bell: false, desktop: true }, serde_json::from_str(r#""desktop""#).unwrap() diff --git a/src/main.rs b/src/main.rs index cee2247d..5f57b55c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,7 +26,7 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; use std::time::{Duration, Instant}; -use clap::Parser; +use clap::{CommandFactory, Parser}; use matrix_sdk::crypto::encrypt_room_key_export; use matrix_sdk::ruma::api::client::error::ErrorKind; use matrix_sdk::ruma::OwnedUserId; @@ -689,7 +689,10 @@ impl Application { .await .map_err(IambError::from)?; - let msg = format!("Imported {} of {} keys", res.imported_count, res.total_count); + let msg = format!( + "Imported {} of {} keys", + res.imported_count, res.total_count + ); Ok(Some(msg.into())) }, @@ -880,7 +883,10 @@ async fn check_import_keys( let keys = sled_export::export_room_keys(&settings.sled_dir).await?; let passphrase = gen_passphrase(); - println!("* Encrypting {} room keys with the passphrase {passphrase:?}...", keys.len()); + println!( + "* Encrypting {} room keys with the passphrase {passphrase:?}...", + keys.len() + ); let encrypted = match encrypt_room_key_export(&keys, &passphrase, 500000) { Ok(encrypted) => encrypted, @@ -986,7 +992,12 @@ fn setup_tty(settings: &ApplicationSettings, enable_enhanced_keys: bool) -> std: crossterm::execute!(stdout(), EnableMouseCapture)?; } - crossterm::execute!(stdout(), EnableBracketedPaste, EnableFocusChange, SetTitle(title)) + crossterm::execute!( + stdout(), + EnableBracketedPaste, + EnableFocusChange, + SetTitle(title) + ) } // Do our best to reverse what we did in setup_tty() when we exit or crash. @@ -1078,6 +1089,11 @@ fn main() -> IambResult<()> { // Parse command-line flags. let iamb = Iamb::parse(); + if let Some(shell) = iamb.completions { + clap_complete::generate(shell, &mut Iamb::command(), "iamb", &mut std::io::stdout()); + return Ok(()); + } + // Load configuration and set up the Matrix SDK. let settings = ApplicationSettings::load(iamb).unwrap_or_else(print_exit); diff --git a/src/message/compose.rs b/src/message/compose.rs index 8fd6ad00..4ebb1d5d 100644 --- a/src/message/compose.rs +++ b/src/message/compose.rs @@ -83,10 +83,18 @@ impl SlashCommand { MessageType::new("io.element.effect.hearts", input.into(), Default::default())? }, SlashCommand::Rainfall => { - MessageType::new("io.element.effect.rainfall", input.into(), Default::default())? + MessageType::new( + "io.element.effect.rainfall", + input.into(), + Default::default(), + )? }, SlashCommand::Snowfall => { - MessageType::new("io.element.effect.snowfall", input.into(), Default::default())? + MessageType::new( + "io.element.effect.snowfall", + input.into(), + Default::default(), + )? }, SlashCommand::SpaceInvaders => { MessageType::new( @@ -214,17 +222,26 @@ pub mod tests { let input = "**bold**\n"; let content = text_to_message_content(input.into()); assert_eq!(content.body, input); - assert_eq!(content.formatted.unwrap().body, "

bold

\n"); + assert_eq!( + content.formatted.unwrap().body, + "

bold

\n" + ); let input = "*emphasis*\n"; let content = text_to_message_content(input.into()); assert_eq!(content.body, input); - assert_eq!(content.formatted.unwrap().body, "

emphasis

\n"); + assert_eq!( + content.formatted.unwrap().body, + "

emphasis

\n" + ); let input = "`code`\n"; let content = text_to_message_content(input.into()); assert_eq!(content.body, input); - assert_eq!(content.formatted.unwrap().body, "

code

\n"); + assert_eq!( + content.formatted.unwrap().body, + "

code

\n" + ); let input = "```rust\nconst A: usize = 1;\n```\n"; let content = text_to_message_content(input.into()); @@ -250,7 +267,10 @@ pub mod tests { let input = "line 1\nline ~~2~~\n"; let content = text_to_message_content(input.into()); assert_eq!(content.body, input); - assert_eq!(content.formatted.unwrap().body, "

line 1
\nline 2

\n"); + assert_eq!( + content.formatted.unwrap().body, + "

line 1
\nline 2

\n" + ); let input = "# Heading\n## Subheading\n\ntext\n"; let content = text_to_message_content(input.into()); diff --git a/src/message/html.rs b/src/message/html.rs index d2b40ae3..78e2e628 100644 --- a/src/message/html.rs +++ b/src/message/html.rs @@ -1062,7 +1062,10 @@ pub mod tests { Line::from(vec![Span::raw("world"), Span::raw("!"), Span::raw(" ")]) ); assert_eq!(text.lines[2], Line::from(vec![Span::raw(" ")])); - assert_eq!(text.lines[3], Line::from(vec![Span::raw("Content"), Span::raw(" ")])); + assert_eq!( + text.lines[3], + Line::from(vec![Span::raw("Content"), Span::raw(" ")]) + ); assert_eq!(text.lines[4], Line::from(vec![Span::raw(" ")])); assert_eq!( text.lines[5], @@ -1406,8 +1409,14 @@ pub mod tests { let tree = parse_matrix_html(s); let text = tree.to_text(7, Style::default(), true, &settings); assert_eq!(text.lines.len(), 3); - assert_eq!(text.lines[0], Line::from(vec![Span::raw("Hello"), Span::raw(" "),])); - assert_eq!(text.lines[1], Line::from(vec![Span::raw("World"), Span::raw(" "),])); + assert_eq!( + text.lines[0], + Line::from(vec![Span::raw("Hello"), Span::raw(" "),]) + ); + assert_eq!( + text.lines[1], + Line::from(vec![Span::raw("World"), Span::raw(" "),]) + ); assert_eq!(text.lines[2], Line::from(vec![Span::raw("Goodbye")]),); } diff --git a/src/message/mod.rs b/src/message/mod.rs index 34f80b16..865f68c7 100644 --- a/src/message/mod.rs +++ b/src/message/mod.rs @@ -1348,10 +1348,19 @@ pub mod tests { ) ); - assert_eq!(placeholder_frame(None, 2, &ImagePreviewSize { width: 4, height: 4 }), None); - assert_eq!(placeholder_frame(None, 4, &ImagePreviewSize { width: 1, height: 4 }), None); + assert_eq!( + placeholder_frame(None, 2, &ImagePreviewSize { width: 4, height: 4 }), + None + ); + assert_eq!( + placeholder_frame(None, 4, &ImagePreviewSize { width: 1, height: 4 }), + None + ); - assert_eq!(placeholder_frame(None, 4, &ImagePreviewSize { width: 4, height: 1 }), None); + assert_eq!( + placeholder_frame(None, 4, &ImagePreviewSize { width: 4, height: 1 }), + None + ); assert_eq!( placeholder_frame(Some("OK"), 4, &ImagePreviewSize { width: 4, height: 4 }), @@ -1392,7 +1401,10 @@ pub mod tests { ) ); assert_eq!( - placeholder_frame(Some("idontfit"), 4, &ImagePreviewSize { width: 4, height: 4 }), + placeholder_frame(Some("idontfit"), 4, &ImagePreviewSize { + width: 4, + height: 4 + }), pretty_frame_test( r#" ⌌ ⌍ diff --git a/src/message/state.rs b/src/message/state.rs index 53885c42..efe4ac87 100644 --- a/src/message/state.rs +++ b/src/message/state.rs @@ -154,7 +154,10 @@ pub fn body_cow_state(ev: &AnySyncStateEvent) -> Cow<'static, str> { content, .. }) => { - format!("* set guest access for the room to {:?}", content.guest_access.as_str()) + format!( + "* set guest access for the room to {:?}", + content.guest_access.as_str() + ) }, AnyFullStateEventContent::RoomHistoryVisibility(FullStateEventContent::Original { content, @@ -169,7 +172,10 @@ pub fn body_cow_state(ev: &AnySyncStateEvent) -> Cow<'static, str> { content, .. }) => { - format!("* update the join rules for the room to {:?}", content.join_rule.as_str()) + format!( + "* update the join rules for the room to {:?}", + content.join_rule.as_str() + ) }, AnyFullStateEventContent::RoomMember(FullStateEventContent::Original { content, @@ -280,7 +286,10 @@ pub fn body_cow_state(ev: &AnySyncStateEvent) -> Cow<'static, str> { } }, ev => { - format!("* made an unknown membership change to {}: {:?}", state_key, ev) + format!( + "* made an unknown membership change to {}: {:?}", + state_key, ev + ) }, } }, diff --git a/src/tests.rs b/src/tests.rs index 73d3e842..8577dd77 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -127,11 +127,26 @@ pub fn mock_message5() -> Message { pub fn mock_keys() -> HashMap { let mut keys = HashMap::new(); - keys.insert(MSG1_EVID.clone(), EventLocation::Message(None, MSG1_KEY.clone())); - keys.insert(MSG2_EVID.clone(), EventLocation::Message(None, MSG2_KEY.clone())); - keys.insert(MSG3_EVID.clone(), EventLocation::Message(None, MSG3_KEY.clone())); - keys.insert(MSG4_EVID.clone(), EventLocation::Message(None, MSG4_KEY.clone())); - keys.insert(MSG5_EVID.clone(), EventLocation::Message(None, MSG5_KEY.clone())); + keys.insert( + MSG1_EVID.clone(), + EventLocation::Message(None, MSG1_KEY.clone()), + ); + keys.insert( + MSG2_EVID.clone(), + EventLocation::Message(None, MSG2_KEY.clone()), + ); + keys.insert( + MSG3_EVID.clone(), + EventLocation::Message(None, MSG3_KEY.clone()), + ); + keys.insert( + MSG4_EVID.clone(), + EventLocation::Message(None, MSG4_KEY.clone()), + ); + keys.insert( + MSG5_EVID.clone(), + EventLocation::Message(None, MSG5_KEY.clone()), + ); keys } diff --git a/src/util.rs b/src/util.rs index bee1810c..2fca2c88 100644 --- a/src/util.rs +++ b/src/util.rs @@ -177,7 +177,10 @@ pub mod tests { let mut iter = wrap(s, 100); assert_eq!(iter.next(), Some((Cow::Borrowed("hello world!"), 12))); - assert_eq!(iter.next(), Some((Cow::Borrowed("abcdefghijklmnopqrstuvwxyz"), 26))); + assert_eq!( + iter.next(), + Some((Cow::Borrowed("abcdefghijklmnopqrstuvwxyz"), 26)) + ); assert_eq!(iter.next(), Some((Cow::Borrowed("goodbye"), 7))); assert_eq!(iter.next(), None); diff --git a/src/windows/mod.rs b/src/windows/mod.rs index 12287817..b21273da 100644 --- a/src/windows/mod.rs +++ b/src/windows/mod.rs @@ -1328,10 +1328,18 @@ impl VerifyItem { if let Some(display_name) = device.display_name() { format!("Device verification with {display_name} ({state})") } else { - format!("Device verification with device {} ({})", device.device_id(), state) + format!( + "Device verification with device {} ({})", + device.device_id(), + state + ) } } else { - format!("User Verification with {} ({})", self.sasv1.other_user_id(), state) + format!( + "User Verification with {} ({})", + self.sasv1.other_user_id(), + state + ) } } } @@ -1426,7 +1434,9 @@ impl ListItem for VerifyItem { lines.push(Line::from("")); } - lines.push(Line::from(" You can start a new verification request with:")); + lines.push(Line::from( + " You can start a new verification request with:", + )); } else if let Some(emoji) = self.sasv1.emoji() { lines.push(Line::from( " Both devices should see the following Emoji sequence:".to_string(), @@ -1445,7 +1455,9 @@ impl ListItem for VerifyItem { bold, ))); lines.push(Line::from("")); - lines.push(Line::from(" If everything looks right, you can confirm with:")); + lines.push(Line::from( + " If everything looks right, you can confirm with:", + )); } else { lines.push(Line::from(" To accept this request, run:")); } @@ -1454,7 +1466,10 @@ impl ListItem for VerifyItem { if !cmd.is_empty() { lines.push(Line::from("")); - lines.push(Line::from(vec![Span::from(" "), Span::styled(cmd, bold)])); + lines.push(Line::from(vec![ + Span::from(" "), + Span::styled(cmd, bold), + ])); lines.push(Line::from("")); lines.push(Line::from(vec![ Span::from("You can copy the above command with "), diff --git a/src/windows/room/chat.rs b/src/windows/room/chat.rs index 8a01a0af..a2c4a6bc 100644 --- a/src/windows/room/chat.rs +++ b/src/windows/room/chat.rs @@ -222,7 +222,10 @@ impl ChatState { let (source, msg_filename) = match &ev.content.msgtype { MessageType::Audio(c) => (c.source.clone(), c.body.as_str()), MessageType::File(c) => { - (c.source.clone(), c.filename.as_deref().unwrap_or(c.body.as_str())) + ( + c.source.clone(), + c.filename.as_deref().unwrap_or(c.body.as_str()), + ) }, MessageType::Image(c) => (c.source.clone(), c.body.as_str()), MessageType::Video(c) => (c.source.clone(), c.body.as_str()), diff --git a/src/windows/room/mod.rs b/src/windows/room/mod.rs index adb3a9cc..bb1e7756 100644 --- a/src/windows/room/mod.rs +++ b/src/windows/room/mod.rs @@ -592,7 +592,10 @@ impl RoomState { RoomField::History => { let visibility = room.history_visibility(); let visibility = visibility.as_ref().map(|v| v.as_str()); - format!("Room history visibility: {}", visibility.unwrap_or("")) + format!( + "Room history visibility: {}", + visibility.unwrap_or("") + ) }, RoomField::Id => { let id = room.room_id(); diff --git a/src/windows/room/scrollback.rs b/src/windows/room/scrollback.rs index 5f328977..14fd706a 100644 --- a/src/windows/room/scrollback.rs +++ b/src/windows/room/scrollback.rs @@ -452,7 +452,11 @@ impl ScrollbackState { ) -> Option> { let other = self.movement(pos.clone(), movement, count, ctx, info)?; - Some(EditRange::inclusive(pos.into(), other, TargetShape::LineWise)) + Some(EditRange::inclusive( + pos.into(), + other, + TargetShape::LineWise, + )) } fn range( @@ -476,7 +480,11 @@ impl ScrollbackState { let start = thread.first_key_value()?.0.clone(); let end = thread.last_key_value()?.0.clone(); - Some(EditRange::inclusive(start.into(), end.into(), TargetShape::LineWise)) + Some(EditRange::inclusive( + start.into(), + end.into(), + TargetShape::LineWise, + )) }, RangeType::Line | RangeType::Paragraph | RangeType::Sentence => { let thread = self.get_thread(info)?; @@ -670,7 +678,9 @@ impl EditorActions for ScrollbackState { }, EditTarget::Motion(mt, count) => self.movement(key, mt, count, ctx, info), EditTarget::Range(_, _, _) => { - return Err(EditError::Failure("Cannot use ranges in a list".to_string())); + return Err(EditError::Failure( + "Cannot use ranges in a list".to_string(), + )); }, EditTarget::Search(SearchType::Char(_), _, _) => { let msg = "Cannot perform character search in a list"; @@ -864,7 +874,9 @@ impl EditorActions for ScrollbackState { _: &ProgramContext, _: &mut ProgramStore, ) -> EditResult { - Err(EditError::Failure("Cannot perform selection actions in a list".into())) + Err(EditError::Failure( + "Cannot perform selection actions in a list".into(), + )) } fn history_command( @@ -1428,7 +1440,11 @@ impl StatefulWidget for Scrollback<'_> { { // If the cursor is at the last message, then update the read marker. if let Some((k, _)) = thread.last_key_value() { - info.set_receipt(thread.1.clone(), settings.profile.user_id.clone(), k.1.clone()); + info.set_receipt( + thread.1.clone(), + settings.profile.user_id.clone(), + k.1.clone(), + ); } } @@ -1577,118 +1593,184 @@ mod tests { assert_eq!(scrollback.cursor, MessageCursor::latest()); assert_eq!(scrollback.viewctx.dimensions, (60, 4)); - assert_eq!(scrollback.viewctx.corner, MessageCursor::new(MSG4_KEY.clone(), 0)); + assert_eq!( + scrollback.viewctx.corner, + MessageCursor::new(MSG4_KEY.clone(), 0) + ); // Scroll up a line at a time until we hit the first message. scrollback .dirscroll(prev, ScrollSize::Cell, &1.into(), &ctx, &mut store) .unwrap(); - assert_eq!(scrollback.viewctx.corner, MessageCursor::new(MSG3_KEY.clone(), 4)); + assert_eq!( + scrollback.viewctx.corner, + MessageCursor::new(MSG3_KEY.clone(), 4) + ); scrollback .dirscroll(prev, ScrollSize::Cell, &1.into(), &ctx, &mut store) .unwrap(); - assert_eq!(scrollback.viewctx.corner, MessageCursor::new(MSG3_KEY.clone(), 3)); + assert_eq!( + scrollback.viewctx.corner, + MessageCursor::new(MSG3_KEY.clone(), 3) + ); scrollback .dirscroll(prev, ScrollSize::Cell, &1.into(), &ctx, &mut store) .unwrap(); - assert_eq!(scrollback.viewctx.corner, MessageCursor::new(MSG3_KEY.clone(), 2)); + assert_eq!( + scrollback.viewctx.corner, + MessageCursor::new(MSG3_KEY.clone(), 2) + ); scrollback .dirscroll(prev, ScrollSize::Cell, &1.into(), &ctx, &mut store) .unwrap(); - assert_eq!(scrollback.viewctx.corner, MessageCursor::new(MSG3_KEY.clone(), 1)); + assert_eq!( + scrollback.viewctx.corner, + MessageCursor::new(MSG3_KEY.clone(), 1) + ); scrollback .dirscroll(prev, ScrollSize::Cell, &1.into(), &ctx, &mut store) .unwrap(); - assert_eq!(scrollback.viewctx.corner, MessageCursor::new(MSG3_KEY.clone(), 0)); + assert_eq!( + scrollback.viewctx.corner, + MessageCursor::new(MSG3_KEY.clone(), 0) + ); scrollback .dirscroll(prev, ScrollSize::Cell, &1.into(), &ctx, &mut store) .unwrap(); - assert_eq!(scrollback.viewctx.corner, MessageCursor::new(MSG2_KEY.clone(), 1)); + assert_eq!( + scrollback.viewctx.corner, + MessageCursor::new(MSG2_KEY.clone(), 1) + ); scrollback .dirscroll(prev, ScrollSize::Cell, &1.into(), &ctx, &mut store) .unwrap(); - assert_eq!(scrollback.viewctx.corner, MessageCursor::new(MSG2_KEY.clone(), 0)); + assert_eq!( + scrollback.viewctx.corner, + MessageCursor::new(MSG2_KEY.clone(), 0) + ); // Cannot scroll any further. scrollback .dirscroll(prev, ScrollSize::Cell, &1.into(), &ctx, &mut store) .unwrap(); - assert_eq!(scrollback.viewctx.corner, MessageCursor::new(MSG2_KEY.clone(), 0)); + assert_eq!( + scrollback.viewctx.corner, + MessageCursor::new(MSG2_KEY.clone(), 0) + ); // Now scroll back down one line at a time. scrollback .dirscroll(next, ScrollSize::Cell, &1.into(), &ctx, &mut store) .unwrap(); - assert_eq!(scrollback.viewctx.corner, MessageCursor::new(MSG2_KEY.clone(), 1)); + assert_eq!( + scrollback.viewctx.corner, + MessageCursor::new(MSG2_KEY.clone(), 1) + ); scrollback .dirscroll(next, ScrollSize::Cell, &1.into(), &ctx, &mut store) .unwrap(); - assert_eq!(scrollback.viewctx.corner, MessageCursor::new(MSG3_KEY.clone(), 0)); + assert_eq!( + scrollback.viewctx.corner, + MessageCursor::new(MSG3_KEY.clone(), 0) + ); scrollback .dirscroll(next, ScrollSize::Cell, &1.into(), &ctx, &mut store) .unwrap(); - assert_eq!(scrollback.viewctx.corner, MessageCursor::new(MSG3_KEY.clone(), 1)); + assert_eq!( + scrollback.viewctx.corner, + MessageCursor::new(MSG3_KEY.clone(), 1) + ); scrollback .dirscroll(next, ScrollSize::Cell, &1.into(), &ctx, &mut store) .unwrap(); - assert_eq!(scrollback.viewctx.corner, MessageCursor::new(MSG3_KEY.clone(), 2)); + assert_eq!( + scrollback.viewctx.corner, + MessageCursor::new(MSG3_KEY.clone(), 2) + ); scrollback .dirscroll(next, ScrollSize::Cell, &1.into(), &ctx, &mut store) .unwrap(); - assert_eq!(scrollback.viewctx.corner, MessageCursor::new(MSG3_KEY.clone(), 3)); + assert_eq!( + scrollback.viewctx.corner, + MessageCursor::new(MSG3_KEY.clone(), 3) + ); scrollback .dirscroll(next, ScrollSize::Cell, &1.into(), &ctx, &mut store) .unwrap(); - assert_eq!(scrollback.viewctx.corner, MessageCursor::new(MSG3_KEY.clone(), 4)); + assert_eq!( + scrollback.viewctx.corner, + MessageCursor::new(MSG3_KEY.clone(), 4) + ); scrollback .dirscroll(next, ScrollSize::Cell, &1.into(), &ctx, &mut store) .unwrap(); - assert_eq!(scrollback.viewctx.corner, MessageCursor::new(MSG4_KEY.clone(), 0)); + assert_eq!( + scrollback.viewctx.corner, + MessageCursor::new(MSG4_KEY.clone(), 0) + ); scrollback .dirscroll(next, ScrollSize::Cell, &1.into(), &ctx, &mut store) .unwrap(); - assert_eq!(scrollback.viewctx.corner, MessageCursor::new(MSG5_KEY.clone(), 0)); + assert_eq!( + scrollback.viewctx.corner, + MessageCursor::new(MSG5_KEY.clone(), 0) + ); scrollback .dirscroll(next, ScrollSize::Cell, &1.into(), &ctx, &mut store) .unwrap(); - assert_eq!(scrollback.viewctx.corner, MessageCursor::new(MSG1_KEY.clone(), 0)); + assert_eq!( + scrollback.viewctx.corner, + MessageCursor::new(MSG1_KEY.clone(), 0) + ); scrollback .dirscroll(next, ScrollSize::Cell, &1.into(), &ctx, &mut store) .unwrap(); - assert_eq!(scrollback.viewctx.corner, MessageCursor::new(MSG1_KEY.clone(), 1)); + assert_eq!( + scrollback.viewctx.corner, + MessageCursor::new(MSG1_KEY.clone(), 1) + ); // Cannot scroll down any further. scrollback .dirscroll(next, ScrollSize::Cell, &1.into(), &ctx, &mut store) .unwrap(); - assert_eq!(scrollback.viewctx.corner, MessageCursor::new(MSG1_KEY.clone(), 1)); + assert_eq!( + scrollback.viewctx.corner, + MessageCursor::new(MSG1_KEY.clone(), 1) + ); // Scroll up two Pages (eight lines). scrollback .dirscroll(prev, ScrollSize::Page, &2.into(), &ctx, &mut store) .unwrap(); - assert_eq!(scrollback.viewctx.corner, MessageCursor::new(MSG3_KEY.clone(), 0)); + assert_eq!( + scrollback.viewctx.corner, + MessageCursor::new(MSG3_KEY.clone(), 0) + ); // Scroll down two HalfPages (four lines). scrollback .dirscroll(next, ScrollSize::HalfPage, &2.into(), &ctx, &mut store) .unwrap(); - assert_eq!(scrollback.viewctx.corner, MessageCursor::new(MSG3_KEY.clone(), 4)); + assert_eq!( + scrollback.viewctx.corner, + MessageCursor::new(MSG3_KEY.clone(), 4) + ); } #[tokio::test] @@ -1722,27 +1804,39 @@ mod tests { assert_eq!(scrollback.cursor, MSG4_KEY.clone().into()); assert_eq!(scrollback.viewctx.dimensions, (60, 3)); - assert_eq!(scrollback.viewctx.corner, MessageCursor::new(MSG3_KEY.clone(), 3)); + assert_eq!( + scrollback.viewctx.corner, + MessageCursor::new(MSG3_KEY.clone(), 3) + ); // Scroll so that the cursor is at the top of the screen. scrollback .cursorpos(MovePosition::Beginning, Axis::Vertical, &ctx, &mut store) .unwrap(); assert_eq!(scrollback.cursor, MSG4_KEY.clone().into()); - assert_eq!(scrollback.viewctx.corner, MessageCursor::new(MSG4_KEY.clone(), 0)); + assert_eq!( + scrollback.viewctx.corner, + MessageCursor::new(MSG4_KEY.clone(), 0) + ); // Scroll so that the cursor is at the bottom of the screen. scrollback .cursorpos(MovePosition::End, Axis::Vertical, &ctx, &mut store) .unwrap(); assert_eq!(scrollback.cursor, MSG4_KEY.clone().into()); - assert_eq!(scrollback.viewctx.corner, MessageCursor::new(MSG3_KEY.clone(), 3)); + assert_eq!( + scrollback.viewctx.corner, + MessageCursor::new(MSG3_KEY.clone(), 3) + ); // Scroll so that the cursor is in the middle of the screen. scrollback .cursorpos(MovePosition::Middle, Axis::Vertical, &ctx, &mut store) .unwrap(); assert_eq!(scrollback.cursor, MSG4_KEY.clone().into()); - assert_eq!(scrollback.viewctx.corner, MessageCursor::new(MSG3_KEY.clone(), 4)); + assert_eq!( + scrollback.viewctx.corner, + MessageCursor::new(MSG3_KEY.clone(), 4) + ); } } diff --git a/src/worker.rs b/src/worker.rs index d0392d15..48f77a68 100644 --- a/src/worker.rs +++ b/src/worker.rs @@ -119,7 +119,11 @@ const MIN_MSG_LOAD: u32 = 50; type MessageFetchResult = IambResult<(Option, Vec<(AnyTimelineEvent, Vec)>)>; fn initial_devname() -> String { - format!("{} on {}", IAMB_DEVICE_NAME, gethostname().to_string_lossy()) + format!( + "{} on {}", + IAMB_DEVICE_NAME, + gethostname().to_string_lossy() + ) } async fn is_direct(room: &MatrixRoom) -> bool { @@ -372,7 +376,11 @@ fn load_insert( info.fetch_id = fetch_id.map_or(RoomFetchStatus::Done, RoomFetchStatus::HaveMore); }, Err(e) => { - warn!(room_id = room_id.as_str(), err = e.to_string(), "Failed to load older messages"); + warn!( + room_id = room_id.as_str(), + err = e.to_string(), + "Failed to load older messages" + ); // Wait and try again. locked.application.need_load.insert(room_id, Need::MESSAGES); @@ -519,7 +527,13 @@ async fn send_receipts_forever(client: &Client, store: &AsyncProgramStore) { if changed { open_notifications.remove(room_id); } - changed.then(|| (room_id.to_owned(), thread.to_owned(), new_receipt.to_owned())) + changed.then(|| { + ( + room_id.to_owned(), + thread.to_owned(), + new_receipt.to_owned(), + ) + }) }); updates.extend(changed); @@ -623,7 +637,11 @@ pub enum WorkerTask { Members(OwnedRoomId, ClientReply>>), SpaceMembers(OwnedRoomId, ClientReply>>), TypingNotice(OwnedRoomId), - Verify(VerifyAction, SasVerification, ClientReply>), + Verify( + VerifyAction, + SasVerification, + ClientReply>, + ), VerifyRequest(OwnedUserId, ClientReply>), }