From 7fdc7524902a35a423b592398504cad2cc26f6e2 Mon Sep 17 00:00:00 2001 From: Benedikt Peetz Date: Sat, 4 May 2024 20:05:05 +0200 Subject: [PATCH] feat(ui/repl): Get the development repl into a useful shape This repl is not really meant for user, as it's quite features striped. Its main goal is to enable a faster debugging turnabout time. --- src/app/events/handlers/command.rs | 1 - src/main.rs | 5 +- src/ui/repl/mod.rs | 107 +++++++++++++++++++++++++---- 3 files changed, 95 insertions(+), 18 deletions(-) diff --git a/src/app/events/handlers/command.rs b/src/app/events/handlers/command.rs index 0c867c0..fab2ef7 100644 --- a/src/app/events/handlers/command.rs +++ b/src/app/events/handlers/command.rs @@ -179,7 +179,6 @@ pub async fn handle( } Raw::send_input_unprocessed { input } => { let key = Key::from_str(input.as_str())?; - // let cross_input: Event = key.try_into()?; match app.status.state() { State::Insert | State::Command => { diff --git a/src/main.rs b/src/main.rs index 1c1571c..8ae4497 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ mod app; mod cli; mod ui; +use anyhow::Context; use clap::Parser; use crate::{ @@ -18,13 +19,13 @@ async fn main() -> anyhow::Result<()> { match command { Command::Start {} => { todo!("The full ui is not yet finished"); - let mut app = app::App::new(Repl::new())?; + 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?; } Command::Repl {} => { - let mut app = app::App::new(Repl::new())?; + let mut app = app::App::new(Repl::new().context("Failed to setup repl")?)?; // NOTE(@soispha): The `None` here is temporary <2024-05-03> app.run(None, args.plugin_path).await?; diff --git a/src/ui/repl/mod.rs b/src/ui/repl/mod.rs index 2aae1ab..487bb2d 100644 --- a/src/ui/repl/mod.rs +++ b/src/ui/repl/mod.rs @@ -1,51 +1,128 @@ -use anyhow::Result; -use cli_log::info; +use std::io::{self, Stdout, Write}; + +use anyhow::{Context, Result}; +use cli_log::{debug, info}; +use crossterm::{ + execute, + terminal::{disable_raw_mode, enable_raw_mode, EnableLineWrap}, +}; use keymaps::key_repr::{Key, KeyValue}; -use crate::app::status::Status; +use crate::app::status::{State, Status}; use super::ui_trait::TrinitrixUi; pub struct Repl { + current_written_buffer: String, current_input: String, enter_new_line: bool, + stdout: Stdout, } impl Repl { - pub fn new() -> Self { - println!("Welcome to the Trinitrix repl! You can now enter trinitry commands."); - print!("{}", Self::prompt(&Status::new())); + pub fn new() -> Result { + enable_raw_mode().context("Failed to enable raw mode")?; + let mut stdout = io::stdout(); - Repl { + write!( + stdout, + "Welcome to the Trinitrix repl! You can now enter trinitry commands." + )?; + write!(stdout, "\n\r{}", Self::prompt(&Status::new()))?; + stdout.flush()?; + + Ok(Repl { + current_written_buffer: String::new(), current_input: String::new(), enter_new_line: false, - } + stdout, + }) } pub fn prompt(status: &Status) -> String { format!("{}> ", status.state()) } } +impl Drop for Repl { + fn drop(&mut self) { + disable_raw_mode().expect("Must work in drop"); + execute!(io::stdout(), EnableLineWrap).expect("Must also work"); + + writeln!(self.stdout, "\n\rBye!").expect("Must also work"); + } +} + 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); + info!("Got '{}' from user in repl.", self.current_written_buffer); + + if status.state() == &State::Command { + write!( + self.stdout, + "\n\rEvaluating: '{}' ..", + self.current_written_buffer + )?; + } else { + write!( + self.stdout, + "\n\rCan't insert anything in the repl besides in command mode!", + )?; + } self.enter_new_line = false; - self.current_input = String::new(); + self.current_written_buffer.clear(); - print!("{}", Self::prompt(status)); + write!(self.stdout, "\n\r{}", Self::prompt(status))?; + } else { + write!( + self.stdout, + "\r{}{}{}", + Self::prompt(status), + self.current_written_buffer, + self.current_input + )?; + + self.current_written_buffer += self.current_input.as_str(); + self.current_input.clear(); } + self.stdout.flush()?; Ok(()) } fn input(&mut self, input: Key) { + debug!("Input received at repl: {}", input); + 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; + match value { + KeyValue::Backspace => { + self.current_written_buffer.pop(); + } + KeyValue::Enter => self.enter_new_line = true, + KeyValue::Left => todo!(), + KeyValue::Right => todo!(), + KeyValue::Up => todo!(), + KeyValue::Down => todo!(), + KeyValue::Home => todo!(), + KeyValue::End => todo!(), + KeyValue::PageUp => todo!(), + KeyValue::PageDown => todo!(), + KeyValue::Tab => todo!(), + KeyValue::BackTab => todo!(), + KeyValue::Delete => todo!(), + KeyValue::Insert => todo!(), + KeyValue::F(_) => todo!(), + KeyValue::Char(ch) => self.current_input.push(*ch), + KeyValue::Null => todo!(), + KeyValue::Esc => todo!(), + KeyValue::CapsLock => todo!(), + KeyValue::ScrollLock => todo!(), + KeyValue::NumLock => todo!(), + KeyValue::PrintScreen => todo!(), + KeyValue::Pause => todo!(), + KeyValue::Menu => todo!(), + KeyValue::KeypadBegin => todo!(), } } else { info!("User wrote: '{}'", input.to_string_repr());