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::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<String>),
}
/// 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<CommandContext<ProgramContext>>),
/// 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<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)]
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<IambAction> for ProgramAction {
}
}
/// Alias for program actions.
pub type ProgramAction = Action<IambInfo>;
/// Alias for program context.
pub type ProgramContext = VimContext<IambInfo>;
/// Alias for program keybindings.
pub type Keybindings = VimMachine<TerminalKey, IambInfo>;
/// Alias for a program command.
pub type ProgramCommand = VimCommand<ProgramContext, IambInfo>;
/// Alias for mapped program commands.
pub type ProgramCommands = VimCommandMachine<ProgramContext, IambInfo>;
/// Alias for program store.
pub type ProgramStore = Store<IambInfo>;
/// Alias for shared program store.
pub type AsyncProgramStore = Arc<AsyncMutex<ProgramStore>>;
/// Alias for an action result.
pub type IambResult<T> = UIResult<T, IambInfo>;
/// Reaction events for some message.
@ -332,61 +404,81 @@ pub type IambResult<T> = UIResult<T, IambInfo>;
/// it's reacting to.
pub type MessageReactions = HashMap<OwnedEventId, (String, OwnedUserId)>;
/// Map of read receipts for different events.
pub type Receipts = HashMap<OwnedEventId, Vec<OwnedUserId>>;
/// 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<IambError> for UIError<IambInfo> {
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<OwnedUserId>) {
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<String, &'static Emoji> {
let mut emojis = CompletionMap::default();
@ -658,27 +772,54 @@ fn emoji_map() -> CompletionMap<String, &'static Emoji> {
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<MatrixRoom>,
/// Rooms that the user is a member of.
pub rooms: Vec<Arc<(MatrixRoom, Option<Tags>)>>,
/// DMs that the user is a member of.
pub dms: Vec<Arc<(MatrixRoom, Option<Tags>)>>,
}
/// 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<OwnedRoomId, RoomInfo>,
/// Map of room names.
pub names: CompletionMap<String, OwnedRoomId>,
/// Presence information for other users.
pub presences: CompletionMap<OwnedUserId, PresenceState>,
/// In-progress and completed verifications.
pub verifications: HashMap<String, SasVerification>,
/// 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<OwnedRoomId>,
/// [CompletionMap] of Emoji shortcodes.
pub emojis: CompletionMap<String, &'static Emoji>,
/// 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<Joined> {
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<IambId> {
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<String> {
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<String> {
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<String> {
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<String> {
let eo = text.cursor_to_offset(cursor);
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 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();

View file

@ -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;

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

View file

@ -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<StyleTreeNode>;
/// 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<TableRow>,
}
@ -113,6 +118,7 @@ impl TableSection {
}
}
/// A table.
pub struct Table {
caption: Option<Box<StyleTreeNode>>,
sections: Vec<TableSection>,
@ -229,6 +235,7 @@ impl Table {
}
}
/// A processed HTML element that we can render to the terminal.
pub enum StyleTreeNode {
Blockquote(Box<StyleTreeNode>),
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(),

View file

@ -1,3 +1,4 @@
//! # Room Messages
use std::borrow::Cow;
use std::cmp::{Ord, Ordering, PartialOrd};
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 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

View file

@ -1,3 +1,4 @@
//! # Utility functions
use std::borrow::Cow;
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::ops::Deref;
use std::sync::Arc;

View file

@ -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<ProgramContext, ProgramStore, IambInfo> for ChatState {
}
}
/// [StatefulWidget] for Matrix rooms.
pub struct Chat<'a> {
store: &'a mut ProgramStore,
focused: bool,

View file

@ -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),

View file

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

View file

@ -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,

View file

@ -1,3 +1,4 @@
//! Welcome Window
use std::ops::{Deref, DerefMut};
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::convert::TryFrom;
use std::fmt::{Debug, Formatter};