runa_tui/app/
actions.rs

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    // Getters/ accessors
33
34    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    // Actions
69
70    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    // Currently, cut/move is not implemented yet. Only copy/yank is used.
85    // This allows for easy addition of a cut/move feature in the future.
86    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}