diff --git a/src/app/config/lua/mod.rs b/src/app/config/lua/mod.rs index ac3a227..f742e25 100644 --- a/src/app/config/lua/mod.rs +++ b/src/app/config/lua/mod.rs @@ -1,14 +1,45 @@ -use std::path::PathBuf; +use std::path::{Path, PathBuf}; -use anyhow::Result; -use cli_log::info; +use anyhow::{Context, Result}; +use cli_log::{info, warn}; use tokio::{fs, sync::mpsc::Sender}; use crate::app::events::Event; +const LUA_CONFIG_FILE_NAME: &str = "init.lua"; + pub async fn load(tx: Sender, path: PathBuf) -> Result<()> { + info!("Loaded config file at '{}'", path.display()); + let lua_config_code = fs::read_to_string(&path).await?; tx.send(Event::LuaCommand(lua_config_code)).await?; - info!("Loaded config file at `{}`", path.display()); + Ok(()) } + +pub async fn check_for_config_file(config_dir: &Path) -> Result> { + if config_dir + .try_exists() + .context("Failed to search for a config file")? + { + info!("Found config dir: `{}`", config_dir.display()); + let config_file = config_dir.join(LUA_CONFIG_FILE_NAME); + + info!("Searching for config file at '{}'", config_file.display()); + if config_file.try_exists().with_context(|| { + format!( + "Failed to check, if the lua config file at '{}' exists.", + config_file.display() + ) + })? { + info!("Found the config file at: '{}'", config_file.display()); + Ok(Some(config_file)) + } else { + warn!("No config file found!"); + Ok(None) + } + } else { + warn!("No config directory found at {}!", config_dir.display()); + Ok(None) + } +} diff --git a/src/app/config/mod.rs b/src/app/config/mod.rs index f97296a..a25d043 100644 --- a/src/app/config/mod.rs +++ b/src/app/config/mod.rs @@ -1 +1,2 @@ pub mod lua; +pub mod shared_objects; diff --git a/src/app/config/shared_objects/mod.rs b/src/app/config/shared_objects/mod.rs new file mode 100644 index 0000000..737eff1 --- /dev/null +++ b/src/app/config/shared_objects/mod.rs @@ -0,0 +1,22 @@ +use std::{ffi::c_int, path::Path}; + +use anyhow::{Context, Result}; +use cli_log::info; +use libloading::{Library, Symbol}; + +pub fn load(plugin: &Path) -> Result<()> { + info!("Loading a plugin from '{}'", plugin.display()); + + unsafe { + let lib = Library::new(plugin).context("Failed to load plugin")?; + let func: Symbol c_int> = lib + .get(b"plugin_main") + .context("Plugin does not have a 'plugin_main' symbol")?; + + info!("Starting plugin"); + let out = func(); + info!("Plugin finished with: {}", out); + } + + Ok(()) +} diff --git a/src/app/events/handlers/command.rs b/src/app/events/handlers/command.rs index 0476b14..0c867c0 100644 --- a/src/app/events/handlers/command.rs +++ b/src/app/events/handlers/command.rs @@ -14,7 +14,7 @@ use crate::{ App, }, trinitrix::api::ui::Mode, - ui::central::InputPosition, + ui::ui_trait::TrinitrixUi, }; use anyhow::Result; use cli_log::{info, trace, warn}; @@ -25,8 +25,8 @@ use keymaps::{ }; use trixy::oneshot; -pub async fn handle( - app: &mut App<'_>, +pub async fn handle( + app: &mut App, command: &Commands, // FIXME(@soispha): The `String` is temporary <2024-05-03> @@ -96,24 +96,24 @@ pub async fn handle( } Mode::Insert => { app.status.set_state(State::Insert); - app.ui.set_input_position(InputPosition::MessageCompose); send_status_output!("Set input mode to Insert"); EventStatus::Ok } Mode::Command => { - app.ui.cli_enable(); app.status.set_state(State::Command); send_status_output!("Set input mode to CLI"); EventStatus::Ok } }, Ui::cycle_planes => { - app.ui.cycle_main_input_position(); + // TODO(@soispha): add this <2024-05-04> + // app.ui.cycle_main_input_position(); send_status_output!("Switched main input position"); EventStatus::Ok } Ui::cycle_planes_rev => { - app.ui.cycle_main_input_position_rev(); + // TODO(@soispha): and this <2024-05-04> + // app.ui.cycle_main_input_position_rev(); send_status_output!("Switched main input position; reversed"); EventStatus::Ok } @@ -178,32 +178,20 @@ pub async fn handle( EventStatus::Ok } Raw::send_input_unprocessed { input } => { - let output = match app.status.state() { - State::Insert => { - let key = Key::from_str(input.as_str())?; - let cross_input: Event = key.try_into()?; - app.ui - .message_compose - .input(tui_textarea::Input::from(cross_input)); - EventStatus::Ok - } - State::Command => { - let key = Key::from_str(input.as_str())?; - let cross_input: Event = key.try_into()?; - app.ui - .cli - .as_mut() - .expect("This should exist, when the state is 'Command'") - .input(tui_textarea::Input::from(cross_input)); - EventStatus::Ok + let key = Key::from_str(input.as_str())?; + // let cross_input: Event = key.try_into()?; + + match app.status.state() { + State::Insert | State::Command => { + app.ui.input(key); } State::Normal | State::KeyInputPending { old_state: _, pending_keys: _, - } => EventStatus::Ok, - }; - output + } => {} + } + EventStatus::Ok } Raw::Private(private) => { // no-op, this was used to store functions (not so sure, if we need it diff --git a/src/app/events/handlers/input.rs b/src/app/events/handlers/input.rs index 2447f46..06931e4 100644 --- a/src/app/events/handlers/input.rs +++ b/src/app/events/handlers/input.rs @@ -3,21 +3,31 @@ use cli_log::info; use crossterm::event::Event as CrosstermEvent; use keymaps::key_repr::{Key, Keys}; -use crate::app::{ - command_interface::{ - trinitrix::{ - api::{raw::Raw, Api}, - Trinitrix, +use crate::{ + app::{ + command_interface::{ + trinitrix::{ + api::{raw::Raw, Api}, + Trinitrix, + }, + Commands, }, - Commands, + events::{Event, EventStatus}, + status::State, + App, }, - events::{Event, EventStatus}, - status::State, - App, + ui::ui_trait::TrinitrixUi, }; -pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result { - async fn default(converted_key: Key, app: &mut App<'_>, old_state: &State) -> Result<()> { +pub async fn handle( + app: &mut App, + input_event: &CrosstermEvent, +) -> Result { + async fn default( + converted_key: Key, + app: &mut App, + old_state: &State, + ) -> Result<()> { info!( "No keymaps exist for key ('{}'), passing it along..", converted_key diff --git a/src/app/events/mod.rs b/src/app/events/mod.rs index 3260e13..51f7c11 100644 --- a/src/app/events/mod.rs +++ b/src/app/events/mod.rs @@ -3,7 +3,10 @@ pub mod listeners; use anyhow::{Context, Result}; -use crate::app::{command_interface::Commands, App}; +use crate::{ + app::{command_interface::Commands, App}, + ui::ui_trait::TrinitrixUi, +}; use cli_log::{trace, warn}; use crossterm::event::Event as CrosstermEvent; use handlers::{command, input}; @@ -18,7 +21,7 @@ pub enum Event { } impl Event { - pub async fn handle(self, app: &mut App<'_>) -> Result { + pub async fn handle(self, app: &mut App) -> Result { trace!("Received event to handle: `{:#?}`", &self); match self { Event::CommandEvent(event, callback_tx) => command::handle(app, &event, callback_tx) diff --git a/src/app/mod.rs b/src/app/mod.rs index c926382..e9d4961 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -3,13 +3,12 @@ pub mod config; pub mod events; pub mod status; -use std::{collections::HashMap, ffi::c_int, path::PathBuf, sync::OnceLock}; +use std::{collections::HashMap, path::PathBuf, sync::OnceLock}; use anyhow::{Context, Result}; -use cli_log::{info, warn}; +use cli_log::warn; use directories::ProjectDirs; use keymaps::trie::Node; -use libloading::{Library, Symbol}; use tokio::sync::mpsc::{self, Sender}; use tokio_util::sync::CancellationToken; @@ -22,11 +21,11 @@ use crate::{ events::{Event, EventStatus}, status::{State, Status}, }, - ui::central, + ui::ui_trait::TrinitrixUi, }; -pub struct App<'runtime> { - ui: central::UI<'runtime>, +pub struct App { + ui: U, status: Status, tx: mpsc::Sender, @@ -42,8 +41,8 @@ pub struct App<'runtime> { pub static COMMAND_TRANSMITTER: OnceLock> = OnceLock::new(); -impl App<'_> { - pub fn new() -> Result { +impl App { + pub fn new(ui: U) -> Result { let (tx, rx) = mpsc::channel(256); COMMAND_TRANSMITTER @@ -51,7 +50,7 @@ impl App<'_> { .expect("The cell should always be empty at this point"); Ok(Self { - ui: central::UI::new()?, + ui, status: Status::new(), tx: tx.clone(), @@ -80,51 +79,24 @@ impl App<'_> { self.input_listener_killer.clone(), )); - let config_dir = self.project_dirs.config_dir(); - let mut lua_config_file = None; - if config_dir - .try_exists() - .context("Failed to search for a config file")? - { - lua_config_file = Some(config_dir.join("init.lua")); - info!("Found config dir: `{}`", config_dir.display()); - } else { - warn!("No config dir found!"); - } - if let Some(config_file) = cli_lua_config_file { config::lua::load(self.tx.clone(), config_file).await?; - } else if let Some(config_file) = lua_config_file { - info!("Searching for config file at `{}`", config_file.display()); - if config_file.try_exists().with_context(|| { - format!( - "Failed to check, if the lua config file at `{}` exists", - config_file.display() - ) - })? { - config::lua::load(self.tx.clone(), config_file).await?; - } else { - warn!("No config file found!"); + warn!("Loading cli config file, will ignore the default locations"); + } else { + let config_file = config::lua::check_for_config_file(self.project_dirs.config_dir()) + .await + .context("Failed to check for the config file")?; + + if let Some(config) = config_file { + config::lua::load(self.tx.clone(), config).await?; } } if let Some(plugin) = plugin_path { - info!("Loading plugin_main() from {}", plugin.display()); - - unsafe { - let lib = Library::new(plugin).context("Failed to load plugin")?; - let func: Symbol c_int> = lib - .get(b"plugin_main") - .context("Plugin does not have a 'plugin_main' symbol")?; - - info!("Starting plugin"); - let out = func(); - info!("Plugin finished with: {}", out); - } + config::shared_objects::load(&plugin) + .with_context(|| format!("Failed to load a pluging at '{}'", plugin.display()))?; } - self.status.set_state(State::Normal); - loop { self.ui.update(&self.status).await?; diff --git a/src/app/status.rs b/src/app/status.rs index 55d3c1e..74d8492 100644 --- a/src/app/status.rs +++ b/src/app/status.rs @@ -3,8 +3,9 @@ use core::fmt; use anyhow::{bail, Result}; use keymaps::key_repr::Keys; -#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Debug)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Debug, Default)] pub enum State { + #[default] Normal, Insert, Command, @@ -66,7 +67,7 @@ impl fmt::Display for State { impl Status { pub fn new() -> Self { Self { - state: State::Normal, + state: State::default(), status_messages: vec![StatusMessage { content: "Initialized!".to_owned(), is_error: false, diff --git a/src/cli.rs b/src/cli.rs index fec5171..d2da450 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -23,4 +23,7 @@ pub struct Args { pub enum Command { /// Starts the main TUI client Start {}, + + /// Starts a repl to the trinitry cli interpreter + Repl {}, } diff --git a/src/main.rs b/src/main.rs index ba0ec74..1c1571c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,10 @@ mod ui; use clap::Parser; -use crate::cli::{Args, Command}; +use crate::{ + cli::{Args, Command}, + ui::repl::Repl, +}; #[tokio::main] async fn main() -> anyhow::Result<()> { @@ -14,9 +17,16 @@ async fn main() -> anyhow::Result<()> { let command = args.subcommand.unwrap_or(Command::Start {}); match command { Command::Start {} => { - let mut app = app::App::new()?; + todo!("The full ui is not yet finished"); + let mut app = app::App::new(Repl::new())?; - // NOTE(@soispha): The 'None' here is temporary <2024-05-03> + // NOTE(@soispha): The `None` here is temporary <2024-05-03> + app.run(None, args.plugin_path).await?; + } + Command::Repl {} => { + let mut app = app::App::new(Repl::new())?; + + // NOTE(@soispha): The `None` here is temporary <2024-05-03> app.run(None, args.plugin_path).await?; } }; diff --git a/src/ui/mod.rs b/src/ui/mod.rs index c948474..c352461 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,44 +1,2 @@ -pub mod central; - -use std::{io, io::Stdout}; - -use anyhow::{Context, Result}; -use cli_log::info; -use crossterm::{ - event::EnableMouseCapture, - execute, - terminal::{enable_raw_mode, EnterAlternateScreen}, -}; -use tui::{ - style::{Color, Modifier, Style}, - widgets::{Block, Borders}, -}; -use tui_textarea::TextArea; - -fn terminal_prepare() -> Result { - enable_raw_mode().context("Failed to enable raw mode")?; - let mut stdout = io::stdout(); - execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?; - info!("Prepared terminal"); - Ok(stdout) -} - -pub fn textarea_activate(textarea: &mut TextArea) { - textarea.set_cursor_line_style(Style::default().add_modifier(Modifier::UNDERLINED)); - textarea.set_cursor_style(Style::default().add_modifier(Modifier::REVERSED)); - let b = textarea - .block() - .cloned() - .unwrap_or_else(|| Block::default().borders(Borders::ALL)); - textarea.set_block(b.style(Style::default())); -} - -pub fn textarea_inactivate(textarea: &mut TextArea) { - textarea.set_cursor_line_style(Style::default()); - textarea.set_cursor_style(Style::default()); - let b = textarea - .block() - .cloned() - .unwrap_or_else(|| Block::default().borders(Borders::ALL)); - textarea.set_block(b.style(Style::default().fg(Color::DarkGray))); -} +pub mod ui_trait; +pub mod repl; diff --git a/src/ui/central/mod.rs b/src/ui/old/central/mod.rs similarity index 99% rename from src/ui/central/mod.rs rename to src/ui/old/central/mod.rs index 066e775..64056ea 100644 --- a/src/ui/central/mod.rs +++ b/src/ui/old/central/mod.rs @@ -19,6 +19,8 @@ use tui_textarea::TextArea; use crate::ui::{terminal_prepare, textarea_activate, textarea_inactivate}; +use super::ui_trait::TirinitrixUi; + #[derive(Clone, Copy, PartialEq)] pub enum InputPosition { Status, @@ -148,6 +150,9 @@ pub struct UI<'a> { pub cli: Option>, } +impl<'r> TirinitrixUi for UI<'r> { +} + impl Drop for UI<'_> { fn drop(&mut self) { info!("Destructing UI"); diff --git a/src/ui/central/update/mod.rs b/src/ui/old/central/update/mod.rs similarity index 100% rename from src/ui/central/update/mod.rs rename to src/ui/old/central/update/mod.rs diff --git a/src/ui/central/update/widgets/command_monitor.rs b/src/ui/old/central/update/widgets/command_monitor.rs similarity index 100% rename from src/ui/central/update/widgets/command_monitor.rs rename to src/ui/old/central/update/widgets/command_monitor.rs diff --git a/src/ui/central/update/widgets/mod.rs b/src/ui/old/central/update/widgets/mod.rs similarity index 100% rename from src/ui/central/update/widgets/mod.rs rename to src/ui/old/central/update/widgets/mod.rs diff --git a/src/ui/old/mod.rs b/src/ui/old/mod.rs new file mode 100644 index 0000000..25db77c --- /dev/null +++ b/src/ui/old/mod.rs @@ -0,0 +1,45 @@ +pub mod central; +pub mod ui_trait; + +use std::{io, io::Stdout}; + +use anyhow::{Context, Result}; +use cli_log::info; +use crossterm::{ + event::EnableMouseCapture, + execute, + terminal::{enable_raw_mode, EnterAlternateScreen}, +}; +use tui::{ + style::{Color, Modifier, Style}, + widgets::{Block, Borders}, +}; +use tui_textarea::TextArea; + +fn terminal_prepare() -> Result { + enable_raw_mode().context("Failed to enable raw mode")?; + let mut stdout = io::stdout(); + execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?; + info!("Prepared terminal"); + Ok(stdout) +} + +pub fn textarea_activate(textarea: &mut TextArea) { + textarea.set_cursor_line_style(Style::default().add_modifier(Modifier::UNDERLINED)); + textarea.set_cursor_style(Style::default().add_modifier(Modifier::REVERSED)); + let b = textarea + .block() + .cloned() + .unwrap_or_else(|| Block::default().borders(Borders::ALL)); + textarea.set_block(b.style(Style::default())); +} + +pub fn textarea_inactivate(textarea: &mut TextArea) { + textarea.set_cursor_line_style(Style::default()); + textarea.set_cursor_style(Style::default()); + let b = textarea + .block() + .cloned() + .unwrap_or_else(|| Block::default().borders(Borders::ALL)); + textarea.set_block(b.style(Style::default().fg(Color::DarkGray))); +} diff --git a/src/ui/repl/mod.rs b/src/ui/repl/mod.rs new file mode 100644 index 0000000..2aae1ab --- /dev/null +++ b/src/ui/repl/mod.rs @@ -0,0 +1,54 @@ +use anyhow::Result; +use cli_log::info; +use keymaps::key_repr::{Key, KeyValue}; + +use crate::app::status::Status; + +use super::ui_trait::TrinitrixUi; + +pub struct Repl { + current_input: String, + enter_new_line: bool, +} + +impl Repl { + pub fn new() -> Self { + println!("Welcome to the Trinitrix repl! You can now enter trinitry commands."); + print!("{}", Self::prompt(&Status::new())); + + Repl { + current_input: String::new(), + enter_new_line: false, + } + } + pub fn prompt(status: &Status) -> String { + format!("{}> ", status.state()) + } +} + +impl TrinitrixUi for Repl { + async fn update(&mut self, status: &Status) -> Result<()> { + if self.enter_new_line { + info!("Got '{}' from user in repl.", self.current_input); + + self.enter_new_line = false; + self.current_input = String::new(); + + print!("{}", Self::prompt(status)); + } + + Ok(()) + } + + fn input(&mut self, input: Key) { + if let Some(value) = input.value() { + if let KeyValue::Char(ch) = value { + self.current_input.push(*ch); + } else if let KeyValue::Enter = value { + self.enter_new_line = true; + } + } else { + info!("User wrote: '{}'", input.to_string_repr()); + } + } +} diff --git a/src/ui/ui_trait/mod.rs b/src/ui/ui_trait/mod.rs new file mode 100644 index 0000000..25fbe83 --- /dev/null +++ b/src/ui/ui_trait/mod.rs @@ -0,0 +1,9 @@ +use anyhow::Result; +use keymaps::key_repr::Key; + +use crate::app::status::Status; + +pub trait TrinitrixUi { + async fn update(&mut self, status: &Status) -> Result<()>; + fn input(&mut self, input: Key); +}