#![allow(clippy::too_many_arguments)] use crate::app; use crate::auto_refresher; use crate::event_reader; use crate::explorer; use crate::pipe_reader; use crate::pwd_watcher; use crate::ui; use anyhow::Result; use crossterm::execute; use crossterm::terminal as term; use handlebars::{handlebars_helper, Handlebars}; use humansize::{file_size_opts as options, FileSize}; use std::fs; use std::io; use std::io::prelude::*; use std::process::{Command, ExitStatus, Stdio}; use std::sync::mpsc; use termion::get_tty; use tui::backend::CrosstermBackend; use tui::Terminal; handlebars_helper!(to_humansize: |size: i64| size.file_size(options::CONVENTIONAL).unwrap_or_default()); fn call(app: &app::App, cmd: app::Command, silent: bool) -> io::Result { let input_buffer = app.input_buffer().unwrap_or_default(); let focus_index = app .directory_buffer() .map(|d| d.focus) .unwrap_or_default() .to_string(); let pipe_msg_in = app.pipe().msg_in.clone(); let pipe_mode_out = app.pipe().mode_out.clone(); let pipe_focus_out = app.pipe().focus_out.clone(); let pipe_selection_out = app.pipe().selection_out.clone(); let pipe_result_out = app.pipe().result_out.clone(); let pipe_directory_nodes_out = app.pipe().directory_nodes_out.clone(); let pipe_global_help_menu_out = app.pipe().global_help_menu_out.clone(); let pipe_logs_out = app.pipe().logs_out.clone(); let pipe_history_out = app.pipe().history_out.clone(); let session_path = app.session_path(); let (stdin, stdout, stderr) = if silent { (Stdio::null(), Stdio::null(), Stdio::null()) } else { (get_tty()?.into(), get_tty()?.into(), get_tty()?.into()) }; Command::new(cmd.command.clone()) .env("XPLR_APP_VERSION", app.version()) .env("XPLR_CONFIG_VERSION", &app.config().version) .env("XPLR_PID", &app.pid().to_string()) .env("XPLR_INPUT_BUFFER", input_buffer) .env("XPLR_FOCUS_PATH", app.focused_node_str()) .env("XPLR_FOCUS_INDEX", focus_index) .env("XPLR_SESSION_PATH", session_path) .env("XPLR_PIPE_MSG_IN", pipe_msg_in) .env("XPLR_PIPE_SELECTION_OUT", pipe_selection_out) .env("XPLR_PIPE_HISTORY_OUT", pipe_history_out) .env("XPLR_PIPE_FOCUS_OUT", pipe_focus_out) .env("XPLR_PIPE_MODE_OUT", pipe_mode_out) .env("XPLR_PIPE_RESULT_OUT", pipe_result_out) .env("XPLR_PIPE_GLOBAL_HELP_MENU_OUT", pipe_global_help_menu_out) .env("XPLR_PIPE_DIRECTORY_NODES_OUT", pipe_directory_nodes_out) .env("XPLR_PIPE_LOGS_OUT", pipe_logs_out) .stdin(stdin) .stdout(stdout) .stderr(stderr) .args(cmd.args) .status() } pub fn run(mut app: app::App, focused_path: Option) -> Result> { let (tx_msg_in, rx_msg_in) = mpsc::channel(); let (tx_event_reader, rx_event_reader) = mpsc::channel(); let (tx_pwd_watcher, rx_pwd_watcher) = mpsc::channel(); fs::write(&app.pipe().global_help_menu_out, app.global_help_menu_str())?; explorer::explore( app.explorer_config().clone(), app.pwd().clone(), focused_path, tx_msg_in.clone(), ); let mut hb = Handlebars::new(); hb.register_helper("humansize", Box::new(to_humansize)); hb.register_template_string( app::TEMPLATE_TABLE_ROW, &app.config() .general .table .row .cols .to_owned() .unwrap_or_default() .iter() .map(|c| c.format.clone().unwrap_or_default()) .collect::>() .join("\t"), )?; let mut result = Ok(None); term::enable_raw_mode()?; let mut stdout = get_tty()?; // let mut stdout = stdout.lock(); execute!(stdout, term::EnterAlternateScreen)?; // let stdout = MouseTerminal::from(stdout); let backend = CrosstermBackend::new(stdout); let mut terminal = Terminal::new(backend)?; terminal.hide_cursor()?; // Threads auto_refresher::start_auto_refreshing(tx_msg_in.clone()); pipe_reader::keep_reading(app.pipe().msg_in.clone(), tx_msg_in.clone()); event_reader::keep_reading(tx_msg_in.clone(), rx_event_reader); pwd_watcher::keep_watching(app.pwd(), tx_msg_in.clone(), rx_pwd_watcher)?; 'outer: for task in rx_msg_in { let last_app = app.clone(); let (new_app, new_result) = match app.handle_task(task) { Ok(a) => (a, Ok(None)), Err(err) => (last_app.clone(), Err(err)), }; app = new_app; result = new_result; if result.is_err() { break; } while let Some(msg) = app.pop_msg_out() { match msg { app::MsgOut::Enque(task) => { tx_msg_in.send(task)?; } app::MsgOut::Quit => { result = Ok(None); break 'outer; } app::MsgOut::PrintResultAndQuit => { result = Ok(Some(app.result_str())); break 'outer; } app::MsgOut::PrintAppStateAndQuit => { let out = serde_yaml::to_string(&app)?; result = Ok(Some(out)); break 'outer; } app::MsgOut::Debug(path) => { fs::write(&path, serde_yaml::to_string(&app)?)?; } app::MsgOut::ClearScreen => { terminal.clear()?; } app::MsgOut::Explore => { explorer::explore( app.explorer_config().clone(), app.pwd().clone(), app.focused_node().map(|n| n.relative_path.clone()), tx_msg_in.clone(), ); } app::MsgOut::Refresh => { app = app.refresh_selection()?; if app.pwd() != last_app.pwd() { tx_pwd_watcher.send(app.pwd().clone())?; explorer::explore( app.explorer_config().clone(), app.pwd().clone(), app.focused_node().map(|n| n.relative_path.clone()), tx_msg_in.clone(), ); }; // UI terminal.draw(|f| ui::draw(f, &app, &hb))?; } app::MsgOut::CallSilently(cmd) => { tx_event_reader.send(true)?; let status = call(&app, cmd, false) .map(|s| { if s.success() { Ok(()) } else { Err(format!("process exited with code {}", &s)) } }) .unwrap_or_else(|e| Err(e.to_string())); if let Err(e) = status { let msg = app::MsgIn::External(app::ExternalMsg::LogError(e)); tx_msg_in.send(app::Task::new(msg, None))?; }; tx_event_reader.send(false)?; } app::MsgOut::Call(cmd) => { tx_event_reader.send(true)?; terminal.clear()?; terminal.set_cursor(0, 0)?; term::disable_raw_mode()?; terminal.show_cursor()?; let status = call(&app, cmd, false) .map(|s| { if s.success() { Ok(()) } else { Err(format!("process exited with code {}", &s)) } }) .unwrap_or_else(|e| Err(e.to_string())); if let Err(e) = status { let msg = app::MsgIn::External(app::ExternalMsg::LogError(e)); tx_msg_in.send(app::Task::new(msg, None))?; }; terminal.clear()?; term::enable_raw_mode()?; terminal.hide_cursor()?; tx_event_reader.send(false)?; } }; } if app.focused_node() != last_app.focused_node() { fs::write(&app.pipe().focus_out, app.focused_node_str())?; }; if app.selection() != last_app.selection() { fs::write(&app.pipe().selection_out, app.selection_str())?; }; if app.history_str() != last_app.history_str() { fs::write(&app.pipe().history_out, app.history_str())?; }; if app.mode_str() != last_app.mode_str() { fs::write(&app.pipe().mode_out, app.mode_str())?; }; if app.directory_buffer() != last_app.directory_buffer() { fs::write(&app.pipe().directory_nodes_out, app.directory_nodes_str())?; }; if app.logs().len() != last_app.logs().len() { let new_logs = app .logs() .iter() .skip(last_app.logs().len()) .map(|l| format!("{}\n", l)) .collect::>() .join(""); let mut file = fs::OpenOptions::new() .append(true) .open(&app.pipe().logs_out)?; file.write_all(new_logs.as_bytes())?; }; if app.result() != last_app.result() { fs::write(&app.pipe().result_out, app.result_str())?; }; } terminal.clear()?; terminal.set_cursor(0, 0)?; execute!(terminal.backend_mut(), term::LeaveAlternateScreen)?; term::disable_raw_mode()?; terminal.show_cursor()?; fs::remove_dir_all(app.session_path())?; result }