mirror of
https://github.com/youwen5/iamb.git
synced 2025-06-20 13:49:52 -07:00
Add more documentation (#166)
This commit is contained in:
parent
2673cfaeb9
commit
9197864c5c
16 changed files with 267 additions and 2 deletions
186
src/base.rs
186
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::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);
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
14
src/main.rs
14
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::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>>,
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
//! # Utility functions
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
//! Message scrollback
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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};
|
||||||
|
|
|
@ -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};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue