use std::fmt; use cursive::theme::{BaseColor, Color, ColorStyle}; use cursive::view::Resizable; use cursive::views::{EditView, LinearLayout, TextView}; use cursive::Cursive; use crate::{app::App, CONFIGURATION}; pub fn open_command_window(s: &mut Cursive) { let command_window = EditView::new() .filler(" ") .on_submit(call_on_app) .style(ColorStyle::new( Color::Dark(BaseColor::Black), Color::Dark(BaseColor::White), )) .fixed_width(CONFIGURATION.view_width * CONFIGURATION.grid_width); s.call_on_name("Frame", |view: &mut LinearLayout| { let mut commandline = LinearLayout::horizontal() .child(TextView::new(":")) .child(command_window); commandline.set_focus_index(1); view.add_child(commandline); view.set_focus_index(1); }); } fn call_on_app(s: &mut Cursive, input: &str) { // things to do after recieving the command // 1. parse the command // 2. clean existing command messages // 3. remove the command window // 4. handle quit command s.call_on_name("Main", |view: &mut App| { let cmd = Command::from_string(input); view.clear_message(); view.parse_command(cmd); }); s.call_on_name("Frame", |view: &mut LinearLayout| { view.set_focus_index(0); view.remove_child(view.get_focus_index()); }); // special command that requires access to // our main cursive object, has to be parsed again // here // TODO: fix this somehow if let Ok(Command::Quit) = Command::from_string(input) { s.quit(); } } #[derive(PartialEq)] pub enum Command { Add(String, Option, bool), MonthPrev, MonthNext, Delete(String), TrackUp(String), TrackDown(String), Quit, Blank, } #[derive(Debug)] pub enum CommandLineError { InvalidCommand(String), InvalidArg(u32), // position NotEnoughArgs(String, u32), } impl std::error::Error for CommandLineError {} impl fmt::Display for CommandLineError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { CommandLineError::InvalidCommand(s) => write!(f, "Invalid command: `{}`", s), CommandLineError::InvalidArg(p) => write!(f, "Invalid argument at position {}", p), CommandLineError::NotEnoughArgs(s, n) => { write!(f, "Command `{}` requires atleast {} argument(s)!", s, n) } } } } type Result = std::result::Result; impl Command { pub fn from_string>(input: P) -> Result { let mut strings: Vec<&str> = input.as_ref().trim().split(' ').collect(); if strings.is_empty() { return Ok(Command::Blank); } let first = strings.first().unwrap().to_string(); let mut args: Vec = strings.iter_mut().skip(1).map(|s| s.to_string()).collect(); let mut _add = |auto: bool, first: String| { if args.is_empty() { return Err(CommandLineError::NotEnoughArgs(first, 1)); } let goal = args .get(1) .map(|x| { x.parse::() .map_err(|_| CommandLineError::InvalidArg(2)) }) .transpose()?; return Ok(Command::Add( args.get_mut(0).unwrap().to_string(), goal, auto, )); }; match first.as_ref() { "add" | "a" => _add(false, first), "add-auto" | "aa" => _add(true, first), "delete" | "d" => { if args.is_empty() { return Err(CommandLineError::NotEnoughArgs(first, 1)); } return Ok(Command::Delete(args[0].to_string())); } "track-up" | "tup" => { if args.is_empty() { return Err(CommandLineError::NotEnoughArgs(first, 1)); } return Ok(Command::TrackUp(args[0].to_string())); } "track-down" | "tdown" => { if args.is_empty() { return Err(CommandLineError::NotEnoughArgs(first, 1)); } return Ok(Command::TrackDown(args[0].to_string())); } "mprev" | "month-prev" => return Ok(Command::MonthPrev), "mnext" | "month-next" => return Ok(Command::MonthNext), "q" | "quit" => return Ok(Command::Quit), "" => return Ok(Command::Blank), s => return Err(CommandLineError::InvalidCommand(s.into())), } } }