use crossterm::event::Event; use crossterm::terminal as term; use crossterm::{event, execute}; use handlebars::{handlebars_helper, Handlebars}; use shellwords; use std::env; use std::fs; use std::io; use std::io::prelude::*; use std::path::PathBuf; use std::sync::mpsc; use std::thread; use std::time::Duration; use termion::get_tty; use tui::backend::CrosstermBackend; use tui::Terminal; use xplr::app; use xplr::config::Config; use xplr::error::Error; use xplr::explorer; use xplr::input::Key; use xplr::ui; handlebars_helper!(shellescape: |v: str| format!("{}", shellwords::escape(v))); handlebars_helper!(readfile: |v: str| fs::read_to_string(v).unwrap_or_default()); fn main() -> Result<(), Error> { let mut pwd = PathBuf::from(env::args().skip(1).next().unwrap_or(".".into())) .canonicalize() .unwrap_or_default(); let mut focused_path = None; if pwd.is_file() { focused_path = pwd.file_name().map(|n| n.to_string_lossy().to_string()); pwd = pwd.parent().map(|p| p.into()).unwrap_or_default(); } let pwd = pwd.to_string_lossy().to_string(); let mut last_pwd = pwd.clone(); let config_dir = dirs::config_dir() .unwrap_or(PathBuf::from(".")) .join("xplr"); let config_file = config_dir.join("config.yml"); let config: Config = if config_file.exists() { serde_yaml::from_reader(io::BufReader::new(&fs::File::open(&config_file)?))? } else { Config::default() }; let mut app = app::App::new(config, pwd.clone()); let mut hb = Handlebars::new(); hb.register_helper("shellescape", Box::new(shellescape)); hb.register_helper("readfile", Box::new(readfile)); hb.register_template_string( app::TEMPLATE_TABLE_ROW, &app.config() .general .table .row .cols .iter() .map(|c| c.format.to_string()) .collect::>() .join("\t"), )?; let mut result = Ok(()); let mut output = None; let (tx_key, rx) = mpsc::channel(); let tx_init = tx_key.clone(); let tx_pipe = tx_key.clone(); let tx_explorer = tx_key.clone(); term::enable_raw_mode().unwrap(); let mut stdout = get_tty().unwrap(); // let mut stdout = stdout.lock(); execute!(stdout, term::EnterAlternateScreen).unwrap(); // let stdout = MouseTerminal::from(stdout); let backend = CrosstermBackend::new(stdout); let mut terminal = Terminal::new(backend).unwrap(); terminal.hide_cursor()?; let (tx, rx_key) = mpsc::channel(); thread::spawn(move || { let mut is_paused = false; loop { if let Some(paused) = rx_key.try_recv().ok() { is_paused = paused; }; if !is_paused { if event::poll(std::time::Duration::from_millis(1)).unwrap() { if let Event::Key(key) = event::read().unwrap() { let key = Key::from_event(key); let msg = app::MsgIn::Internal(app::InternalMsg::HandleKey(key)); tx_key.send(app::Task::new(0, msg, Some(key))).unwrap(); } } } } }); let pipe_msg_in = app.config().pipes.msg_in.clone(); thread::spawn(move || loop { let in_str = fs::read_to_string(&pipe_msg_in).unwrap_or_default(); if !in_str.is_empty() { let msgs = in_str .lines() .filter_map(|s| serde_yaml::from_str::(s.trim()).ok()); msgs.for_each(|msg| { tx_pipe .send(app::Task::new(2, app::MsgIn::External(msg), None)) .unwrap(); }); fs::write(&pipe_msg_in, "").unwrap(); }; thread::sleep(Duration::from_millis(10)); }); explorer::explore(pwd.clone(), focused_path, tx_init); 'outer: while result.is_ok() { while let Some(msg) = app.pop_msg_out() { match msg { app::MsgOut::Debug(path) => { fs::write(&path, serde_yaml::to_string(&app).unwrap_or_default())?; } app::MsgOut::PrintResultAndQuit => { let out = if app.selected().is_empty() { app.focused_node() .map(|n| n.absolute_path.clone()) .unwrap_or_default() } else { app.selected() .into_iter() .map(|n| n.absolute_path.clone()) .collect::>() .join("\n") }; output = Some(out); break 'outer; } app::MsgOut::PrintAppStateAndQuit => { let out = serde_yaml::to_string(&app)?; output = Some(out); break 'outer; } app::MsgOut::Refresh => { if app.pwd() != &last_pwd { explorer::explore( app.pwd().clone(), app.focused_node().map(|n| n.relative_path.clone()), tx_explorer.clone(), ); last_pwd = app.pwd().to_owned(); }; // UI terminal.draw(|f| ui::draw(f, &app, &hb)).unwrap(); // Pipes let focused = app .focused_node() .map(|n| n.absolute_path.clone()) .unwrap_or_default(); fs::write(&app.config().pipes.focus_out, focused).unwrap(); let selected = app .selected() .iter() .map(|n| n.absolute_path.clone()) .collect::>() .join("\n"); fs::write(&app.config().pipes.selected_out, selected).unwrap(); fs::write(&app.config().pipes.mode_out, &app.mode().name).unwrap(); } app::MsgOut::Call(cmd) => { tx.send(true).unwrap(); terminal.clear()?; term::disable_raw_mode().unwrap(); execute!(terminal.backend_mut(), term::LeaveAlternateScreen).unwrap(); terminal.show_cursor()?; let focus_path = app .focused_node() .map(|n| n.absolute_path.clone()) .unwrap_or_default(); let focus_index = app .directory_buffer() .map(|d| d.focus) .unwrap_or_default() .to_string(); let selected = app .selected() .iter() .map(|n| n.absolute_path.clone()) .collect::>() .join(","); let directory_nodes = app .directory_buffer() .map(|d| { d.nodes .iter() .map(|n| n.absolute_path.clone()) .collect::>() .join(",") }) .unwrap_or_default(); let pipe_msg_in = app.config().pipes.msg_in.clone(); let pipe_focus_out = app.config().pipes.focus_out.clone(); let pipe_selected_out = app.config().pipes.selected_out.clone(); let app_yaml = serde_yaml::to_string(&app).unwrap_or_default(); let _ = std::process::Command::new(cmd.command.clone()) .current_dir(app.pwd()) .env("XPLR_FOCUS_PATH", focus_path) .env("XPLR_FOCUS_INDEX", focus_index) .env("XPLR_SELECTED", selected) .env("XPLR_PIPE_MSG_IN", pipe_msg_in) .env("XPLR_PIPE_SELECTED_OUT", pipe_selected_out) .env("XPLR_PIPE_FOCUS_OUT", pipe_focus_out) .env("XPLR_APP_YAML", app_yaml) .env("XPLR_DIRECTORY_NODES", directory_nodes) .args(cmd.args.clone()) .status(); terminal.hide_cursor()?; execute!(terminal.backend_mut(), term::EnterAlternateScreen).unwrap(); term::enable_raw_mode().unwrap(); tx.send(false).unwrap(); terminal.draw(|f| ui::draw(f, &app, &hb)).unwrap(); } }; } for task in rx.try_iter() { app = app.enqueue(task); } let (new_app, new_result) = match app.clone().possibly_mutate() { Ok(a) => (a, Ok(())), Err(e) => (app, Err(e)), }; app = new_app; result = new_result; // thread::sleep(Duration::from_millis(10)); } term::disable_raw_mode().unwrap(); execute!(terminal.backend_mut(), term::LeaveAlternateScreen).unwrap(); terminal.show_cursor()?; if let Some(out) = output { println!("{}", out); } result }