summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorqkzk <qkzk@users.noreply.github.com>2022-10-29 22:35:18 +0200
committerGitHub <noreply@github.com>2022-10-29 22:35:18 +0200
commitddf39e6675420fdc83363b757caaac647abff6d4 (patch)
tree4d5391439dc1749d7a0dc2d591588ea309122e52
parentfae46d52d6b7e4b48bf9174251db78c2c394eafe (diff)
parent26ec7fe77dcc56975232bd7c2579827eb1212c05 (diff)
Merge pull request #34 from qkzk/copy_progress
Copy progress
-rw-r--r--Cargo.lock81
-rw-r--r--Cargo.toml2
-rw-r--r--readme.md6
-rw-r--r--src/actioner.rs20
-rw-r--r--src/copy_move.rs88
-rw-r--r--src/fileinfo.rs4
-rw-r--r--src/fm_error.rs7
-rw-r--r--src/help.rs1
-rw-r--r--src/lib.rs1
-rw-r--r--src/main.rs5
-rw-r--r--src/marks.rs2
-rw-r--r--src/status.rs46
-rw-r--r--src/tab.rs39
13 files changed, 245 insertions, 57 deletions
diff --git a/Cargo.lock b/Cargo.lock
index cfd01352..49133620 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -208,6 +208,20 @@ dependencies = [
]
[[package]]
+name = "console"
+version = "0.15.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c050367d967ced717c04b65d8c619d863ef9292ce0c5760028655a2fb298718c"
+dependencies = [
+ "encode_unicode",
+ "lazy_static",
+ "libc",
+ "terminal_size",
+ "unicode-width",
+ "winapi",
+]
+
+[[package]]
name = "const_fn"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -484,6 +498,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
[[package]]
+name = "encode_unicode"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
+
+[[package]]
name = "encoding"
version = "0.2.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -587,6 +607,8 @@ dependencies = [
"clap 4.0.2",
"content_inspector",
"copypasta",
+ "fs_extra",
+ "indicatif",
"pdf-extract",
"rand",
"regex",
@@ -605,6 +627,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
+name = "fs_extra"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394"
+
+[[package]]
name = "fuzzy-matcher"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -682,6 +710,18 @@ dependencies = [
]
[[package]]
+name = "indicatif"
+version = "0.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfddc9561e8baf264e0e45e197fd7696320026eb10a8180340debc27b18f535b"
+dependencies = [
+ "console",
+ "number_prefix",
+ "unicode-width",
+ "vt100",
+]
+
+[[package]]
name = "itoa"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -905,6 +945,12 @@ dependencies = [
]
[[package]]
+name = "number_prefix"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
+
+[[package]]
name = "objc"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1334,7 +1380,7 @@ dependencies = [
"timer",
"tuikit",
"unicode-width",
- "vte",
+ "vte 0.9.0",
]
[[package]]
@@ -1502,6 +1548,16 @@ dependencies = [
]
[[package]]
+name = "terminal_size"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1702,6 +1758,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
+name = "vt100"
+version = "0.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7541312ce0411d878458abf25175d878e8edc38f9f12ee8eed1d65870cacf540"
+dependencies = [
+ "itoa 1.0.3",
+ "log",
+ "unicode-width",
+ "vte 0.10.1",
+]
+
+[[package]]
name = "vte"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1713,6 +1781,17 @@ dependencies = [
]
[[package]]
+name = "vte"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6cbce692ab4ca2f1f3047fcf732430249c0e971bfdd2b234cf2c47ad93af5983"
+dependencies = [
+ "arrayvec",
+ "utf8parse",
+ "vte_generate_state_changes",
+]
+
+[[package]]
name = "vte_generate_state_changes"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 35707c2b..d9642276 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -17,6 +17,8 @@ chrono = "*"
clap = { version = "4.0", features = ["derive"] }
content_inspector = "0.2.4"
copypasta = "0.8.1"
+fs_extra = "1.2.0"
+indicatif = { version = "0.17.1", features= ["in_memory"] }
pdf-extract = "0.6.4"
rand = "^0"
regex = "1"
diff --git a/readme.md b/readme.md
index 3b4078cb..70b272d6 100644
--- a/readme.md
+++ b/readme.md
@@ -86,6 +86,7 @@
- [x] display link destination
- [x] copy filename/filepath to clipboard with ctrl+c & ctrl+p
- [x] filters by ext / name / only dirs / all (aka no filter)
+- [x] FIX: broken links aren't shown
## TODO
@@ -104,12 +105,13 @@
- [ ] preview images @ranger [ueberzug-rs](https://github.com/Adit-Chauhan/Ueberzug-rs) @[termimage](https://rawcdn.githack.com/nabijaczleweli/termimage/doc/termimage/index.html)
- [ ] COPY improvment
- [ ] async/threaded copy -- move & delete should be quick enough
- - [ ] progress bar for copy
+ - [x] progress bar for copy
+ - [x] move/copy progress displayed, nothing else
+- [ ] opener crash, right on file crash when in nvim toggleterm
## BUGS
- [ ] when opening a file with rifle opener into nvim and closing, the terminal hangs
-- [ ] broken links aren't shown
## Sources
diff --git a/src/actioner.rs b/src/actioner.rs
index 07e97d96..adc509e4 100644
--- a/src/actioner.rs
+++ b/src/actioner.rs
@@ -91,6 +91,8 @@ impl Actioner {
Event::Key(Key::Ctrl('f')) => self.ctrl_f(status),
Event::Key(Key::Ctrl('c')) => self.ctrl_c(status),
Event::Key(Key::Ctrl('p')) => self.ctrl_p(status),
+ Event::Key(Key::Ctrl('r')) => self.refresh_selected_view(status),
+ Event::User(_) => self.refresh_selected_view(status),
_ => Ok(()),
}
}
@@ -140,7 +142,8 @@ impl Actioner {
| Mode::Newfile
| Mode::Exec
| Mode::Search
- | Mode::Goto => {
+ | Mode::Goto
+ | Mode::Filter => {
status.selected().event_move_cursor_left();
Ok(())
}
@@ -152,14 +155,15 @@ impl Actioner {
/// Move right in a string, move to children in normal mode.
fn right(&self, status: &mut Status) -> FmResult<()> {
match status.selected().mode {
- Mode::Normal => status.selected().event_go_to_child(),
+ Mode::Normal => status.selected().event_child_or_open(),
Mode::Rename
| Mode::Chmod
| Mode::Newdir
| Mode::Newfile
| Mode::Exec
| Mode::Search
- | Mode::Goto => {
+ | Mode::Goto
+ | Mode::Filter => {
status.selected().event_move_cursor_right();
Ok(())
}
@@ -176,7 +180,8 @@ impl Actioner {
| Mode::Newfile
| Mode::Exec
| Mode::Search
- | Mode::Goto => {
+ | Mode::Goto
+ | Mode::Filter => {
status.selected().event_delete_char_left();
Ok(())
}
@@ -195,7 +200,8 @@ impl Actioner {
| Mode::Newfile
| Mode::Exec
| Mode::Search
- | Mode::Goto => {
+ | Mode::Goto
+ | Mode::Filter => {
status.selected().event_delete_chars_right();
Ok(())
}
@@ -332,6 +338,10 @@ impl Actioner {
Ok(())
}
+ fn refresh_selected_view(&self, status: &mut Status) -> FmResult<()> {
+ status.selected().refresh_view()
+ }
+
/// Match read key to a relevent event, depending on keybindings.
/// Keybindings are read from `Config`.
fn char(&self, status: &mut Status, c: char) -> FmResult<()> {
diff --git a/src/copy_move.rs b/src/copy_move.rs
new file mode 100644
index 00000000..1675da8d
--- /dev/null
+++ b/src/copy_move.rs
@@ -0,0 +1,88 @@
+use std::fmt::Write;
+use std::path::PathBuf;
+use std::sync::Arc;
+use std::thread;
+
+use fs_extra;
+use indicatif::{InMemoryTerm, ProgressBar, ProgressDrawTarget, ProgressState, ProgressStyle};
+use tuikit::prelude::{Attr, Color, Effect, Event, Term};
+
+use crate::fm_error::FmResult;
+
+fn setup(
+ action: String,
+ height: usize,
+ width: usize,
+) -> FmResult<(InMemoryTerm, ProgressBar, fs_extra::dir::CopyOptions)> {
+ let in_mem = InMemoryTerm::new(height as u16, width as u16);
+ let pb = ProgressBar::with_draw_target(
+ Some(100),
+ ProgressDrawTarget::term_like(Box::new(in_mem.clone())),
+ );
+ pb.set_style(
+ ProgressStyle::with_template(
+ "{spinner} {action} [{elapsed}] [{wide_bar}] {percent}% ({eta})",
+ )
+ .unwrap()
+ .with_key("eta", |state: &ProgressState, w: &mut dyn Write| {
+ write!(w, "{:.1}s", state.eta().as_secs_f64()).unwrap()
+ })
+ .with_key("action", move |_: &ProgressState, w: &mut dyn Write| {
+ write!(w, "{}", &action).unwrap()
+ })
+ .progress_chars("#>-"),
+ );
+ let options = fs_extra::dir::CopyOptions::new();
+ Ok((in_mem, pb, options))
+}
+
+fn handle_progress_display(
+ in_mem: &InMemoryTerm,
+ pb: &ProgressBar,
+ term: &Arc<Term>,
+ process_info: fs_extra::TransitProcess,
+) -> fs_extra::dir::TransitProcessResult {
+ pb.set_position(100 * process_info.copied_bytes / process_info.total_bytes);
+ let _ = term.print_with_attr(
+ 1,
+ 0,
+ &in_mem.to_owned().contents(),
+ Attr {
+ fg: Color::CYAN,
+ bg: Color::default(),
+ effect: Effect::REVERSE | Effect::BOLD,
+ },
+ );
+ let _ = term.present();
+ fs_extra::dir::TransitProcessResult::ContinueOrAbort
+}
+
+pub fn copy(sources: Vec<PathBuf>, dest: String, term: Arc<Term>) -> FmResult<()> {
+ let c_term = term.clone();
+ let (height, width) = term.term_size()?;
+ let (in_mem, pb, options) = setup("copy".to_owned(), height, width)?;
+ let handle_progress = move |process_info: fs_extra::TransitProcess| {
+ handle_progress_display(&in_mem, &pb, &term, process_info)
+ };
+ let _ = thread::spawn(move || {
+ fs_extra::copy_items_with_progress(&sources, &dest, &options, handle_progress)
+ .unwrap_or_default();
+ let _ = c_term.send_event(Event::User(()));
+ });
+ Ok(())
+}
+
+pub fn mover(sources: Vec<PathBuf>, dest: String, term: Arc<Term>) -> FmResult<()> {
+ let c_term = term.clone();
+ let (height, width) = term.term_size()?;
+ let (in_mem, pb, options) = setup("move".to_owned(), height, width)?;
+ let handle_progress = move |process_info: fs_extra::TransitProcess| {
+ handle_progress_display(&in_mem, &pb, &term, process_info)
+ };
+ let _ = thread::spawn(move || {
+ fs_extra::move_items_with_progress(&sources, dest, &options, handle_progress)
+ .unwrap_or_default();
+ let _ = c_term.send_event(Event::User(()));
+ });
+ Ok(())
+}
diff --git a/src/fileinfo.rs b/src/fileinfo.rs
index ed3b9017..bb676bb1 100644
--- a/src/fileinfo.rs
+++ b/src/fileinfo.rs
@@ -176,7 +176,7 @@ impl FileInfo {
);
if let FileKind::SymbolicLink = self.file_kind {
repr.push_str(" -> ");
- repr.push_str(&self.read_dest().unwrap());
+ repr.push_str(&self.read_dest().unwrap_or("Broken link".to_owned()));
}
Ok(repr)
}
@@ -295,7 +295,7 @@ impl PathContent {
/// Select the next file, if any.
pub fn select_next(&mut self) {
- if self.selected < self.files.len() - 1 {
+ if !self.files.is_empty() && self.selected < self.files.len() - 1 {
self.files[self.selected].unselect();
self.selected += 1;
self.files[self.selected].select();
diff --git a/src/fm_error.rs b/src/fm_error.rs
index 77fc8ccc..e861cfa2 100644
--- a/src/fm_error.rs
+++ b/src/fm_error.rs
@@ -2,6 +2,7 @@ use std::borrow::Borrow;
use std::error::Error;
use std::fmt;
+use fs_extra::error::Error as FsExtraError;
use tuikit::error::TuikitError;
#[derive(Debug)]
@@ -69,4 +70,10 @@ impl From<Box<dyn Error + Send + Sync + 'static>> for FmError {
}
}
+impl From<FsExtraError> for FmError {
+ fn from(fs_extra_error: FsExtraError) -> Self {
+ Self::new(&fs_extra_error.to_string())
+ }
+}
+
pub type FmResult<T> = Result<T, FmError>;
diff --git a/src/help.rs b/src/help.rs
index b60e4b53..dd907432 100644
--- a/src/help.rs
+++ b/src/help.rs
@@ -22,6 +22,7 @@ o: open this file
i: open in current nvim session
P: preview this file
Ctrl+f: fuzzy finder
+Ctrl+r: refresh view
Ctrl+c: copy filename to clipboard
Ctrl+p: copy filepath to clipboard
M: Mark current path
diff --git a/src/lib.rs b/src/lib.rs
index 2605c052..9e927294 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -5,6 +5,7 @@ pub mod color_cache;
pub mod completion;
pub mod config;
pub mod content_window;
+pub mod copy_move;
pub mod display;
pub mod event_char;
pub mod fileinfo;
diff --git a/src/main.rs b/src/main.rs
index 6d80a0e5..ed368cd1 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -27,12 +27,13 @@ fn reset_cursor(display: &Display) -> FmResult<()> {
/// Main function.
/// Init the status and display and listen to events from keyboard and mouse.
/// The application is redrawn after every event.
+
fn main() -> FmResult<()> {
let config = load_config(CONFIG_PATH);
let term = Arc::new(init_term()?);
let actioner = Actioner::new(&config.keybindings, term.clone());
- let mut display = Display::new(term, config.colors.clone());
- let mut status = Status::new(Args::parse(), config, display.height()?)?;
+ let mut display = Display::new(term.clone(), config.colors.clone());
+ let mut status = Status::new(Args::parse(), config, display.height()?, term)?;
while let Ok(event) = display.term.poll_event() {
let _ = display.term.clear();
diff --git a/src/marks.rs b/src/marks.rs
index 7a079ad2..74716d53 100644
--- a/src/marks.rs
+++ b/src/marks.rs
@@ -65,7 +65,7 @@ impl Marks {
let mut buf = BufWriter::new(file);
for (ch, path) in self.marks.iter() {
- let _ = write!(buf, "{}:{}", ch, Self::path_as_string(path)?);
+ let _ = writeln!(buf, "{}:{}", ch, Self::path_as_string(path)?);
}
Ok(())
}
diff --git a/src/status.rs b/src/status.rs
index fbe6e79b..6e99c9cb 100644
--- a/src/status.rs
+++ b/src/status.rs
@@ -1,15 +1,18 @@
-use regex::Regex;
-use skim::SkimItem;
use std::collections::HashSet;
use std::fs;
use std::os::unix::fs::PermissionsExt;
use std::path::{self, Path, PathBuf};
use std::sync::Arc;
+use regex::Regex;
+use skim::SkimItem;
+use tuikit::term::Term;
+
use crate::args::Args;
use crate::bulkrename::Bulkrename;
use crate::color_cache::ColorCache;
use crate::config::Config;
+use crate::copy_move::{copy, mover};
use crate::fileinfo::PathContent;
use crate::fm_error::{FmError, FmResult};
use crate::last_edition::LastEdition;
@@ -36,12 +39,14 @@ pub struct Status {
pub marks: Marks,
/// Colors for extension
pub colors: ColorCache,
+ /// terminal
+ term: Arc<Term>,
}
impl Status {
const MAX_PERMISSIONS: u32 = 0o777;
- pub fn new(args: Args, config: Config, height: usize) -> FmResult<Self> {
+ pub fn new(args: Args, config: Config, height: usize, term: Arc<Term>) -> FmResult<Self> {
Ok(Self {
tabs: vec![Tab::new(args, config, height)?],
index: 0,
@@ -49,6 +54,7 @@ impl Status {
jump_index: 0,
marks: Marks::read_from_config_file(),
colors: ColorCache::default(),
+ term,
})
}
@@ -249,12 +255,7 @@ impl Status {
);
std::os::unix::fs::symlink(oldpath, newpath)?;
}
-
- self.flagged.clear();
- self.selected().path_content.reset_files()?;
- let len = self.tabs[self.index].path_content.files.len();
- self.tabs[self.index].window.reset(len);
- self.reset_statuses()
+ self.clear_flags_and_reset_view()
}
pub fn event_bulkrename(&mut self) -> FmResult<()> {
@@ -272,28 +273,17 @@ impl Status {
}
fn cut_or_copy_flagged_files(&mut self, cut_or_copy: CutOrCopy) -> FmResult<()> {
- for oldpath in self.flagged.iter() {
- let filename = oldpath
- .as_path()
- .file_name()
- .ok_or_else(|| FmError::new("Couldn't parse the filename"))?;
- let newpath = self.tabs[self.index]
- .path_content
- .path
- .clone()
- .join(filename);
- Self::cut_or_copy(cut_or_copy.clone(), oldpath, newpath)?
- }
- self.clear_flags_and_reset_view()
- }
-
- fn cut_or_copy(cut_or_copy: CutOrCopy, oldpath: &PathBuf, newpath: PathBuf) -> FmResult<()> {
+ let sources: Vec<PathBuf> = self.flagged.iter().map(|path| path.to_owned()).collect();
+ let dest = self
+ .selected_non_mut()
+ .path_str()
+ .ok_or_else(|| FmError::new("unreadable path"))?;
if let CutOrCopy::Cut = cut_or_copy {
- std::fs::rename(oldpath, newpath)?
+ mover(sources, dest, self.term.clone())?
} else {
- std::fs::copy(oldpath, newpath)?;
+ copy(sources, dest, self.term.clone())?
}
- Ok(())
+ self.clear_flags_and_reset_view()
}
fn clear_flags_and_reset_view(&mut self) -> FmResult<()> {
diff --git a/src/tab.rs b/src/tab.rs
index bfb9e23c..a3a16a36 100644
--- a/src/tab.rs
+++ b/src/tab.rs
@@ -129,7 +129,9 @@ impl Tab {
} else {
self.preview.len()
};
- if self.line_index < max_line - ContentWindow::WINDOW_MARGIN_TOP {
+ if max_line >= ContentWindow::WINDOW_MARGIN_TOP
+ && self.line_index < max_line - ContentWindow::WINDOW_MARGIN_TOP
+ {
self.line_index += 1;
}
self.window.scroll_down_one(self.line_index);
@@ -231,27 +233,32 @@ impl Tab {
self.input.cursor_left()
}
- pub fn event_go_to_child(&mut self) -> FmResult<()> {
+ pub fn event_child_or_open(&mut self) -> FmResult<()> {
if let FileKind::Directory = self
.path_content
.selected_file()
.ok_or_else(|| FmError::new("Empty directory"))?
.file_kind
{
- let childpath = self
- .path_content
- .selected_file()
- .ok_or_else(|| FmError::new("Empty directory"))?
- .path
- .clone();
- self.history.push(&childpath);
- self.path_content = PathContent::new(childpath, self.show_hidden)?;
- self.window.reset(self.path_content.files.len());
- self.line_index = 0;
- self.input.cursor_start();
- return Ok(());
+ self.go_to_child()
+ } else {
+ self.event_open_file()
}
- Err(FmError::new("File not found"))
+ }
+
+ fn go_to_child(&mut self) -> FmResult<()> {
+ let childpath = self
+ .path_content
+ .selected_file()
+ .ok_or_else(|| FmError::new("Empty directory"))?
+ .path
+ .clone();
+ self.history.push(&childpath);
+ self.path_content = PathContent::new(childpath, self.show_hidden)?;
+ self.window.reset(self.path_content.files.len());
+ self.line_index = 0;
+ self.input.cursor_start();
+ Ok(())
}
pub fn event_move_cursor_right(&mut self) {
@@ -426,7 +433,7 @@ impl Tab {
.ok_or_else(|| FmError::new("not found"))?
.file_kind
{
- self.event_go_to_child()
+ self.event_child_or_open()
} else {
self.event_open_file()
}