runa_tui/app/
handlers.rs

1use crate::app::actions::{ActionMode, InputMode};
2use crate::app::{AppState, KeypressResult, NavState};
3use crate::keymap::{FileAction, NavAction};
4use crossterm::event::{KeyCode::*, KeyEvent};
5use std::time::{Duration, Instant};
6
7impl<'a> AppState<'a> {
8    // Handlers for app.rs
9
10    pub fn handle_input_mode(&mut self, key: KeyEvent) -> KeypressResult {
11        let mode = if let ActionMode::Input { mode, .. } = &self.actions().mode() {
12            *mode
13        } else {
14            return KeypressResult::Continue;
15        };
16
17        match key.code {
18            Enter => {
19                match mode {
20                    InputMode::NewFile => self.create_file(),
21                    InputMode::NewFolder => self.create_folder(),
22                    InputMode::Rename => self.rename_entry(),
23                    InputMode::Filter => self.apply_filter(),
24                    InputMode::ConfirmDelete => self.confirm_delete(),
25                }
26                self.exit_input_mode();
27                KeypressResult::Consumed
28            }
29
30            Esc => {
31                self.exit_input_mode();
32                KeypressResult::Consumed
33            }
34
35            Backspace => {
36                self.actions.input_buffer_mut().pop();
37                if matches!(mode, InputMode::Filter) {
38                    self.apply_filter();
39                }
40                KeypressResult::Consumed
41            }
42
43            Char(c) => match mode {
44                InputMode::ConfirmDelete => {
45                    self.process_confirm_delete_char(c);
46                    KeypressResult::Consumed
47                }
48                InputMode::Filter => {
49                    self.actions.input_buffer_mut().push(c);
50                    self.apply_filter();
51                    KeypressResult::Consumed
52                }
53                InputMode::Rename | InputMode::NewFile | InputMode::NewFolder => {
54                    self.actions.input_buffer_mut().push(c);
55                    if matches!(mode, InputMode::Filter) {
56                        self.apply_filter();
57                    }
58                    KeypressResult::Consumed
59                }
60            },
61
62            _ => KeypressResult::Consumed,
63        }
64    }
65
66    // Input proccess handlers
67
68    pub fn process_confirm_delete_char(&mut self, c: char) {
69        if matches!(c, 'y' | 'Y') {
70            self.confirm_delete();
71        }
72        self.exit_input_mode();
73    }
74
75    // Actions
76
77    pub fn exit_input_mode(&mut self) {
78        self.actions.exit_mode();
79    }
80
81    fn create_file(&mut self) {
82        if !self.actions.input_buffer().is_empty() {
83            self.actions
84                .action_create(&mut self.nav, false, &self.worker_tx);
85        }
86    }
87
88    fn create_folder(&mut self) {
89        if !self.actions.input_buffer().is_empty() {
90            self.actions
91                .action_create(&mut self.nav, true, &self.worker_tx);
92        }
93    }
94
95    fn rename_entry(&mut self) {
96        self.actions.action_rename(&mut self.nav, &self.worker_tx);
97    }
98
99    fn apply_filter(&mut self) {
100        self.actions.action_filter(&mut self.nav);
101    }
102
103    fn confirm_delete(&mut self) {
104        self.actions.action_delete(&mut self.nav, &self.worker_tx);
105    }
106
107    // Nav actions handlers
108
109    pub fn handle_nav_action(&mut self, action: NavAction) -> KeypressResult {
110        match action {
111            NavAction::GoUp => self.move_nav_if_possible(|nav| nav.move_up()),
112            NavAction::GoDown => self.move_nav_if_possible(|nav| nav.move_down()),
113            NavAction::GoParent => return self.handle_go_parent(),
114            NavAction::GoIntoDir => return self.handle_go_into_dir(),
115            NavAction::ToggleMarker => self.nav.toggle_marker(),
116        }
117        KeypressResult::Continue
118    }
119
120    fn move_nav_if_possible<F>(&mut self, f: F)
121    where
122        F: FnOnce(&mut NavState) -> bool,
123    {
124        if f(&mut self.nav) {
125            self.preview.mark_pending();
126        }
127    }
128
129    fn handle_go_parent(&mut self) -> KeypressResult {
130        if let Some(parent) = self.nav.current_dir().parent() {
131            let exited_name = self.nav.current_dir().file_name().map(|n| n.to_os_string());
132            let parent_path = parent.to_path_buf();
133            self.nav.save_position();
134            self.nav.set_path(parent_path);
135            // Clear the applied filter when we go into a parent directory
136            self.nav.clear_filters();
137
138            self.request_dir_load(exited_name);
139            self.request_parent_content();
140        }
141        KeypressResult::Continue
142    }
143
144    fn handle_go_into_dir(&mut self) -> KeypressResult {
145        if let Some(entry) = self.nav.selected_shown_entry()
146            && entry.is_dir()
147        {
148            let new_path = self.nav.current_dir().join(entry.name());
149            self.nav.save_position();
150            self.nav.set_path(new_path);
151
152            self.request_dir_load(None);
153            self.request_parent_content();
154        }
155        KeypressResult::Continue
156    }
157
158    // File action handlers
159
160    pub fn handle_file_action(&mut self, action: FileAction) -> KeypressResult {
161        match action {
162            FileAction::Open => return self.handle_open_file(),
163            FileAction::Delete => self.prompt_delete(),
164            FileAction::Copy => {
165                self.actions.action_copy(&self.nav, false);
166                self.notification_time = Some(Instant::now() + Duration::from_secs(2));
167            }
168            FileAction::Paste => self.actions.action_paste(&mut self.nav, &self.worker_tx),
169            FileAction::Rename => self.prompt_rename(),
170            FileAction::Create => self.prompt_create_file(),
171            FileAction::CreateDirectory => self.prompt_create_folder(),
172            FileAction::Filter => self.prompt_filter(),
173        }
174        KeypressResult::Continue
175    }
176
177    fn handle_open_file(&mut self) -> KeypressResult {
178        if let Some(entry) = self.nav.selected_shown_entry() {
179            let path = self.nav.current_dir().join(entry.name());
180            if let Err(e) = crate::utils::open_in_editor(self.config.editor(), &path) {
181                eprintln!("Error: {}", e);
182            }
183            KeypressResult::OpenedEditor
184        } else {
185            KeypressResult::Continue
186        }
187    }
188
189    // Action prompts
190
191    fn prompt_delete(&mut self) {
192        let targets = self.nav.get_action_targets();
193        if targets.is_empty() {
194            return;
195        }
196        let prompt_text = format!(
197            "Delete {} item{}? [Y/N]",
198            targets.len(),
199            if targets.len() > 1 { "s" } else { "" }
200        );
201        self.enter_input_mode(InputMode::ConfirmDelete, prompt_text, None);
202    }
203
204    fn prompt_rename(&mut self) {
205        if let Some(entry) = self.nav.selected_shown_entry() {
206            let name = entry.name().to_string_lossy().to_string();
207            self.enter_input_mode(InputMode::Rename, "Rename: ".to_string(), Some(name));
208        }
209    }
210
211    fn prompt_create_file(&mut self) {
212        self.enter_input_mode(InputMode::NewFile, "New File: ".to_string(), None);
213    }
214
215    fn prompt_create_folder(&mut self) {
216        self.enter_input_mode(InputMode::NewFolder, "New Folder: ".to_string(), None);
217    }
218
219    fn prompt_filter(&mut self) {
220        let current_filter = self.nav.filter().to_string();
221        self.enter_input_mode(
222            InputMode::Filter,
223            "Filter: ".to_string(),
224            Some(current_filter),
225        );
226    }
227
228    pub fn enter_input_mode(&mut self, mode: InputMode, prompt: String, initial: Option<String>) {
229        let buffer = initial.unwrap_or_default();
230        self.actions
231            .enter_mode(ActionMode::Input { mode, prompt }, buffer);
232    }
233}