refactor(src/ui): Allow for a repl
This commit is contained in:
parent
08c4724a94
commit
d76f279a05
|
@ -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<Event>, 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<Option<PathBuf>> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
pub mod lua;
|
||||
pub mod shared_objects;
|
||||
|
|
|
@ -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<unsafe fn() -> 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(())
|
||||
}
|
|
@ -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<U: TrinitrixUi>(
|
||||
app: &mut App<U>,
|
||||
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
|
||||
|
|
|
@ -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<EventStatus> {
|
||||
async fn default(converted_key: Key, app: &mut App<'_>, old_state: &State) -> Result<()> {
|
||||
pub async fn handle<U: TrinitrixUi>(
|
||||
app: &mut App<U>,
|
||||
input_event: &CrosstermEvent,
|
||||
) -> Result<EventStatus> {
|
||||
async fn default<U: TrinitrixUi>(
|
||||
converted_key: Key,
|
||||
app: &mut App<U>,
|
||||
old_state: &State,
|
||||
) -> Result<()> {
|
||||
info!(
|
||||
"No keymaps exist for key ('{}'), passing it along..",
|
||||
converted_key
|
||||
|
|
|
@ -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<EventStatus> {
|
||||
pub async fn handle<U: TrinitrixUi>(self, app: &mut App<U>) -> Result<EventStatus> {
|
||||
trace!("Received event to handle: `{:#?}`", &self);
|
||||
match self {
|
||||
Event::CommandEvent(event, callback_tx) => command::handle(app, &event, callback_tx)
|
||||
|
|
|
@ -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<U: TrinitrixUi> {
|
||||
ui: U,
|
||||
status: Status,
|
||||
|
||||
tx: mpsc::Sender<Event>,
|
||||
|
@ -42,8 +41,8 @@ pub struct App<'runtime> {
|
|||
|
||||
pub static COMMAND_TRANSMITTER: OnceLock<Sender<Event>> = OnceLock::new();
|
||||
|
||||
impl App<'_> {
|
||||
pub fn new() -> Result<Self> {
|
||||
impl<U: TrinitrixUi> App<U> {
|
||||
pub fn new(ui: U) -> Result<Self> {
|
||||
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<unsafe fn() -> 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?;
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {},
|
||||
}
|
||||
|
|
16
src/main.rs
16
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?;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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<Stdout> {
|
||||
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;
|
||||
|
|
|
@ -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<TextArea<'a>>,
|
||||
}
|
||||
|
||||
impl<'r> TirinitrixUi for UI<'r> {
|
||||
}
|
||||
|
||||
impl Drop for UI<'_> {
|
||||
fn drop(&mut self) {
|
||||
info!("Destructing UI");
|
|
@ -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<Stdout> {
|
||||
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)));
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
Reference in New Issue