From 08c4724a94c30c288b62aad441fb9cb8107d88c3 Mon Sep 17 00:00:00 2001 From: Benedikt Peetz Date: Sat, 4 May 2024 15:43:31 +0200 Subject: [PATCH] refactor(src): Remove all matrix related code This obviously is a big regression, but having this matrix code in the core trinitrix tree is no longer planned. And if we start writing the matrix cbs, referring to this commit should be possible. --- Cargo.toml | 19 +- src/accounts/mod.rs | 223 ------------------ .../command_interface/command_list/api.tri | 8 - src/app/events/handlers/command.rs | 16 -- src/app/events/handlers/matrix.rs | 23 -- src/app/events/handlers/mod.rs | 4 - src/app/events/handlers/setup.rs | 75 ------ src/app/events/listeners/input.rs | 21 -- src/app/events/listeners/matrix.rs | 41 ---- src/app/events/listeners/mod.rs | 23 +- src/app/events/mod.rs | 15 +- src/app/mod.rs | 127 +--------- src/app/status.rs | 187 +-------------- src/main.rs | 3 +- src/ui/central/mod.rs | 18 +- src/ui/central/update/mod.rs | 15 +- src/ui/central/update/widgets/messages.rs | 109 --------- src/ui/central/update/widgets/mod.rs | 4 - src/ui/central/update/widgets/room_info.rs | 37 --- src/ui/central/update/widgets/rooms.rs | 29 --- src/ui/central/update/widgets/status.rs | 30 --- src/ui/mod.rs | 1 - src/ui/setup.rs | 172 -------------- 23 files changed, 43 insertions(+), 1157 deletions(-) delete mode 100644 src/accounts/mod.rs delete mode 100644 src/app/events/handlers/matrix.rs delete mode 100644 src/app/events/handlers/setup.rs delete mode 100644 src/app/events/listeners/input.rs delete mode 100644 src/app/events/listeners/matrix.rs delete mode 100644 src/ui/central/update/widgets/messages.rs delete mode 100644 src/ui/central/update/widgets/room_info.rs delete mode 100644 src/ui/central/update/widgets/rooms.rs delete mode 100644 src/ui/central/update/widgets/status.rs delete mode 100644 src/ui/setup.rs diff --git a/Cargo.toml b/Cargo.toml index 58f3d4c..cf7fd9e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,20 +7,17 @@ default-run = "trinitrix" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[features] -default = ["tui"] -tui = ["dep:tui", "dep:tui-textarea", "dep:crossterm", "dep:tokio-util", "dep:serde", "dep:indexmap"] - [dependencies] clap = { version = "4.5.4", features = ["derive"] } cli-log = "2.0" anyhow = "1.0" -matrix-sdk = "0.6" -tokio = { version = "1.37", features = ["macros", "rt-multi-thread"] } +tokio = { version = "1.37", features = ["macros", "rt-multi-thread", "fs", "time"] } +tokio-util = {version = "0.7.10"} # config trinitry = {version = "0.1.0"} keymaps = {version = "0.1.1", features = ["crossterm"] } +directories = "5.0.1" # c api libloading = "0.8.3" @@ -31,13 +28,9 @@ mlua = { version = "0.9.7", features = ["lua54", "async", "send", "serialize"] } once_cell = "1.19.0" # tui feature specific parts -tui = {version = "0.19", optional = true} -tui-textarea = { version = "0.2", features = ["crossterm"], optional = true } -crossterm = { version = "0.25", optional = true } -tokio-util = { version = "0.7", optional = true } -serde = { version = "1.0", optional = true } -indexmap = { version = "2.2.6", optional = true } -directories = "5.0.1" +tui = {version = "0.19"} +tui-textarea = { version = "0.2", features = ["crossterm"]} +crossterm = { version = "0.25"} [dev-dependencies] pretty_assertions = "1.4.0" diff --git a/src/accounts/mod.rs b/src/accounts/mod.rs deleted file mode 100644 index b9311eb..0000000 --- a/src/accounts/mod.rs +++ /dev/null @@ -1,223 +0,0 @@ -use std::fs; - -use anyhow::{Error, Result}; -use cli_log::{error, info}; -use matrix_sdk::{ruma::exports::serde_json, Client, Session}; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Account { - homeserver: String, - id: u32, - name: String, - - session: Session, - sync_token: Option, -} - -#[derive(Debug, Serialize, Deserialize)] -struct AccountsData { - current_account: u32, - accounts: Vec, -} - -pub struct AccountsManager { - current_account: u32, - num_accounts: u32, - accounts: Vec, - clients: Vec>, -} - -impl Account { - pub fn name(&self) -> &String { - &self.name - } - - pub fn user_id(&self) -> String { - self.session.user_id.to_string() - } -} - -impl AccountsManager { - pub fn new(config: Option) -> Result { - return match config { - Some(s) => { - info!("Loading serialized AccountsManager"); - let accounts_data: AccountsData = serde_json::from_str(&s)?; - let mut clients = Vec::new(); - clients.resize(accounts_data.accounts.len(), None); - Ok(Self { - current_account: accounts_data.current_account, - num_accounts: accounts_data.accounts.len() as u32, - accounts: accounts_data.accounts, - clients, - }) - } - None => { - info!("Creating empty AccountsManager"); - Ok(Self { - current_account: 0, - num_accounts: 0, - accounts: Vec::new(), - clients: Vec::new(), - }) - } - }; - } - - pub async fn restore(&mut self) -> Result<()> { - self.login(self.current_account).await?; - Ok(()) - } - - pub async fn add( - &mut self, - homeserver: &String, - username: &String, - password: &String, - ) -> Result { - let id = self.num_accounts; - self.num_accounts += 1; - - let client = Client::builder() - .homeserver_url(homeserver) - .sled_store(format!("userdata/{id}"), Some("supersecure"))? - .build() - .await?; - - client - .login_username(username, password) - .initial_device_display_name("Trinitrix") - .send() - .await?; - - let session = match client.session() { - Some(s) => s, - None => return Err(Error::msg("Failed to get session")), - }; - - let name = match client.account().get_display_name().await? { - Some(n) => n, - None => return Err(Error::msg("Failed to get display name")), - }; - - let account = Account { - homeserver: homeserver.to_string(), - id, - name, - session: session.clone(), - sync_token: None, - }; - - self.logout().await?; - self.current_account = id; - self.accounts.push(account); - self.clients.push(Some(client)); - self.save()?; - - info!( - "Logged in as '{}' device ID: {}", - session.user_id.to_string(), - session.device_id.to_string() - ); - - Ok(id) - } - - pub async fn login(&mut self, account_id: u32) -> Result<()> { - self.logout().await?; // log out the current account - - let account = if account_id >= self.num_accounts { - error!("Tried to log in with an invalid account ID {}", account_id); - return Err(Error::msg("Invalid account ID")); - } else { - if let Some(a) = self.get(account_id) { - a - } else { - return Err(Error::msg("Failed to get account")); - } - }; - - if self - .clients - .get(account_id as usize) - .expect("client lookup failed") - .is_none() - { - info!( - "No client cached for account: '{}' -> requesting a new one", - &account.session.user_id - ); - let client = Client::builder() - .homeserver_url(&account.homeserver) - .sled_store(format!("userdata/{account_id}"), Some("supersecure"))? - .build() - .await?; - client.restore_login(account.session.clone()).await?; - self.clients.insert(account_id as usize, Some(client)); - } else { - info!( - "Using cached client for account: '{}'", - &account.session.user_id - ); - }; - - info!("Restored account"); - - self.current_account = account_id; - - Ok(()) - } - - pub async fn logout(&mut self) -> Result<()> { - // idk, do some matrix related stuff in here or something like that - if self.clients.get(self.current_account as usize).is_none() { - return Ok(()); - } - let client = match self.clients.get(self.current_account as usize).unwrap() { - None => return Ok(()), - Some(c) => c, - }; - - info!("Logged out '{}' locally", client.session().unwrap().user_id); - client.logout().await?; - - Ok(()) - } - - pub fn save(&self) -> Result<()> { - let accounts_data = AccountsData { - current_account: self.current_account, - accounts: self.accounts.clone(), - }; - - let serialized = serde_json::to_string(&accounts_data)?; - fs::write("userdata/accounts.json", serialized)?; - - info!("Saved serialized accounts config (userdata/accounts.json)"); - - Ok(()) - } - - pub fn get(&self, id: u32) -> Option<&Account> { - self.accounts.get(id as usize) - } - - pub fn current(&self) -> Option<&Account> { - self.get(self.current_account) - } - - pub fn client(&self) -> Option<&Client> { - match self.clients.get(self.current_account as usize) { - None => None, - Some(oc) => match oc { - None => None, - Some(c) => Some(c), - }, - } - } - - pub fn num_accounts(&self) -> u32 { - self.num_accounts - } -} diff --git a/src/app/command_interface/command_list/api.tri b/src/app/command_interface/command_list/api.tri index 8d7f6de..47b4834 100644 --- a/src/app/command_interface/command_list/api.tri +++ b/src/app/command_interface/command_list/api.tri @@ -40,20 +40,12 @@ mod trinitrix { /// Closes the application fn exit(); - /// Send a message to the current room - /// The send message is interpreted literally. - fn room_message_send(message: String); - //// Open the help pages at the first occurrence of //// the input string if it is Some, otherwise open //// the help pages at the start // TODO(@soispha): To be implemented <2024-03-09> // fn help(Option); - //// Register a function to be used with the Trinitrix api - // (This function is not actually implemented here) - /* declare register_function: false, */ - /// Function that change the UI, or UI state mod ui { enum Mode { diff --git a/src/app/events/handlers/command.rs b/src/app/events/handlers/command.rs index c678fbb..0476b14 100644 --- a/src/app/events/handlers/command.rs +++ b/src/app/events/handlers/command.rs @@ -87,21 +87,6 @@ pub async fn handle( warn!("Terminating the application"); EventStatus::Terminate } - Api::room_message_send { message } => { - if let Some(room) = app.status.room_mut() { - room.send(message.to_string()).await?; - send_status_output!("Sent message: `{}`", message); - } else { - // FIXME(@soispha): This should raise an error within lua, as it would - // otherwise be very confusing <2023-09-20> - warn!( - "Can't send message: `{}`, as there is no open room!", - &message - ); - } - - EventStatus::Ok - } Api::Ui(ui) => match ui { Ui::set_mode { mode } => match mode { Mode::Normal => { @@ -213,7 +198,6 @@ pub async fn handle( EventStatus::Ok } State::Normal - | State::Setup | State::KeyInputPending { old_state: _, pending_keys: _, diff --git a/src/app/events/handlers/matrix.rs b/src/app/events/handlers/matrix.rs deleted file mode 100644 index 8c2b5d4..0000000 --- a/src/app/events/handlers/matrix.rs +++ /dev/null @@ -1,23 +0,0 @@ -use anyhow::Result; -use matrix_sdk::deserialized_responses::SyncResponse; - -use crate::app::{events::EventStatus, App}; - -pub async fn handle(app: &mut App<'_>, sync: &SyncResponse) -> Result { - for (m_room_id, m_room) in sync.rooms.join.iter() { - let room = match app.status.get_room_mut(m_room_id) { - Some(r) => r, - None => continue, - }; - for m_event in m_room.timeline.events.clone() { - let event = m_event - .event - .deserialize() - .unwrap() - .into_full_event(m_room_id.clone()); - room.timeline_add(event); - } - } - - Ok(EventStatus::Ok) -} diff --git a/src/app/events/handlers/mod.rs b/src/app/events/handlers/mod.rs index 4a9b144..3cf95ee 100644 --- a/src/app/events/handlers/mod.rs +++ b/src/app/events/handlers/mod.rs @@ -1,9 +1,5 @@ // input events pub mod input; -pub mod setup; - -// matrix -pub mod matrix; // ci pub mod command; diff --git a/src/app/events/handlers/setup.rs b/src/app/events/handlers/setup.rs deleted file mode 100644 index fe20751..0000000 --- a/src/app/events/handlers/setup.rs +++ /dev/null @@ -1,75 +0,0 @@ -use anyhow::{bail, Context, Result}; -use crossterm::event::{Event as CrosstermEvent, KeyCode, KeyEvent}; - -use crate::{ - app::{events::EventStatus, App}, - ui::setup, -}; - -pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result { - let ui = match &mut app.ui.setup_ui { - Some(ui) => ui, - None => bail!("SetupUI instance not found"), - }; - - match input_event { - CrosstermEvent::Key(KeyEvent { - code: KeyCode::Esc, .. - }) => return Ok(EventStatus::Terminate), - CrosstermEvent::Key(KeyEvent { - code: KeyCode::Tab, .. - }) => { - ui.cycle_input_position(); - } - CrosstermEvent::Key(KeyEvent { - code: KeyCode::BackTab, - .. - }) => { - ui.cycle_input_position_rev(); - } - CrosstermEvent::Key(KeyEvent { - code: KeyCode::Enter, - .. - }) => { - match ui.input_position() { - setup::InputPosition::Ok => { - let homeserver = ui.homeserver.lines()[0].clone(); - let username = ui.username.lines()[0].clone(); - let password = ui.password_data.lines()[0].clone(); - app.login(&homeserver, &username, &password) - .await - .context("Failed to login")?; - // We bailed in the line above, thus login must have succeeded - return Ok(EventStatus::Finished); - } - _ => ui.cycle_input_position(), - }; - } - input => match ui.input_position() { - setup::InputPosition::Homeserver => { - ui.homeserver.input(input.to_owned()); - } - setup::InputPosition::Username => { - ui.username.input(input.to_owned()); - } - setup::InputPosition::Password => { - let textarea_input = tui_textarea::Input::from(input.to_owned()); - ui.password_data.input(textarea_input.clone()); - match textarea_input.key { - tui_textarea::Key::Char(_) => { - ui.password.input(tui_textarea::Input { - key: tui_textarea::Key::Char('*'), - ctrl: false, - alt: false, - }); - } - _ => { - ui.password.input(textarea_input); - } - } - } - _ => (), - }, - }; - Ok(EventStatus::Ok) -} diff --git a/src/app/events/listeners/input.rs b/src/app/events/listeners/input.rs deleted file mode 100644 index 351eb18..0000000 --- a/src/app/events/listeners/input.rs +++ /dev/null @@ -1,21 +0,0 @@ -use crate::app::events::Event; -use anyhow::{bail, Result}; -use tokio::{sync::mpsc, time::Duration}; -use tokio_util::sync::CancellationToken; - -pub async fn poll(channel: mpsc::Sender, kill: CancellationToken) -> Result<()> { - async fn stage_2(channel: mpsc::Sender) -> Result<()> { - loop { - if crossterm::event::poll(Duration::from_millis(100))? { - let event = Event::InputEvent(crossterm::event::read()?); - channel.send(event).await?; - } else { - tokio::task::yield_now().await; - } - } - } - tokio::select! { - output = stage_2(channel) => output, - _ = kill.cancelled() => bail!("received kill signal") - } -} diff --git a/src/app/events/listeners/matrix.rs b/src/app/events/listeners/matrix.rs deleted file mode 100644 index 5db0c10..0000000 --- a/src/app/events/listeners/matrix.rs +++ /dev/null @@ -1,41 +0,0 @@ -/* WARNING(@antifallobst): - * This file is going to be removed while implementing Chat Backend Servers! - * <19-10-2023> - */ - -use crate::app::events::Event; -use anyhow::{bail, Result}; -use matrix_sdk::{config::SyncSettings, Client, LoopCtrl}; -use tokio::sync::mpsc; -use tokio_util::sync::CancellationToken; - -pub async fn poll( - channel: mpsc::Sender, - kill: CancellationToken, - client: Client, -) -> Result<()> { - async fn stage_2(channel: mpsc::Sender, client: Client) -> Result<()> { - let sync_settings = SyncSettings::default(); - // .token(sync_token) - // .timeout(Duration::from_secs(30)); - - let tx = &channel; - - client - .sync_with_callback(sync_settings, |response| async move { - let event = Event::MatrixEvent(response); - - match tx.send(event).await { - Ok(_) => LoopCtrl::Continue, - Err(_) => LoopCtrl::Break, - } - }) - .await?; - - Ok(()) - } - tokio::select! { - output = stage_2(channel, client) => output, - _ = kill.cancelled() => bail!("received kill signal"), - } -} diff --git a/src/app/events/listeners/mod.rs b/src/app/events/listeners/mod.rs index c8f1fee..351eb18 100644 --- a/src/app/events/listeners/mod.rs +++ b/src/app/events/listeners/mod.rs @@ -1,2 +1,21 @@ -pub mod input; -pub mod matrix; +use crate::app::events::Event; +use anyhow::{bail, Result}; +use tokio::{sync::mpsc, time::Duration}; +use tokio_util::sync::CancellationToken; + +pub async fn poll(channel: mpsc::Sender, kill: CancellationToken) -> Result<()> { + async fn stage_2(channel: mpsc::Sender) -> Result<()> { + loop { + if crossterm::event::poll(Duration::from_millis(100))? { + let event = Event::InputEvent(crossterm::event::read()?); + channel.send(event).await?; + } else { + tokio::task::yield_now().await; + } + } + } + tokio::select! { + output = stage_2(channel) => output, + _ = kill.cancelled() => bail!("received kill signal") + } +} diff --git a/src/app/events/mod.rs b/src/app/events/mod.rs index 2cf81eb..3260e13 100644 --- a/src/app/events/mod.rs +++ b/src/app/events/mod.rs @@ -3,16 +3,16 @@ pub mod listeners; use anyhow::{Context, Result}; -use crate::app::{command_interface::Commands, status::State, App}; +use crate::app::{command_interface::Commands, App}; use cli_log::{trace, warn}; use crossterm::event::Event as CrosstermEvent; -use handlers::{command, input, lua_command, matrix, setup}; +use handlers::{command, input}; #[derive(Debug)] pub enum Event { InputEvent(CrosstermEvent), - MatrixEvent(matrix_sdk::deserialized_responses::SyncResponse), - // FIXME(@soispha): The `String` is also wrong <2024-05-03> + + // FIXME(@soispha): The `String` here is just wrong <2024-05-03> CommandEvent(Commands, Option>), LuaCommand(String), } @@ -21,10 +21,6 @@ impl Event { pub async fn handle(self, app: &mut App<'_>) -> Result { trace!("Received event to handle: `{:#?}`", &self); match self { - Event::MatrixEvent(event) => matrix::handle(app, &event) - .await - .with_context(|| format!("Failed to handle matrix event: `{:#?}`", event)), - Event::CommandEvent(event, callback_tx) => command::handle(app, &event, callback_tx) .await .with_context(|| format!("Failed to handle command event: `{:#?}`", event)), @@ -43,9 +39,6 @@ impl Event { // .await // .with_context(|| format!("Failed to handle function: `{}`", function)), Event::InputEvent(event) => match app.status.state() { - State::Setup => setup::handle(app, &event).await.with_context(|| { - format!("Failed to handle input (setup) event: `{:#?}`", event) - }), _ => input::handle(app, &event).await.with_context(|| { format!("Failed to handle input (non-setup) event: `{:#?}`", event) }), diff --git a/src/app/mod.rs b/src/app/mod.rs index 98f8569..c926382 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -3,24 +3,13 @@ pub mod config; pub mod events; pub mod status; -use std::{ - collections::HashMap, - ffi::c_int, - path::{Path, PathBuf}, - sync::OnceLock, -}; +use std::{collections::HashMap, ffi::c_int, path::PathBuf, sync::OnceLock}; -use anyhow::{Context, Error, Result}; +use anyhow::{Context, Result}; use cli_log::{info, warn}; -use crossterm::{ - event::DisableMouseCapture, - execute, - terminal::{disable_raw_mode, LeaveAlternateScreen}, -}; use directories::ProjectDirs; use keymaps::trie::Node; use libloading::{Library, Symbol}; -use matrix_sdk::Client; use tokio::sync::mpsc::{self, Sender}; use tokio_util::sync::CancellationToken; @@ -29,24 +18,21 @@ use tokio_util::sync::CancellationToken; // }; use crate::{ - accounts::{Account, AccountsManager}, app::{ events::{Event, EventStatus}, status::{State, Status}, }, - ui::{central, setup}, + ui::central, }; pub struct App<'runtime> { ui: central::UI<'runtime>, - accounts_manager: AccountsManager, status: Status, tx: mpsc::Sender, rx: mpsc::Receiver, input_listener_killer: CancellationToken, - matrix_listener_killer: CancellationToken, // lua: LuaCommandManager, project_dirs: ProjectDirs, @@ -58,14 +44,6 @@ pub static COMMAND_TRANSMITTER: OnceLock> = OnceLock::new(); impl App<'_> { pub fn new() -> Result { - let path: &std::path::Path = Path::new("userdata/accounts.json"); - let config = if path.exists() { - info!("Reading account config (userdata/accounts.json)"); - Some(std::fs::read_to_string(path)?) - } else { - None - }; - let (tx, rx) = mpsc::channel(256); COMMAND_TRANSMITTER @@ -74,13 +52,11 @@ impl App<'_> { Ok(Self { ui: central::UI::new()?, - accounts_manager: AccountsManager::new(config)?, - status: Status::new(None), + status: Status::new(), tx: tx.clone(), rx, input_listener_killer: CancellationToken::new(), - matrix_listener_killer: CancellationToken::new(), // lua: LuaCommandManager::new(tx), @@ -99,7 +75,7 @@ impl App<'_> { plugin_path: Option, ) -> Result<()> { // Spawn input event listener - tokio::task::spawn(events::listeners::input::poll( + tokio::task::spawn(events::listeners::poll( self.tx.clone(), self.input_listener_killer.clone(), )); @@ -147,14 +123,6 @@ impl App<'_> { } } - if self.account().is_err() { - info!("No saved sessions found -> jumping into setup"); - self.setup().await?; - } else { - self.accounts_manager.restore().await?; - self.init_account().await?; - } - self.status.set_state(State::Normal); loop { @@ -171,89 +139,4 @@ impl App<'_> { self.input_listener_killer.cancel(); Ok(()) } - - async fn setup(&mut self) -> Result<()> { - self.ui.setup_ui = Some(setup::UI::new()); - - self.status.set_state(State::Setup); - - loop { - self.ui.update_setup().await?; - - let event = self.rx.recv().await.context("Failed to get next event")?; - - match event.handle(self).await? { - EventStatus::Ok => (), - EventStatus::Finished => return Ok(()), - EventStatus::Terminate => return Err(Error::msg("Terminated by user")), - } - } - } - - pub async fn init_account(&mut self) -> Result<()> { - let client = match self.client() { - Some(c) => c, - None => return Err(Error::msg("failed to get current client")), - } - .clone(); - - self.matrix_listener_killer.cancel(); - self.matrix_listener_killer = CancellationToken::new(); - - // Spawn Matrix Event Listener - tokio::task::spawn(events::listeners::matrix::poll( - self.tx.clone(), - self.matrix_listener_killer.clone(), - client.clone(), - )); - - // Reset Status - self.status = Status::new(Some(client)); - - let account = self.account()?; - let name = account.name().clone(); - let user_id = account.user_id().clone(); - self.status.set_account_name(name); - self.status.set_account_user_id(user_id); - - for (_, room) in self.status.rooms_mut() { - room.update_name().await?; - for _ in 0..3 { - room.poll_old_timeline().await?; - } - } - - info!("Initializing client for the current account"); - - Ok(()) - } - - pub async fn switch_account(&mut self, account_id: u32) -> Result<()> { - Ok(()) - } - - pub async fn login( - &mut self, - homeserver: &String, - username: &String, - password: &String, - ) -> Result<()> { - self.accounts_manager - .add(homeserver, username, password) - .await?; - self.init_account().await?; - Ok(()) - } - - pub fn account(&self) -> Result<&Account> { - let account = self.accounts_manager.current(); - match account { - None => Err(Error::msg("failed to resolve current account")), - Some(a) => Ok(a), - } - } - - pub fn client(&self) -> Option<&Client> { - self.accounts_manager.client() - } } diff --git a/src/app/status.rs b/src/app/status.rs index 814fc80..55d3c1e 100644 --- a/src/app/status.rs +++ b/src/app/status.rs @@ -1,25 +1,13 @@ use core::fmt; -use anyhow::{bail, Error, Result}; -use cli_log::warn; -use indexmap::IndexMap; +use anyhow::{bail, Result}; use keymaps::key_repr::Keys; -use matrix_sdk::{ - room::MessagesOptions, - ruma::{ - events::{room::message::RoomMessageEventContent, AnyTimelineEvent, StateEventType}, - RoomId, TransactionId, - }, - Client, -}; #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Debug)] pub enum State { Normal, Insert, Command, - /// Temporary workaround until command based login is working - Setup, /// Only used internally to signal, that we are waiting on further keyinputs, if multiple /// keymappings have the same prefix KeyInputPending { @@ -34,7 +22,6 @@ impl State { 'n' => State::Normal, 'i' => State::Insert, 'c' => State::Command, - 's' => State::Setup, _ => bail!( "The letter '{}' is either not connected to a state or not yet implemented", c @@ -43,15 +30,6 @@ impl State { } } -pub struct Room { - matrix_room: matrix_sdk::room::Joined, - name: String, - encrypted: bool, - timeline: Vec, - timeline_end: Option, - view_scroll: Option, -} - pub struct StatusMessage { content: String, is_error: bool, @@ -67,12 +45,7 @@ impl StatusMessage { pub struct Status { state: State, - account_name: String, - account_user_id: String, - client: Option, - rooms: IndexMap, - current_room_id: String, status_messages: Vec, } @@ -82,7 +55,6 @@ impl fmt::Display for State { Self::Normal => write!(f, "Normal"), Self::Insert => write!(f, "Insert"), Self::Command => write!(f, "Command"), - Self::Setup => write!(f, "Setup (!! workaround !!)"), Self::KeyInputPending { old_state: _, pending_keys: keys, @@ -91,101 +63,10 @@ impl fmt::Display for State { } } -impl Room { - pub fn new(matrix_room: matrix_sdk::room::Joined) -> Self { - Self { - matrix_room, - name: "".to_string(), - encrypted: false, - timeline: Vec::new(), - timeline_end: None, - view_scroll: None, - } - } - - pub async fn poll_old_timeline(&mut self) -> Result<()> { - if let Some(AnyTimelineEvent::State(event)) = &self.timeline.get(0) { - if event.event_type() == StateEventType::RoomCreate { - return Ok(()); - } - } - - let mut messages_options = MessagesOptions::backward(); - messages_options = match &self.timeline_end { - Some(end) => messages_options.from(end.as_str()), - None => messages_options, - }; - let events = self.matrix_room.messages(messages_options).await?; - self.timeline_end = events.end; - - for event in events.chunk.iter() { - self.timeline.insert( - 0, - match event.event.deserialize() { - Ok(ev) => ev, - Err(err) => { - warn!("Failed to deserialize timeline event - {err}"); - continue; - } - }, - ); - } - Ok(()) - } - - pub fn name(&self) -> &String { - &self.name - } - - pub async fn update_name(&mut self) -> Result<()> { - self.name = self.matrix_room.display_name().await?.to_string(); - Ok(()) - } - - pub fn timeline_add(&mut self, event: AnyTimelineEvent) { - self.timeline.push(event); - } - - pub fn timeline(&self) -> &Vec { - &self.timeline - } - - pub async fn send(&mut self, message: String) -> Result<()> { - let content = RoomMessageEventContent::text_plain(message); - let id = TransactionId::new(); - self.matrix_room.send(content, Some(&id)).await?; - Ok(()) - } - - pub fn view_scroll(&self) -> Option { - self.view_scroll - } - - pub fn set_view_scroll(&mut self, scroll: Option) { - self.view_scroll = scroll; - } - - pub fn encrypted(&self) -> bool { - self.matrix_room.is_encrypted() - } -} - impl Status { - pub fn new(client: Option) -> Self { - let mut rooms = IndexMap::new(); - if let Some(c) = &client { - for r in c.joined_rooms() { - rooms.insert(r.room_id().to_string(), Room::new(r.clone())); - } - }; - + pub fn new() -> Self { Self { state: State::Normal, - account_name: "".to_owned(), - account_user_id: "".to_owned(), - client, - rooms, - current_room_id: "".to_owned(), status_messages: vec![StatusMessage { content: "Initialized!".to_owned(), is_error: false, @@ -215,70 +96,6 @@ impl Status { &self.status_messages } - pub fn account_name(&self) -> &String { - &self.account_name - } - - pub fn set_account_name(&mut self, name: String) { - self.account_name = name; - } - - pub fn account_user_id(&self) -> &String { - &self.account_user_id - } - - pub fn set_account_user_id(&mut self, user_id: String) { - self.account_user_id = user_id; - } - - pub fn room(&self) -> Option<&Room> { - self.rooms.get(self.current_room_id.as_str()) - } - - pub fn room_mut(&mut self) -> Option<&mut Room> { - self.rooms.get_mut(self.current_room_id.as_str()) - } - - pub fn rooms(&self) -> &IndexMap { - &self.rooms - } - - pub fn rooms_mut(&mut self) -> &mut IndexMap { - &mut self.rooms - } - - pub fn set_room(&mut self, room_id: &RoomId) -> Result<()> { - if self.rooms.contains_key(room_id.as_str()) { - self.current_room_id = room_id.to_string(); - Ok(()) - } else { - Err(Error::msg(format!( - "failed to set room -> invalid room id {}", - room_id.to_string() - ))) - } - } - - pub fn set_room_by_index(&mut self, room_index: usize) -> Result<()> { - if let Some((room_id, _)) = self.rooms.get_index(room_index) { - self.current_room_id = room_id.clone(); - Ok(()) - } else { - Err(Error::msg(format!( - "failed to set room -> invalid room index {}", - room_index - ))) - } - } - - pub fn get_room(&self, room_id: &RoomId) -> Option<&Room> { - self.rooms.get(room_id.as_str()) - } - - pub fn get_room_mut(&mut self, room_id: &RoomId) -> Option<&mut Room> { - self.rooms.get_mut(room_id.as_str()) - } - pub fn state(&self) -> &State { &self.state } diff --git a/src/main.rs b/src/main.rs index 3d3fe01..ba0ec74 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,3 @@ -mod accounts; mod app; mod cli; mod ui; @@ -17,7 +16,7 @@ async fn main() -> anyhow::Result<()> { Command::Start {} => { let mut app = app::App::new()?; - // NOTE(@soispha): The `None` is temporary <2024-05-03> + // NOTE(@soispha): The 'None' here is temporary <2024-05-03> app.run(None, args.plugin_path).await?; } }; diff --git a/src/ui/central/mod.rs b/src/ui/central/mod.rs index a37d43c..066e775 100644 --- a/src/ui/central/mod.rs +++ b/src/ui/central/mod.rs @@ -2,7 +2,7 @@ pub mod update; use std::io::Stdout; -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result}; use cli_log::{info, warn}; use crossterm::{ event::DisableMouseCapture, @@ -16,9 +16,7 @@ use tui::{ Terminal, }; use tui_textarea::TextArea; -pub use update::*; -use super::setup; use crate::ui::{terminal_prepare, textarea_activate, textarea_inactivate}; #[derive(Clone, Copy, PartialEq)] @@ -148,8 +146,6 @@ pub struct UI<'a> { pub rooms_state: ListState, pub message_compose: TextArea<'a>, pub cli: Option>, - - pub setup_ui: Option>, } impl Drop for UI<'_> { @@ -190,7 +186,6 @@ impl UI<'_> { rooms_state: ListState::default(), message_compose, cli: None, - setup_ui: None, }) } @@ -267,15 +262,4 @@ impl UI<'_> { } self.cli = None; } - - pub async fn update_setup(&mut self) -> Result<()> { - let setup_ui = match &mut self.setup_ui { - Some(c) => c, - None => bail!("SetupUI instance not found"), - }; - - setup_ui.update(&mut self.terminal).await?; - - Ok(()) - } } diff --git a/src/ui/central/update/mod.rs b/src/ui/central/update/mod.rs index 76d56ea..6b62076 100644 --- a/src/ui/central/update/mod.rs +++ b/src/ui/central/update/mod.rs @@ -1,13 +1,13 @@ use std::cmp; -use anyhow::{Context, Result}; +use anyhow::Result; use tui::{ layout::{Constraint, Direction, Layout}, style::{Color, Style}, widgets::{Block, Borders, Paragraph}, }; -use self::widgets::{command_monitor, messages, room_info, rooms, status}; +use self::widgets::command_monitor; use super::UI; use crate::app::status::Status; @@ -43,7 +43,7 @@ impl UI<'_> { ) .split(chunks[0]); - let left_chunks = Layout::default() + let _left_chunks = Layout::default() .direction(Direction::Vertical) .constraints([Constraint::Length(5), Constraint::Min(4)].as_ref()) .split(main_chunks[0]); @@ -76,25 +76,16 @@ impl UI<'_> { .style(Style::default().fg(Color::DarkGray)), ) .style(Style::default().fg(Color::LightYellow)); - let status_panel = status::init(status, &colors); - let rooms_panel = rooms::init(status, &colors); - let (messages_panel, mut messages_state) = messages::init(status.room(), &colors) - .context("Failed to initiate the messages widget")?; - let room_info_panel = room_info::init(status.room(), &colors); let command_monitor = command_monitor::init(status.status_messages(), &colors); // render the widgets self.terminal.draw(|frame| { - frame.render_widget(status_panel, left_chunks[0]); - frame.render_stateful_widget(rooms_panel, left_chunks[1], &mut self.rooms_state); - frame.render_stateful_widget(messages_panel, middle_chunks[0], &mut messages_state); frame.render_widget(self.message_compose.widget(), middle_chunks[1]); frame.render_widget(mode_indicator, bottom_chunks[0]); match &self.cli { Some(cli) => frame.render_widget(cli.widget(), bottom_chunks[1]), None => (), }; - frame.render_widget(room_info_panel, right_chunks[0]); frame.render_widget(command_monitor, right_chunks[1]); })?; diff --git a/src/ui/central/update/widgets/messages.rs b/src/ui/central/update/widgets/messages.rs deleted file mode 100644 index 9a4f64d..0000000 --- a/src/ui/central/update/widgets/messages.rs +++ /dev/null @@ -1,109 +0,0 @@ -use anyhow::{Context, Result}; -use matrix_sdk::ruma::events::{AnyMessageLikeEvent, AnyTimelineEvent}; -use tui::{ - layout::Corner, - style::{Color, Modifier, Style}, - text::{Span, Spans, Text}, - widgets::{Block, Borders, List, ListItem, ListState}, -}; - -use crate::{app::status::Room, ui::central::InputPosition}; - -pub fn init<'a>(room: Option<&'a Room>, colors: &Vec) -> Result<(List<'a>, ListState)> { - let content = match room { - Some(room) => get_content_from_room(room).context("Failed to get content from room")?, - None => vec![ListItem::new(Text::styled( - "No room selected!", - Style::default().fg(Color::Red), - ))], - }; - - let mut messages_state = ListState::default(); - - if let Some(room) = room { - messages_state.select(room.view_scroll()); - } - - Ok(( - List::new(content) - .block( - Block::default() - .title("Messages") - .borders(Borders::ALL) - .style(Style::default().fg(colors[InputPosition::Messages as usize])), - ) - .start_corner(Corner::BottomLeft) - .highlight_symbol(">") - .highlight_style( - Style::default() - .fg(Color::LightMagenta) - .add_modifier(Modifier::BOLD), - ), - messages_state, - )) -} - -fn get_content_from_room(room: &Room) -> Result> { - let results: Vec> = room - .timeline() - .iter() - .rev() - .map(|event| filter_event(event).context("Failed to filter event")) - .collect(); - - let mut output = Vec::with_capacity(results.len()); - for result in results { - output.push(result?); - } - Ok(output) -} - -fn filter_event<'a>(event: &AnyTimelineEvent) -> Result> { - match event { - // Message Like Events - AnyTimelineEvent::MessageLike(message_like_event) => { - let (content, color) = match &message_like_event { - AnyMessageLikeEvent::RoomMessage(room_message_event) => { - let message_content = &room_message_event - .as_original() - .context("Failed to get inner original message_event")? - .content - .body(); - - (message_content.to_string(), Color::White) - } - _ => ( - format!( - "~~~ not supported message like event: {} ~~~", - message_like_event.event_type().to_string() - ), - Color::Red, - ), - }; - let mut text = Text::styled( - message_like_event.sender().to_string(), - Style::default() - .fg(Color::Cyan) - .add_modifier(Modifier::BOLD), - ); - text.extend(Text::styled( - content.to_string(), - Style::default().fg(color), - )); - Ok(ListItem::new(text)) - } - - // State Events - AnyTimelineEvent::State(state) => Ok(ListItem::new(vec![Spans::from(vec![ - Span::styled( - state.sender().to_string(), - Style::default().fg(Color::DarkGray), - ), - Span::styled(": ", Style::default().fg(Color::DarkGray)), - Span::styled( - state.event_type().to_string(), - Style::default().fg(Color::DarkGray), - ), - ])])), - } -} diff --git a/src/ui/central/update/widgets/mod.rs b/src/ui/central/update/widgets/mod.rs index f35b656..e72d096 100644 --- a/src/ui/central/update/widgets/mod.rs +++ b/src/ui/central/update/widgets/mod.rs @@ -1,5 +1 @@ pub mod command_monitor; -pub mod messages; -pub mod room_info; -pub mod rooms; -pub mod status; diff --git a/src/ui/central/update/widgets/room_info.rs b/src/ui/central/update/widgets/room_info.rs deleted file mode 100644 index 599280c..0000000 --- a/src/ui/central/update/widgets/room_info.rs +++ /dev/null @@ -1,37 +0,0 @@ -use tui::{ - layout::Alignment, - style::{Color, Style}, - text::Text, - widgets::{Block, Borders, Paragraph}, -}; - -use crate::{app::status::Room, ui::central::InputPosition}; - -pub fn init<'a>(room: Option<&'a Room>, colors: &Vec) -> Paragraph<'a> { - let mut room_info_content = Text::default(); - if let Some(room) = room { - room_info_content.extend(Text::styled(room.name(), Style::default().fg(Color::Cyan))); - if room.encrypted() { - room_info_content.extend(Text::styled("Encrypted", Style::default().fg(Color::Green))); - } else { - room_info_content.extend(Text::styled( - "Not Encrypted!", - Style::default().fg(Color::Red), - )); - } - } else { - room_info_content.extend(Text::styled( - "No room selected!", - Style::default().fg(Color::Red), - )); - } - - Paragraph::new(room_info_content) - .block( - Block::default() - .title("Room Info") - .borders(Borders::ALL) - .style(Style::default().fg(colors[InputPosition::RoomInfo as usize])), - ) - .alignment(Alignment::Center) -} diff --git a/src/ui/central/update/widgets/rooms.rs b/src/ui/central/update/widgets/rooms.rs deleted file mode 100644 index feab3c9..0000000 --- a/src/ui/central/update/widgets/rooms.rs +++ /dev/null @@ -1,29 +0,0 @@ -use tui::{ - style::{Color, Modifier, Style}, - text::Span, - widgets::{Block, Borders, List, ListItem}, -}; - -use crate::{app::status::Status, ui::central::InputPosition}; - -pub fn init<'a>(status: &'a Status, colors: &Vec) -> List<'a> { - let rooms_content: Vec<_> = status - .rooms() - .iter() - .map(|(_, room)| ListItem::new(Span::styled(room.name(), Style::default()))) - .collect(); - List::new(rooms_content) - .block( - Block::default() - .title("Rooms (navigate: arrow keys)") - .borders(Borders::ALL) - .style(Style::default().fg(colors[InputPosition::Rooms as usize])), - ) - .style(Style::default().fg(Color::DarkGray)) - .highlight_style( - Style::default() - .fg(Color::Cyan) - .add_modifier(Modifier::BOLD), - ) - .highlight_symbol(">") -} diff --git a/src/ui/central/update/widgets/status.rs b/src/ui/central/update/widgets/status.rs deleted file mode 100644 index 0d87034..0000000 --- a/src/ui/central/update/widgets/status.rs +++ /dev/null @@ -1,30 +0,0 @@ -use tui::{ - layout::Alignment, - style::{Color, Modifier, Style}, - text::Text, - widgets::{Block, Borders, Paragraph}, -}; - -use crate::{app::status::Status, ui::central::InputPosition}; - -pub fn init<'a>(status: &'a Status, colors: &Vec) -> Paragraph<'a> { - let mut status_content = Text::styled( - status.account_name(), - Style::default().add_modifier(Modifier::BOLD), - ); - status_content.extend(Text::styled(status.account_user_id(), Style::default())); - status_content.extend(Text::styled( - "settings", - Style::default() - .fg(Color::LightMagenta) - .add_modifier(Modifier::ITALIC | Modifier::UNDERLINED), - )); - Paragraph::new(status_content) - .block( - Block::default() - .title("Status") - .borders(Borders::ALL) - .style(Style::default().fg(colors[InputPosition::Status as usize])), - ) - .alignment(Alignment::Left) -} diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 28ebea2..c948474 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,5 +1,4 @@ pub mod central; -pub mod setup; use std::{io, io::Stdout}; diff --git a/src/ui/setup.rs b/src/ui/setup.rs deleted file mode 100644 index b336452..0000000 --- a/src/ui/setup.rs +++ /dev/null @@ -1,172 +0,0 @@ -use std::io::Stdout; - -use anyhow::Result; -use tui::{ - backend::CrosstermBackend, - layout::{Alignment, Constraint, Direction, Layout}, - style::{Color, Modifier, Style}, - text::Span, - widgets::{Block, Borders, Paragraph}, - Terminal, -}; -use tui_textarea::TextArea; - -use crate::ui::{textarea_activate, textarea_inactivate}; - -pub struct UI<'a> { - input_position: InputPosition, - - pub homeserver: TextArea<'a>, - pub username: TextArea<'a>, - pub password: TextArea<'a>, - pub password_data: TextArea<'a>, -} - -#[derive(Clone, Copy)] -pub enum InputPosition { - Homeserver, - Username, - Password, - Ok, -} - -impl UI<'_> { - pub fn new() -> Self { - let mut homeserver = TextArea::new(vec!["https://matrix.org".to_string()]); - let mut username = TextArea::default(); - let mut password = TextArea::default(); - let password_data = TextArea::default(); - - homeserver.set_block(Block::default().title("Homeserver").borders(Borders::ALL)); - username.set_block(Block::default().title("Username").borders(Borders::ALL)); - password.set_block(Block::default().title("Password").borders(Borders::ALL)); - - textarea_activate(&mut homeserver); - textarea_inactivate(&mut username); - textarea_inactivate(&mut password); - - Self { - input_position: InputPosition::Homeserver, - homeserver, - username, - password, - password_data, - } - } - - pub fn cycle_input_position(&mut self) { - self.input_position = match self.input_position { - InputPosition::Homeserver => { - textarea_inactivate(&mut self.homeserver); - textarea_activate(&mut self.username); - textarea_inactivate(&mut self.password); - InputPosition::Username - } - InputPosition::Username => { - textarea_inactivate(&mut self.homeserver); - textarea_inactivate(&mut self.username); - textarea_activate(&mut self.password); - InputPosition::Password - } - InputPosition::Password => { - textarea_inactivate(&mut self.homeserver); - textarea_inactivate(&mut self.username); - textarea_inactivate(&mut self.password); - InputPosition::Ok - } - InputPosition::Ok => { - textarea_activate(&mut self.homeserver); - textarea_inactivate(&mut self.username); - textarea_inactivate(&mut self.password); - InputPosition::Homeserver - } - }; - } - - pub fn cycle_input_position_rev(&mut self) { - self.input_position = match self.input_position { - InputPosition::Homeserver => { - textarea_inactivate(&mut self.homeserver); - textarea_inactivate(&mut self.username); - textarea_inactivate(&mut self.password); - InputPosition::Ok - } - InputPosition::Username => { - textarea_activate(&mut self.homeserver); - textarea_inactivate(&mut self.username); - textarea_inactivate(&mut self.password); - InputPosition::Homeserver - } - InputPosition::Password => { - textarea_inactivate(&mut self.homeserver); - textarea_activate(&mut self.username); - textarea_inactivate(&mut self.password); - InputPosition::Username - } - InputPosition::Ok => { - textarea_inactivate(&mut self.homeserver); - textarea_inactivate(&mut self.username); - textarea_activate(&mut self.password); - InputPosition::Password - } - }; - } - - pub fn input_position(&self) -> &InputPosition { - &self.input_position - } - - pub async fn update( - &mut self, - terminal: &mut Terminal>, - ) -> Result<()> { - let strings: Vec = vec!["".to_owned(); 3]; - - let content_ok = match self.input_position { - InputPosition::Ok => { - Span::styled("OK", Style::default().add_modifier(Modifier::UNDERLINED)) - } - _ => Span::styled("OK", Style::default().fg(Color::DarkGray)), - }; - - let block = Block::default().title("Login").borders(Borders::ALL); - - let ok = Paragraph::new(content_ok).alignment(Alignment::Center); - - // define a 32 * 6 chunk in the middle of the screen - let mut chunk = terminal.size()?; - chunk.x = (chunk.width / 2) - 16; - chunk.y = (chunk.height / 2) - 5; - chunk.height = 12; - chunk.width = 32; - - let mut split_chunk = chunk.clone(); - split_chunk.x += 1; - split_chunk.y += 1; - split_chunk.height -= 1; - split_chunk.width -= 2; - - let chunks = Layout::default() - .direction(Direction::Vertical) - .constraints( - [ - Constraint::Length(3), // 0. Homserver: - Constraint::Length(3), // 1. Username: - Constraint::Length(3), // 2. Password: - Constraint::Length(1), // 3. OK - ] - .as_ref(), - ) - .split(split_chunk); - - terminal.draw(|frame| { - frame.render_widget(block.clone(), chunk); - frame.render_widget(self.homeserver.widget(), chunks[0]); - frame.render_widget(self.username.widget(), chunks[1]); - frame.render_widget(self.password.widget(), chunks[2]); - frame.render_widget(ok.clone(), chunks[3]); - })?; - - Ok(()) - } -}