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 anyhow::{Context, Result};
|
||||||
use cli_log::info;
|
use cli_log::{info, warn};
|
||||||
use tokio::{fs, sync::mpsc::Sender};
|
use tokio::{fs, sync::mpsc::Sender};
|
||||||
|
|
||||||
use crate::app::events::Event;
|
use crate::app::events::Event;
|
||||||
|
|
||||||
|
const LUA_CONFIG_FILE_NAME: &str = "init.lua";
|
||||||
|
|
||||||
pub async fn load(tx: Sender<Event>, path: PathBuf) -> Result<()> {
|
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?;
|
let lua_config_code = fs::read_to_string(&path).await?;
|
||||||
tx.send(Event::LuaCommand(lua_config_code)).await?;
|
tx.send(Event::LuaCommand(lua_config_code)).await?;
|
||||||
info!("Loaded config file at `{}`", path.display());
|
|
||||||
Ok(())
|
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 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,
|
App,
|
||||||
},
|
},
|
||||||
trinitrix::api::ui::Mode,
|
trinitrix::api::ui::Mode,
|
||||||
ui::central::InputPosition,
|
ui::ui_trait::TrinitrixUi,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use cli_log::{info, trace, warn};
|
use cli_log::{info, trace, warn};
|
||||||
|
@ -25,8 +25,8 @@ use keymaps::{
|
||||||
};
|
};
|
||||||
use trixy::oneshot;
|
use trixy::oneshot;
|
||||||
|
|
||||||
pub async fn handle(
|
pub async fn handle<U: TrinitrixUi>(
|
||||||
app: &mut App<'_>,
|
app: &mut App<U>,
|
||||||
command: &Commands,
|
command: &Commands,
|
||||||
|
|
||||||
// FIXME(@soispha): The `String` is temporary <2024-05-03>
|
// FIXME(@soispha): The `String` is temporary <2024-05-03>
|
||||||
|
@ -96,24 +96,24 @@ pub async fn handle(
|
||||||
}
|
}
|
||||||
Mode::Insert => {
|
Mode::Insert => {
|
||||||
app.status.set_state(State::Insert);
|
app.status.set_state(State::Insert);
|
||||||
app.ui.set_input_position(InputPosition::MessageCompose);
|
|
||||||
send_status_output!("Set input mode to Insert");
|
send_status_output!("Set input mode to Insert");
|
||||||
EventStatus::Ok
|
EventStatus::Ok
|
||||||
}
|
}
|
||||||
Mode::Command => {
|
Mode::Command => {
|
||||||
app.ui.cli_enable();
|
|
||||||
app.status.set_state(State::Command);
|
app.status.set_state(State::Command);
|
||||||
send_status_output!("Set input mode to CLI");
|
send_status_output!("Set input mode to CLI");
|
||||||
EventStatus::Ok
|
EventStatus::Ok
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Ui::cycle_planes => {
|
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");
|
send_status_output!("Switched main input position");
|
||||||
EventStatus::Ok
|
EventStatus::Ok
|
||||||
}
|
}
|
||||||
Ui::cycle_planes_rev => {
|
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");
|
send_status_output!("Switched main input position; reversed");
|
||||||
EventStatus::Ok
|
EventStatus::Ok
|
||||||
}
|
}
|
||||||
|
@ -178,32 +178,20 @@ pub async fn handle(
|
||||||
EventStatus::Ok
|
EventStatus::Ok
|
||||||
}
|
}
|
||||||
Raw::send_input_unprocessed { input } => {
|
Raw::send_input_unprocessed { input } => {
|
||||||
let output = match app.status.state() {
|
|
||||||
State::Insert => {
|
|
||||||
let key = Key::from_str(input.as_str())?;
|
let key = Key::from_str(input.as_str())?;
|
||||||
let cross_input: Event = key.try_into()?;
|
// let cross_input: Event = key.try_into()?;
|
||||||
app.ui
|
|
||||||
.message_compose
|
match app.status.state() {
|
||||||
.input(tui_textarea::Input::from(cross_input));
|
State::Insert | State::Command => {
|
||||||
EventStatus::Ok
|
app.ui.input(key);
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
State::Normal
|
State::Normal
|
||||||
| State::KeyInputPending {
|
| State::KeyInputPending {
|
||||||
old_state: _,
|
old_state: _,
|
||||||
pending_keys: _,
|
pending_keys: _,
|
||||||
} => EventStatus::Ok,
|
} => {}
|
||||||
};
|
}
|
||||||
output
|
EventStatus::Ok
|
||||||
}
|
}
|
||||||
Raw::Private(private) => {
|
Raw::Private(private) => {
|
||||||
// no-op, this was used to store functions (not so sure, if we need it
|
// no-op, this was used to store functions (not so sure, if we need it
|
||||||
|
|
|
@ -3,7 +3,8 @@ use cli_log::info;
|
||||||
use crossterm::event::Event as CrosstermEvent;
|
use crossterm::event::Event as CrosstermEvent;
|
||||||
use keymaps::key_repr::{Key, Keys};
|
use keymaps::key_repr::{Key, Keys};
|
||||||
|
|
||||||
use crate::app::{
|
use crate::{
|
||||||
|
app::{
|
||||||
command_interface::{
|
command_interface::{
|
||||||
trinitrix::{
|
trinitrix::{
|
||||||
api::{raw::Raw, Api},
|
api::{raw::Raw, Api},
|
||||||
|
@ -14,10 +15,19 @@ use crate::app::{
|
||||||
events::{Event, EventStatus},
|
events::{Event, EventStatus},
|
||||||
status::State,
|
status::State,
|
||||||
App,
|
App,
|
||||||
|
},
|
||||||
|
ui::ui_trait::TrinitrixUi,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result<EventStatus> {
|
pub async fn handle<U: TrinitrixUi>(
|
||||||
async fn default(converted_key: Key, app: &mut App<'_>, old_state: &State) -> Result<()> {
|
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!(
|
info!(
|
||||||
"No keymaps exist for key ('{}'), passing it along..",
|
"No keymaps exist for key ('{}'), passing it along..",
|
||||||
converted_key
|
converted_key
|
||||||
|
|
|
@ -3,7 +3,10 @@ pub mod listeners;
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
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 cli_log::{trace, warn};
|
||||||
use crossterm::event::Event as CrosstermEvent;
|
use crossterm::event::Event as CrosstermEvent;
|
||||||
use handlers::{command, input};
|
use handlers::{command, input};
|
||||||
|
@ -18,7 +21,7 @@ pub enum Event {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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);
|
trace!("Received event to handle: `{:#?}`", &self);
|
||||||
match self {
|
match self {
|
||||||
Event::CommandEvent(event, callback_tx) => command::handle(app, &event, callback_tx)
|
Event::CommandEvent(event, callback_tx) => command::handle(app, &event, callback_tx)
|
||||||
|
|
|
@ -3,13 +3,12 @@ pub mod config;
|
||||||
pub mod events;
|
pub mod events;
|
||||||
pub mod status;
|
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 anyhow::{Context, Result};
|
||||||
use cli_log::{info, warn};
|
use cli_log::warn;
|
||||||
use directories::ProjectDirs;
|
use directories::ProjectDirs;
|
||||||
use keymaps::trie::Node;
|
use keymaps::trie::Node;
|
||||||
use libloading::{Library, Symbol};
|
|
||||||
use tokio::sync::mpsc::{self, Sender};
|
use tokio::sync::mpsc::{self, Sender};
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
|
@ -22,11 +21,11 @@ use crate::{
|
||||||
events::{Event, EventStatus},
|
events::{Event, EventStatus},
|
||||||
status::{State, Status},
|
status::{State, Status},
|
||||||
},
|
},
|
||||||
ui::central,
|
ui::ui_trait::TrinitrixUi,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct App<'runtime> {
|
pub struct App<U: TrinitrixUi> {
|
||||||
ui: central::UI<'runtime>,
|
ui: U,
|
||||||
status: Status,
|
status: Status,
|
||||||
|
|
||||||
tx: mpsc::Sender<Event>,
|
tx: mpsc::Sender<Event>,
|
||||||
|
@ -42,8 +41,8 @@ pub struct App<'runtime> {
|
||||||
|
|
||||||
pub static COMMAND_TRANSMITTER: OnceLock<Sender<Event>> = OnceLock::new();
|
pub static COMMAND_TRANSMITTER: OnceLock<Sender<Event>> = OnceLock::new();
|
||||||
|
|
||||||
impl App<'_> {
|
impl<U: TrinitrixUi> App<U> {
|
||||||
pub fn new() -> Result<Self> {
|
pub fn new(ui: U) -> Result<Self> {
|
||||||
let (tx, rx) = mpsc::channel(256);
|
let (tx, rx) = mpsc::channel(256);
|
||||||
|
|
||||||
COMMAND_TRANSMITTER
|
COMMAND_TRANSMITTER
|
||||||
|
@ -51,7 +50,7 @@ impl App<'_> {
|
||||||
.expect("The cell should always be empty at this point");
|
.expect("The cell should always be empty at this point");
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
ui: central::UI::new()?,
|
ui,
|
||||||
status: Status::new(),
|
status: Status::new(),
|
||||||
|
|
||||||
tx: tx.clone(),
|
tx: tx.clone(),
|
||||||
|
@ -80,50 +79,23 @@ impl App<'_> {
|
||||||
self.input_listener_killer.clone(),
|
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 {
|
if let Some(config_file) = cli_lua_config_file {
|
||||||
config::lua::load(self.tx.clone(), config_file).await?;
|
config::lua::load(self.tx.clone(), config_file).await?;
|
||||||
} else if let Some(config_file) = lua_config_file {
|
warn!("Loading cli config file, will ignore the default locations");
|
||||||
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 {
|
} else {
|
||||||
warn!("No config file found!");
|
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 {
|
if let Some(plugin) = plugin_path {
|
||||||
info!("Loading plugin_main() from {}", plugin.display());
|
config::shared_objects::load(&plugin)
|
||||||
|
.with_context(|| format!("Failed to load a pluging at '{}'", 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);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
self.status.set_state(State::Normal);
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
self.ui.update(&self.status).await?;
|
self.ui.update(&self.status).await?;
|
||||||
|
|
|
@ -3,8 +3,9 @@ use core::fmt;
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use keymaps::key_repr::Keys;
|
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 {
|
pub enum State {
|
||||||
|
#[default]
|
||||||
Normal,
|
Normal,
|
||||||
Insert,
|
Insert,
|
||||||
Command,
|
Command,
|
||||||
|
@ -66,7 +67,7 @@ impl fmt::Display for State {
|
||||||
impl Status {
|
impl Status {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
state: State::Normal,
|
state: State::default(),
|
||||||
status_messages: vec![StatusMessage {
|
status_messages: vec![StatusMessage {
|
||||||
content: "Initialized!".to_owned(),
|
content: "Initialized!".to_owned(),
|
||||||
is_error: false,
|
is_error: false,
|
||||||
|
|
|
@ -23,4 +23,7 @@ pub struct Args {
|
||||||
pub enum Command {
|
pub enum Command {
|
||||||
/// Starts the main TUI client
|
/// Starts the main TUI client
|
||||||
Start {},
|
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 clap::Parser;
|
||||||
|
|
||||||
use crate::cli::{Args, Command};
|
use crate::{
|
||||||
|
cli::{Args, Command},
|
||||||
|
ui::repl::Repl,
|
||||||
|
};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> anyhow::Result<()> {
|
async fn main() -> anyhow::Result<()> {
|
||||||
|
@ -14,9 +17,16 @@ async fn main() -> anyhow::Result<()> {
|
||||||
let command = args.subcommand.unwrap_or(Command::Start {});
|
let command = args.subcommand.unwrap_or(Command::Start {});
|
||||||
match command {
|
match command {
|
||||||
Command::Start {} => {
|
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?;
|
app.run(None, args.plugin_path).await?;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,44 +1,2 @@
|
||||||
pub mod central;
|
pub mod ui_trait;
|
||||||
|
pub mod repl;
|
||||||
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)));
|
|
||||||
}
|
|
||||||
|
|
|
@ -19,6 +19,8 @@ use tui_textarea::TextArea;
|
||||||
|
|
||||||
use crate::ui::{terminal_prepare, textarea_activate, textarea_inactivate};
|
use crate::ui::{terminal_prepare, textarea_activate, textarea_inactivate};
|
||||||
|
|
||||||
|
use super::ui_trait::TirinitrixUi;
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq)]
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
pub enum InputPosition {
|
pub enum InputPosition {
|
||||||
Status,
|
Status,
|
||||||
|
@ -148,6 +150,9 @@ pub struct UI<'a> {
|
||||||
pub cli: Option<TextArea<'a>>,
|
pub cli: Option<TextArea<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'r> TirinitrixUi for UI<'r> {
|
||||||
|
}
|
||||||
|
|
||||||
impl Drop for UI<'_> {
|
impl Drop for UI<'_> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
info!("Destructing UI");
|
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