tenseleyflow/fussr / 3f145c3

Browse files

prompt for confirmation on discard changes

Authored by espadonne
SHA
3f145c33a3ae9d41a8462b70262897f3383dbec2
Parents
ed9137e
Tree
56058ae

3 changed files

StatusFile+-
M src/app.rs 49 0
M src/main.rs 7 4
M src/ui.rs 43 0
src/app.rsmodified
@@ -297,6 +297,23 @@ impl App {
297297
         self.refresh_files()
298298
     }
299299
 
300
+    /// Show confirmation dialog for discard
301
+    pub fn confirm_discard(&mut self) {
302
+        if let Some(item) = self.selected_item().cloned() {
303
+            if item.is_file && item.status.is_dirty() {
304
+                let path = item.path.clone();
305
+                let filename = path.file_name()
306
+                    .and_then(|n| n.to_str())
307
+                    .unwrap_or("file")
308
+                    .to_string();
309
+                self.input_mode = InputMode::Confirm {
310
+                    message: format!("Discard changes to '{}'?", filename),
311
+                    action: crate::types::ConfirmAction::Discard(path),
312
+                };
313
+            }
314
+        }
315
+    }
316
+
300317
     /// Discard changes to selected file
301318
     pub fn discard_selected(&mut self) -> Result<()> {
302319
         if let Some(item) = self.selected_item().cloned() {
@@ -309,6 +326,38 @@ impl App {
309326
         Ok(())
310327
     }
311328
 
329
+    /// Execute a confirmed action
330
+    pub fn execute_confirm_action(&mut self, action: &crate::types::ConfirmAction) -> Result<()> {
331
+        match action {
332
+            crate::types::ConfirmAction::Discard(path) => {
333
+                // Find status for this path
334
+                let is_untracked = self.items.iter()
335
+                    .find(|i| &i.path == path)
336
+                    .map(|i| i.status.is_untracked)
337
+                    .unwrap_or(false);
338
+                self.repo.discard_changes(path, is_untracked)?;
339
+                self.set_status(format!("Discarded: {}", path.display()));
340
+                self.refresh_files()?;
341
+            }
342
+            crate::types::ConfirmAction::Delete(path) => {
343
+                let is_untracked = self.items.iter()
344
+                    .find(|i| &i.path == path)
345
+                    .map(|i| i.status.is_untracked)
346
+                    .unwrap_or(false);
347
+                self.repo.delete_file(path, is_untracked)?;
348
+                self.set_status(format!("Deleted: {}", path.display()));
349
+                self.refresh_files()?;
350
+            }
351
+            crate::types::ConfirmAction::StageAll => {
352
+                self.stage_all()?;
353
+            }
354
+            crate::types::ConfirmAction::UnstageAll => {
355
+                self.unstage_all()?;
356
+            }
357
+        }
358
+        Ok(())
359
+    }
360
+
312361
     /// Delete selected file
313362
     pub fn delete_selected(&mut self) -> Result<()> {
314363
         if let Some(item) = self.selected_item().cloned() {
src/main.rsmodified
@@ -192,7 +192,7 @@ fn handle_navigation_key(app: &mut App, code: KeyCode, modifiers: KeyModifiers)
192192
             app.unstage_all()?;
193193
         }
194194
         KeyCode::Char('x') | KeyCode::Char('X') if app.mode == AppMode::Git => {
195
-            app.discard_selected()?;
195
+            app.confirm_discard();
196196
         }
197197
         KeyCode::Char('r') if app.mode == AppMode::Git => {
198198
             app.delete_selected()?;
@@ -410,9 +410,12 @@ fn handle_search_key(app: &mut App, code: KeyCode) -> Result<()> {
410410
 fn handle_confirm_key(app: &mut App, code: KeyCode) -> Result<()> {
411411
     match code {
412412
         KeyCode::Char('y') | KeyCode::Char('Y') => {
413
-            // Execute the confirmed action
414
-            // TODO: Implement confirmation actions
415
-            app.input_mode = InputMode::Navigation;
413
+            // Extract action and execute
414
+            if let InputMode::Confirm { action, .. } = &app.input_mode {
415
+                let action = action.clone();
416
+                app.input_mode = InputMode::Navigation;
417
+                app.execute_confirm_action(&action)?;
418
+            }
416419
         }
417420
         KeyCode::Esc | KeyCode::Char('n') | KeyCode::Char('N') => {
418421
             app.input_mode = InputMode::Navigation;
src/ui.rsmodified
@@ -32,6 +32,11 @@ pub fn draw(frame: &mut Frame, app: &App) {
3232
     if let InputMode::Push { remotes, selected, status } = &app.input_mode {
3333
         draw_push_modal(frame, remotes, *selected, status, &app.branch_name);
3434
     }
35
+
36
+    // Draw modal overlay if in confirm mode
37
+    if let InputMode::Confirm { message, .. } = &app.input_mode {
38
+        draw_confirm_modal(frame, message);
39
+    }
3540
 }
3641
 
3742
 /// Draw commit message modal
@@ -201,6 +206,44 @@ fn draw_push_modal(frame: &mut Frame, remotes: &[String], selected: usize, statu
201206
     frame.render_widget(widget, modal_area);
202207
 }
203208
 
209
+/// Draw confirmation modal
210
+fn draw_confirm_modal(frame: &mut Frame, message: &str) {
211
+    let area = frame.area();
212
+
213
+    let modal_width = 50.min(area.width.saturating_sub(4));
214
+    let modal_height = 5;
215
+    let x = (area.width.saturating_sub(modal_width)) / 2;
216
+    let y = (area.height.saturating_sub(modal_height)) / 2;
217
+
218
+    let modal_area = Rect::new(x, y, modal_width, modal_height);
219
+
220
+    // Clear area behind modal
221
+    frame.render_widget(Clear, modal_area);
222
+
223
+    let block = Block::default()
224
+        .title(" Confirm ")
225
+        .borders(Borders::ALL)
226
+        .border_style(Style::default().fg(Color::Yellow));
227
+
228
+    let content = vec![
229
+        Line::from(""),
230
+        Line::from(Span::styled(
231
+            format!("  {}", message),
232
+            Style::default().fg(Color::White),
233
+        )),
234
+        Line::from(vec![
235
+            Span::raw("  "),
236
+            Span::styled("y", Style::default().fg(Color::Green).add_modifier(Modifier::BOLD)),
237
+            Span::raw("es / "),
238
+            Span::styled("n", Style::default().fg(Color::Red).add_modifier(Modifier::BOLD)),
239
+            Span::raw("o"),
240
+        ]),
241
+    ];
242
+
243
+    let widget = Paragraph::new(content).block(block);
244
+    frame.render_widget(widget, modal_area);
245
+}
246
+
204247
 /// Draw header with repo:branch info
205248
 fn draw_header(frame: &mut Frame, app: &App, area: Rect) {
206249
     let mut spans = vec![