diff --git a/src/base.rs b/src/base.rs index 83f63db..ce744c2 100644 --- a/src/base.rs +++ b/src/base.rs @@ -1,3 +1,6 @@ +//! # Common types and utilities +//! +//! The types defined here get used throughout iamb. use std::borrow::Cow; use std::collections::{HashMap, HashSet}; use std::convert::TryFrom; @@ -84,6 +87,7 @@ use crate::{ ApplicationSettings, }; +/// The set of characters used in different Matrix IDs. pub const MATRIX_ID_WORD: WordStyle = WordStyle::CharSet(is_mxid_char); /// Find the boundaries for a Matrix username, room alias, or room ID. @@ -100,17 +104,27 @@ fn is_mxid_char(c: char) -> bool { const ROOM_FETCH_DEBOUNCE: Duration = Duration::from_secs(2); +/// Empty type used solely to implement [ApplicationInfo]. #[derive(Clone, Debug, Eq, PartialEq)] pub enum IambInfo {} +/// An action taken against an ongoing verification request. #[derive(Clone, Debug, Eq, PartialEq)] pub enum VerifyAction { + /// Accept a verification request. Accept, + + /// Cancel an in-progress verification. Cancel, + + /// Confirm an in-progress verification. Confirm, + + /// Reject an in-progress verification due to mismatched Emoji. Mismatch, } +/// An action taken against the currently selected message. #[derive(Clone, Debug, Eq, PartialEq)] pub enum MessageAction { /// Cance the current reply or edit. @@ -145,6 +159,7 @@ pub enum MessageAction { Unreact(Option), } +/// The type of room being created. #[derive(Clone, Debug, Eq, PartialEq)] pub enum CreateRoomType { /// A direct message room. @@ -158,7 +173,9 @@ pub enum CreateRoomType { } bitflags::bitflags! { + /// Available options for newly created rooms. pub struct CreateRoomFlags: u32 { + /// No flags specified. const NONE = 0b00000000; /// Make the room public. @@ -170,7 +187,9 @@ bitflags::bitflags! { } bitflags::bitflags! { + /// Available options when downloading files. pub struct DownloadFlags: u32 { + /// No flags specified. const NONE = 0b00000000; /// Overwrite file if it already exists. @@ -181,45 +200,91 @@ bitflags::bitflags! { } } +/// A room property. #[derive(Clone, Debug, Eq, PartialEq)] pub enum RoomField { + /// The room name. Name, + + /// A room tag. Tag(TagName), + + /// The room topic. Topic, } +/// An action that operates on a focused room. #[derive(Clone, Debug, Eq, PartialEq)] pub enum RoomAction { + /// Accept an invitation to join this room. InviteAccept, + + /// Reject an invitation to join this room. InviteReject, + + /// Invite a user to this room. InviteSend(OwnedUserId), + + /// Leave this room. Leave(bool), + + /// Open the members window. Members(Box>), + + /// Set a room property. Set(RoomField, String), + + /// Unset a room property. Unset(RoomField), } +/// An action that sends a message to a room. #[derive(Clone, Debug, Eq, PartialEq)] pub enum SendAction { + /// Send the text in the message bar. Submit, + + /// Send text provided from an external editor. SubmitFromEditor, + + /// Upload a file. Upload(String), + + /// Upload the image data. UploadImage(usize, usize, Cow<'static, [u8]>), } +/// An action performed against the user's homeserver. #[derive(Clone, Debug, Eq, PartialEq)] pub enum HomeserverAction { + /// Create a new room with an optional localpart. CreateRoom(Option, CreateRoomType, CreateRoomFlags), } +/// An action that the main program loop should. +/// +/// See [the commands module][super::commands] for where these are usually created. #[derive(Clone, Debug, Eq, PartialEq)] pub enum IambAction { + /// Perform an action against the homeserver. Homeserver(HomeserverAction), + + /// Perform an action on the currently selected message. Message(MessageAction), + + /// Perform an action on the currently focused room. Room(RoomAction), + + /// Send a message to the currently focused room. Send(SendAction), + + /// Perform an action for an in-progress verification. Verify(VerifyAction, String), + + /// Request a new verification with the specified user. VerifyRequest(String), + + /// Toggle the focus within the focused room. ToggleScrollbackFocus, } @@ -316,14 +381,21 @@ impl From for ProgramAction { } } +/// Alias for program actions. pub type ProgramAction = Action; +/// Alias for program context. pub type ProgramContext = VimContext; +/// Alias for program keybindings. pub type Keybindings = VimMachine; +/// Alias for a program command. pub type ProgramCommand = VimCommand; +/// Alias for mapped program commands. pub type ProgramCommands = VimCommandMachine; +/// Alias for program store. pub type ProgramStore = Store; +/// Alias for shared program store. pub type AsyncProgramStore = Arc>; - +/// Alias for an action result. pub type IambResult = UIResult; /// Reaction events for some message. @@ -332,61 +404,81 @@ pub type IambResult = UIResult; /// it's reacting to. pub type MessageReactions = HashMap; +/// Map of read receipts for different events. pub type Receipts = HashMap>; +/// Errors encountered during application use. #[derive(thiserror::Error, Debug)] pub enum IambError { + /// An invalid user identifier was specified. #[error("Invalid user identifier: {0}")] InvalidUserId(String), + /// An invalid verification identifier was specified. #[error("Invalid verification user/device pair: {0}")] InvalidVerificationId(String), + /// A failure related to the cryptographic store. #[error("Cryptographic storage error: {0}")] CryptoStore(#[from] matrix_sdk::encryption::CryptoStoreError), + /// An HTTP error. #[error("HTTP client error: {0}")] Http(#[from] matrix_sdk::HttpError), + /// A failure from the Matrix client. #[error("Matrix client error: {0}")] Matrix(#[from] matrix_sdk::Error), + /// A failure in the sled storage. #[error("Matrix client storage error: {0}")] Store(#[from] matrix_sdk::StoreError), + /// A failure during serialization or deserialization. #[error("Serialization/deserialization error: {0}")] Serde(#[from] serde_json::Error), + /// A failure due to not having a configured download directory. #[error("No download directory configured")] NoDownloadDir, + /// A failure due to not having a message with an attachment selected. #[error("Selected message does not have any attachments")] NoAttachment, + /// A failure due to not having a message selected. #[error("No message currently selected")] NoSelectedMessage, + /// A failure due to not having a room or space selected. #[error("Current window is not a room or space")] NoSelectedRoomOrSpace, + /// A failure due to not having a room selected. #[error("Current window is not a room")] NoSelectedRoom, + /// A failure due to not having an outstanding room invitation. #[error("You do not have a current invitation to this room")] NotInvited, + /// A failure due to not being a joined room member. #[error("You need to join the room before you can do that")] NotJoined, + /// An unknown room was specified. #[error("Unknown room identifier: {0}")] UnknownRoom(OwnedRoomId), + /// A failure occurred during verification. #[error("Verification request error: {0}")] VerificationRequestError(#[from] matrix_sdk::encryption::identities::RequestVerificationError), + /// A failure related to images. #[error("Image error: {0}")] Image(#[from] image::ImageError), + /// A failure to access the system's clipboard. #[error("Could not use system clipboard data")] Clipboard, } @@ -399,16 +491,26 @@ impl From for UIError { impl ApplicationError for IambError {} +/// Status for tracking how much room scrollback we've fetched. #[derive(Default)] pub enum RoomFetchStatus { + /// Room history has been completely fetched. Done, + + /// More room history can be fetched. HaveMore(String), + + /// We have not yet started fetching history for this room. #[default] NotStarted, } +/// Indicates where an [EventId] lives in the [ChatStore]. pub enum EventLocation { + /// The [EventId] belongs to a message. Message(MessageKey), + + /// The [EventId] belongs to a reaction to the given event. Reaction(OwnedEventId), } @@ -422,6 +524,7 @@ impl EventLocation { } } +/// Information about room's the user's joined. #[derive(Default)] pub struct RoomInfo { /// The display name for this room. @@ -462,6 +565,7 @@ pub struct RoomInfo { } impl RoomInfo { + /// Get the reactions and their counts for a message. pub fn get_reactions(&self, event_id: &EventId) -> Vec<(&str, usize)> { if let Some(reacts) = self.reactions.get(event_id) { let mut counts = HashMap::new(); @@ -480,14 +584,17 @@ impl RoomInfo { } } + /// Map an event identifier to its [MessageKey]. pub fn get_message_key(&self, event_id: &EventId) -> Option<&MessageKey> { self.keys.get(event_id)?.to_message_key() } + /// Get an event for an identifier. pub fn get_event(&self, event_id: &EventId) -> Option<&Message> { self.messages.get(self.get_message_key(event_id)?) } + /// Insert a reaction to a message. pub fn insert_reaction(&mut self, react: ReactionEvent) { match react { MessageLikeEvent::Original(react) => { @@ -509,6 +616,7 @@ impl RoomInfo { } } + /// Insert an edit. pub fn insert_edit(&mut self, msg: Replacement) { let event_id = msg.event_id; let new_content = msg.new_content; @@ -551,6 +659,7 @@ impl RoomInfo { self.messages.insert(key, msg.into()); } + /// Insert a new message. pub fn insert_message(&mut self, msg: RoomMessageEvent) { let event_id = msg.event_id().to_owned(); let key = (msg.origin_server_ts().into(), event_id.clone()); @@ -563,6 +672,7 @@ impl RoomInfo { let _ = self.messages.remove(&key); } + /// Insert a new message event. pub fn insert(&mut self, msg: RoomMessageEvent) { match msg { RoomMessageEvent::Original(OriginalRoomMessageEvent { @@ -574,6 +684,7 @@ impl RoomInfo { } } + /// Indicates whether we've recently fetched scrollback for this room. pub fn recently_fetched(&self) -> bool { self.fetch_last.map_or(false, |i| i.elapsed() < ROOM_FETCH_DEBOUNCE) } @@ -617,10 +728,12 @@ impl RoomInfo { } } + /// Update typing information for this room. pub fn set_typing(&mut self, user_ids: Vec) { self.users_typing = (Instant::now(), user_ids).into(); } + /// Create a [Rect] that displays what users are typing. pub fn render_typing( &mut self, area: Rect, @@ -646,6 +759,7 @@ impl RoomInfo { } } +/// Generate a [CompletionMap] for Emoji shortcodes. fn emoji_map() -> CompletionMap { let mut emojis = CompletionMap::default(); @@ -658,27 +772,54 @@ fn emoji_map() -> CompletionMap { return emojis; } +/// Information gathered during server syncs about joined rooms. #[derive(Default)] pub struct SyncInfo { + /// Spaces that the user is a member of. pub spaces: Vec, + + /// Rooms that the user is a member of. pub rooms: Vec)>>, + + /// DMs that the user is a member of. pub dms: Vec)>>, } +/// The main application state. pub struct ChatStore { + /// `:`-commands pub cmds: ProgramCommands, + + /// Handle for communicating w/ the worker thread. pub worker: Requester, + + /// Map of joined rooms. pub rooms: CompletionMap, + + /// Map of room names. pub names: CompletionMap, + + /// Presence information for other users. pub presences: CompletionMap, + + /// In-progress and completed verifications. pub verifications: HashMap, + + /// Settings for the current profile loaded from config file. pub settings: ApplicationSettings, + + /// Set of rooms that need more messages loaded in their scrollback. pub need_load: HashSet, + + /// [CompletionMap] of Emoji shortcodes. pub emojis: CompletionMap, + + /// Information gathered by the background thread. pub sync_info: SyncInfo, } impl ChatStore { + /// Create a new [ChatStore]. pub fn new(worker: Requester, settings: ApplicationSettings) -> Self { ChatStore { worker, @@ -696,10 +837,12 @@ impl ChatStore { } } + /// Get a joined room. pub fn get_joined_room(&self, room_id: &RoomId) -> Option { self.worker.client.get_joined_room(room_id) } + /// Get the title for a room. pub fn get_room_title(&self, room_id: &RoomId) -> String { self.rooms .get(room_id) @@ -708,6 +851,7 @@ impl ChatStore { .unwrap_or_else(|| "Untitled Matrix Room".to_string()) } + /// Update the receipts for multiple rooms. pub async fn set_receipts( &mut self, receipts: Vec<(OwnedRoomId, Receipts)>, @@ -727,18 +871,22 @@ impl ChatStore { return updates; } + /// Mark a room for loading more scrollback. pub fn mark_for_load(&mut self, room_id: OwnedRoomId) { self.need_load.insert(room_id); } + /// Get the [RoomInfo] for a given room identifier. pub fn get_room_info(&mut self, room_id: OwnedRoomId) -> &mut RoomInfo { self.rooms.get_or_default(room_id) } + /// Set the name for a room. pub fn set_room_name(&mut self, room_id: &RoomId, name: &str) { self.rooms.get_or_default(room_id.to_owned()).name = name.to_string().into(); } + /// Insert a new E2EE verification. pub fn insert_sas(&mut self, sas: SasVerification) { let key = format!("{}/{}", sas.other_user_id(), sas.other_device().device_id()); @@ -748,6 +896,7 @@ impl ChatStore { impl ApplicationStore for ChatStore {} +/// Identified used to track window content. #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub enum IambId { /// A Matrix room. @@ -810,6 +959,7 @@ impl<'de> Deserialize<'de> for IambId { } } +/// [serde] visitor for deserializing [IambId]. struct IambIdVisitor; impl<'de> Visitor<'de> for IambIdVisitor { @@ -903,35 +1053,61 @@ impl<'de> Visitor<'de> for IambIdVisitor { } } +/// Which part of the room window's UI is focused. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum RoomFocus { + /// The scrollback for a room window is focused. Scrollback, + + /// The message bar for a room window is focused. MessageBar, } impl RoomFocus { + /// Whether this is [RoomFocus::Scrollback]. pub fn is_scrollback(&self) -> bool { matches!(self, RoomFocus::Scrollback) } + /// Whether this is [RoomFocus::MessageBar]. pub fn is_msgbar(&self) -> bool { matches!(self, RoomFocus::MessageBar) } } +/// Identifiers used to track where a mark was placed. +/// +/// While this is the "buffer identifier" for the mark, +/// not all of these are necessarily actual buffers. #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub enum IambBufferId { + /// The command bar buffer. Command(CommandType), + + /// The message buffer or a specific message in a room. Room(OwnedRoomId, RoomFocus), + + /// The `:dms` window. DirectList, + + /// The `:members` window for a room. MemberList(OwnedRoomId), + + /// The `:rooms` window. RoomList, + + /// The `:spaces` window. SpaceList, + + /// The `:verify` window. VerifyList, + + /// The buffer for the `:rooms` window. Welcome, } impl IambBufferId { + /// Get the identifier for the window that contains this buffer. pub fn to_window(&self) -> Option { match self { IambBufferId::Command(_) => None, @@ -982,6 +1158,7 @@ impl ApplicationInfo for IambInfo { } } +/// Tab completion for user IDs. fn complete_users(text: &EditRope, cursor: &mut Cursor, store: &ProgramStore) -> Vec { let id = text .get_prefix_word_mut(cursor, &MATRIX_ID_WORD) @@ -997,6 +1174,7 @@ fn complete_users(text: &EditRope, cursor: &mut Cursor, store: &ProgramStore) -> .collect() } +/// Tab completion within the message bar. fn complete_msgbar(text: &EditRope, cursor: &mut Cursor, store: &ProgramStore) -> Vec { let id = text .get_prefix_word_mut(cursor, &MATRIX_ID_WORD) @@ -1044,6 +1222,7 @@ fn complete_msgbar(text: &EditRope, cursor: &mut Cursor, store: &ProgramStore) - } } +/// Tab completion for Matrix identifiers (usernames, room aliases, etc.) fn complete_matrix_names( text: &EditRope, cursor: &mut Cursor, @@ -1073,6 +1252,7 @@ fn complete_matrix_names( .collect() } +/// Tab completion for Emoji shortcode names. fn complete_emoji(text: &EditRope, cursor: &mut Cursor, store: &ProgramStore) -> Vec { let sc = text.get_prefix_word_mut(cursor, &WordStyle::Little); let sc = sc.unwrap_or_else(EditRope::empty); @@ -1081,6 +1261,7 @@ fn complete_emoji(text: &EditRope, cursor: &mut Cursor, store: &ProgramStore) -> store.application.emojis.complete(sc.as_ref()) } +/// Tab completion for command names. fn complete_cmdname( desc: CommandDescription, text: &EditRope, @@ -1092,6 +1273,7 @@ fn complete_cmdname( store.application.cmds.complete_name(desc.command.as_str()) } +/// Tab completion for command arguments. fn complete_cmdarg( desc: CommandDescription, text: &EditRope, @@ -1120,6 +1302,7 @@ fn complete_cmdarg( } } +/// Tab completion for commands. fn complete_cmd( cmd: &str, text: &EditRope, @@ -1141,6 +1324,7 @@ fn complete_cmd( } } +/// Tab completion for the command bar. fn complete_cmdbar(text: &EditRope, cursor: &mut Cursor, store: &ProgramStore) -> Vec { let eo = text.cursor_to_offset(cursor); let slice = text.slice(0.into(), eo, false); diff --git a/src/commands.rs b/src/commands.rs index 1d7ae74..58a954f 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1,3 +1,7 @@ +//! # Default Commands +//! +//! The command-bar commands are set up here, and iamb-specific commands are defined here. See +//! [modalkit::env::vim::command] for additional Vim commands we pull in. use std::convert::TryFrom; use matrix_sdk::ruma::{events::tag::TagName, OwnedUserId}; @@ -555,6 +559,7 @@ fn add_iamb_commands(cmds: &mut ProgramCommands) { }); } +/// Initialize the default command state. pub fn setup_commands() -> ProgramCommands { let mut cmds = ProgramCommands::default(); diff --git a/src/config.rs b/src/config.rs index e3eba94..86ddec5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,3 +1,4 @@ +//! # Logic for loading and validating application configuration use std::borrow::Cow; use std::collections::hash_map::DefaultHasher; use std::collections::HashMap; diff --git a/src/keybindings.rs b/src/keybindings.rs index 4b93efe..bb619db 100644 --- a/src/keybindings.rs +++ b/src/keybindings.rs @@ -1,3 +1,7 @@ +//! # Default Keybindings +//! +//! The keybindings are set up here. We define some iamb-specific keybindings, but the default Vim +//! keys come from [modalkit::env::vim::keybindings]. use modalkit::{ editing::action::WindowAction, env::vim::keybindings::{InputStep, VimBindings}, @@ -10,6 +14,7 @@ use crate::base::{IambAction, IambInfo, Keybindings, MATRIX_ID_WORD}; type IambStep = InputStep; +/// Initialize the default keybinding state. pub fn setup_keybindings() -> Keybindings { let mut ism = Keybindings::empty(); diff --git a/src/main.rs b/src/main.rs index 14bf665..6bef110 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,16 @@ +//! # iamb +//! +//! The iamb client loops over user input and commands, and turns them into actions, [some of +//! which][IambAction] are specific to iamb, and [some of which][Action] come from [modalkit]. When +//! adding new functionality, you will usually want to extend [IambAction] or one of its variants +//! (like [RoomAction][base::RoomAction]), and then add an appropriate [command][commands] or +//! [keybinding][keybindings]. +//! +//! For more complicated changes, you may need to update [the async worker thread][worker], which +//! handles background Matrix tasks with [matrix-rust-sdk][matrix_sdk]. +//! +//! Most rendering logic lives under the [windows] module, but [Matrix messages][message] have +//! their own module. #![allow(clippy::manual_range_contains)] #![allow(clippy::needless_return)] #![allow(clippy::result_large_err)] @@ -200,6 +213,7 @@ fn setup_screen( return Ok(ScreenState::new(win, cmd)); } +/// The main application state and event loop. struct Application { /// Terminal backend. terminal: Terminal>, diff --git a/src/message/html.rs b/src/message/html.rs index 215e2ec..5f69285 100644 --- a/src/message/html.rs +++ b/src/message/html.rs @@ -37,7 +37,8 @@ use crate::{ util::{join_cell_text, space_text}, }; -struct BulletIterator { +/// Generate bullet points from a [ListStyle]. +pub struct BulletIterator { style: ListStyle, pos: usize, len: usize, @@ -74,6 +75,7 @@ impl Iterator for BulletIterator { } } +/// Whether this list is ordered or unordered. #[derive(Clone, Copy, Debug)] pub enum ListStyle { Ordered, @@ -88,11 +90,13 @@ impl ListStyle { pub type StyleTreeChildren = Vec; +/// Type of contents in a table cell. pub enum CellType { Data, Header, } +/// A collection of cells for a single row in a table. pub struct TableRow { cells: Vec<(CellType, StyleTreeNode)>, } @@ -103,6 +107,7 @@ impl TableRow { } } +/// A collection of rows in a table. pub struct TableSection { rows: Vec, } @@ -113,6 +118,7 @@ impl TableSection { } } +/// A table. pub struct Table { caption: Option>, sections: Vec, @@ -229,6 +235,7 @@ impl Table { } } +/// A processed HTML element that we can render to the terminal. pub enum StyleTreeNode { Blockquote(Box), Break, @@ -380,6 +387,7 @@ impl StyleTreeNode { } } +/// A processed HTML document. pub struct StyleTree { children: StyleTreeChildren, } @@ -649,6 +657,7 @@ fn dom_to_style_tree(dom: RcDom) -> StyleTree { StyleTree { children: h2t(&dom.document) } } +/// Parse an HTML document from a string. pub fn parse_matrix_html(s: &str) -> StyleTree { let dom = parse_fragment( RcDom::default(), diff --git a/src/message/mod.rs b/src/message/mod.rs index 299ea44..1aa4f66 100644 --- a/src/message/mod.rs +++ b/src/message/mod.rs @@ -1,3 +1,4 @@ +//! # Room Messages use std::borrow::Cow; use std::cmp::{Ord, Ordering, PartialOrd}; use std::collections::hash_map::DefaultHasher; diff --git a/src/message/printer.rs b/src/message/printer.rs index 2d2b8b5..f1d08de 100644 --- a/src/message/printer.rs +++ b/src/message/printer.rs @@ -1,3 +1,8 @@ +//! # Line Wrapping Logic +//! +//! The [TextPrinter] handles wrapping stylized text and inserting spaces for padding at the end of +//! lines to make concatenation work right (e.g., combining table cells after wrapping their +//! contents). use std::borrow::Cow; use modalkit::tui::layout::Alignment; @@ -8,6 +13,7 @@ use unicode_width::UnicodeWidthStr; use crate::util::{space_span, take_width}; +/// Wrap styled text for the current terminal width. pub struct TextPrinter<'a> { text: Text<'a>, width: usize, @@ -21,6 +27,7 @@ pub struct TextPrinter<'a> { } impl<'a> TextPrinter<'a> { + /// Create a new printer. pub fn new(width: usize, base_style: Style, hide_reply: bool) -> Self { TextPrinter { text: Text::default(), @@ -35,24 +42,29 @@ impl<'a> TextPrinter<'a> { } } + /// Configure the alignment for each line. pub fn align(mut self, alignment: Alignment) -> Self { self.alignment = alignment; self } + /// Set whether newlines should be treated literally, or turned into spaces. pub fn literal(mut self, literal: bool) -> Self { self.literal = literal; self } + /// Indicates whether replies should be pushed to the printer. pub fn hide_reply(&self) -> bool { self.hide_reply } + /// Indicates the current printer's width. pub fn width(&self) -> usize { self.width } + /// Create a new printer with a smaller width. pub fn sub(&self, indent: usize) -> Self { TextPrinter { text: Text::default(), @@ -71,6 +83,7 @@ impl<'a> TextPrinter<'a> { self.width - self.curr_width } + /// If there is any text on the current line, start a new one. pub fn commit(&mut self) { if self.curr_width > 0 { self.push_break(); @@ -82,6 +95,7 @@ impl<'a> TextPrinter<'a> { self.text.lines.push(Spans(std::mem::take(&mut self.curr_spans))); } + /// Start a new line. pub fn push_break(&mut self) { if self.curr_width == 0 && self.text.lines.is_empty() { // Disallow leading breaks. @@ -149,6 +163,7 @@ impl<'a> TextPrinter<'a> { } } + /// Push a [Span] that isn't allowed to break across lines. pub fn push_span_nobreak(&mut self, span: Span<'a>) { let sw = UnicodeWidthStr::width(span.content.as_ref()); @@ -161,6 +176,7 @@ impl<'a> TextPrinter<'a> { self.curr_width += sw; } + /// Push text with a [Style]. pub fn push_str(&mut self, s: &'a str, style: Style) { let style = self.base_style.patch(style); @@ -212,16 +228,19 @@ impl<'a> TextPrinter<'a> { } } + /// Push [Spans] into the printer. pub fn push_line(&mut self, spans: Spans<'a>) { self.commit(); self.text.lines.push(spans); } + /// Push multiline [Text] into the printer. pub fn push_text(&mut self, text: Text<'a>) { self.commit(); self.text.lines.extend(text.lines); } + /// Render the contents of this printer as [Text]. pub fn finish(mut self) -> Text<'a> { self.commit(); self.text diff --git a/src/util.rs b/src/util.rs index 46a5d91..55d47c2 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,3 +1,4 @@ +//! # Utility functions use std::borrow::Cow; use unicode_segmentation::UnicodeSegmentation; diff --git a/src/windows/mod.rs b/src/windows/mod.rs index 9f72a5b..09abf44 100644 --- a/src/windows/mod.rs +++ b/src/windows/mod.rs @@ -1,3 +1,11 @@ +//! # Windows for the User Interface +//! +//! This module contains the logic for rendering windows, and handling UI actions that get +//! delegated to individual windows/UI elements (e.g., typing text or selecting a list item). +//! +//! Additionally, some of the iamb commands delegate behaviour to the current UI element. For +//! example, [sending messages][crate::base::SendAction] delegate to the [room window][RoomState], +//! where we have the message bar and room ID easily accesible and resetable. use std::cmp::{Ord, Ordering, PartialOrd}; use std::ops::Deref; use std::sync::Arc; diff --git a/src/windows/room/chat.rs b/src/windows/room/chat.rs index 94fde1f..610853e 100644 --- a/src/windows/room/chat.rs +++ b/src/windows/room/chat.rs @@ -1,3 +1,4 @@ +//! Window for Matrix rooms use std::borrow::Cow; use std::ffi::{OsStr, OsString}; use std::fs; @@ -85,6 +86,7 @@ use crate::worker::Requester; use super::scrollback::{Scrollback, ScrollbackState}; +/// State needed for rendering [Chat]. pub struct ChatState { room_id: OwnedRoomId, room: MatrixRoom, @@ -786,6 +788,7 @@ impl Promptable for ChatState { } } +/// [StatefulWidget] for Matrix rooms. pub struct Chat<'a> { store: &'a mut ProgramStore, focused: bool, diff --git a/src/windows/room/mod.rs b/src/windows/room/mod.rs index 73d99b2..ec4483b 100644 --- a/src/windows/room/mod.rs +++ b/src/windows/room/mod.rs @@ -1,3 +1,4 @@ +//! # Windows for Matrix rooms and spaces use matrix_sdk::{ room::{Invited, Room as MatrixRoom}, ruma::{ @@ -79,6 +80,11 @@ macro_rules! delegate { }; } +/// State for a Matrix room or space. +/// +/// Since spaces function as special rooms within Matrix, we wrap their window state together, so +/// that operations like sending and accepting invites, opening the members window, etc., all work +/// similarly. pub enum RoomState { Chat(ChatState), Space(SpaceState), diff --git a/src/windows/room/scrollback.rs b/src/windows/room/scrollback.rs index 961ee98..590c785 100644 --- a/src/windows/room/scrollback.rs +++ b/src/windows/room/scrollback.rs @@ -1,3 +1,4 @@ +//! Message scrollback use std::collections::HashSet; use regex::Regex; diff --git a/src/windows/room/space.rs b/src/windows/room/space.rs index 4ea4f93..d3f49b7 100644 --- a/src/windows/room/space.rs +++ b/src/windows/room/space.rs @@ -1,3 +1,4 @@ +//! Window for Matrix spaces use std::ops::{Deref, DerefMut}; use std::time::{Duration, Instant}; @@ -25,6 +26,7 @@ use crate::windows::RoomItem; const SPACE_HIERARCHY_DEBOUNCE: Duration = Duration::from_secs(5); +/// State needed for rendering [Space]. pub struct SpaceState { room_id: OwnedRoomId, room: MatrixRoom, @@ -86,6 +88,7 @@ impl DerefMut for SpaceState { } } +/// [StatefulWidget] for Matrix spaces. pub struct Space<'a> { focused: bool, store: &'a mut ProgramStore, diff --git a/src/windows/welcome.rs b/src/windows/welcome.rs index 543a582..d57bd3b 100644 --- a/src/windows/welcome.rs +++ b/src/windows/welcome.rs @@ -1,3 +1,4 @@ +//! Welcome Window use std::ops::{Deref, DerefMut}; use modalkit::tui::{buffer::Buffer, layout::Rect}; diff --git a/src/worker.rs b/src/worker.rs index 2141d15..19d1037 100644 --- a/src/worker.rs +++ b/src/worker.rs @@ -1,3 +1,7 @@ +//! # Async Matrix Client Worker +//! +//! The worker thread handles asynchronous work, and can receive messages from the main thread that +//! block on a reply from the async worker. use std::collections::HashMap; use std::convert::TryFrom; use std::fmt::{Debug, Formatter};