1use crate::app::nav::NavState;
2use crate::keymap::FileAction;
3use crate::worker::{FileOperation, WorkerTask};
4use crossbeam_channel::Sender;
5use std::collections::HashSet;
6use std::path::PathBuf;
7
8#[derive(Clone, PartialEq)]
9pub enum ActionMode {
10 Normal,
11 Input { mode: InputMode, prompt: String },
12 Confim { prompt: String, action: FileAction },
13}
14
15#[derive(Clone, Copy, PartialEq)]
16pub enum InputMode {
17 Rename,
18 NewFile,
19 NewFolder,
20 Filter,
21 ConfirmDelete,
22}
23
24pub struct ActionContext {
25 mode: ActionMode,
26 input_buffer: String,
27 clipboard: Option<HashSet<PathBuf>>,
28 is_cut: bool,
29}
30
31impl ActionContext {
32 pub fn mode(&self) -> &ActionMode {
35 &self.mode
36 }
37
38 pub fn input_buffer(&self) -> &str {
39 &self.input_buffer
40 }
41
42 pub fn input_buffer_mut(&mut self) -> &mut String {
43 &mut self.input_buffer
44 }
45
46 pub fn clipboard(&self) -> &Option<HashSet<PathBuf>> {
47 &self.clipboard
48 }
49
50 pub fn is_cut(&self) -> bool {
51 self.is_cut
52 }
53
54 pub fn is_input_mode(&self) -> bool {
55 matches!(self.mode, ActionMode::Input { .. })
56 }
57
58 pub fn enter_mode(&mut self, mode: ActionMode, initial_value: String) {
59 self.mode = mode;
60 self.input_buffer = initial_value;
61 }
62
63 pub fn exit_mode(&mut self) {
64 self.mode = ActionMode::Normal;
65 self.input_buffer.clear();
66 }
67
68 pub fn action_delete(&mut self, nav: &mut NavState, worker_tx: &Sender<WorkerTask>) {
71 let targets = nav.get_action_targets();
72 if targets.is_empty() {
73 return;
74 }
75
76 let _ = worker_tx.send(WorkerTask::FileOp {
77 op: FileOperation::Delete(targets.into_iter().collect()),
78 request_id: nav.prepare_new_request(),
79 });
80
81 nav.clear_markers();
82 }
83
84 pub fn action_copy(&mut self, nav: &NavState, is_cut: bool) {
87 let mut set = HashSet::new();
88 if !nav.markers().is_empty() {
89 for path in nav.markers() {
90 set.insert(path.clone());
91 }
92 } else if let Some(entry) = nav.selected_entry() {
93 set.insert(nav.current_dir().join(entry.name()));
94 }
95 if !set.is_empty() {
96 self.clipboard = Some(set);
97 self.is_cut = is_cut;
98 }
99 }
100
101 pub fn action_paste(&mut self, nav: &mut NavState, worker_tx: &Sender<WorkerTask>) {
102 if let Some(source) = &self.clipboard {
103 let first_file_name = source
104 .iter()
105 .next()
106 .and_then(|p| p.file_name())
107 .map(|n| n.to_os_string());
108
109 let _ = worker_tx.send(WorkerTask::FileOp {
110 op: FileOperation::Copy {
111 src: source.iter().cloned().collect(),
112 dest: nav.current_dir().to_path_buf(),
113 cut: self.is_cut,
114 focus: first_file_name,
115 },
116 request_id: nav.prepare_new_request(),
117 });
118 if self.is_cut {
119 self.clipboard = None;
120 }
121 nav.clear_markers();
122 }
123 }
124
125 pub fn action_filter(&mut self, nav: &mut NavState) {
126 nav.set_filter(self.input_buffer.clone());
127 }
128
129 pub fn action_rename(&mut self, nav: &mut NavState, worker_tx: &Sender<WorkerTask>) {
130 if self.input_buffer.is_empty() {
131 return;
132 }
133 if let Some(entry) = nav.selected_entry() {
134 let old_path = nav.current_dir().join(entry.name());
135 let new_path = old_path.with_file_name(&self.input_buffer);
136
137 let _ = worker_tx.send(WorkerTask::FileOp {
138 op: FileOperation::Rename {
139 old: old_path,
140 new: new_path,
141 },
142 request_id: nav.prepare_new_request(),
143 });
144 }
145 self.exit_mode();
146 }
147
148 pub fn action_create(
149 &mut self,
150 nav: &mut NavState,
151 is_dir: bool,
152 worker_tx: &Sender<WorkerTask>,
153 ) {
154 if self.input_buffer.is_empty() {
155 return;
156 }
157
158 let path = nav.current_dir().join(&self.input_buffer);
159 let _ = worker_tx.send(WorkerTask::FileOp {
160 op: FileOperation::Create { path, is_dir },
161 request_id: nav.prepare_new_request(),
162 });
163 self.exit_mode();
164 }
165}
166
167impl Default for ActionContext {
168 fn default() -> Self {
169 Self {
170 mode: ActionMode::Normal,
171 input_buffer: String::new(),
172 clipboard: None,
173 is_cut: false,
174 }
175 }
176}