1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
use crate::app::{Action, AppState};
use crate::errors::ComponentError;
use crate::input::InputEvent;
use crossterm::event::{KeyCode, KeyEventKind};
pub fn handle_event(
event: InputEvent,
state: &AppState,
) -> Result<Option<Action>, ComponentError> {
match event {
InputEvent::Key(key) if key.kind == KeyEventKind::Press => {
match key.code {
KeyCode::Char('s') => Ok(Some(Action::StageSelectedFile)),
KeyCode::Char('u') => Ok(Some(Action::UnstageSelectedFile)),
KeyCode::Char('S') => Ok(Some(Action::StageAllFiles)),
KeyCode::Char('U') => Ok(Some(Action::UnstageAllFiles)),
KeyCode::Char('k') | KeyCode::Up => {
if state.status_selected == 0 {
Ok(None)
} else {
Ok(Some(Action::StatusUp))
}
}
KeyCode::Char('j') | KeyCode::Down => {
let len = state.status_entries.len();
if len == 0 || state.status_selected + 1 >= len {
Ok(None)
} else {
Ok(Some(Action::StatusDown))
}
}
KeyCode::PageUp => {
if state.status_selected == 0 {
Ok(None)
} else {
Ok(Some(Action::StatusPageUp))
}
}
KeyCode::PageDown => {
let len = state.status_entries.len();
if len == 0 || state.status_selected + 1 >= len {
Ok(None)
} else {
Ok(Some(Action::StatusPageDown))
}
}
KeyCode::Home => Ok(Some(Action::StatusTop)),
KeyCode::End => Ok(Some(Action::StatusBottom)),
KeyCode::Char('r') => {
// Smart refresh handling: visual feedback prevents abuse, not artificial limits
// Philosophy: Like IDEs, users should be able to refresh whenever they want
// The "⟳ REFRESHING…" indicator naturally prevents spam - users see it and wait
// Only prevent if already actively refreshing (prevents queueing multiple refreshes)
// This is not a rate limit - it's preventing redundant work
if state.refreshing {
// Already refreshing - ignore to prevent queueing multiple git operations
// User sees "⟳ REFRESHING…" so they know their action is being processed
Ok(None)
} else {
// Not refreshing - allow it immediately with visual feedback
// The visual indicator will prevent most abuse naturally
Ok(Some(Action::RequestRefreshStatus))
}
},
KeyCode::Char(' ') => {
if !state.status_selected_path.is_empty() {
Ok(Some(Action::ToggleFileSelect(state.status_selected_path.clone())))
} else {
Ok(None)
}
}
KeyCode::Char('n') => Ok(Some(Action::StatusNextStaged)),
KeyCode::Char('N') => Ok(Some(Action::StatusPrevStaged)),
KeyCode::Char('/') => Ok(Some(Action::StartFilter)),
_ => Ok(None),
}
}
_ => Ok(None),
}
}