ru_shell 0.1.0

A Shell built around a semantic grammar
//! Some options for statements to run, or persistent data
use crate::cursor::Cursor;
use crate::partial::Item;
use crate::Action;
use bogobble::traits::*;
use termion::event::Key;

use crate::store::Store;
use crate::tab_complete::*;
use crate::{parser, prompt::Prompt, RT};
use std::io::Read;
use std::io::Write;
use std::path::Path;

#[derive(Clone, Debug)]

pub struct Shell {
    pub prompt: Prompt,
    pub store: Store,
    pub history: HistoryStore,
}

impl Shell {
    /// Invariants : Settings must always have at least one layer in scope.
    pub fn new() -> Shell {
        let mut history = HistoryStore::new();
        history.load_history();
        Shell {
            prompt: Prompt::new(">>".to_string()),
            store: Store::new(),
            history,
        }
    }

    pub fn do_print<T, F: Fn(&mut Self) -> T>(&mut self, rt: &mut RT, f: F) -> T {
        self.prompt.unprint(rt);
        let r = f(self);
        self.prompt.print(rt);
        r
    }

    fn tab_complete(&mut self) {
        self.prompt.clear_help();
        let c_line = &self.prompt.cursor.on_to_space();
        let clen = c_line.len();
        let top = match crate::partial::Lines.parse_s(c_line) {
            Ok(t) => t,
            Err(e) => {
                self.prompt.message = Some(format!("{}", e));
                return;
            }
        };

        if let Some(a) = top.find_at_end(c_line, |&i| i == Item::Arg) {
            let s = a.on_str(c_line);

            match crate::tab_complete::tab_complete_path(s) {
                Complete::None => self.prompt.message = Some(format!("Could not complete '{}'", s)),
                Complete::One(tc) => {
                    self.prompt
                        .cursor
                        .replace_range(a.start.unwrap_or(0)..clen, &tc);
                }
                Complete::Many(v) => self.prompt.options = Some((a.range().with_end(clen), v)),
            }
        }
    }
    pub fn on_enter(&mut self, rt: &mut RT) {
        let c_line = &self.prompt.cursor.s;
        self.history.pos = None;
        self.history.guesses = None;
        match parser::Lines.parse_s(c_line) {
            Ok(v) => {
                let hist_r = self.history.push_command(c_line.clone());
                if !self.prompt.cursor.is_end() {
                    self.prompt.unprint(rt);
                    self.prompt.print_end(rt);
                }

                rt.suspend_raw_mode().ok();
                print!("\n\r");
                rt.flush().ok();
                for s in v {
                    match s.run(&mut self.store) {
                        Ok(false) => print!("\n\rOK - fail\n\r"),
                        Err(e) => print!("\n\rErr - {}\n\r", e),
                        _ => {}
                    }
                }
                rt.activate_raw_mode().ok();
                self.reset(rt);
                self.prompt.unprint(rt);
                match hist_r {
                    Err(e) => self.prompt.message = Some(e.to_string()),
                    Ok(_) => {} // self.prompt.message = Some(s),
                }
                self.prompt.print(rt);
            }
            Err(_) => self.do_print(rt, |sh| sh.prompt.add_char('\n')),
        }
    }

    pub fn reset(&mut self, rt: &mut RT) {
        let pt = self
            .store
            .get("RU_PROMPT")
            .map(|d| d.to_string())
            .unwrap_or(String::from(">>"));
        let pt = match parser::QuotedString.parse_s(&pt) {
            Ok(v) => v
                .run(&mut self.store)
                .map(|s| s.to_string())
                .unwrap_or("PromptErr:>>".to_string()),
            Err(_) => pt,
        };
        self.prompt.reset(pt, rt);
        rt.flush().ok();
    }

    pub fn source_path<P: AsRef<Path>>(&mut self, p: P) -> anyhow::Result<()> {
        let mut f = std::fs::File::open(p)?;
        let mut buf = String::new();
        f.read_to_string(&mut buf)?;
        let p = parser::Lines.parse_s(&buf).map_err(|e| e.strung())?;
        for v in p {
            v.run(&mut self.store).ok();
        }
        Ok(())
    }

    pub fn do_key(&mut self, k: Key, rt: &mut RT) -> anyhow::Result<Action> {
        match k {
            Key::Ctrl('d') => return Ok(Action::Quit),
            Key::Char('\n') => self.on_enter(rt),
            Key::Char('\t') => self.do_print(rt, Shell::tab_complete),
            Key::Char(c) => self.prompt.do_print(rt, |p| p.add_char(c)),
            Key::Backspace => self.prompt.do_cursor(rt, Cursor::backspace),
            Key::Delete => self.prompt.do_cursor(rt, Cursor::del_char),
            Key::Ctrl('n') => self.prompt.do_print(rt, |p| p.add_char('\n')),
            Key::Ctrl('h') => self.prompt.do_cursor(rt, Cursor::del_line),
            Key::Esc => {
                self.prompt.esc(rt);
                self.history.guesses = None;
            }
            Key::Up => match self.prompt.do_cursor(rt, Cursor::up) {
                false => self.do_print(rt, |p| p.prompt.replace_line(p.history.up_recent())),
                _ => {}
            },

            Key::Down => match self.prompt.do_cursor(rt, Cursor::down) {
                false => self.do_print(rt, |p| p.prompt.replace_line(p.history.down_recent())),
                _ => {}
            },

            Key::End => self.prompt.do_cursor(rt, Cursor::to_line_end),
            Key::Right => {
                if !self.prompt.do_cursor(rt, Cursor::right) {
                    match self.history.guess(&self.prompt.cursor.s) {
                        true => {
                            self.do_print(rt, |s| s.prompt.replace_line(s.history.select_recent(0)))
                        }
                        false => self.prompt.do_print(rt, |p| p.replace_line(None)),
                    }
                }
            }
            Key::Left => {
                if !self.prompt.do_cursor(rt, Cursor::left) {
                    self.history.guesses = None;
                    self.prompt.do_print(rt, |p| p.replace_line(None));
                }
            }
            e => self
                .prompt
                .do_print(rt, |p| p.message = Some(format!("{:?}", e))),
        }

        Ok(Action::Cont)
    }

    pub fn do_unsupported(&mut self, b: &[u8], rt: &mut RT) -> anyhow::Result<()> {
        match b {
            //Ctrl Up:
            [27, 91, 49, 59, 53, 65] => {
                self.do_print(rt, |p| p.prompt.replace_line(p.history.up_recent()))
            }
            //Ctrl End
            [27, 91, 49, 59, 53, 70] => self.prompt.do_cursor(rt, Cursor::to_end),
            c => self.prompt.do_print(rt, |p| {
                p.message = Some(format!("Unsupported Action {:?}", c))
            }),
        }
        Ok(())
    }
}