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);
        }
    }
}
```
pull/229/head
Arijit Basu 3 years ago committed by Arijit Basu
parent fd3e8a5a9f
commit 2962a8d52d

@ -1,6 +1,5 @@
fn main() { fn main() {
let pwd = std::path::PathBuf::from("/"); match xplr::runner(None).and_then(|a| a.run()) {
match xplr::run(pwd, None, None) {
Ok(Some(out)) => print!("{}", out), Ok(Some(out)) => print!("{}", out),
Ok(None) => {} Ok(None) => {}
Err(err) => { Err(err) => {

@ -4,7 +4,7 @@ use crate::explorer;
use crate::input::Key; use crate::input::Key;
use crate::lua; use crate::lua;
use crate::permissions::Permissions; use crate::permissions::Permissions;
use crate::runner; use crate::runner::Runner;
use crate::ui::Layout; use crate::ui::Layout;
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use chrono::{DateTime, Local}; use chrono::{DateTime, Local};
@ -1595,6 +1595,8 @@ impl App {
last_modes: Default::default(), last_modes: Default::default(),
}; };
fs::create_dir_all(app.session_path())?;
if let Some(err) = load_err { if let Some(err) = load_err {
app.log_error(err) app.log_error(err)
} else { } else {
@ -2796,24 +2798,8 @@ impl App {
last_modes: self.last_modes.clone(), last_modes: self.last_modes.clone(),
} }
} }
pub fn run(self, focused_path: Option<PathBuf>, lua: &mlua::Lua) -> Result<Option<String>> {
runner::run(self, focused_path, lua)
}
} }
/// Run xplr TUI pub fn runner(path: Option<PathBuf>) -> Result<Runner> {
pub fn run( Runner::new(path)
pwd: PathBuf,
focused_path: Option<PathBuf>,
on_load: Option<Vec<ExternalMsg>>,
) -> Result<Option<String>> {
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)
} }

@ -108,17 +108,10 @@ fn main() {
} else if cli.version { } else if cli.version {
println!("xplr {}", xplr::app::VERSION); println!("xplr {}", xplr::app::VERSION);
} else { } else {
let mut pwd = PathBuf::from(cli.path.unwrap_or_else(|| ".".into())) match app::runner(cli.path.as_ref().map(PathBuf::from))
.canonicalize() .map(|a| a.with_on_load(cli.on_load))
.unwrap_or_default(); .and_then(|a| a.run())
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)) {
Ok(Some(out)) => print!("{}", out), Ok(Some(out)) => print!("{}", out),
Ok(None) => {} Ok(None) => {}
Err(err) => { Err(err) => {

@ -15,4 +15,4 @@ pub mod pwd_watcher;
pub mod runner; pub mod runner;
pub mod ui; pub mod ui;
pub use app::run; pub use app::runner;

@ -86,341 +86,387 @@ fn call(app: &app::App, cmd: app::Command, silent: bool) -> io::Result<ExitStatu
.status() .status()
} }
pub(crate) fn run( pub struct Runner {
mut app: app::App, pwd: PathBuf,
focused_path: Option<PathBuf>, focused_path: Option<PathBuf>,
lua: &mlua::Lua, on_load: Vec<app::ExternalMsg>,
) -> Result<Option<String>> { }
fs::create_dir_all(app.session_path())?;
let (tx_msg_in, rx_msg_in) = mpsc::channel(); impl Runner {
let (tx_event_reader, rx_event_reader) = mpsc::channel(); pub(crate) fn new(path: Option<PathBuf>) -> Result<Self> {
let (tx_pwd_watcher, rx_pwd_watcher) = mpsc::channel(); 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 Ok(Self {
.clone() pwd,
.map(|f| f.to_string_lossy().to_string()) focused_path,
{ on_load: Default::default(),
app.focus_by_file_name(&f, true)? })
} else { }
app.focus_first(true)?
};
explorer::explore_recursive_async( pub fn with_on_load(mut self, on_load: Vec<app::ExternalMsg>) -> Self {
app.explorer_config().clone(), self.on_load = on_load;
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); pub fn run(self) -> Result<Option<String>> {
let mut terminal = Terminal::new(backend)?; let lua = mlua::Lua::new();
terminal.hide_cursor()?; let mut app = app::App::create(self.pwd, &lua)?;
// Threads fs::create_dir_all(app.session_path())?;
auto_refresher::start_auto_refreshing(tx_msg_in.clone());
event_reader::keep_reading(tx_msg_in.clone(), rx_event_reader); let (tx_msg_in, rx_msg_in) = mpsc::channel();
pwd_watcher::keep_watching(app.pwd(), tx_msg_in.clone(), rx_pwd_watcher)?; let (tx_event_reader, rx_event_reader) = mpsc::channel();
// pipe_reader::keep_reading(app.pipe().msg_in().clone(), tx_msg_in.clone()); let (tx_pwd_watcher, rx_pwd_watcher) = mpsc::channel();
'outer: for task in rx_msg_in { app = app.explore_pwd()?;
match app.handle_task(task) {
Ok(a) => { app = if let Some(f) = self
app = a; .focused_path
while let Some(msg) = app.pop_msg_out() { .clone()
match msg { .map(|f| f.to_string_lossy().to_string())
// NOTE: Do not schedule critical tasks via tx_msg_in in this loop. {
// Try handling them immediately. app.focus_by_file_name(&f, true)?
app::MsgOut::Enque(task) => { } else {
tx_msg_in.send(task)?; app.focus_first(true)?
} };
app::MsgOut::Quit => { explorer::explore_recursive_async(
result = Ok(None); app.explorer_config().clone(),
break 'outer; app.pwd().into(),
} self.focused_path,
app.directory_buffer().map(|d| d.focus()).unwrap_or(0),
app::MsgOut::PrintResultAndQuit => { tx_msg_in.clone(),
result = Ok(Some(app.result_str())); );
break 'outer; tx_pwd_watcher.send(app.pwd().clone())?;
}
let mut result = Ok(None);
app::MsgOut::PrintAppStateAndQuit => { let session_path = app.session_path().to_owned();
let out = serde_yaml::to_string(&app)?;
result = Ok(Some(out)); term::enable_raw_mode()?;
break 'outer; let mut stdout = get_tty()?;
} // let mut stdout = stdout.lock();
execute!(stdout, term::EnterAlternateScreen)?;
app::MsgOut::Debug(path) => {
fs::write(&path, serde_yaml::to_string(&app)?)?; let mut mouse_enabled = app.config().general().enable_mouse();
} if mouse_enabled {
if let Err(e) = execute!(stdout, event::EnableMouseCapture) {
app::MsgOut::ClearScreen => { app = app.log_error(e.to_string())?;
terminal.clear()?; }
} }
app::MsgOut::ExplorePwdAsync => { let backend = CrosstermBackend::new(stdout);
explorer::explore_async( let mut terminal = Terminal::new(backend)?;
app.explorer_config().clone(), terminal.hide_cursor()?;
app.pwd().into(),
app.focused_node().map(|n| n.relative_path().into()), // Threads
app.directory_buffer().map(|d| d.focus()).unwrap_or(0), auto_refresher::start_auto_refreshing(tx_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)?;
tx_pwd_watcher.send(app.pwd().clone())?; // pipe_reader::keep_reading(app.pipe().msg_in().clone(), tx_msg_in.clone());
}
// Enqueue on_load messages
app::MsgOut::ExploreParentsAsync => { for msg in self.on_load {
explorer::explore_recursive_async( tx_msg_in.send(app::Task::new(app::MsgIn::External(msg), None))?;
app.explorer_config().clone(), }
app.pwd().into(),
app.focused_node().map(|n| n.relative_path().into()), 'outer: for task in rx_msg_in {
app.directory_buffer().map(|d| d.focus()).unwrap_or(0), match app.handle_task(task) {
tx_msg_in.clone(), Ok(a) => {
); app = a;
tx_pwd_watcher.send(app.pwd().clone())?; while let Some(msg) = app.pop_msg_out() {
} match msg {
// NOTE: Do not schedule critical tasks via tx_msg_in in this loop.
app::MsgOut::Refresh => { // Try handling them immediately.
// $PWD watcher app::MsgOut::Enque(task) => {
tx_pwd_watcher.send(app.pwd().clone())?; tx_msg_in.send(task)?;
// 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())?;
}
}
} }
}
app::MsgOut::Quit => {
app::MsgOut::ToggleMouse => { result = Ok(None);
let msg = if mouse_enabled { break 'outer;
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::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) => { app::MsgOut::PrintAppStateAndQuit => {
tx_event_reader.send(true)?; let out = serde_yaml::to_string(&app)?;
result = Ok(Some(out));
break 'outer;
}
app.write_pipes()?; app::MsgOut::Debug(path) => {
let status = call(&app, cmd, true) fs::write(&path, serde_yaml::to_string(&app)?)?;
.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.cleanup_pipes()?; app::MsgOut::ClearScreen => {
terminal.clear()?;
}
if let Err(e) = status { app::MsgOut::ExplorePwdAsync => {
app = app.log_error(e.to_string())?; 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) => { app::MsgOut::Refresh => {
execute!(terminal.backend_mut(), event::DisableMouseCapture) // $PWD watcher
.unwrap_or_default(); 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()?; app::MsgOut::ToggleMouse => {
terminal.set_cursor(0, 0)?; let msg = if mouse_enabled {
term::disable_raw_mode()?; app::ExternalMsg::DisableMouse
terminal.show_cursor()?; } else {
app::ExternalMsg::EnableMouse
};
app = app
.handle_task(app::Task::new(app::MsgIn::External(msg), None))?;
}
match call_lua(&app, &lua, &func, false) { app::MsgOut::DisableMouse => {
Ok(Some(msgs)) => { if mouse_enabled {
for msg in msgs { match execute!(
app = app.handle_task(app::Task::new( terminal.backend_mut(),
app::MsgIn::External(msg), event::DisableMouseCapture
None, ) {
))?; 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 { app::MsgOut::CallLuaSilently(func) => {
match execute!(terminal.backend_mut(), event::EnableMouseCapture) { tx_event_reader.send(true)?;
Ok(_) => {
mouse_enabled = 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) => { Ok(None) => {}
app = app.log_error(e.to_string())?; Err(err) => {
app = app.log_error(err.to_string())?;
} }
} };
tx_event_reader.send(false)?;
} }
}
app::MsgOut::CallSilently(cmd) => {
app::MsgOut::Call(cmd) => { tx_event_reader.send(true)?;
execute!(terminal.backend_mut(), event::DisableMouseCapture)
.unwrap_or_default(); app.write_pipes()?;
let status = call(&app, cmd, true)
tx_event_reader.send(true)?; .map(|s| {
if s.success() {
terminal.clear()?; Ok(())
terminal.set_cursor(0, 0)?; } else {
term::disable_raw_mode()?; Err(format!("process exited with code {}", &s))
terminal.show_cursor()?; }
})
app.write_pipes()?; .unwrap_or_else(|e| Err(e.to_string()));
let status = call(&app, cmd, false)
.map(|s| { match pipe_reader::read_all(app.pipe().msg_in()) {
if s.success() { Ok(msgs) => {
Ok(()) for msg in msgs {
} else { app = app.handle_task(app::Task::new(
Err(format!("process exited with code {}", &s)) app::MsgIn::External(msg),
None,
))?;
}
} }
}) Err(err) => {
.unwrap_or_else(|e| Err(e.to_string())); app = app.log_error(err.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.cleanup_pipes()?; app.cleanup_pipes()?;
if let Err(e) = status { if let Err(e) = status {
app = app.log_error(e.to_string())?; app = app.log_error(e.to_string())?;
}; };
tx_event_reader.send(false)?;
}
terminal.clear()?; app::MsgOut::CallLua(func) => {
term::enable_raw_mode()?; execute!(terminal.backend_mut(), event::DisableMouseCapture)
terminal.hide_cursor()?; .unwrap_or_default();
tx_event_reader.send(false)?;
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 { app::MsgOut::Call(cmd) => {
match execute!(terminal.backend_mut(), event::EnableMouseCapture) { execute!(terminal.backend_mut(), event::DisableMouseCapture)
Ok(_) => { .unwrap_or_default();
mouse_enabled = true;
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) => { Err(err) => {
app = app.log_error(e.to_string())?; 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) => { Err(e) => {
result = Err(e); result = Err(e);
break; break;
}
} }
} }
}
terminal.clear()?; terminal.clear()?;
terminal.set_cursor(0, 0)?; terminal.set_cursor(0, 0)?;
execute!(terminal.backend_mut(), term::LeaveAlternateScreen)?; execute!(terminal.backend_mut(), term::LeaveAlternateScreen)?;
execute!(terminal.backend_mut(), event::DisableMouseCapture).unwrap_or_default(); execute!(terminal.backend_mut(), event::DisableMouseCapture).unwrap_or_default();
term::disable_raw_mode()?; term::disable_raw_mode()?;
terminal.show_cursor()?; terminal.show_cursor()?;
fs::remove_dir(session_path)?; fs::remove_dir(session_path)?;
result result
}
} }

Loading…
Cancel
Save