Move, Copy, Create, Delete

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

2
Cargo.lock generated

@ -1117,7 +1117,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "xplr"
version = "0.2.9"
version = "0.2.10"
dependencies = [
"criterion",
"crossterm",

@ -1,6 +1,6 @@
[package]
name = "xplr"
version = "0.2.9" # Update app.rs
version = "0.2.10" # Update app.rs
authors = ["Arijit Basu <sayanarijit@gmail.com>"]
edition = "2018"
description = "An experimental, minimal, configurable TUI file explorer, stealing ideas from nnn and fzf."

@ -11,7 +11,7 @@ use std::collections::VecDeque;
use std::fs;
use std::path::PathBuf;
pub const VERSION: &str = "v0.2.9"; // Update Cargo.toml
pub const VERSION: &str = "v0.2.10"; // Update Cargo.toml
pub const TEMPLATE_TABLE_ROW: &str = "TEMPLATE_TABLE_ROW";
@ -162,6 +162,7 @@ pub enum InternalMsg {
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub enum ExternalMsg {
Explore,
Refresh,
ClearScreen,
FocusNext,
@ -184,7 +185,10 @@ pub enum ExternalMsg {
ResetInputBuffer,
SwitchMode(String),
Call(Command),
Select,
UnSelect,
ToggleSelection,
ClearSelection,
PrintResultAndQuit,
PrintAppStateAndQuit,
Debug(String),
@ -207,6 +211,7 @@ pub struct Command {
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub enum MsgOut {
Explore,
Refresh,
ClearScreen,
PrintResultAndQuit,
@ -324,6 +329,7 @@ impl App {
fn handle_external(self, msg: ExternalMsg, key: Option<Key>) -> Result<Self, Error> {
match msg {
ExternalMsg::Explore => self.explore(),
ExternalMsg::Refresh => self.refresh(),
ExternalMsg::ClearScreen => self.clear_screen(),
ExternalMsg::FocusFirst => self.focus_first(),
@ -353,7 +359,10 @@ impl App {
ExternalMsg::ResetInputBuffer => self.reset_input_buffer(),
ExternalMsg::SwitchMode(mode) => self.switch_mode(&mode),
ExternalMsg::Call(cmd) => self.call(cmd),
ExternalMsg::Select => self.select(),
ExternalMsg::UnSelect => self.un_select(),
ExternalMsg::ToggleSelection => self.toggle_selection(),
ExternalMsg::ClearSelection => self.clear_selection(),
ExternalMsg::PrintResultAndQuit => self.print_result_and_quit(),
ExternalMsg::PrintAppStateAndQuit => self.print_app_state_and_quit(),
ExternalMsg::Debug(path) => self.debug(&path),
@ -388,6 +397,11 @@ impl App {
Ok(self)
}
fn explore(mut self) -> Result<Self, Error> {
self.msg_out.push_back(MsgOut::Explore);
Ok(self)
}
fn refresh(mut self) -> Result<Self, Error> {
self.msg_out.push_back(MsgOut::Refresh);
Ok(self)
@ -572,30 +586,42 @@ impl App {
}
fn add_directory(mut self, parent: String, dir: DirectoryBuffer) -> Result<Self, Error> {
// TODO: Optimize
self.directory_buffers.insert(parent, dir);
self.msg_out.push_back(MsgOut::Refresh);
Ok(self)
}
fn select(mut self) -> Result<Self, Error> {
if let Some(n) = self.focused_node().map(|n| n.to_owned()) {
self.selection.push(n.clone());
self.msg_out.push_back(MsgOut::Refresh);
}
Ok(self)
}
fn un_select(mut self) -> Result<Self, Error> {
if let Some(n) = self.focused_node().map(|n| n.to_owned()) {
self.selection = self.selection.into_iter().filter(|s| s != &n).collect();
self.msg_out.push_back(MsgOut::Refresh);
}
Ok(self)
}
fn toggle_selection(mut self) -> Result<Self, Error> {
self.clone()
.focused_node()
.map(|n| {
if self.selection().contains(n) {
self.selection = self
.clone()
.selection
.into_iter()
.filter(|s| s != n)
.collect();
Ok(self.clone())
} else {
self.selection.push(n.to_owned());
Ok(self.clone())
}
})
.unwrap_or(Ok(self))
if let Some(n) = self.focused_node() {
if self.selection().contains(n) {
self = self.un_select()?;
} else {
self = self.select()?;
}
}
Ok(self)
}
fn clear_selection(mut self) -> Result<Self, Error> {
self.selection.clear();
self.msg_out.push_back(MsgOut::Refresh);
Ok(self)
}
fn print_result_and_quit(mut self) -> Result<Self, Error> {
@ -652,8 +678,8 @@ impl App {
}
/// Get a reference to the app's input buffer.
pub fn input_buffer(&self) -> Option<&String> {
self.input_buffer.as_ref()
pub fn input_buffer(&self) -> Option<String> {
self.input_buffer.clone()
}
/// Get a reference to the app's pipes.
@ -670,4 +696,29 @@ impl App {
pub fn session_path(&self) -> &String {
&self.session_path
}
pub fn refresh_selection(mut self) -> Result<Self, Error> {
self.selection = self
.selection
.into_iter()
.filter(|n| PathBuf::from(&n.absolute_path).exists())
.collect();
Ok(self)
}
pub fn result(&self) -> Vec<&Node> {
if self.selection.is_empty() {
self.focused_node().map(|n| vec![n]).unwrap_or_default()
} else {
self.selection.iter().map(|n| n).collect()
}
}
pub fn result_str(&self) -> String {
self.result()
.into_iter()
.map(|n| n.absolute_path.clone())
.collect::<Vec<String>>()
.join("\n")
}
}

@ -289,7 +289,7 @@ impl Default for KeyBindings {
args:
- "-c"
- |
PTH="$(echo -e ${XPLR_DIRECTORY_NODES:?} | sed -s 's/,/\n/g' | fzf)"
PTH=$(echo -e "${XPLR_DIRECTORY_NODES:?}" | fzf)
if [ -d "$PTH" ]; then
echo "ChangeDirectory: ${PTH:?}" >> "${XPLR_PIPE_MSG_IN:?}"
elif [ -f "$PTH" ]; then
@ -302,8 +302,8 @@ impl Default for KeyBindings {
- ToggleSelection
- FocusNext
c:
help: create
n:
help: create new
messages:
- SwitchMode: create
@ -312,6 +312,35 @@ impl Default for KeyBindings {
messages:
- SwitchMode: delete
c:
help: copy here
messages:
- Call:
command: bash
args:
- -c
- |
while IFS= read -r line; do
cp -v "${line:?}" ./
done <<< "${XPLR_SELECTION:?}"
read -p "[enter to continue]"
- ClearSelection
- Explore
m:
help: move here
messages:
- Call:
command: bash
args:
- -c
- |
while IFS= read -r line; do
mv -v "${line:?}" ./
done <<< "${XPLR_SELECTION:?}"
read -p "[enter to continue]"
- Explore
enter:
help: quit with result
messages:
@ -503,6 +532,7 @@ impl Default for Config {
touch "${XPLR_INPUT_BUFFER:?}"
- ResetInputBuffer
- SwitchMode: default
- Explore
d:
help: create directory
@ -515,6 +545,7 @@ impl Default for Config {
mkdir -p "${XPLR_INPUT_BUFFER:?}"
- ResetInputBuffer
- SwitchMode: default
- Explore
esc:
help: cancel
@ -547,15 +578,16 @@ impl Default for Config {
args:
- -c
- |
if [ ! -e "$XPLR_SELECTION" ]; then
while IFS= read -r line; do
rm -i "${line:?}"
done < ${XPLR_PIPE_SELECTION_OUT:?}
else
rm -i ${XPLR_FOCUS_PATH:?}
fi
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]"
- SwitchMode: default
- Explore
D:
help: force delete
@ -565,15 +597,10 @@ impl Default for Config {
args:
- -c
- |
if [ ! -e "$XPLR_SELECTION" ]; then
while IFS= read -r line; do
rm -rf "${line:?}"
done < ${XPLR_PIPE_SELECTION_OUT:?}
else
rm -rf ${XPLR_FOCUS_PATH:?}
fi
read -p "[Enter to continue]"
echo -e "${XPLR_RESULT:?}" | xargs -l rm -rfv
read -p "[enter to continue]"
- SwitchMode: default
- Explore
ctrl-c:
help: cancel & quit

@ -138,18 +138,7 @@ fn main() -> Result<(), Error> {
}
app::MsgOut::PrintResultAndQuit => {
let out = if app.selection().is_empty() {
app.focused_node()
.map(|n| n.absolute_path.clone())
.unwrap_or_default()
} else {
app.selection()
.into_iter()
.map(|n| n.absolute_path.clone())
.collect::<Vec<String>>()
.join("\n")
};
output = Some(out);
output = Some(app.result_str());
break 'outer;
}
@ -163,6 +152,14 @@ fn main() -> Result<(), Error> {
terminal.clear()?;
}
app::MsgOut::Explore => {
explorer::explore(
app.pwd().clone(),
app.focused_node().map(|n| n.relative_path.clone()),
tx_explorer.clone(),
);
}
app::MsgOut::Refresh => {
if app.pwd() != &last_pwd {
explorer::explore(
@ -184,6 +181,8 @@ fn main() -> Result<(), Error> {
fs::write(&app.pipes().focus_out, focused).unwrap();
app = app.refresh_selection()?;
let selection = app
.selection()
.iter()
@ -222,7 +221,7 @@ fn main() -> Result<(), Error> {
.iter()
.map(|n| n.absolute_path.clone())
.collect::<Vec<String>>()
.join(",");
.join("\n");
let directory_nodes = app
.directory_buffer()
@ -231,7 +230,7 @@ fn main() -> Result<(), Error> {
.iter()
.map(|n| n.absolute_path.clone())
.collect::<Vec<String>>()
.join(",")
.join("\n")
})
.unwrap_or_default();
@ -240,6 +239,8 @@ fn main() -> Result<(), Error> {
let pipe_selection_out = app.pipes().selection_out.clone();
let app_yaml = serde_yaml::to_string(&app).unwrap_or_default();
let session_path = app.session_path();
let result = app.result_str();
let _ = std::process::Command::new(cmd.command.clone())
.current_dir(app.pwd())
@ -248,11 +249,12 @@ fn main() -> Result<(), Error> {
.env("XPLR_FOCUS_PATH", focus_path)
.env("XPLR_FOCUS_INDEX", focus_index)
.env("XPLR_SELECTION", selection)
.env("XPLR_SESSION_PATH", app.session_path())
.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_FOCUS_OUT", pipe_focus_out)
.env("XPLR_APP_YAML", app_yaml)
.env("XPLR_RESULT", result)
.env("XPLR_DIRECTORY_NODES", directory_nodes)
.args(cmd.args.clone())
.status();

@ -358,7 +358,7 @@ fn draw_help_menu<B: Backend>(f: &mut Frame<B>, rect: Rect, app: &app::App, _: &
}
fn draw_input_buffer<B: Backend>(f: &mut Frame<B>, rect: Rect, app: &app::App, _: &Handlebars) {
let input_buf = Paragraph::new(format!("> {}", app.input_buffer().unwrap_or(&"".into())))
let input_buf = Paragraph::new(format!("> {}", app.input_buffer().unwrap_or("".into())))
.block(Block::default().borders(Borders::ALL).title(" input "));
f.render_widget(input_buf, rect);
}
@ -373,10 +373,15 @@ pub fn draw<B: Backend>(f: &mut Frame<B>, app: &app::App, hb: &Handlebars) {
let left_chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([TUIConstraint::Length(rect.height - 3), TUIConstraint::Min(3)].as_ref())
.constraints(
[
TUIConstraint::Length(rect.height - 3),
TUIConstraint::Length(3),
]
.as_ref(),
)
.split(chunks[0]);
let right_chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([TUIConstraint::Percentage(50), TUIConstraint::Percentage(50)].as_ref())

Loading…
Cancel
Save