diff --git a/Cargo.lock b/Cargo.lock index 901f34f..70e0c34 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1123,7 +1123,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "xplr" -version = "0.2.13" +version = "0.2.14" dependencies = [ "anyhow", "criterion", diff --git a/Cargo.toml b/Cargo.toml index 3adcb8a..653c1bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "xplr" -version = "0.2.13" # Update app.rs +version = "0.2.14" # Update app.rs authors = ["Arijit Basu "] edition = "2018" -description = "An experimental, minimal, configurable TUI file explorer, stealing ideas from nnn and fzf." +description = "A hackable, minimal, fast TUI file explorer, stealing ideas from nnn and fzf" license = "MIT" readme = "README.md" repository = "https://github.com/sayanarijit/xplr" diff --git a/README.md b/README.md index 97dcab6..16ac1d5 100644 --- a/README.md +++ b/README.md @@ -1,64 +1,30 @@ -An experimental, minimal, configurable TUI file explorer, stealing ideas from [`nnn`](https://github.com/jarun/nnn) and [`fzf`](https://github.com/junegunn/fzf). +

xplr

-![Screenshot](https://user-images.githubusercontent.com/11632726/109526906-1b555080-7ad9-11eb-9fd7-03e092220618.gif) +

+A hackable, minimal, fast TUI file explorer, stealing ideas from nnn and fzf. +

+

+ + + +

-Example usage: --------------- +

+[Features] +[Quickstart] +[Plugins] +[Documentation] +[TODO] +

-```bash -# Edit file -vim "$(xplr)" +
-# Copy file(s) -cp "$(xplr)" "$(xplr)/" -# Search and move file -mv "$(fzf)" "$(xplr)/" -``` +Though [xplr](https://github.com/sayanarijit/xplr) strives to be fast and minimalist, it's speciality is it's hackability. -As of now the fuctionality is pretty limited. You basically have to drop -into a shell (default key `s`) in the directory to do things like -create/delete/rename files. A lot of research and implementations to go. +Once you read the [documentation](https://github.com/sayanarijit/xplr/wiki), you should be able to configure the key bindings, +different run modes, and also the way it looks by modifying one single configuration file. -Although, it's currently satisfying my needs (for customization and speed) -with this vim plugin https://github.com/sayanarijit/xplr.vim. - - -Let's brainstorm ----------------- - -You can also experiment and help by suggesting ideas/opinions. - -1. Install - -```bash -cargo install xplr -``` - -2. Create the customizable config file (requires [`yq`](https://github.com/mikefarah/yq)) - -```bash -mkdir -p ~/.config/xplr - -xplr | yq ".config" -y | tee ~/.config/xplr/config.yml - -# When the app loads, press `#` -``` - -3. Check the key bindings in the config file. - -4. Run `xplr`. - - -TODO research -------------- - -- [ ] Research FIFO/socket options for better integration with other tools. -- [ ] Research saner configuration formats. -- [ ] Research saner key binding options. -- [ ] Research how to go beyond filesystem and explore any tree-like structure. -- [ ] Research ways to make it faster (load and run). -- [ ] Research ways to implement a plugin system. -- [ ] CLI options and help menu. -- [ ] Go beyond research and implement things. +If you come up with something cool, or if you feel it's lacking somewhere in portability, flexibility or performance, don't hesitate to +[start a conversation](https://github.com/sayanarijit/xplr/discussions/2). diff --git a/src/app.rs b/src/app.rs index 024af0d..8752d48 100644 --- a/src/app.rs +++ b/src/app.rs @@ -13,21 +13,21 @@ use std::fs; use std::io; use std::path::PathBuf; -pub const VERSION: &str = "v0.2.13"; // Update Cargo.toml +pub const VERSION: &str = "v0.2.14"; // Update Cargo.toml pub const TEMPLATE_TABLE_ROW: &str = "TEMPLATE_TABLE_ROW"; pub const UNSUPPORTED_STR: &str = "???"; #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct PipesConfig { +pub struct Pipe { pub msg_in: String, pub focus_out: String, pub selection_out: String, pub mode_out: String, } -impl PipesConfig { +impl Pipe { fn from_session_path(path: &String) -> Self { let pipesdir = PathBuf::from(path).join("pipe"); @@ -162,6 +162,237 @@ pub enum InternalMsg { HandleKey(Key), } +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub enum NodeFilter { + RelativePathIs, + RelativePathIsNot, + + RelativePathDoesStartWith, + RelativePathDoesNotStartWith, + + RelativePathDoesContain, + RelativePathDoesNotContain, + + RelativePathDoesEndWith, + RelativePathDoesNotEndWith, + + AbsolutePathIs, + AbsolutePathIsNot, + + AbsolutePathDoesStartWith, + AbsolutePathDoesNotStartWith, + + AbsolutePathDoesContain, + AbsolutePathDoesNotContain, + + AbsolutePathDoesEndWith, + AbsolutePathDoesNotEndWith, +} + +impl NodeFilter { + fn apply(&self, node: &Node, input: &String, case_sensitive: bool) -> bool { + match self { + Self::RelativePathIs => { + if case_sensitive { + &node.relative_path == input + } else { + node.relative_path.to_lowercase() == input.to_lowercase() + } + } + + Self::RelativePathIsNot => { + if case_sensitive { + &node.relative_path != input + } else { + node.relative_path.to_lowercase() != input.to_lowercase() + } + } + + Self::RelativePathDoesStartWith => { + if case_sensitive { + node.relative_path.starts_with(input) + } else { + node.relative_path + .to_lowercase() + .starts_with(&input.to_lowercase()) + } + } + + Self::RelativePathDoesNotStartWith => { + if case_sensitive { + !node.relative_path.starts_with(input) + } else { + !node + .relative_path + .to_lowercase() + .starts_with(&input.to_lowercase()) + } + } + + Self::RelativePathDoesContain => { + if case_sensitive { + node.relative_path.contains(input) + } else { + node.relative_path + .to_lowercase() + .contains(&input.to_lowercase()) + } + } + + Self::RelativePathDoesNotContain => { + if case_sensitive { + !node.relative_path.contains(input) + } else { + !node + .relative_path + .to_lowercase() + .contains(&input.to_lowercase()) + } + } + + Self::RelativePathDoesEndWith => { + if case_sensitive { + node.relative_path.ends_with(input) + } else { + node.relative_path + .to_lowercase() + .ends_with(&input.to_lowercase()) + } + } + + Self::RelativePathDoesNotEndWith => { + if case_sensitive { + !node.relative_path.ends_with(input) + } else { + !node + .relative_path + .to_lowercase() + .ends_with(&input.to_lowercase()) + } + } + + Self::AbsolutePathIs => { + if case_sensitive { + &node.absolute_path == input + } else { + node.absolute_path.to_lowercase() == input.to_lowercase() + } + } + + Self::AbsolutePathIsNot => { + if case_sensitive { + &node.absolute_path != input + } else { + node.absolute_path.to_lowercase() != input.to_lowercase() + } + } + + Self::AbsolutePathDoesStartWith => { + if case_sensitive { + node.absolute_path.starts_with(input) + } else { + node.absolute_path + .to_lowercase() + .starts_with(&input.to_lowercase()) + } + } + + Self::AbsolutePathDoesNotStartWith => { + if case_sensitive { + !node.absolute_path.starts_with(input) + } else { + !node + .absolute_path + .to_lowercase() + .starts_with(&input.to_lowercase()) + } + } + + Self::AbsolutePathDoesContain => { + if case_sensitive { + node.absolute_path.contains(input) + } else { + node.absolute_path + .to_lowercase() + .contains(&input.to_lowercase()) + } + } + + Self::AbsolutePathDoesNotContain => { + if case_sensitive { + !node.absolute_path.contains(input) + } else { + !node + .absolute_path + .to_lowercase() + .contains(&input.to_lowercase()) + } + } + + Self::AbsolutePathDoesEndWith => { + if case_sensitive { + node.absolute_path.ends_with(input) + } else { + node.absolute_path + .to_lowercase() + .ends_with(&input.to_lowercase()) + } + } + + Self::AbsolutePathDoesNotEndWith => { + if case_sensitive { + !node.absolute_path.ends_with(input) + } else { + !node + .absolute_path + .to_lowercase() + .ends_with(&input.to_lowercase()) + } + } + } + } +} + +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub struct NodeFilterApplicable { + filter: NodeFilter, + input: String, + #[serde(default)] + case_sensitive: bool, +} + +impl NodeFilterApplicable { + pub fn new(filter: NodeFilter, input: String, case_sensitive: bool) -> Self { + Self { + filter, + input, + case_sensitive, + } + } + + fn apply(&self, node: &Node) -> bool { + self.filter.apply(node, &self.input, self.case_sensitive) + } +} + +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub struct NodeFilterFromInputString { + filter: NodeFilter, + #[serde(default)] + case_sensitive: bool, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub struct ExplorerConfig { + filters: Vec, +} + +impl ExplorerConfig { + pub fn apply(&self, node: &Node) -> bool { + self.filters.iter().all(|f| f.apply(node)) + } +} + #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] pub enum ExternalMsg { Explore, @@ -191,6 +422,11 @@ pub enum ExternalMsg { UnSelect, ToggleSelection, ClearSelection, + AddNodeFilter(NodeFilterApplicable), + RemoveNodeFilter(NodeFilterApplicable), + ToggleNodeFilter(NodeFilterApplicable), + AddNodeFilterFromInputString(NodeFilterFromInputString), + ResetNodeFilters, PrintResultAndQuit, PrintAppStateAndQuit, Debug(String), @@ -261,7 +497,8 @@ pub struct App { input_buffer: Option, pid: u32, session_path: String, - pipes: PipesConfig, + pipe: Pipe, + explorer_config: ExplorerConfig, } impl App { @@ -310,6 +547,15 @@ impl App { .to_string_lossy() .to_string(); + let mut explorer_config = ExplorerConfig::default(); + if !config.general.show_hidden { + explorer_config.filters.push(NodeFilterApplicable::new( + NodeFilter::RelativePathDoesNotStartWith, + ".".into(), + Default::default(), + )); + } + Ok(Self { config, pwd, @@ -321,7 +567,8 @@ impl App { input_buffer: Default::default(), pid, session_path: session_path.clone(), - pipes: PipesConfig::from_session_path(&session_path), + pipe: Pipe::from_session_path(&session_path), + explorer_config, }) } } @@ -389,6 +636,13 @@ impl App { ExternalMsg::UnSelect => self.un_select(), ExternalMsg::ToggleSelection => self.toggle_selection(), ExternalMsg::ClearSelection => self.clear_selection(), + ExternalMsg::AddNodeFilter(f) => self.add_node_filter(f), + ExternalMsg::AddNodeFilterFromInputString(f) => { + self.add_node_filter_from_input_string(f) + } + ExternalMsg::RemoveNodeFilter(f) => self.remove_node_filter(f), + ExternalMsg::ToggleNodeFilter(f) => self.toggle_node_filter(f), + ExternalMsg::ResetNodeFilters => self.reset_node_filters(), ExternalMsg::PrintResultAndQuit => self.print_result_and_quit(), ExternalMsg::PrintAppStateAndQuit => self.print_app_state_and_quit(), ExternalMsg::Debug(path) => self.debug(&path), @@ -600,6 +854,7 @@ impl App { fn switch_mode(mut self, mode: &String) -> Result { if let Some(mode) = self.config.modes.get(mode) { + self.input_buffer = None; self.mode = mode.to_owned(); self.msg_out.push_back(MsgOut::Refresh); }; @@ -627,7 +882,12 @@ impl App { fn un_select(mut self) -> Result { if let Some(n) = self.focused_node().map(|n| n.to_owned()) { - self.selection = self.selection.into_iter().filter(|s| s != &n).collect(); + self.selection = self + .selection + .clone() + .into_iter() + .filter(|s| s != &n) + .collect(); self.msg_out.push_back(MsgOut::Refresh); } Ok(self) @@ -650,6 +910,61 @@ impl App { Ok(self) } + fn add_node_filter(mut self, filter: NodeFilterApplicable) -> Result { + self.explorer_config.filters.push(filter); + self.msg_out.push_back(MsgOut::Explore); + Ok(self) + } + + fn add_node_filter_from_input_string( + mut self, + filter: NodeFilterFromInputString, + ) -> Result { + if let Some(input) = self.input_buffer() { + self.explorer_config.filters.push(NodeFilterApplicable::new( + filter.filter, + input, + filter.case_sensitive, + )); + self.msg_out.push_back(MsgOut::Explore); + }; + Ok(self) + } + + fn remove_node_filter(mut self, filter: NodeFilterApplicable) -> Result { + self.explorer_config.filters = self + .explorer_config + .filters + .into_iter() + .filter(|f| f != &filter) + .collect(); + self.msg_out.push_back(MsgOut::Explore); + Ok(self) + } + + fn toggle_node_filter(self, filter: NodeFilterApplicable) -> Result { + if self.explorer_config.filters.contains(&filter) { + self.remove_node_filter(filter) + } else { + self.add_node_filter(filter) + } + } + + fn reset_node_filters(mut self) -> Result { + self.explorer_config.filters.clear(); + + if !self.config.general.show_hidden { + self.explorer_config.filters.push(NodeFilterApplicable::new( + NodeFilter::RelativePathDoesNotStartWith, + ".".into(), + Default::default(), + )); + }; + self.msg_out.push_back(MsgOut::Explore); + + Ok(self) + } + fn print_result_and_quit(mut self) -> Result { self.msg_out.push_back(MsgOut::PrintResultAndQuit); Ok(self) @@ -709,8 +1024,8 @@ impl App { } /// Get a reference to the app's pipes. - pub fn pipes(&self) -> &PipesConfig { - &self.pipes + pub fn pipe(&self) -> &Pipe { + &self.pipe } /// Get a reference to the app's pid. @@ -726,6 +1041,7 @@ impl App { pub fn refresh_selection(mut self) -> Result { self.selection = self .selection + .clone() .into_iter() .filter(|n| PathBuf::from(&n.absolute_path).exists()) .collect(); @@ -747,4 +1063,9 @@ impl App { .collect::>() .join("\n") } + + /// Get a reference to the app's explorer config. + pub fn explorer_config(&self) -> &ExplorerConfig { + &self.explorer_config + } } diff --git a/src/config.rs b/src/config.rs index 7b20410..ad8671d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -46,19 +46,19 @@ impl Default for FileTypesConfig { fn default() -> Self { FileTypesConfig { directory: FileTypeConfig { - icon: "d".into(), + icon: "ð".into(), style: Style::default() .add_modifier(Modifier::BOLD) .fg(Color::Blue), }, file: FileTypeConfig { - icon: "f".into(), + icon: "ƒ".into(), style: Default::default(), }, symlink: FileTypeConfig { - icon: "s".into(), + icon: "§".into(), style: Style::default() .add_modifier(Modifier::ITALIC) .fg(Color::Cyan), @@ -293,31 +293,11 @@ impl Default for KeyBindings { ctrl-f: help: search [/] messages: - - Call: - command: bash - args: - - -c - - | - PTH=$(echo -e "${XPLR_DIRECTORY_NODES:?}" | fzf) - if [ -d "$PTH" ]; then - echo "ChangeDirectory: ${PTH:?}" >> "${XPLR_PIPE_MSG_IN:?}" - elif [ -f "$PTH" ]; then - echo "FocusPath: ${PTH:?}" >> "${XPLR_PIPE_MSG_IN:?}" - fi + - SwitchMode: search /: messages: - - Call: - command: bash - args: - - -c - - | - PTH=$(echo -e "${XPLR_DIRECTORY_NODES:?}" | fzf) - if [ -d "$PTH" ]; then - echo "ChangeDirectory: ${PTH:?}" >> "${XPLR_PIPE_MSG_IN:?}" - elif [ -f "$PTH" ]; then - echo "FocusPath: ${PTH:?}" >> "${XPLR_PIPE_MSG_IN:?}" - fi + - SwitchMode: search d: help: delete @@ -340,6 +320,13 @@ impl Default for KeyBindings { - ToggleSelection - FocusNext + ".": + help: show hidden + messages: + - ToggleNodeFilter: + filter: RelativePathDoesNotStartWith + input: . + enter: help: quit with result messages: @@ -429,6 +416,69 @@ pub struct Config { impl Default for Config { fn default() -> Self { + let search_mode: Mode = serde_yaml::from_str( + r###" + name: search + key_bindings: + on_key: + enter: + help: focus + messages: + - ResetNodeFilters + - SwitchMode: default + + up: + help: up + messages: + - FocusPrevious + + down: + help: down + messages: + - FocusNext + + right: + help: enter + messages: + - Enter + - ResetInputBuffer + - ResetNodeFilters + - SwitchMode: default + + left: + help: back + messages: + - Back + - ResetInputBuffer + - SwitchMode: default + + esc: + help: cancel + messages: + - ResetNodeFilters + - SwitchMode: default + + backspace: + help: clear + messages: + - ResetInputBuffer + - ResetNodeFilters + + ctrl-c: + help: cancel & quit + messages: + - Terminate + + default: + messages: + - BufferStringFromKey + - AddNodeFilterFromInputString: + filter: RelativePathDoesContain + case_sensitive: false + "###, + ) + .unwrap(); + let goto_mode: Mode = serde_yaml::from_str( r###" name: go to @@ -515,9 +565,9 @@ impl Default for Config { args: - -c - | - while IFS= read -r line; do + (while IFS= read -r line; do cp -v "${line:?}" ./ - done <<< "${XPLR_SELECTION:?}" + done <<< "${XPLR_SELECTION:?}") read -p "[enter to continue]" - ClearSelection - Explore @@ -531,9 +581,9 @@ impl Default for Config { args: - -c - | - while IFS= read -r line; do + (while IFS= read -r line; do mv -v "${line:?}" ./ - done <<< "${XPLR_SELECTION:?}" + done <<< "${XPLR_SELECTION:?}") read -p "[enter to continue]" - Explore - SwitchMode: default @@ -559,35 +609,35 @@ impl Default for Config { help: to up [k] messages: - FocusPreviousByRelativeIndexFromInput - - ResetInputBuffer - SwitchMode: default k: messages: - FocusPreviousByRelativeIndexFromInput - - ResetInputBuffer - SwitchMode: default down: help: to down [j] messages: - FocusNextByRelativeIndexFromInput - - ResetInputBuffer - SwitchMode: default j: messages: - FocusNextByRelativeIndexFromInput - - ResetInputBuffer - SwitchMode: default enter: help: to index messages: - FocusByIndexFromInput - - ResetInputBuffer - SwitchMode: default + backspace: + help: clear + messages: + - ResetInputBuffer + ctrl-c: help: cancel & quit messages: @@ -600,7 +650,6 @@ impl Default for Config { default: messages: - - ResetInputBuffer - SwitchMode: default "###, ) @@ -620,7 +669,6 @@ impl Default for Config { - -c - | touch "${XPLR_INPUT_BUFFER:?}" - - ResetInputBuffer - SwitchMode: default - Explore @@ -633,14 +681,17 @@ impl Default for Config { - -c - | mkdir -p "${XPLR_INPUT_BUFFER:?}" - - ResetInputBuffer - SwitchMode: default - Explore + backspace: + help: clear + messages: + - ResetInputBuffer + esc: help: cancel messages: - - ResetInputBuffer - SwitchMode: default ctrl-c: @@ -668,14 +719,14 @@ impl Default for Config { args: - -c - | - while IFS= read -r line; do + (while IFS= read -r line; do if [ -d "$line" ]; then rmdir -v "${line:?}" else rm -v "${line:?}" fi - done <<< "${XPLR_RESULT:?}" - read -p "[Enter to continue]" + done <<< "${XPLR_RESULT:?}") + read -p "[enter to continue]" - SwitchMode: default - Explore @@ -687,7 +738,7 @@ impl Default for Config { args: - -c - | - echo -e "${XPLR_RESULT:?}" | xargs -l rm -rfv + (echo -e "${XPLR_RESULT:?}" | xargs -l rm -rfv) read -p "[enter to continue]" - SwitchMode: default - Explore @@ -711,6 +762,7 @@ impl Default for Config { modes.insert("create".into(), create_mode); modes.insert("delete".into(), delete_mode); modes.insert("action".into(), action_mode); + modes.insert("search".into(), search_mode); modes.insert("selection ops".into(), selection_ops_mode); Self { diff --git a/src/explorer.rs b/src/explorer.rs index f7b8882..87a86be 100644 --- a/src/explorer.rs +++ b/src/explorer.rs @@ -1,4 +1,5 @@ use crate::app::DirectoryBuffer; +use crate::app::ExplorerConfig; use crate::app::Node; use crate::app::Task; use crate::app::{InternalMsg, MsgIn}; @@ -7,10 +8,16 @@ use std::path::PathBuf; use std::sync::mpsc::Sender; use std::thread; -pub fn explore(parent: String, focused_path: Option, tx: Sender) { +pub fn explore( + config: ExplorerConfig, + parent: String, + focused_path: Option, + tx: Sender, +) { let path = PathBuf::from(&parent); let path_cloned = path.clone(); let tx_cloned = tx.clone(); + let config_cloned = config.clone(); thread::spawn(move || { let nodes: Vec = fs::read_dir(&path) @@ -24,6 +31,7 @@ pub fn explore(parent: String, focused_path: Option, tx: Sender) { }) }) .map(|name| Node::new(parent.clone(), name)) + .filter(|n| config.apply(n)) .collect(); let focus_index = if let Some(focus) = focused_path { @@ -49,6 +57,7 @@ pub fn explore(parent: String, focused_path: Option, tx: Sender) { if let Some(grand_parent) = path_cloned.parent() { explore( + config_cloned, grand_parent.to_string_lossy().to_string(), path_cloned .file_name() diff --git a/src/main.rs b/src/main.rs index dafe693..e559901 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ use anyhow::Result; -use crossterm::terminal as term; use crossterm::execute; +use crossterm::terminal as term; use handlebars::Handlebars; use std::fs; use std::io::prelude::*; @@ -51,9 +51,14 @@ fn main() -> Result<()> { .file_name() .map(|n| n.to_string_lossy().to_string()) }); - explorer::explore(app.pwd().clone(), focused_path, tx_msg_in.clone()); + explorer::explore( + app.explorer_config().clone(), + app.pwd().clone(), + focused_path, + tx_msg_in.clone(), + ); - pipe_reader::keep_reading(app.pipes().msg_in.clone(), tx_msg_in.clone()); + pipe_reader::keep_reading(app.pipe().msg_in.clone(), tx_msg_in.clone()); let (tx_event_reader, rx_event_reader) = mpsc::channel(); event_reader::keep_reading(tx_msg_in.clone(), rx_event_reader); @@ -83,6 +88,7 @@ fn main() -> Result<()> { 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(), @@ -92,6 +98,7 @@ fn main() -> Result<()> { app::MsgOut::Refresh => { if app.pwd() != &last_pwd { explorer::explore( + app.explorer_config().clone(), app.pwd().clone(), app.focused_node().map(|n| n.relative_path.clone()), tx_msg_in.clone(), @@ -108,7 +115,7 @@ fn main() -> Result<()> { .map(|n| n.absolute_path.clone()) .unwrap_or_default(); - fs::write(&app.pipes().focus_out, focused)?; + fs::write(&app.pipe().focus_out, focused)?; app = app.refresh_selection()?; @@ -119,9 +126,9 @@ fn main() -> Result<()> { .collect::>() .join("\n"); - fs::write(&app.pipes().selection_out, selection)?; + fs::write(&app.pipe().selection_out, selection)?; - fs::write(&app.pipes().mode_out, &app.mode().name)?; + fs::write(&app.pipe().mode_out, &app.mode().name)?; } app::MsgOut::Call(cmd) => { @@ -163,9 +170,9 @@ fn main() -> Result<()> { }) .unwrap_or_default(); - let pipe_msg_in = app.pipes().msg_in.clone(); - let pipe_focus_out = app.pipes().focus_out.clone(); - let pipe_selection_out = app.pipes().selection_out.clone(); + let pipe_msg_in = app.pipe().msg_in.clone(); + let pipe_focus_out = app.pipe().focus_out.clone(); + let pipe_selection_out = app.pipe().selection_out.clone(); let app_yaml = serde_yaml::to_string(&app)?; let session_path = app.session_path(); @@ -216,6 +223,8 @@ fn main() -> Result<()> { execute!(terminal.backend_mut(), term::LeaveAlternateScreen)?; terminal.show_cursor()?; + fs::remove_dir_all(app.session_path())?; + if let Some(out) = output { println!("{}", out); }