Implement native search and filter

pull/3/head
Arijit Basu 3 years ago
parent 7cbb9d2baf
commit 71a23e1f64
No known key found for this signature in database
GPG Key ID: 7D7BF809E7378863

2
Cargo.lock generated

@ -1123,7 +1123,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]] [[package]]
name = "xplr" name = "xplr"
version = "0.2.13" version = "0.2.14"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"criterion", "criterion",

@ -1,9 +1,9 @@
[package] [package]
name = "xplr" name = "xplr"
version = "0.2.13" # Update app.rs version = "0.2.14" # Update app.rs
authors = ["Arijit Basu <sayanarijit@gmail.com>"] authors = ["Arijit Basu <sayanarijit@gmail.com>"]
edition = "2018" 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" license = "MIT"
readme = "README.md" readme = "README.md"
repository = "https://github.com/sayanarijit/xplr" repository = "https://github.com/sayanarijit/xplr"

@ -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). <h1 align="center">xplr</h1>
![Screenshot](https://user-images.githubusercontent.com/11632726/109526906-1b555080-7ad9-11eb-9fd7-03e092220618.gif) <p align="center">
A hackable, minimal, fast TUI file explorer, stealing ideas from <a href="https://github.com/jarun/nnn">nnn</a> and <a href="https://github.com/junegunn/fzf">fzf</a>.
</p>
<p align="center">
<a href="https://asciinema.org/a/3THQPXNVi801Yu8nWxO6qfUa4" target="_blank">
<img src="https://s4.gifyu.com/images/xplr.gif"/>
</a>
</p>
Example usage: <h3 align="center">
-------------- [<a href="https://github.com/sayanarijit/xplr/wiki/Features">Features</a>]
[<a href="https://github.com/sayanarijit/xplr/wiki/Quickstart">Quickstart</a>]
[<a href="https://github.com/sayanarijit/xplr/wiki/Plugins">Plugins</a>]
[<a href="https://github.com/sayanarijit/xplr/wiki">Documentation</a>]
[<a href="https://github.com/sayanarijit/xplr/wiki/TODO">TODO</a>]
</h3>
```bash <br>
# Edit file
vim "$(xplr)"
# Copy file(s)
cp "$(xplr)" "$(xplr)/"
# Search and move file Though [xplr](https://github.com/sayanarijit/xplr) strives to be fast and minimalist, it's speciality is it's hackability.
mv "$(fzf)" "$(xplr)/"
```
As of now the fuctionality is pretty limited. You basically have to drop Once you read the [documentation](https://github.com/sayanarijit/xplr/wiki), you should be able to configure the key bindings,
into a shell (default key `s`) in the directory to do things like different run modes, and also the way it looks by modifying one single configuration file.
create/delete/rename files. A lot of research and implementations to go.
Although, it's currently satisfying my needs (for customization and speed) If you come up with something cool, or if you feel it's lacking somewhere in portability, flexibility or performance, don't hesitate to
with this vim plugin https://github.com/sayanarijit/xplr.vim. [start a conversation](https://github.com/sayanarijit/xplr/discussions/2).
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.

@ -13,21 +13,21 @@ use std::fs;
use std::io; use std::io;
use std::path::PathBuf; 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 TEMPLATE_TABLE_ROW: &str = "TEMPLATE_TABLE_ROW";
pub const UNSUPPORTED_STR: &str = "???"; pub const UNSUPPORTED_STR: &str = "???";
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PipesConfig { pub struct Pipe {
pub msg_in: String, pub msg_in: String,
pub focus_out: String, pub focus_out: String,
pub selection_out: String, pub selection_out: String,
pub mode_out: String, pub mode_out: String,
} }
impl PipesConfig { impl Pipe {
fn from_session_path(path: &String) -> Self { fn from_session_path(path: &String) -> Self {
let pipesdir = PathBuf::from(path).join("pipe"); let pipesdir = PathBuf::from(path).join("pipe");
@ -162,6 +162,237 @@ pub enum InternalMsg {
HandleKey(Key), 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<NodeFilterApplicable>,
}
impl ExplorerConfig {
pub fn apply(&self, node: &Node) -> bool {
self.filters.iter().all(|f| f.apply(node))
}
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub enum ExternalMsg { pub enum ExternalMsg {
Explore, Explore,
@ -191,6 +422,11 @@ pub enum ExternalMsg {
UnSelect, UnSelect,
ToggleSelection, ToggleSelection,
ClearSelection, ClearSelection,
AddNodeFilter(NodeFilterApplicable),
RemoveNodeFilter(NodeFilterApplicable),
ToggleNodeFilter(NodeFilterApplicable),
AddNodeFilterFromInputString(NodeFilterFromInputString),
ResetNodeFilters,
PrintResultAndQuit, PrintResultAndQuit,
PrintAppStateAndQuit, PrintAppStateAndQuit,
Debug(String), Debug(String),
@ -261,7 +497,8 @@ pub struct App {
input_buffer: Option<String>, input_buffer: Option<String>,
pid: u32, pid: u32,
session_path: String, session_path: String,
pipes: PipesConfig, pipe: Pipe,
explorer_config: ExplorerConfig,
} }
impl App { impl App {
@ -310,6 +547,15 @@ impl App {
.to_string_lossy() .to_string_lossy()
.to_string(); .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 { Ok(Self {
config, config,
pwd, pwd,
@ -321,7 +567,8 @@ impl App {
input_buffer: Default::default(), input_buffer: Default::default(),
pid, pid,
session_path: session_path.clone(), 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::UnSelect => self.un_select(),
ExternalMsg::ToggleSelection => self.toggle_selection(), ExternalMsg::ToggleSelection => self.toggle_selection(),
ExternalMsg::ClearSelection => self.clear_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::PrintResultAndQuit => self.print_result_and_quit(),
ExternalMsg::PrintAppStateAndQuit => self.print_app_state_and_quit(), ExternalMsg::PrintAppStateAndQuit => self.print_app_state_and_quit(),
ExternalMsg::Debug(path) => self.debug(&path), ExternalMsg::Debug(path) => self.debug(&path),
@ -600,6 +854,7 @@ impl App {
fn switch_mode(mut self, mode: &String) -> Result<Self> { fn switch_mode(mut self, mode: &String) -> Result<Self> {
if let Some(mode) = self.config.modes.get(mode) { if let Some(mode) = self.config.modes.get(mode) {
self.input_buffer = None;
self.mode = mode.to_owned(); self.mode = mode.to_owned();
self.msg_out.push_back(MsgOut::Refresh); self.msg_out.push_back(MsgOut::Refresh);
}; };
@ -627,7 +882,12 @@ impl App {
fn un_select(mut self) -> Result<Self> { fn un_select(mut self) -> Result<Self> {
if let Some(n) = self.focused_node().map(|n| n.to_owned()) { 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); self.msg_out.push_back(MsgOut::Refresh);
} }
Ok(self) Ok(self)
@ -650,6 +910,61 @@ impl App {
Ok(self) Ok(self)
} }
fn add_node_filter(mut self, filter: NodeFilterApplicable) -> Result<Self> {
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<Self> {
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> {
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<Self> {
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> {
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> { fn print_result_and_quit(mut self) -> Result<Self> {
self.msg_out.push_back(MsgOut::PrintResultAndQuit); self.msg_out.push_back(MsgOut::PrintResultAndQuit);
Ok(self) Ok(self)
@ -709,8 +1024,8 @@ impl App {
} }
/// Get a reference to the app's pipes. /// Get a reference to the app's pipes.
pub fn pipes(&self) -> &PipesConfig { pub fn pipe(&self) -> &Pipe {
&self.pipes &self.pipe
} }
/// Get a reference to the app's pid. /// Get a reference to the app's pid.
@ -726,6 +1041,7 @@ impl App {
pub fn refresh_selection(mut self) -> Result<Self> { pub fn refresh_selection(mut self) -> Result<Self> {
self.selection = self self.selection = self
.selection .selection
.clone()
.into_iter() .into_iter()
.filter(|n| PathBuf::from(&n.absolute_path).exists()) .filter(|n| PathBuf::from(&n.absolute_path).exists())
.collect(); .collect();
@ -747,4 +1063,9 @@ impl App {
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join("\n") .join("\n")
} }
/// Get a reference to the app's explorer config.
pub fn explorer_config(&self) -> &ExplorerConfig {
&self.explorer_config
}
} }

@ -46,19 +46,19 @@ impl Default for FileTypesConfig {
fn default() -> Self { fn default() -> Self {
FileTypesConfig { FileTypesConfig {
directory: FileTypeConfig { directory: FileTypeConfig {
icon: "d".into(), icon: "ð".into(),
style: Style::default() style: Style::default()
.add_modifier(Modifier::BOLD) .add_modifier(Modifier::BOLD)
.fg(Color::Blue), .fg(Color::Blue),
}, },
file: FileTypeConfig { file: FileTypeConfig {
icon: "f".into(), icon: "ƒ".into(),
style: Default::default(), style: Default::default(),
}, },
symlink: FileTypeConfig { symlink: FileTypeConfig {
icon: "s".into(), icon: "§".into(),
style: Style::default() style: Style::default()
.add_modifier(Modifier::ITALIC) .add_modifier(Modifier::ITALIC)
.fg(Color::Cyan), .fg(Color::Cyan),
@ -293,31 +293,11 @@ impl Default for KeyBindings {
ctrl-f: ctrl-f:
help: search [/] help: search [/]
messages: messages:
- Call: - SwitchMode: search
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
/: /:
messages: messages:
- Call: - SwitchMode: search
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
d: d:
help: delete help: delete
@ -340,6 +320,13 @@ impl Default for KeyBindings {
- ToggleSelection - ToggleSelection
- FocusNext - FocusNext
".":
help: show hidden
messages:
- ToggleNodeFilter:
filter: RelativePathDoesNotStartWith
input: .
enter: enter:
help: quit with result help: quit with result
messages: messages:
@ -429,6 +416,69 @@ pub struct Config {
impl Default for Config { impl Default for Config {
fn default() -> Self { 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( let goto_mode: Mode = serde_yaml::from_str(
r###" r###"
name: go to name: go to
@ -515,9 +565,9 @@ impl Default for Config {
args: args:
- -c - -c
- | - |
while IFS= read -r line; do (while IFS= read -r line; do
cp -v "${line:?}" ./ cp -v "${line:?}" ./
done <<< "${XPLR_SELECTION:?}" done <<< "${XPLR_SELECTION:?}")
read -p "[enter to continue]" read -p "[enter to continue]"
- ClearSelection - ClearSelection
- Explore - Explore
@ -531,9 +581,9 @@ impl Default for Config {
args: args:
- -c - -c
- | - |
while IFS= read -r line; do (while IFS= read -r line; do
mv -v "${line:?}" ./ mv -v "${line:?}" ./
done <<< "${XPLR_SELECTION:?}" done <<< "${XPLR_SELECTION:?}")
read -p "[enter to continue]" read -p "[enter to continue]"
- Explore - Explore
- SwitchMode: default - SwitchMode: default
@ -559,35 +609,35 @@ impl Default for Config {
help: to up [k] help: to up [k]
messages: messages:
- FocusPreviousByRelativeIndexFromInput - FocusPreviousByRelativeIndexFromInput
- ResetInputBuffer
- SwitchMode: default - SwitchMode: default
k: k:
messages: messages:
- FocusPreviousByRelativeIndexFromInput - FocusPreviousByRelativeIndexFromInput
- ResetInputBuffer
- SwitchMode: default - SwitchMode: default
down: down:
help: to down [j] help: to down [j]
messages: messages:
- FocusNextByRelativeIndexFromInput - FocusNextByRelativeIndexFromInput
- ResetInputBuffer
- SwitchMode: default - SwitchMode: default
j: j:
messages: messages:
- FocusNextByRelativeIndexFromInput - FocusNextByRelativeIndexFromInput
- ResetInputBuffer
- SwitchMode: default - SwitchMode: default
enter: enter:
help: to index help: to index
messages: messages:
- FocusByIndexFromInput - FocusByIndexFromInput
- ResetInputBuffer
- SwitchMode: default - SwitchMode: default
backspace:
help: clear
messages:
- ResetInputBuffer
ctrl-c: ctrl-c:
help: cancel & quit help: cancel & quit
messages: messages:
@ -600,7 +650,6 @@ impl Default for Config {
default: default:
messages: messages:
- ResetInputBuffer
- SwitchMode: default - SwitchMode: default
"###, "###,
) )
@ -620,7 +669,6 @@ impl Default for Config {
- -c - -c
- | - |
touch "${XPLR_INPUT_BUFFER:?}" touch "${XPLR_INPUT_BUFFER:?}"
- ResetInputBuffer
- SwitchMode: default - SwitchMode: default
- Explore - Explore
@ -633,14 +681,17 @@ impl Default for Config {
- -c - -c
- | - |
mkdir -p "${XPLR_INPUT_BUFFER:?}" mkdir -p "${XPLR_INPUT_BUFFER:?}"
- ResetInputBuffer
- SwitchMode: default - SwitchMode: default
- Explore - Explore
backspace:
help: clear
messages:
- ResetInputBuffer
esc: esc:
help: cancel help: cancel
messages: messages:
- ResetInputBuffer
- SwitchMode: default - SwitchMode: default
ctrl-c: ctrl-c:
@ -668,14 +719,14 @@ impl Default for Config {
args: args:
- -c - -c
- | - |
while IFS= read -r line; do (while IFS= read -r line; do
if [ -d "$line" ]; then if [ -d "$line" ]; then
rmdir -v "${line:?}" rmdir -v "${line:?}"
else else
rm -v "${line:?}" rm -v "${line:?}"
fi fi
done <<< "${XPLR_RESULT:?}" done <<< "${XPLR_RESULT:?}")
read -p "[Enter to continue]" read -p "[enter to continue]"
- SwitchMode: default - SwitchMode: default
- Explore - Explore
@ -687,7 +738,7 @@ impl Default for Config {
args: args:
- -c - -c
- | - |
echo -e "${XPLR_RESULT:?}" | xargs -l rm -rfv (echo -e "${XPLR_RESULT:?}" | xargs -l rm -rfv)
read -p "[enter to continue]" read -p "[enter to continue]"
- SwitchMode: default - SwitchMode: default
- Explore - Explore
@ -711,6 +762,7 @@ impl Default for Config {
modes.insert("create".into(), create_mode); modes.insert("create".into(), create_mode);
modes.insert("delete".into(), delete_mode); modes.insert("delete".into(), delete_mode);
modes.insert("action".into(), action_mode); modes.insert("action".into(), action_mode);
modes.insert("search".into(), search_mode);
modes.insert("selection ops".into(), selection_ops_mode); modes.insert("selection ops".into(), selection_ops_mode);
Self { Self {

@ -1,4 +1,5 @@
use crate::app::DirectoryBuffer; use crate::app::DirectoryBuffer;
use crate::app::ExplorerConfig;
use crate::app::Node; use crate::app::Node;
use crate::app::Task; use crate::app::Task;
use crate::app::{InternalMsg, MsgIn}; use crate::app::{InternalMsg, MsgIn};
@ -7,10 +8,16 @@ use std::path::PathBuf;
use std::sync::mpsc::Sender; use std::sync::mpsc::Sender;
use std::thread; use std::thread;
pub fn explore(parent: String, focused_path: Option<String>, tx: Sender<Task>) { pub fn explore(
config: ExplorerConfig,
parent: String,
focused_path: Option<String>,
tx: Sender<Task>,
) {
let path = PathBuf::from(&parent); let path = PathBuf::from(&parent);
let path_cloned = path.clone(); let path_cloned = path.clone();
let tx_cloned = tx.clone(); let tx_cloned = tx.clone();
let config_cloned = config.clone();
thread::spawn(move || { thread::spawn(move || {
let nodes: Vec<Node> = fs::read_dir(&path) let nodes: Vec<Node> = fs::read_dir(&path)
@ -24,6 +31,7 @@ pub fn explore(parent: String, focused_path: Option<String>, tx: Sender<Task>) {
}) })
}) })
.map(|name| Node::new(parent.clone(), name)) .map(|name| Node::new(parent.clone(), name))
.filter(|n| config.apply(n))
.collect(); .collect();
let focus_index = if let Some(focus) = focused_path { let focus_index = if let Some(focus) = focused_path {
@ -49,6 +57,7 @@ pub fn explore(parent: String, focused_path: Option<String>, tx: Sender<Task>) {
if let Some(grand_parent) = path_cloned.parent() { if let Some(grand_parent) = path_cloned.parent() {
explore( explore(
config_cloned,
grand_parent.to_string_lossy().to_string(), grand_parent.to_string_lossy().to_string(),
path_cloned path_cloned
.file_name() .file_name()

@ -1,6 +1,6 @@
use anyhow::Result; use anyhow::Result;
use crossterm::terminal as term;
use crossterm::execute; use crossterm::execute;
use crossterm::terminal as term;
use handlebars::Handlebars; use handlebars::Handlebars;
use std::fs; use std::fs;
use std::io::prelude::*; use std::io::prelude::*;
@ -51,9 +51,14 @@ fn main() -> Result<()> {
.file_name() .file_name()
.map(|n| n.to_string_lossy().to_string()) .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(); let (tx_event_reader, rx_event_reader) = mpsc::channel();
event_reader::keep_reading(tx_msg_in.clone(), rx_event_reader); event_reader::keep_reading(tx_msg_in.clone(), rx_event_reader);
@ -83,6 +88,7 @@ fn main() -> Result<()> {
app::MsgOut::Explore => { app::MsgOut::Explore => {
explorer::explore( explorer::explore(
app.explorer_config().clone(),
app.pwd().clone(), app.pwd().clone(),
app.focused_node().map(|n| n.relative_path.clone()), app.focused_node().map(|n| n.relative_path.clone()),
tx_msg_in.clone(), tx_msg_in.clone(),
@ -92,6 +98,7 @@ fn main() -> Result<()> {
app::MsgOut::Refresh => { app::MsgOut::Refresh => {
if app.pwd() != &last_pwd { if app.pwd() != &last_pwd {
explorer::explore( explorer::explore(
app.explorer_config().clone(),
app.pwd().clone(), app.pwd().clone(),
app.focused_node().map(|n| n.relative_path.clone()), app.focused_node().map(|n| n.relative_path.clone()),
tx_msg_in.clone(), tx_msg_in.clone(),
@ -108,7 +115,7 @@ fn main() -> Result<()> {
.map(|n| n.absolute_path.clone()) .map(|n| n.absolute_path.clone())
.unwrap_or_default(); .unwrap_or_default();
fs::write(&app.pipes().focus_out, focused)?; fs::write(&app.pipe().focus_out, focused)?;
app = app.refresh_selection()?; app = app.refresh_selection()?;
@ -119,9 +126,9 @@ fn main() -> Result<()> {
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join("\n"); .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) => { app::MsgOut::Call(cmd) => {
@ -163,9 +170,9 @@ fn main() -> Result<()> {
}) })
.unwrap_or_default(); .unwrap_or_default();
let pipe_msg_in = app.pipes().msg_in.clone(); let pipe_msg_in = app.pipe().msg_in.clone();
let pipe_focus_out = app.pipes().focus_out.clone(); let pipe_focus_out = app.pipe().focus_out.clone();
let pipe_selection_out = app.pipes().selection_out.clone(); let pipe_selection_out = app.pipe().selection_out.clone();
let app_yaml = serde_yaml::to_string(&app)?; let app_yaml = serde_yaml::to_string(&app)?;
let session_path = app.session_path(); let session_path = app.session_path();
@ -216,6 +223,8 @@ fn main() -> Result<()> {
execute!(terminal.backend_mut(), term::LeaveAlternateScreen)?; execute!(terminal.backend_mut(), term::LeaveAlternateScreen)?;
terminal.show_cursor()?; terminal.show_cursor()?;
fs::remove_dir_all(app.session_path())?;
if let Some(out) = output { if let Some(out) = output {
println!("{}", out); println!("{}", out);
} }

Loading…
Cancel
Save