accepted 0.3.2

A text editor to be ACCEPTED.
Documentation
use std::collections::HashMap;
use std::fs;
use std::io::{stdin, stdout, Write};
use std::path::PathBuf;
use std::thread;
use std::time::{Duration, Instant};

use clap::{crate_authors, crate_version, App, Arg};
use rbtag::BuildInfo;
use serde_derive::{Deserialize, Serialize};
use termion::event::{Event, Key};
use termion::input::{MouseTerminal, TermRead};
use termion::raw::IntoRawMode;
use termion::screen::AlternateScreen;

use accepted::buffer_tab::BufferTab;
use accepted::config;
use accepted::draw::DoubleBuffer;
use anyhow::Context;

#[derive(BuildInfo)]
struct BuildTag;

#[derive(Serialize, Deserialize, Debug)]
struct SnippetSet(HashMap<String, Snippet>);

#[derive(Serialize, Deserialize, Debug)]
struct Snippet {
    prefix: String,
    body: Vec<String>,
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let config_path = dirs::config_dir().map(|mut p| {
        p.push("acc");
        p.push("config.toml");
        p
    });

    let after_help = if let Some(config_path) = config_path.as_ref() {
        format!("Config file will be loaded from {}", config_path.display())
    } else {
        "No config path detected in this system".to_string()
    };

    let matches = App::new("Accepted")
        .author(crate_authors!())
        .version(crate_version!())
        .long_version(format!("v{} {}", crate_version!(), BuildTag.get_build_commit(),).as_str())
        .about("A text editor to be ACCEPTED")
        .after_help(after_help.as_str())
        .bin_name("acc")
        .arg(Arg::with_name("config").long("config"))
        .arg(Arg::with_name("file").multiple(true))
        .get_matches();

    let config = config_path
        .as_ref()
        .and_then(|config_path| fs::read_to_string(config_path).ok())
        .and_then(|s| {
            let result = config::parse_config_with_default(s.as_str());
            match result {
                Err(err) => {
                    let mut buf = String::new();
                    println!("Failed to load config.toml");
                    println!("Reason: {}", err);
                    println!();
                    println!("Press Enter to continue");
                    std::io::stdin().read_line(&mut buf).unwrap();
                    None
                }
                Ok(config) => Some(config),
            }
        })
        .unwrap_or_default();

    let stdin = stdin();
    let mut stdout = MouseTerminal::from(AlternateScreen::from(stdout()).into_raw_mode().unwrap());
    // let mut stdout = MouseTerminal::from(stdout().into_raw_mode().unwrap());

    let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();

    thread::spawn(move || {
        for c in stdin.events() {
            if let Ok(evt) = c {
                tx.send(evt).unwrap();
            }
        }
    });

    let syntax_parent = accepted::syntax::SyntaxParent::default();

    let mut state: BufferTab<accepted::core::buffer::RopeyCoreBuffer> =
        BufferTab::new(&syntax_parent, &config);

    if matches.is_present("config") {
        let config_path = config_path.as_ref().context("Get config path")?;
        if let Some(parent) = config_path.parent() {
            std::fs::create_dir_all(parent)?;
        }
        state.open(config_path.clone());
    }

    let files = matches.values_of_os("file");
    if let Some(files) = files {
        for path in files {
            state.open(PathBuf::from(path));
        }
    }

    let mut draw = DoubleBuffer::default();

    let frame = Duration::from_secs(1) / 60;

    loop {
        let start_frame = Instant::now();
        state.buffer_mode_mut().background_task_duration(frame);
        let now = Instant::now();

        let evt = if (now - start_frame) > frame {
            rx.try_recv().ok()
        } else {
            tokio::time::timeout(frame - (now - start_frame), rx.recv())
                .await
                .ok()
                .flatten()
        };

        if let Some(evt) = evt {
            if evt == Event::Key(Key::Ctrl('l')) {
                draw.redraw();
            }
            if state.event(evt).await {
                return Ok(());
            }
        }

        draw.back.cursor = state.draw(draw.back.view((0, 0), draw.back.height, draw.back.width));
        draw.present(
            &mut stdout,
            config
                .get::<config::types::keys::ANSIColor>(None)
                .cloned()
                .unwrap_or(false),
        )
        .unwrap();
        stdout.flush().unwrap();
    }
}