prompt for confirmation on discard changes
- SHA
3f145c33a3ae9d41a8462b70262897f3383dbec2- Parents
-
ed9137e - Tree
56058ae
3f145c3
3f145c33a3ae9d41a8462b70262897f3383dbec2ed9137e
56058ae| Status | File | + | - |
|---|---|---|---|
| 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 { | ||
| 297 | 297 | self.refresh_files() |
| 298 | 298 | } |
| 299 | 299 | |
| 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 | + | |
| 300 | 317 | /// Discard changes to selected file |
| 301 | 318 | pub fn discard_selected(&mut self) -> Result<()> { |
| 302 | 319 | if let Some(item) = self.selected_item().cloned() { |
@@ -309,6 +326,38 @@ impl App { | ||
| 309 | 326 | Ok(()) |
| 310 | 327 | } |
| 311 | 328 | |
| 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 | + | |
| 312 | 361 | /// Delete selected file |
| 313 | 362 | pub fn delete_selected(&mut self) -> Result<()> { |
| 314 | 363 | 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) | ||
| 192 | 192 | app.unstage_all()?; |
| 193 | 193 | } |
| 194 | 194 | KeyCode::Char('x') | KeyCode::Char('X') if app.mode == AppMode::Git => { |
| 195 | - app.discard_selected()?; | |
| 195 | + app.confirm_discard(); | |
| 196 | 196 | } |
| 197 | 197 | KeyCode::Char('r') if app.mode == AppMode::Git => { |
| 198 | 198 | app.delete_selected()?; |
@@ -410,9 +410,12 @@ fn handle_search_key(app: &mut App, code: KeyCode) -> Result<()> { | ||
| 410 | 410 | fn handle_confirm_key(app: &mut App, code: KeyCode) -> Result<()> { |
| 411 | 411 | match code { |
| 412 | 412 | 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 | + } | |
| 416 | 419 | } |
| 417 | 420 | KeyCode::Esc | KeyCode::Char('n') | KeyCode::Char('N') => { |
| 418 | 421 | app.input_mode = InputMode::Navigation; |
src/ui.rsmodified@@ -32,6 +32,11 @@ pub fn draw(frame: &mut Frame, app: &App) { | ||
| 32 | 32 | if let InputMode::Push { remotes, selected, status } = &app.input_mode { |
| 33 | 33 | draw_push_modal(frame, remotes, *selected, status, &app.branch_name); |
| 34 | 34 | } |
| 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 | + } | |
| 35 | 40 | } |
| 36 | 41 | |
| 37 | 42 | /// Draw commit message modal |
@@ -201,6 +206,44 @@ fn draw_push_modal(frame: &mut Frame, remotes: &[String], selected: usize, statu | ||
| 201 | 206 | frame.render_widget(widget, modal_area); |
| 202 | 207 | } |
| 203 | 208 | |
| 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 | + | |
| 204 | 247 | /// Draw header with repo:branch info |
| 205 | 248 | fn draw_header(frame: &mut Frame, app: &App, area: Rect) { |
| 206 | 249 | let mut spans = vec![ |