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 (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();
}
}