Add more documentation (#166)

This commit is contained in:
Ulyssa 2023-10-06 22:35:27 -07:00
parent 2673cfaeb9
commit 9197864c5c
No known key found for this signature in database
GPG key ID: F2873CA2997B83C5
16 changed files with 267 additions and 2 deletions

View file

@ -1,3 +1,6 @@
//! # Common types and utilities
//!
//! The types defined here get used throughout iamb.
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::convert::TryFrom; use std::convert::TryFrom;
@ -84,6 +87,7 @@ use crate::{
ApplicationSettings, ApplicationSettings,
}; };
/// The set of characters used in different Matrix IDs.
pub const MATRIX_ID_WORD: WordStyle = WordStyle::CharSet(is_mxid_char); pub const MATRIX_ID_WORD: WordStyle = WordStyle::CharSet(is_mxid_char);
/// Find the boundaries for a Matrix username, room alias, or room ID. /// 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); const ROOM_FETCH_DEBOUNCE: Duration = Duration::from_secs(2);
/// Empty type used solely to implement [ApplicationInfo].
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub enum IambInfo {} pub enum IambInfo {}
/// An action taken against an ongoing verification request.
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub enum VerifyAction { pub enum VerifyAction {
/// Accept a verification request.
Accept, Accept,
/// Cancel an in-progress verification.
Cancel, Cancel,
/// Confirm an in-progress verification.
Confirm, Confirm,
/// Reject an in-progress verification due to mismatched Emoji.
Mismatch, Mismatch,
} }
/// An action taken against the currently selected message.
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub enum MessageAction { pub enum MessageAction {
/// Cance the current reply or edit. /// Cance the current reply or edit.
@ -145,6 +159,7 @@ pub enum MessageAction {
Unreact(Option<String>), Unreact(Option<String>),
} }
/// The type of room being created.
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub enum CreateRoomType { pub enum CreateRoomType {
/// A direct message room. /// A direct message room.
@ -158,7 +173,9 @@ pub enum CreateRoomType {
} }
bitflags::bitflags! { bitflags::bitflags! {
/// Available options for newly created rooms.
pub struct CreateRoomFlags: u32 { pub struct CreateRoomFlags: u32 {
/// No flags specified.
const NONE = 0b00000000; const NONE = 0b00000000;
/// Make the room public. /// Make the room public.
@ -170,7 +187,9 @@ bitflags::bitflags! {
} }
bitflags::bitflags! { bitflags::bitflags! {
/// Available options when downloading files.
pub struct DownloadFlags: u32 { pub struct DownloadFlags: u32 {
/// No flags specified.
const NONE = 0b00000000; const NONE = 0b00000000;
/// Overwrite file if it already exists. /// Overwrite file if it already exists.
@ -181,45 +200,91 @@ bitflags::bitflags! {
} }
} }
/// A room property.
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub enum RoomField { pub enum RoomField {
/// The room name.
Name, Name,
/// A room tag.
Tag(TagName), Tag(TagName),
/// The room topic.
Topic, Topic,
} }
/// An action that operates on a focused room.
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub enum RoomAction { pub enum RoomAction {
/// Accept an invitation to join this room.
InviteAccept, InviteAccept,
/// Reject an invitation to join this room.
InviteReject, InviteReject,
/// Invite a user to this room.
InviteSend(OwnedUserId), InviteSend(OwnedUserId),
/// Leave this room.
Leave(bool), Leave(bool),
/// Open the members window.
Members(Box<CommandContext<ProgramContext>>), Members(Box<CommandContext<ProgramContext>>),
/// Set a room property.
Set(RoomField, String), Set(RoomField, String),
/// Unset a room property.
Unset(RoomField), Unset(RoomField),
} }
/// An action that sends a message to a room.
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub enum SendAction { pub enum SendAction {
/// Send the text in the message bar.
Submit, Submit,
/// Send text provided from an external editor.
SubmitFromEditor, SubmitFromEditor,
/// Upload a file.
Upload(String), Upload(String),
/// Upload the image data.
UploadImage(usize, usize, Cow<'static, [u8]>), UploadImage(usize, usize, Cow<'static, [u8]>),
} }
/// An action performed against the user's homeserver.
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub enum HomeserverAction { pub enum HomeserverAction {
/// Create a new room with an optional localpart.
CreateRoom(Option<String>, CreateRoomType, CreateRoomFlags), CreateRoom(Option<String>, 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)] #[derive(Clone, Debug, Eq, PartialEq)]
pub enum IambAction { pub enum IambAction {
/// Perform an action against the homeserver.
Homeserver(HomeserverAction), Homeserver(HomeserverAction),
/// Perform an action on the currently selected message.
Message(MessageAction), Message(MessageAction),
/// Perform an action on the currently focused room.
Room(RoomAction), Room(RoomAction),
/// Send a message to the currently focused room.
Send(SendAction), Send(SendAction),
/// Perform an action for an in-progress verification.
Verify(VerifyAction, String), Verify(VerifyAction, String),
/// Request a new verification with the specified user.
VerifyRequest(String), VerifyRequest(String),
/// Toggle the focus within the focused room.
ToggleScrollbackFocus, ToggleScrollbackFocus,
} }
@ -316,14 +381,21 @@ impl From<IambAction> for ProgramAction {
} }
} }
/// Alias for program actions.
pub type ProgramAction = Action<IambInfo>; pub type ProgramAction = Action<IambInfo>;
/// Alias for program context.
pub type ProgramContext = VimContext<IambInfo>; pub type ProgramContext = VimContext<IambInfo>;
/// Alias for program keybindings.
pub type Keybindings = VimMachine<TerminalKey, IambInfo>; pub type Keybindings = VimMachine<TerminalKey, IambInfo>;
/// Alias for a program command.
pub type ProgramCommand = VimCommand<ProgramContext, IambInfo>; pub type ProgramCommand = VimCommand<ProgramContext, IambInfo>;
/// Alias for mapped program commands.
pub type ProgramCommands = VimCommandMachine<ProgramContext, IambInfo>; pub type ProgramCommands = VimCommandMachine<ProgramContext, IambInfo>;
/// Alias for program store.
pub type ProgramStore = Store<IambInfo>; pub type ProgramStore = Store<IambInfo>;
/// Alias for shared program store.
pub type AsyncProgramStore = Arc<AsyncMutex<ProgramStore>>; pub type AsyncProgramStore = Arc<AsyncMutex<ProgramStore>>;
/// Alias for an action result.
pub type IambResult<T> = UIResult<T, IambInfo>; pub type IambResult<T> = UIResult<T, IambInfo>;
/// Reaction events for some message. /// Reaction events for some message.
@ -332,61 +404,81 @@ pub type IambResult<T> = UIResult<T, IambInfo>;
/// it's reacting to. /// it's reacting to.
pub type MessageReactions = HashMap<OwnedEventId, (String, OwnedUserId)>; pub type MessageReactions = HashMap<OwnedEventId, (String, OwnedUserId)>;
/// Map of read receipts for different events.
pub type Receipts = HashMap<OwnedEventId, Vec<OwnedUserId>>; pub type Receipts = HashMap<OwnedEventId, Vec<OwnedUserId>>;
/// Errors encountered during application use.
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub enum IambError { pub enum IambError {
/// An invalid user identifier was specified.
#[error("Invalid user identifier: {0}")] #[error("Invalid user identifier: {0}")]
InvalidUserId(String), InvalidUserId(String),
/// An invalid verification identifier was specified.
#[error("Invalid verification user/device pair: {0}")] #[error("Invalid verification user/device pair: {0}")]
InvalidVerificationId(String), InvalidVerificationId(String),
/// A failure related to the cryptographic store.
#[error("Cryptographic storage error: {0}")] #[error("Cryptographic storage error: {0}")]
CryptoStore(#[from] matrix_sdk::encryption::CryptoStoreError), CryptoStore(#[from] matrix_sdk::encryption::CryptoStoreError),
/// An HTTP error.
#[error("HTTP client error: {0}")] #[error("HTTP client error: {0}")]
Http(#[from] matrix_sdk::HttpError), Http(#[from] matrix_sdk::HttpError),
/// A failure from the Matrix client.
#[error("Matrix client error: {0}")] #[error("Matrix client error: {0}")]
Matrix(#[from] matrix_sdk::Error), Matrix(#[from] matrix_sdk::Error),
/// A failure in the sled storage.
#[error("Matrix client storage error: {0}")] #[error("Matrix client storage error: {0}")]
Store(#[from] matrix_sdk::StoreError), Store(#[from] matrix_sdk::StoreError),
/// A failure during serialization or deserialization.
#[error("Serialization/deserialization error: {0}")] #[error("Serialization/deserialization error: {0}")]
Serde(#[from] serde_json::Error), Serde(#[from] serde_json::Error),
/// A failure due to not having a configured download directory.
#[error("No download directory configured")] #[error("No download directory configured")]
NoDownloadDir, NoDownloadDir,
/// A failure due to not having a message with an attachment selected.
#[error("Selected message does not have any attachments")] #[error("Selected message does not have any attachments")]
NoAttachment, NoAttachment,
/// A failure due to not having a message selected.
#[error("No message currently selected")] #[error("No message currently selected")]
NoSelectedMessage, NoSelectedMessage,
/// A failure due to not having a room or space selected.
#[error("Current window is not a room or space")] #[error("Current window is not a room or space")]
NoSelectedRoomOrSpace, NoSelectedRoomOrSpace,
/// A failure due to not having a room selected.
#[error("Current window is not a room")] #[error("Current window is not a room")]
NoSelectedRoom, NoSelectedRoom,
/// A failure due to not having an outstanding room invitation.
#[error("You do not have a current invitation to this room")] #[error("You do not have a current invitation to this room")]
NotInvited, NotInvited,
/// A failure due to not being a joined room member.
#[error("You need to join the room before you can do that")] #[error("You need to join the room before you can do that")]
NotJoined, NotJoined,
/// An unknown room was specified.
#[error("Unknown room identifier: {0}")] #[error("Unknown room identifier: {0}")]
UnknownRoom(OwnedRoomId), UnknownRoom(OwnedRoomId),
/// A failure occurred during verification.
#[error("Verification request error: {0}")] #[error("Verification request error: {0}")]
VerificationRequestError(#[from] matrix_sdk::encryption::identities::RequestVerificationError), VerificationRequestError(#[from] matrix_sdk::encryption::identities::RequestVerificationError),
/// A failure related to images.
#[error("Image error: {0}")] #[error("Image error: {0}")]
Image(#[from] image::ImageError), Image(#[from] image::ImageError),
/// A failure to access the system's clipboard.
#[error("Could not use system clipboard data")] #[error("Could not use system clipboard data")]
Clipboard, Clipboard,
} }
@ -399,16 +491,26 @@ impl From<IambError> for UIError<IambInfo> {
impl ApplicationError for IambError {} impl ApplicationError for IambError {}
/// Status for tracking how much room scrollback we've fetched.
#[derive(Default)] #[derive(Default)]
pub enum RoomFetchStatus { pub enum RoomFetchStatus {
/// Room history has been completely fetched.
Done, Done,
/// More room history can be fetched.
HaveMore(String), HaveMore(String),
/// We have not yet started fetching history for this room.
#[default] #[default]
NotStarted, NotStarted,
} }
/// Indicates where an [EventId] lives in the [ChatStore].
pub enum EventLocation { pub enum EventLocation {
/// The [EventId] belongs to a message.
Message(MessageKey), Message(MessageKey),
/// The [EventId] belongs to a reaction to the given event.
Reaction(OwnedEventId), Reaction(OwnedEventId),
} }
@ -422,6 +524,7 @@ impl EventLocation {
} }
} }
/// Information about room's the user's joined.
#[derive(Default)] #[derive(Default)]
pub struct RoomInfo { pub struct RoomInfo {
/// The display name for this room. /// The display name for this room.
@ -462,6 +565,7 @@ pub struct RoomInfo {
} }
impl RoomInfo { impl RoomInfo {
/// Get the reactions and their counts for a message.
pub fn get_reactions(&self, event_id: &EventId) -> Vec<(&str, usize)> { pub fn get_reactions(&self, event_id: &EventId) -> Vec<(&str, usize)> {
if let Some(reacts) = self.reactions.get(event_id) { if let Some(reacts) = self.reactions.get(event_id) {
let mut counts = HashMap::new(); 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> { pub fn get_message_key(&self, event_id: &EventId) -> Option<&MessageKey> {
self.keys.get(event_id)?.to_message_key() self.keys.get(event_id)?.to_message_key()
} }
/// Get an event for an identifier.
pub fn get_event(&self, event_id: &EventId) -> Option<&Message> { pub fn get_event(&self, event_id: &EventId) -> Option<&Message> {
self.messages.get(self.get_message_key(event_id)?) self.messages.get(self.get_message_key(event_id)?)
} }
/// Insert a reaction to a message.
pub fn insert_reaction(&mut self, react: ReactionEvent) { pub fn insert_reaction(&mut self, react: ReactionEvent) {
match react { match react {
MessageLikeEvent::Original(react) => { MessageLikeEvent::Original(react) => {
@ -509,6 +616,7 @@ impl RoomInfo {
} }
} }
/// Insert an edit.
pub fn insert_edit(&mut self, msg: Replacement) { pub fn insert_edit(&mut self, msg: Replacement) {
let event_id = msg.event_id; let event_id = msg.event_id;
let new_content = msg.new_content; let new_content = msg.new_content;
@ -551,6 +659,7 @@ impl RoomInfo {
self.messages.insert(key, msg.into()); self.messages.insert(key, msg.into());
} }
/// Insert a new message.
pub fn insert_message(&mut self, msg: RoomMessageEvent) { pub fn insert_message(&mut self, msg: RoomMessageEvent) {
let event_id = msg.event_id().to_owned(); let event_id = msg.event_id().to_owned();
let key = (msg.origin_server_ts().into(), event_id.clone()); let key = (msg.origin_server_ts().into(), event_id.clone());
@ -563,6 +672,7 @@ impl RoomInfo {
let _ = self.messages.remove(&key); let _ = self.messages.remove(&key);
} }
/// Insert a new message event.
pub fn insert(&mut self, msg: RoomMessageEvent) { pub fn insert(&mut self, msg: RoomMessageEvent) {
match msg { match msg {
RoomMessageEvent::Original(OriginalRoomMessageEvent { 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 { pub fn recently_fetched(&self) -> bool {
self.fetch_last.map_or(false, |i| i.elapsed() < ROOM_FETCH_DEBOUNCE) 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<OwnedUserId>) { pub fn set_typing(&mut self, user_ids: Vec<OwnedUserId>) {
self.users_typing = (Instant::now(), user_ids).into(); self.users_typing = (Instant::now(), user_ids).into();
} }
/// Create a [Rect] that displays what users are typing.
pub fn render_typing( pub fn render_typing(
&mut self, &mut self,
area: Rect, area: Rect,
@ -646,6 +759,7 @@ impl RoomInfo {
} }
} }
/// Generate a [CompletionMap] for Emoji shortcodes.
fn emoji_map() -> CompletionMap<String, &'static Emoji> { fn emoji_map() -> CompletionMap<String, &'static Emoji> {
let mut emojis = CompletionMap::default(); let mut emojis = CompletionMap::default();
@ -658,27 +772,54 @@ fn emoji_map() -> CompletionMap<String, &'static Emoji> {
return emojis; return emojis;
} }
/// Information gathered during server syncs about joined rooms.
#[derive(Default)] #[derive(Default)]
pub struct SyncInfo { pub struct SyncInfo {
/// Spaces that the user is a member of.
pub spaces: Vec<MatrixRoom>, pub spaces: Vec<MatrixRoom>,
/// Rooms that the user is a member of.
pub rooms: Vec<Arc<(MatrixRoom, Option<Tags>)>>, pub rooms: Vec<Arc<(MatrixRoom, Option<Tags>)>>,
/// DMs that the user is a member of.
pub dms: Vec<Arc<(MatrixRoom, Option<Tags>)>>, pub dms: Vec<Arc<(MatrixRoom, Option<Tags>)>>,
} }
/// The main application state.
pub struct ChatStore { pub struct ChatStore {
/// `:`-commands
pub cmds: ProgramCommands, pub cmds: ProgramCommands,
/// Handle for communicating w/ the worker thread.
pub worker: Requester, pub worker: Requester,
/// Map of joined rooms.
pub rooms: CompletionMap<OwnedRoomId, RoomInfo>, pub rooms: CompletionMap<OwnedRoomId, RoomInfo>,
/// Map of room names.
pub names: CompletionMap<String, OwnedRoomId>, pub names: CompletionMap<String, OwnedRoomId>,
/// Presence information for other users.
pub presences: CompletionMap<OwnedUserId, PresenceState>, pub presences: CompletionMap<OwnedUserId, PresenceState>,
/// In-progress and completed verifications.
pub verifications: HashMap<String, SasVerification>, pub verifications: HashMap<String, SasVerification>,
/// Settings for the current profile loaded from config file.
pub settings: ApplicationSettings, pub settings: ApplicationSettings,
/// Set of rooms that need more messages loaded in their scrollback.
pub need_load: HashSet<OwnedRoomId>, pub need_load: HashSet<OwnedRoomId>,
/// [CompletionMap] of Emoji shortcodes.
pub emojis: CompletionMap<String, &'static Emoji>, pub emojis: CompletionMap<String, &'static Emoji>,
/// Information gathered by the background thread.
pub sync_info: SyncInfo, pub sync_info: SyncInfo,
} }
impl ChatStore { impl ChatStore {
/// Create a new [ChatStore].
pub fn new(worker: Requester, settings: ApplicationSettings) -> Self { pub fn new(worker: Requester, settings: ApplicationSettings) -> Self {
ChatStore { ChatStore {
worker, worker,
@ -696,10 +837,12 @@ impl ChatStore {
} }
} }
/// Get a joined room.
pub fn get_joined_room(&self, room_id: &RoomId) -> Option<Joined> { pub fn get_joined_room(&self, room_id: &RoomId) -> Option<Joined> {
self.worker.client.get_joined_room(room_id) self.worker.client.get_joined_room(room_id)
} }
/// Get the title for a room.
pub fn get_room_title(&self, room_id: &RoomId) -> String { pub fn get_room_title(&self, room_id: &RoomId) -> String {
self.rooms self.rooms
.get(room_id) .get(room_id)
@ -708,6 +851,7 @@ impl ChatStore {
.unwrap_or_else(|| "Untitled Matrix Room".to_string()) .unwrap_or_else(|| "Untitled Matrix Room".to_string())
} }
/// Update the receipts for multiple rooms.
pub async fn set_receipts( pub async fn set_receipts(
&mut self, &mut self,
receipts: Vec<(OwnedRoomId, Receipts)>, receipts: Vec<(OwnedRoomId, Receipts)>,
@ -727,18 +871,22 @@ impl ChatStore {
return updates; return updates;
} }
/// Mark a room for loading more scrollback.
pub fn mark_for_load(&mut self, room_id: OwnedRoomId) { pub fn mark_for_load(&mut self, room_id: OwnedRoomId) {
self.need_load.insert(room_id); 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 { pub fn get_room_info(&mut self, room_id: OwnedRoomId) -> &mut RoomInfo {
self.rooms.get_or_default(room_id) 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) { 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(); 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) { pub fn insert_sas(&mut self, sas: SasVerification) {
let key = format!("{}/{}", sas.other_user_id(), sas.other_device().device_id()); let key = format!("{}/{}", sas.other_user_id(), sas.other_device().device_id());
@ -748,6 +896,7 @@ impl ChatStore {
impl ApplicationStore for ChatStore {} impl ApplicationStore for ChatStore {}
/// Identified used to track window content.
#[derive(Clone, Debug, Eq, Hash, PartialEq)] #[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub enum IambId { pub enum IambId {
/// A Matrix room. /// A Matrix room.
@ -810,6 +959,7 @@ impl<'de> Deserialize<'de> for IambId {
} }
} }
/// [serde] visitor for deserializing [IambId].
struct IambIdVisitor; struct IambIdVisitor;
impl<'de> Visitor<'de> for 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)] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum RoomFocus { pub enum RoomFocus {
/// The scrollback for a room window is focused.
Scrollback, Scrollback,
/// The message bar for a room window is focused.
MessageBar, MessageBar,
} }
impl RoomFocus { impl RoomFocus {
/// Whether this is [RoomFocus::Scrollback].
pub fn is_scrollback(&self) -> bool { pub fn is_scrollback(&self) -> bool {
matches!(self, RoomFocus::Scrollback) matches!(self, RoomFocus::Scrollback)
} }
/// Whether this is [RoomFocus::MessageBar].
pub fn is_msgbar(&self) -> bool { pub fn is_msgbar(&self) -> bool {
matches!(self, RoomFocus::MessageBar) 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)] #[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub enum IambBufferId { pub enum IambBufferId {
/// The command bar buffer.
Command(CommandType), Command(CommandType),
/// The message buffer or a specific message in a room.
Room(OwnedRoomId, RoomFocus), Room(OwnedRoomId, RoomFocus),
/// The `:dms` window.
DirectList, DirectList,
/// The `:members` window for a room.
MemberList(OwnedRoomId), MemberList(OwnedRoomId),
/// The `:rooms` window.
RoomList, RoomList,
/// The `:spaces` window.
SpaceList, SpaceList,
/// The `:verify` window.
VerifyList, VerifyList,
/// The buffer for the `:rooms` window.
Welcome, Welcome,
} }
impl IambBufferId { impl IambBufferId {
/// Get the identifier for the window that contains this buffer.
pub fn to_window(&self) -> Option<IambId> { pub fn to_window(&self) -> Option<IambId> {
match self { match self {
IambBufferId::Command(_) => None, 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<String> { fn complete_users(text: &EditRope, cursor: &mut Cursor, store: &ProgramStore) -> Vec<String> {
let id = text let id = text
.get_prefix_word_mut(cursor, &MATRIX_ID_WORD) .get_prefix_word_mut(cursor, &MATRIX_ID_WORD)
@ -997,6 +1174,7 @@ fn complete_users(text: &EditRope, cursor: &mut Cursor, store: &ProgramStore) ->
.collect() .collect()
} }
/// Tab completion within the message bar.
fn complete_msgbar(text: &EditRope, cursor: &mut Cursor, store: &ProgramStore) -> Vec<String> { fn complete_msgbar(text: &EditRope, cursor: &mut Cursor, store: &ProgramStore) -> Vec<String> {
let id = text let id = text
.get_prefix_word_mut(cursor, &MATRIX_ID_WORD) .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( fn complete_matrix_names(
text: &EditRope, text: &EditRope,
cursor: &mut Cursor, cursor: &mut Cursor,
@ -1073,6 +1252,7 @@ fn complete_matrix_names(
.collect() .collect()
} }
/// Tab completion for Emoji shortcode names.
fn complete_emoji(text: &EditRope, cursor: &mut Cursor, store: &ProgramStore) -> Vec<String> { fn complete_emoji(text: &EditRope, cursor: &mut Cursor, store: &ProgramStore) -> Vec<String> {
let sc = text.get_prefix_word_mut(cursor, &WordStyle::Little); let sc = text.get_prefix_word_mut(cursor, &WordStyle::Little);
let sc = sc.unwrap_or_else(EditRope::empty); 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()) store.application.emojis.complete(sc.as_ref())
} }
/// Tab completion for command names.
fn complete_cmdname( fn complete_cmdname(
desc: CommandDescription, desc: CommandDescription,
text: &EditRope, text: &EditRope,
@ -1092,6 +1273,7 @@ fn complete_cmdname(
store.application.cmds.complete_name(desc.command.as_str()) store.application.cmds.complete_name(desc.command.as_str())
} }
/// Tab completion for command arguments.
fn complete_cmdarg( fn complete_cmdarg(
desc: CommandDescription, desc: CommandDescription,
text: &EditRope, text: &EditRope,
@ -1120,6 +1302,7 @@ fn complete_cmdarg(
} }
} }
/// Tab completion for commands.
fn complete_cmd( fn complete_cmd(
cmd: &str, cmd: &str,
text: &EditRope, 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<String> { fn complete_cmdbar(text: &EditRope, cursor: &mut Cursor, store: &ProgramStore) -> Vec<String> {
let eo = text.cursor_to_offset(cursor); let eo = text.cursor_to_offset(cursor);
let slice = text.slice(0.into(), eo, false); let slice = text.slice(0.into(), eo, false);

View file

@ -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 std::convert::TryFrom;
use matrix_sdk::ruma::{events::tag::TagName, OwnedUserId}; 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 { pub fn setup_commands() -> ProgramCommands {
let mut cmds = ProgramCommands::default(); let mut cmds = ProgramCommands::default();

View file

@ -1,3 +1,4 @@
//! # Logic for loading and validating application configuration
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::hash_map::DefaultHasher; use std::collections::hash_map::DefaultHasher;
use std::collections::HashMap; use std::collections::HashMap;

View file

@ -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::{ use modalkit::{
editing::action::WindowAction, editing::action::WindowAction,
env::vim::keybindings::{InputStep, VimBindings}, env::vim::keybindings::{InputStep, VimBindings},
@ -10,6 +14,7 @@ use crate::base::{IambAction, IambInfo, Keybindings, MATRIX_ID_WORD};
type IambStep = InputStep<IambInfo>; type IambStep = InputStep<IambInfo>;
/// Initialize the default keybinding state.
pub fn setup_keybindings() -> Keybindings { pub fn setup_keybindings() -> Keybindings {
let mut ism = Keybindings::empty(); let mut ism = Keybindings::empty();

View file

@ -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::manual_range_contains)]
#![allow(clippy::needless_return)] #![allow(clippy::needless_return)]
#![allow(clippy::result_large_err)] #![allow(clippy::result_large_err)]
@ -200,6 +213,7 @@ fn setup_screen(
return Ok(ScreenState::new(win, cmd)); return Ok(ScreenState::new(win, cmd));
} }
/// The main application state and event loop.
struct Application { struct Application {
/// Terminal backend. /// Terminal backend.
terminal: Terminal<CrosstermBackend<Stdout>>, terminal: Terminal<CrosstermBackend<Stdout>>,

View file

@ -37,7 +37,8 @@ use crate::{
util::{join_cell_text, space_text}, util::{join_cell_text, space_text},
}; };
struct BulletIterator { /// Generate bullet points from a [ListStyle].
pub struct BulletIterator {
style: ListStyle, style: ListStyle,
pos: usize, pos: usize,
len: usize, len: usize,
@ -74,6 +75,7 @@ impl Iterator for BulletIterator {
} }
} }
/// Whether this list is ordered or unordered.
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub enum ListStyle { pub enum ListStyle {
Ordered, Ordered,
@ -88,11 +90,13 @@ impl ListStyle {
pub type StyleTreeChildren = Vec<StyleTreeNode>; pub type StyleTreeChildren = Vec<StyleTreeNode>;
/// Type of contents in a table cell.
pub enum CellType { pub enum CellType {
Data, Data,
Header, Header,
} }
/// A collection of cells for a single row in a table.
pub struct TableRow { pub struct TableRow {
cells: Vec<(CellType, StyleTreeNode)>, cells: Vec<(CellType, StyleTreeNode)>,
} }
@ -103,6 +107,7 @@ impl TableRow {
} }
} }
/// A collection of rows in a table.
pub struct TableSection { pub struct TableSection {
rows: Vec<TableRow>, rows: Vec<TableRow>,
} }
@ -113,6 +118,7 @@ impl TableSection {
} }
} }
/// A table.
pub struct Table { pub struct Table {
caption: Option<Box<StyleTreeNode>>, caption: Option<Box<StyleTreeNode>>,
sections: Vec<TableSection>, sections: Vec<TableSection>,
@ -229,6 +235,7 @@ impl Table {
} }
} }
/// A processed HTML element that we can render to the terminal.
pub enum StyleTreeNode { pub enum StyleTreeNode {
Blockquote(Box<StyleTreeNode>), Blockquote(Box<StyleTreeNode>),
Break, Break,
@ -380,6 +387,7 @@ impl StyleTreeNode {
} }
} }
/// A processed HTML document.
pub struct StyleTree { pub struct StyleTree {
children: StyleTreeChildren, children: StyleTreeChildren,
} }
@ -649,6 +657,7 @@ fn dom_to_style_tree(dom: RcDom) -> StyleTree {
StyleTree { children: h2t(&dom.document) } StyleTree { children: h2t(&dom.document) }
} }
/// Parse an HTML document from a string.
pub fn parse_matrix_html(s: &str) -> StyleTree { pub fn parse_matrix_html(s: &str) -> StyleTree {
let dom = parse_fragment( let dom = parse_fragment(
RcDom::default(), RcDom::default(),

View file

@ -1,3 +1,4 @@
//! # Room Messages
use std::borrow::Cow; use std::borrow::Cow;
use std::cmp::{Ord, Ordering, PartialOrd}; use std::cmp::{Ord, Ordering, PartialOrd};
use std::collections::hash_map::DefaultHasher; use std::collections::hash_map::DefaultHasher;

View file

@ -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 std::borrow::Cow;
use modalkit::tui::layout::Alignment; use modalkit::tui::layout::Alignment;
@ -8,6 +13,7 @@ use unicode_width::UnicodeWidthStr;
use crate::util::{space_span, take_width}; use crate::util::{space_span, take_width};
/// Wrap styled text for the current terminal width.
pub struct TextPrinter<'a> { pub struct TextPrinter<'a> {
text: Text<'a>, text: Text<'a>,
width: usize, width: usize,
@ -21,6 +27,7 @@ pub struct TextPrinter<'a> {
} }
impl<'a> TextPrinter<'a> { impl<'a> TextPrinter<'a> {
/// Create a new printer.
pub fn new(width: usize, base_style: Style, hide_reply: bool) -> Self { pub fn new(width: usize, base_style: Style, hide_reply: bool) -> Self {
TextPrinter { TextPrinter {
text: Text::default(), 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 { pub fn align(mut self, alignment: Alignment) -> Self {
self.alignment = alignment; self.alignment = alignment;
self self
} }
/// Set whether newlines should be treated literally, or turned into spaces.
pub fn literal(mut self, literal: bool) -> Self { pub fn literal(mut self, literal: bool) -> Self {
self.literal = literal; self.literal = literal;
self self
} }
/// Indicates whether replies should be pushed to the printer.
pub fn hide_reply(&self) -> bool { pub fn hide_reply(&self) -> bool {
self.hide_reply self.hide_reply
} }
/// Indicates the current printer's width.
pub fn width(&self) -> usize { pub fn width(&self) -> usize {
self.width self.width
} }
/// Create a new printer with a smaller width.
pub fn sub(&self, indent: usize) -> Self { pub fn sub(&self, indent: usize) -> Self {
TextPrinter { TextPrinter {
text: Text::default(), text: Text::default(),
@ -71,6 +83,7 @@ impl<'a> TextPrinter<'a> {
self.width - self.curr_width self.width - self.curr_width
} }
/// If there is any text on the current line, start a new one.
pub fn commit(&mut self) { pub fn commit(&mut self) {
if self.curr_width > 0 { if self.curr_width > 0 {
self.push_break(); self.push_break();
@ -82,6 +95,7 @@ impl<'a> TextPrinter<'a> {
self.text.lines.push(Spans(std::mem::take(&mut self.curr_spans))); self.text.lines.push(Spans(std::mem::take(&mut self.curr_spans)));
} }
/// Start a new line.
pub fn push_break(&mut self) { pub fn push_break(&mut self) {
if self.curr_width == 0 && self.text.lines.is_empty() { if self.curr_width == 0 && self.text.lines.is_empty() {
// Disallow leading breaks. // 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>) { pub fn push_span_nobreak(&mut self, span: Span<'a>) {
let sw = UnicodeWidthStr::width(span.content.as_ref()); let sw = UnicodeWidthStr::width(span.content.as_ref());
@ -161,6 +176,7 @@ impl<'a> TextPrinter<'a> {
self.curr_width += sw; self.curr_width += sw;
} }
/// Push text with a [Style].
pub fn push_str(&mut self, s: &'a str, style: Style) { pub fn push_str(&mut self, s: &'a str, style: Style) {
let style = self.base_style.patch(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>) { pub fn push_line(&mut self, spans: Spans<'a>) {
self.commit(); self.commit();
self.text.lines.push(spans); self.text.lines.push(spans);
} }
/// Push multiline [Text] into the printer.
pub fn push_text(&mut self, text: Text<'a>) { pub fn push_text(&mut self, text: Text<'a>) {
self.commit(); self.commit();
self.text.lines.extend(text.lines); self.text.lines.extend(text.lines);
} }
/// Render the contents of this printer as [Text].
pub fn finish(mut self) -> Text<'a> { pub fn finish(mut self) -> Text<'a> {
self.commit(); self.commit();
self.text self.text

View file

@ -1,3 +1,4 @@
//! # Utility functions
use std::borrow::Cow; use std::borrow::Cow;
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;

View file

@ -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::cmp::{Ord, Ordering, PartialOrd};
use std::ops::Deref; use std::ops::Deref;
use std::sync::Arc; use std::sync::Arc;

View file

@ -1,3 +1,4 @@
//! Window for Matrix rooms
use std::borrow::Cow; use std::borrow::Cow;
use std::ffi::{OsStr, OsString}; use std::ffi::{OsStr, OsString};
use std::fs; use std::fs;
@ -85,6 +86,7 @@ use crate::worker::Requester;
use super::scrollback::{Scrollback, ScrollbackState}; use super::scrollback::{Scrollback, ScrollbackState};
/// State needed for rendering [Chat].
pub struct ChatState { pub struct ChatState {
room_id: OwnedRoomId, room_id: OwnedRoomId,
room: MatrixRoom, room: MatrixRoom,
@ -786,6 +788,7 @@ impl Promptable<ProgramContext, ProgramStore, IambInfo> for ChatState {
} }
} }
/// [StatefulWidget] for Matrix rooms.
pub struct Chat<'a> { pub struct Chat<'a> {
store: &'a mut ProgramStore, store: &'a mut ProgramStore,
focused: bool, focused: bool,

View file

@ -1,3 +1,4 @@
//! # Windows for Matrix rooms and spaces
use matrix_sdk::{ use matrix_sdk::{
room::{Invited, Room as MatrixRoom}, room::{Invited, Room as MatrixRoom},
ruma::{ 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 { pub enum RoomState {
Chat(ChatState), Chat(ChatState),
Space(SpaceState), Space(SpaceState),

View file

@ -1,3 +1,4 @@
//! Message scrollback
use std::collections::HashSet; use std::collections::HashSet;
use regex::Regex; use regex::Regex;

View file

@ -1,3 +1,4 @@
//! Window for Matrix spaces
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
@ -25,6 +26,7 @@ use crate::windows::RoomItem;
const SPACE_HIERARCHY_DEBOUNCE: Duration = Duration::from_secs(5); const SPACE_HIERARCHY_DEBOUNCE: Duration = Duration::from_secs(5);
/// State needed for rendering [Space].
pub struct SpaceState { pub struct SpaceState {
room_id: OwnedRoomId, room_id: OwnedRoomId,
room: MatrixRoom, room: MatrixRoom,
@ -86,6 +88,7 @@ impl DerefMut for SpaceState {
} }
} }
/// [StatefulWidget] for Matrix spaces.
pub struct Space<'a> { pub struct Space<'a> {
focused: bool, focused: bool,
store: &'a mut ProgramStore, store: &'a mut ProgramStore,

View file

@ -1,3 +1,4 @@
//! Welcome Window
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use modalkit::tui::{buffer::Buffer, layout::Rect}; use modalkit::tui::{buffer::Buffer, layout::Rect};

View file

@ -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::collections::HashMap;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::fmt::{Debug, Formatter}; use std::fmt::{Debug, Formatter};