tui-clap 0.1.0

Provides tui-rs widgets to output and text input which is parsed by clap
Documentation
use tui::widgets::{Widget, StatefulWidget};
use tui::buffer::Buffer;
use tui::layout::Rect;
use tui::style::Style;
use std::sync::{mpsc, Arc};
use crossterm::event::{read, Event, KeyCode, poll};
use std::{thread};
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Duration;
use clap::{App, ArgMatches, ErrorKind};
use std::borrow::BorrowMut;
use tui::Frame;
use tui::backend::{Backend};
use std::str::Lines;

pub struct Events {
    rx: mpsc::Receiver<Event>,
    ignore_exit_key: Arc<AtomicBool>,
}

#[derive(Default, Clone)]
pub struct CommandInput {
    prompt: String,
}

#[derive(Default)]
pub struct CommandInputState {
    content: String,
}

#[derive(Default, Clone)]
pub struct CommandOutput {
}

#[derive(Default)]
pub struct CommandOutputState {
    history: Vec<String>
}

impl CommandInputState {
    pub fn add_char(&mut self, c: char) {
        self.content.push(c);
    }

    pub fn del_char(&mut self) {
        self.content.pop();
    }

    pub fn reset(&mut self) {
        self.content.drain(..);
    }
}

impl CommandInput {
    pub fn prompt(&mut self, prompt: &str) {
        self.prompt = prompt.to_string();
    }
}

impl StatefulWidget for CommandInput {
    type State = CommandInputState;

    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
        buf.set_string(area.left(), area.top(), &self.prompt, Style::default());
        buf.set_string(area.left() + self.prompt.len() as u16, area.top(), &state.content, Style::default());
    }
}

impl Widget for CommandInput {
    fn render(self, area: Rect, buf: &mut Buffer) {
        StatefulWidget::render(self, area, buf, &mut CommandInputState::default())
    }
}

impl StatefulWidget for CommandOutput {
    type State = CommandOutputState;

    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
        let max_lines = area.height - 1;
        let max_chars_per_line = area.width - 1;

        let mut lines_to_render: Vec<&str> = vec![];

        let history_to_show = state.history.iter().rev().take(max_lines as usize).rev();
        let mut y = 0;
        for line in history_to_show {
            if line.len() > max_chars_per_line as usize {
                let mut rest_of_line = line.as_str();
                loop {
                    if rest_of_line.len() > max_chars_per_line as usize {
                        let split_line = rest_of_line.split_at(max_chars_per_line as usize);
                        lines_to_render.push(split_line.0);
                        rest_of_line = split_line.1;
                    } else {
                        lines_to_render.push(rest_of_line);
                        break;
                    }
                }
            } else {
                lines_to_render.push(line);
            }
        }

        for line in lines_to_render {
            buf.set_string(area.left(), area.top() + y, line, Style::default());
            y += 1;
        }
    }
}

impl Widget for CommandOutput {
    fn render(self, area: Rect, buf: &mut Buffer) {
        StatefulWidget::render(self, area, buf, &mut CommandOutputState::default())
    }
}

#[derive(Debug, Clone, Copy)]
pub struct Config {
    pub exit_key: KeyCode,
    pub tick_rate: Duration,
}

impl Default for Config {
    fn default() -> Config {
        Config {
            exit_key: KeyCode::Char('q'),
            tick_rate: Duration::from_millis(250),
        }
    }
}

impl Default for Events {
    fn default() -> Self {
        Events::from_config(Config::default())
    }
}

impl Events {
    pub fn from_config(config: Config) -> Events {
        let (tx, rx) = mpsc::channel();
        let ignore_exit_key = Arc::new(AtomicBool::new(false));
        {
            let ignore_exit_key = ignore_exit_key.clone();
            thread::spawn(move || {
                loop {
                    if let Ok(b) = poll(config.tick_rate) {
                        if !b {
                            continue;
                        }
                        let read = read();
                        if let Ok(event) = read {
                            if let Err(err) = tx.send(event) {
                                eprintln!("{}", err);
                                return;
                            }
                            if !ignore_exit_key.load(Ordering::Relaxed) {
                                if let Event::Key(key) = event {
                                    if key.code == config.exit_key {
                                        return;
                                    }
                                }
                            }
                        }
                    }
                }
            })
        };
        Events {
            rx,
            ignore_exit_key,
        }
    }

    pub fn next(&self) -> Result<Event, mpsc::RecvError> {
        self.rx.recv()
    }

    pub fn disable_exit_key(&mut self) {
        self.ignore_exit_key.store(true, Ordering::Relaxed);
    }

    pub fn enable_exit_key(&mut self) {
        self.ignore_exit_key.store(false, Ordering::Relaxed);
    }
}



pub struct TuiClap<'a> {
    command_input_state: CommandInputState,
    command_output_state: CommandOutputState,
    command_input_widget: CommandInput,
    command_output_widget: CommandOutput,
    clap: App<'a>,
    events: Events,
    handle_matches: Arc<dyn Fn(ArgMatches) -> Result<Vec<String>, String> + Send + Sync>,
}

impl TuiClap<'_> {
    pub fn from_app<'a>(app: App<'a>, handle_matches: Arc<impl Fn(ArgMatches) -> Result<Vec<String>, String>  + 'a + 'static + Send + Sync>) -> TuiClap {
        TuiClap {
            command_input_state: CommandInputState::default(),
            command_output_state: CommandOutputState::default(),
            command_input_widget: Default::default(),
            command_output_widget: Default::default(),
            clap: app,
            events: Events::default(),
            handle_matches
        }
    }

    pub fn fetch_event(&mut self) -> Result<(), mpsc::RecvError> {
        if let Event::Key(input) = self.events.next()? {
            match input.code {
                KeyCode::Enter => {
                    self.parse();
                    self.command_input_state.content.clear();
                }
                KeyCode::Char(char) => {
                    self.command_input_state.add_char(char);
                }
                KeyCode::Backspace => {
                    self.command_input_state.del_char();
                }
                _ => {}
            }
        }
        Ok(())
    }

    pub fn write_to_output(&mut self, string: String) {
        let lines: Lines = string.lines();
        for str in lines {
            self.command_output_state.history.push(str.to_string());
        }
    }

    pub fn state(&mut self) -> &mut CommandInputState {
        self.command_input_state.borrow_mut()
    }

    pub fn parse(&mut self) {
        let content = self.command_input_state.content.clone();
        let commands_vec = content.split(' ').collect::<Vec<&str>>();
        let matches_result = self.clap.try_get_matches_from_mut(commands_vec.clone());

        match matches_result {
            Ok(matches) => {
                self.handle_matches(matches)
            }
            Err(err) => {
                match err.kind {
                    ErrorKind::DisplayHelp => {
                        let mut buf = Vec::new();
                        let mut writer = Box::new(&mut buf);
                        self.clap.write_help(&mut writer).expect("Could not write help");
                        self.write_to_output(std::str::from_utf8(buf.as_slice()).unwrap().to_string());
                    }
                    ErrorKind::DisplayVersion => {
                        self.write_to_output(self.clap.render_long_version());
                    }
                    ErrorKind::Format => {}
                    _ => {
                        self.write_to_output(format!("error: {}", err))
                    }
                }
            }
        }


    }

    fn handle_matches(&mut self, matches: ArgMatches) {
        let handle = &self.handle_matches;
        let output_res: Result<Vec<String>, String> = handle(matches);
        if let Ok(output) = output_res {
            for out in output {
                self.write_to_output(out);
            }
        } else {
            self.write_to_output(output_res.unwrap_err())
        }

    }

    pub fn input_widget(&mut self) -> &mut CommandInput {
        self.command_input_widget.borrow_mut()
    }

    pub fn render_input<B: Backend>(&mut self, frame: &mut Frame<B>, area: Rect) {
        frame.render_stateful_widget(self.command_input_widget.clone(), area, self.command_input_state.borrow_mut());
    }

    pub fn output_widget(&mut self) -> &mut CommandOutput {
        self.command_output_widget.borrow_mut()
    }

    pub fn render_output<B: Backend>(&mut self, frame: &mut Frame<B>, area: Rect) {
        frame.render_stateful_widget(self.command_output_widget.clone(), area, self.command_output_state.borrow_mut());
    }
}