From 2962a8d52daf744bf9fa4626cf508421d7a354fe Mon Sep 17 00:00:00 2001 From: Arijit Basu Date: Sun, 6 Jun 2021 10:41:58 +0530 Subject: [PATCH] Further improve the API. This improves the compatibility and adds the ability to introduce non-breaking changes by using a builder pattern. Example: ```rust fn main() { match xplr::runner(None).and_then(|a| a.run()) { Ok(Some(out)) => print!("{}", out), Ok(None) => {} Err(err) => { if !err.to_string().is_empty() { eprintln!("error: {}", err); }; std::process::exit(1); } } } ``` --- examples/run.rs | 3 +- src/app.rs | 24 +- src/bin/xplr.rs | 15 +- src/lib.rs | 2 +- src/runner.rs | 636 ++++++++++++++++++++++++++---------------------- 5 files changed, 352 insertions(+), 328 deletions(-) diff --git a/examples/run.rs b/examples/run.rs index 0334ce8..47b62de 100644 --- a/examples/run.rs +++ b/examples/run.rs @@ -1,6 +1,5 @@ fn main() { - let pwd = std::path::PathBuf::from("/"); - match xplr::run(pwd, None, None) { + match xplr::runner(None).and_then(|a| a.run()) { Ok(Some(out)) => print!("{}", out), Ok(None) => {} Err(err) => { diff --git a/src/app.rs b/src/app.rs index dfeab67..9de2260 100644 --- a/src/app.rs +++ b/src/app.rs @@ -4,7 +4,7 @@ use crate::explorer; use crate::input::Key; use crate::lua; use crate::permissions::Permissions; -use crate::runner; +use crate::runner::Runner; use crate::ui::Layout; use anyhow::{bail, Result}; use chrono::{DateTime, Local}; @@ -1595,6 +1595,8 @@ impl App { last_modes: Default::default(), }; + fs::create_dir_all(app.session_path())?; + if let Some(err) = load_err { app.log_error(err) } else { @@ -2796,24 +2798,8 @@ impl App { last_modes: self.last_modes.clone(), } } - - pub fn run(self, focused_path: Option, lua: &mlua::Lua) -> Result> { - runner::run(self, focused_path, lua) - } } -/// Run xplr TUI -pub fn run( - pwd: PathBuf, - focused_path: Option, - on_load: Option>, -) -> Result> { - let lua = mlua::Lua::new(); - let mut app = App::create(pwd, &lua)?; - if let Some(msgs) = on_load { - for msg in msgs { - app = app.enqueue(Task::new(MsgIn::External(msg), None)); - } - } - app.run(focused_path, &lua) +pub fn runner(path: Option) -> Result { + Runner::new(path) } diff --git a/src/bin/xplr.rs b/src/bin/xplr.rs index cd8bff8..78ed84d 100644 --- a/src/bin/xplr.rs +++ b/src/bin/xplr.rs @@ -108,17 +108,10 @@ fn main() { } else if cli.version { println!("xplr {}", xplr::app::VERSION); } else { - let mut pwd = PathBuf::from(cli.path.unwrap_or_else(|| ".".into())) - .canonicalize() - .unwrap_or_default(); - let mut focused_path = None; - - if pwd.is_file() { - focused_path = pwd.file_name().map(|p| p.into()); - pwd = pwd.parent().map(|p| p.into()).unwrap_or_else(|| ".".into()); - } - - match app::run(pwd, focused_path, Some(cli.on_load)) { + match app::runner(cli.path.as_ref().map(PathBuf::from)) + .map(|a| a.with_on_load(cli.on_load)) + .and_then(|a| a.run()) + { Ok(Some(out)) => print!("{}", out), Ok(None) => {} Err(err) => { diff --git a/src/lib.rs b/src/lib.rs index 2237f5d..0f63314 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,4 +15,4 @@ pub mod pwd_watcher; pub mod runner; pub mod ui; -pub use app::run; +pub use app::runner; diff --git a/src/runner.rs b/src/runner.rs index 7f1d84a..b3acc9f 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -86,341 +86,387 @@ fn call(app: &app::App, cmd: app::Command, silent: bool) -> io::Result, - lua: &mlua::Lua, -) -> Result> { - fs::create_dir_all(app.session_path())?; + on_load: Vec, +} - 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(); +impl Runner { + pub(crate) fn new(path: Option) -> Result { + let mut pwd = path.unwrap_or_else(|| ".".into()).canonicalize()?; + let mut focused_path = None; - app = app.explore_pwd()?; + if pwd.is_file() { + focused_path = pwd.file_name().map(|p| p.into()); + pwd = pwd.parent().map(|p| p.into()).unwrap_or_else(|| ".".into()); + } - app = if let Some(f) = focused_path - .clone() - .map(|f| f.to_string_lossy().to_string()) - { - app.focus_by_file_name(&f, true)? - } else { - app.focus_first(true)? - }; + Ok(Self { + pwd, + focused_path, + on_load: Default::default(), + }) + } - explorer::explore_recursive_async( - app.explorer_config().clone(), - app.pwd().into(), - focused_path, - app.directory_buffer().map(|d| d.focus()).unwrap_or(0), - tx_msg_in.clone(), - ); - tx_pwd_watcher.send(app.pwd().clone())?; - - let mut result = Ok(None); - let session_path = app.session_path().to_owned(); - - term::enable_raw_mode()?; - let mut stdout = get_tty()?; - // let mut stdout = stdout.lock(); - execute!(stdout, term::EnterAlternateScreen)?; - - let mut mouse_enabled = app.config().general().enable_mouse(); - if mouse_enabled { - if let Err(e) = execute!(stdout, event::EnableMouseCapture) { - app = app.log_error(e.to_string())?; - } + pub fn with_on_load(mut self, on_load: Vec) -> Self { + self.on_load = on_load; + self } - let backend = CrosstermBackend::new(stdout); - let mut terminal = Terminal::new(backend)?; - terminal.hide_cursor()?; - - // Threads - auto_refresher::start_auto_refreshing(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)?; - // pipe_reader::keep_reading(app.pipe().msg_in().clone(), tx_msg_in.clone()); - - 'outer: for task in rx_msg_in { - match app.handle_task(task) { - Ok(a) => { - app = a; - while let Some(msg) = app.pop_msg_out() { - match msg { - // NOTE: Do not schedule critical tasks via tx_msg_in in this loop. - // Try handling them immediately. - 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::ExplorePwdAsync => { - explorer::explore_async( - app.explorer_config().clone(), - app.pwd().into(), - app.focused_node().map(|n| n.relative_path().into()), - app.directory_buffer().map(|d| d.focus()).unwrap_or(0), - tx_msg_in.clone(), - ); - tx_pwd_watcher.send(app.pwd().clone())?; - } - - app::MsgOut::ExploreParentsAsync => { - explorer::explore_recursive_async( - app.explorer_config().clone(), - app.pwd().into(), - app.focused_node().map(|n| n.relative_path().into()), - app.directory_buffer().map(|d| d.focus()).unwrap_or(0), - tx_msg_in.clone(), - ); - tx_pwd_watcher.send(app.pwd().clone())?; - } - - app::MsgOut::Refresh => { - // $PWD watcher - tx_pwd_watcher.send(app.pwd().clone())?; - // UI - terminal.draw(|f| ui::draw(f, &app, &lua))?; - } - - app::MsgOut::EnableMouse => { - if !mouse_enabled { - match execute!(terminal.backend_mut(), event::EnableMouseCapture) { - Ok(_) => { - mouse_enabled = true; - } - Err(e) => { - app = app.log_error(e.to_string())?; - } - } + pub fn run(self) -> Result> { + let lua = mlua::Lua::new(); + let mut app = app::App::create(self.pwd, &lua)?; + + fs::create_dir_all(app.session_path())?; + + 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(); + + app = app.explore_pwd()?; + + app = if let Some(f) = self + .focused_path + .clone() + .map(|f| f.to_string_lossy().to_string()) + { + app.focus_by_file_name(&f, true)? + } else { + app.focus_first(true)? + }; + + explorer::explore_recursive_async( + app.explorer_config().clone(), + app.pwd().into(), + self.focused_path, + app.directory_buffer().map(|d| d.focus()).unwrap_or(0), + tx_msg_in.clone(), + ); + tx_pwd_watcher.send(app.pwd().clone())?; + + let mut result = Ok(None); + let session_path = app.session_path().to_owned(); + + term::enable_raw_mode()?; + let mut stdout = get_tty()?; + // let mut stdout = stdout.lock(); + execute!(stdout, term::EnterAlternateScreen)?; + + let mut mouse_enabled = app.config().general().enable_mouse(); + if mouse_enabled { + if let Err(e) = execute!(stdout, event::EnableMouseCapture) { + app = app.log_error(e.to_string())?; + } + } + + let backend = CrosstermBackend::new(stdout); + let mut terminal = Terminal::new(backend)?; + terminal.hide_cursor()?; + + // Threads + auto_refresher::start_auto_refreshing(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)?; + // pipe_reader::keep_reading(app.pipe().msg_in().clone(), tx_msg_in.clone()); + + // Enqueue on_load messages + for msg in self.on_load { + tx_msg_in.send(app::Task::new(app::MsgIn::External(msg), None))?; + } + + 'outer: for task in rx_msg_in { + match app.handle_task(task) { + Ok(a) => { + app = a; + while let Some(msg) = app.pop_msg_out() { + match msg { + // NOTE: Do not schedule critical tasks via tx_msg_in in this loop. + // Try handling them immediately. + app::MsgOut::Enque(task) => { + tx_msg_in.send(task)?; } - } - - app::MsgOut::ToggleMouse => { - let msg = if mouse_enabled { - app::ExternalMsg::DisableMouse - } else { - app::ExternalMsg::EnableMouse - }; - app = - app.handle_task(app::Task::new(app::MsgIn::External(msg), None))?; - } - - app::MsgOut::DisableMouse => { - if mouse_enabled { - match execute!(terminal.backend_mut(), event::DisableMouseCapture) { - Ok(_) => { - mouse_enabled = false; - } - Err(e) => { - app = app.log_error(e.to_string())?; - } - } + + app::MsgOut::Quit => { + result = Ok(None); + break 'outer; } - } - - app::MsgOut::CallLuaSilently(func) => { - tx_event_reader.send(true)?; - - match call_lua(&app, &lua, &func, false) { - Ok(Some(msgs)) => { - for msg in msgs { - app = app.handle_task(app::Task::new( - app::MsgIn::External(msg), - None, - ))?; - } - } - Ok(None) => {} - Err(err) => { - app = app.log_error(err.to_string())?; - } - }; - tx_event_reader.send(false)?; - } + app::MsgOut::PrintResultAndQuit => { + result = Ok(Some(app.result_str())); + break 'outer; + } - app::MsgOut::CallSilently(cmd) => { - tx_event_reader.send(true)?; + app::MsgOut::PrintAppStateAndQuit => { + let out = serde_yaml::to_string(&app)?; + result = Ok(Some(out)); + break 'outer; + } - app.write_pipes()?; - let status = call(&app, cmd, true) - .map(|s| { - if s.success() { - Ok(()) - } else { - Err(format!("process exited with code {}", &s)) - } - }) - .unwrap_or_else(|e| Err(e.to_string())); - - match pipe_reader::read_all(app.pipe().msg_in()) { - Ok(msgs) => { - for msg in msgs { - app = app.handle_task(app::Task::new( - app::MsgIn::External(msg), - None, - ))?; - } - } - Err(err) => { - app = app.log_error(err.to_string())?; - } - }; + app::MsgOut::Debug(path) => { + fs::write(&path, serde_yaml::to_string(&app)?)?; + } - app.cleanup_pipes()?; + app::MsgOut::ClearScreen => { + terminal.clear()?; + } - if let Err(e) = status { - app = app.log_error(e.to_string())?; - }; + app::MsgOut::ExplorePwdAsync => { + explorer::explore_async( + app.explorer_config().clone(), + app.pwd().into(), + app.focused_node().map(|n| n.relative_path().into()), + app.directory_buffer().map(|d| d.focus()).unwrap_or(0), + tx_msg_in.clone(), + ); + tx_pwd_watcher.send(app.pwd().clone())?; + } - tx_event_reader.send(false)?; - } + app::MsgOut::ExploreParentsAsync => { + explorer::explore_recursive_async( + app.explorer_config().clone(), + app.pwd().into(), + app.focused_node().map(|n| n.relative_path().into()), + app.directory_buffer().map(|d| d.focus()).unwrap_or(0), + tx_msg_in.clone(), + ); + tx_pwd_watcher.send(app.pwd().clone())?; + } - app::MsgOut::CallLua(func) => { - execute!(terminal.backend_mut(), event::DisableMouseCapture) - .unwrap_or_default(); + app::MsgOut::Refresh => { + // $PWD watcher + tx_pwd_watcher.send(app.pwd().clone())?; + // UI + terminal.draw(|f| ui::draw(f, &app, &lua))?; + } - tx_event_reader.send(true)?; + app::MsgOut::EnableMouse => { + if !mouse_enabled { + match execute!( + terminal.backend_mut(), + event::EnableMouseCapture + ) { + Ok(_) => { + mouse_enabled = true; + } + Err(e) => { + app = app.log_error(e.to_string())?; + } + } + } + } - terminal.clear()?; - terminal.set_cursor(0, 0)?; - term::disable_raw_mode()?; - terminal.show_cursor()?; + app::MsgOut::ToggleMouse => { + let msg = if mouse_enabled { + app::ExternalMsg::DisableMouse + } else { + app::ExternalMsg::EnableMouse + }; + app = app + .handle_task(app::Task::new(app::MsgIn::External(msg), None))?; + } - match call_lua(&app, &lua, &func, false) { - Ok(Some(msgs)) => { - for msg in msgs { - app = app.handle_task(app::Task::new( - app::MsgIn::External(msg), - None, - ))?; + app::MsgOut::DisableMouse => { + if mouse_enabled { + match execute!( + terminal.backend_mut(), + event::DisableMouseCapture + ) { + Ok(_) => { + mouse_enabled = false; + } + Err(e) => { + app = app.log_error(e.to_string())?; + } } } - Ok(None) => {} - Err(err) => { - app = app.log_error(err.to_string())?; - } - }; - - terminal.clear()?; - term::enable_raw_mode()?; - terminal.hide_cursor()?; - tx_event_reader.send(false)?; + } - if mouse_enabled { - match execute!(terminal.backend_mut(), event::EnableMouseCapture) { - Ok(_) => { - mouse_enabled = true; + app::MsgOut::CallLuaSilently(func) => { + tx_event_reader.send(true)?; + + match call_lua(&app, &lua, &func, false) { + Ok(Some(msgs)) => { + for msg in msgs { + app = app.handle_task(app::Task::new( + app::MsgIn::External(msg), + None, + ))?; + } } - Err(e) => { - app = app.log_error(e.to_string())?; + Ok(None) => {} + Err(err) => { + app = app.log_error(err.to_string())?; } - } + }; + + tx_event_reader.send(false)?; } - } - - app::MsgOut::Call(cmd) => { - execute!(terminal.backend_mut(), event::DisableMouseCapture) - .unwrap_or_default(); - - tx_event_reader.send(true)?; - - terminal.clear()?; - terminal.set_cursor(0, 0)?; - term::disable_raw_mode()?; - terminal.show_cursor()?; - - app.write_pipes()?; - let status = call(&app, cmd, false) - .map(|s| { - if s.success() { - Ok(()) - } else { - Err(format!("process exited with code {}", &s)) + + app::MsgOut::CallSilently(cmd) => { + tx_event_reader.send(true)?; + + app.write_pipes()?; + let status = call(&app, cmd, true) + .map(|s| { + if s.success() { + Ok(()) + } else { + Err(format!("process exited with code {}", &s)) + } + }) + .unwrap_or_else(|e| Err(e.to_string())); + + match pipe_reader::read_all(app.pipe().msg_in()) { + Ok(msgs) => { + for msg in msgs { + app = app.handle_task(app::Task::new( + app::MsgIn::External(msg), + None, + ))?; + } } - }) - .unwrap_or_else(|e| Err(e.to_string())); - - match pipe_reader::read_all(app.pipe().msg_in()) { - Ok(msgs) => { - for msg in msgs { - app = app.handle_task(app::Task::new( - app::MsgIn::External(msg), - None, - ))?; + Err(err) => { + app = app.log_error(err.to_string())?; } - } - Err(err) => { - app = app.log_error(err.to_string())?; - } - }; + }; - app.cleanup_pipes()?; + app.cleanup_pipes()?; - if let Err(e) = status { - app = app.log_error(e.to_string())?; - }; + if let Err(e) = status { + app = app.log_error(e.to_string())?; + }; + + tx_event_reader.send(false)?; + } - terminal.clear()?; - term::enable_raw_mode()?; - terminal.hide_cursor()?; - tx_event_reader.send(false)?; + app::MsgOut::CallLua(func) => { + execute!(terminal.backend_mut(), event::DisableMouseCapture) + .unwrap_or_default(); + + tx_event_reader.send(true)?; + + terminal.clear()?; + terminal.set_cursor(0, 0)?; + term::disable_raw_mode()?; + terminal.show_cursor()?; + + match call_lua(&app, &lua, &func, false) { + Ok(Some(msgs)) => { + for msg in msgs { + app = app.handle_task(app::Task::new( + app::MsgIn::External(msg), + None, + ))?; + } + } + Ok(None) => {} + Err(err) => { + app = app.log_error(err.to_string())?; + } + }; + + terminal.clear()?; + term::enable_raw_mode()?; + terminal.hide_cursor()?; + tx_event_reader.send(false)?; + + if mouse_enabled { + match execute!( + terminal.backend_mut(), + event::EnableMouseCapture + ) { + Ok(_) => { + mouse_enabled = true; + } + Err(e) => { + app = app.log_error(e.to_string())?; + } + } + } + } - if mouse_enabled { - match execute!(terminal.backend_mut(), event::EnableMouseCapture) { - Ok(_) => { - mouse_enabled = true; + app::MsgOut::Call(cmd) => { + execute!(terminal.backend_mut(), event::DisableMouseCapture) + .unwrap_or_default(); + + tx_event_reader.send(true)?; + + terminal.clear()?; + terminal.set_cursor(0, 0)?; + term::disable_raw_mode()?; + terminal.show_cursor()?; + + app.write_pipes()?; + 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())); + + match pipe_reader::read_all(app.pipe().msg_in()) { + Ok(msgs) => { + for msg in msgs { + app = app.handle_task(app::Task::new( + app::MsgIn::External(msg), + None, + ))?; + } } - Err(e) => { - app = app.log_error(e.to_string())?; + Err(err) => { + app = app.log_error(err.to_string())?; + } + }; + + app.cleanup_pipes()?; + + if let Err(e) = status { + app = app.log_error(e.to_string())?; + }; + + terminal.clear()?; + term::enable_raw_mode()?; + terminal.hide_cursor()?; + tx_event_reader.send(false)?; + + if mouse_enabled { + match execute!( + terminal.backend_mut(), + event::EnableMouseCapture + ) { + Ok(_) => { + mouse_enabled = true; + } + Err(e) => { + app = app.log_error(e.to_string())?; + } } } } - } - }; + }; + } } - } - Err(e) => { - result = Err(e); - break; + Err(e) => { + result = Err(e); + break; + } } } - } - terminal.clear()?; - terminal.set_cursor(0, 0)?; - execute!(terminal.backend_mut(), term::LeaveAlternateScreen)?; - execute!(terminal.backend_mut(), event::DisableMouseCapture).unwrap_or_default(); - term::disable_raw_mode()?; - terminal.show_cursor()?; + terminal.clear()?; + terminal.set_cursor(0, 0)?; + execute!(terminal.backend_mut(), term::LeaveAlternateScreen)?; + execute!(terminal.backend_mut(), event::DisableMouseCapture).unwrap_or_default(); + term::disable_raw_mode()?; + terminal.show_cursor()?; - fs::remove_dir(session_path)?; + fs::remove_dir(session_path)?; - result + result + } }