From 2279d95fab08aeb84549045fb318c5002f836ee1 Mon Sep 17 00:00:00 2001 From: sxyazi Date: Thu, 13 Jul 2023 21:25:03 +0800 Subject: [PATCH] feat: confirm on deleting --- src/core/input/input.rs | 17 +++++++++++++--- src/core/manager/folder.rs | 3 +++ src/core/manager/manager.rs | 30 ++++++++++++---------------- src/core/manager/tab.rs | 5 ++--- src/core/tasks/tasks.rs | 40 +++++++++++++++++++++++++++---------- src/ui/app.rs | 14 +++++++++++-- 6 files changed, 74 insertions(+), 35 deletions(-) diff --git a/src/core/input/input.rs b/src/core/input/input.rs index 405fd9b3..8ca2f43d 100644 --- a/src/core/input/input.rs +++ b/src/core/input/input.rs @@ -24,7 +24,13 @@ pub struct Input { pub struct InputOpt { pub title: String, pub value: String, - pub position: (u16, u16), + pub position: InputPos, +} + +pub enum InputPos { + Top, + Hovered, + Coords(u16, u16), } #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] @@ -46,7 +52,10 @@ impl Input { pub fn show(&mut self, opt: InputOpt, tx: Sender>) { self.title = opt.title; self.value = opt.value; - self.position = opt.position; + self.position = match opt.position { + InputPos::Coords(x, y) => (x, y), + _ => unimplemented!(), + }; self.mode = InputMode::Insert; self.cursor = self.count(); @@ -276,7 +285,9 @@ impl Input { } #[inline] - pub fn top_position() -> (u16, u16) { ((tty_size().ws_col / 2).saturating_sub(25), 2) } + pub fn top_position() -> InputPos { + InputPos::Coords((tty_size().ws_col / 2).saturating_sub(25), 2) + } #[inline] fn count(&self) -> usize { self.value.chars().count() } diff --git a/src/core/manager/folder.rs b/src/core/manager/folder.rs index 397fde3f..534f1a00 100644 --- a/src/core/manager/folder.rs +++ b/src/core/manager/folder.rs @@ -127,6 +127,9 @@ impl Folder { false } + #[inline] + pub fn has_selected(&self) -> bool { self.files.iter().any(|(_, item)| item.is_selected) } + pub fn selected(&self) -> Option> { let v = self .files diff --git a/src/core/manager/manager.rs b/src/core/manager/manager.rs index f345b504..cec661d0 100644 --- a/src/core/manager/manager.rs +++ b/src/core/manager/manager.rs @@ -3,7 +3,7 @@ use std::{collections::{BTreeSet, HashMap, HashSet}, mem, path::PathBuf}; use tokio::fs; use super::{PreviewData, Tab, Tabs, Watcher}; -use crate::{core::{files::{File, FilesOp}, input::{Input, InputOpt}, manager::Folder, tasks::Precache}, emit}; +use crate::{core::{files::{File, FilesOp}, input::{Input, InputOpt, InputPos}, manager::Folder, tasks::Precache}, emit}; pub struct Manager { tabs: Tabs, @@ -92,14 +92,12 @@ impl Manager { pub fn yanked(&self) -> &(bool, HashSet) { &self.yanked } pub fn create(&self) -> bool { - let pos = Input::top_position(); let cwd = self.current().cwd.clone(); - tokio::spawn(async move { let result = emit!(Input(InputOpt { title: "Create:".to_string(), value: "".to_string(), - position: pos, + position: InputPos::Top, })) .await; @@ -113,35 +111,33 @@ impl Manager { } } }); - false } pub fn rename(&self) -> bool { - let selected = self.selected(); - if selected.is_empty() { - return false; - } - - if selected.len() > 1 { + if self.current().has_selected() { return self.bulk_rename(); } - let rect = self.current().rect_current(&selected[0]).unwrap(); + let hovered = if let Some(h) = self.hovered() { + h.path.clone() + } else { + return false; + }; + tokio::spawn(async move { let result = emit!(Input(InputOpt { title: "Rename:".to_string(), - value: selected[0].file_name().unwrap().to_string_lossy().to_string(), - position: (rect.x, rect.y), + value: hovered.file_name().unwrap().to_string_lossy().to_string(), + position: InputPos::Hovered, })) .await; if let Ok(new) = result { - let to = selected[0].parent().unwrap().join(new); - fs::rename(&selected[0], to).await.ok(); + let to = hovered.parent().unwrap().join(new); + fs::rename(&hovered, to).await.ok(); } }); - false } diff --git a/src/core/manager/tab.rs b/src/core/manager/tab.rs index c5906586..d8645045 100644 --- a/src/core/manager/tab.rs +++ b/src/core/manager/tab.rs @@ -5,7 +5,7 @@ use tokio::task::JoinHandle; use tracing::info; use super::{Folder, Mode, Preview}; -use crate::{core::{external::{self, FzfOpt, ZoxideOpt}, files::{File, Files, FilesOp}, input::{Input, InputOpt}, Event, BLOCKER}, emit, misc::Defer}; +use crate::{core::{external::{self, FzfOpt, ZoxideOpt}, files::{File, Files, FilesOp}, input::{Input, InputOpt, InputPos}, Event, BLOCKER}, emit, misc::Defer}; pub struct Tab { pub(super) current: Folder, @@ -184,12 +184,11 @@ impl Tab { let cwd = self.current.cwd.clone(); let hidden = self.current.files.show_hidden; - let pos = Input::top_position(); self.search = Some(tokio::spawn(async move { let subject = emit!(Input(InputOpt { title: "Search:".to_string(), value: "".to_string(), - position: pos, + position: InputPos::Top, })) .await?; diff --git a/src/core/tasks/tasks.rs b/src/core/tasks/tasks.rs index e3c31c41..cf4e9155 100644 --- a/src/core/tasks/tasks.rs +++ b/src/core/tasks/tasks.rs @@ -1,9 +1,9 @@ -use std::{collections::{BTreeMap, HashSet}, path::PathBuf}; +use std::{collections::{BTreeMap, HashSet}, path::PathBuf, sync::Arc}; use tracing::trace; use super::{Scheduler, TASKS_PADDING, TASKS_PERCENT}; -use crate::{config::OPEN, misc::tty_size}; +use crate::{config::OPEN, core::input::{Input, InputOpt, InputPos}, emit, misc::tty_size}; #[derive(Clone, Debug)] pub struct Task { @@ -43,7 +43,7 @@ pub enum TaskStage { } pub struct Tasks { - scheduler: Scheduler, + scheduler: Arc, pub visible: bool, pub cursor: usize, @@ -52,7 +52,12 @@ pub struct Tasks { impl Tasks { pub fn start() -> Self { - Self { scheduler: Scheduler::start(), visible: false, cursor: 0, progress: (100, 0) } + Self { + scheduler: Arc::new(Scheduler::start()), + visible: false, + cursor: 0, + progress: (100, 0), + } } #[inline] @@ -143,13 +148,28 @@ impl Tasks { } pub fn file_remove(&self, targets: Vec, permanently: bool) -> bool { - for p in targets { - if permanently { - self.scheduler.file_delete(p); - } else { - self.scheduler.file_trash(p); + let scheduler = self.scheduler.clone(); + tokio::spawn(async move { + let result = emit!(Input(InputOpt { + title: "Are you sure delete these files? (Y/n)".to_string(), + value: "".to_string(), + position: InputPos::Hovered, + })) + .await; + + if let Ok(choice) = result { + if choice.to_lowercase() != "y" { + return; + } + for p in targets { + if permanently { + scheduler.file_delete(p); + } else { + scheduler.file_trash(p); + } + } } - } + }); false } diff --git a/src/ui/app.rs b/src/ui/app.rs index 49d7a12d..01387597 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -3,7 +3,7 @@ use crossterm::event::KeyEvent; use tokio::sync::oneshot::{self}; use super::{root::Root, Ctx, Executor, Logs, Signals, Term}; -use crate::{config::keymap::Key, core::{files::FilesOp, Event}, emit}; +use crate::{config::keymap::Key, core::{files::FilesOp, input::{Input, InputPos}, Event}, emit}; pub struct App { cx: Ctx, @@ -104,7 +104,17 @@ impl App { emit!(Render); } - Event::Input(opt, tx) => { + Event::Input(mut opt, tx) => { + opt.position = match opt.position { + InputPos::Top => Input::top_position(), + InputPos::Hovered => manager + .hovered() + .and_then(|h| manager.current().rect_current(&h.path)) + .map(|r| InputPos::Coords(r.x, r.y)) + .unwrap_or_else(|| Input::top_position()), + p @ InputPos::Coords(..) => p, + }; + self.cx.input.show(opt, tx); emit!(Render); }