1use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
2
3#[derive(Debug, Clone, PartialEq, Eq)]
5pub enum Action {
6 Submit,
8 NewLine,
9 InsertChar(char),
10 Backspace,
11 Delete,
12 CursorLeft,
13 CursorRight,
14 CursorUp,
15 CursorDown,
16 CursorHome,
17 CursorEnd,
18 WordLeft,
19 WordRight,
20 DeleteWordBack,
21 DeleteToStart,
22 DeleteToEnd,
23 SelectAll,
24 ScrollUp,
26 ScrollDown,
27 PageUp,
28 PageDown,
29 Cancel,
31 FollowUp,
32 SelectModel,
34 CycleModelForward,
35 CycleModelBackward,
36 CycleThinking,
37 Peek,
38 SidebarToggle,
39 SessionTree,
40 Reload,
41 Quit,
42 OpenCommandPalette,
44 OverlayUp,
46 OverlayDown,
47 OverlaySelect,
48 OverlayDismiss,
49 OverlayFilter(char),
50 OverlayBackspace,
51 ToolFocusNext,
53 ToolFocusPrev,
54 OpenSelectedReadFile,
56 ToolToggle,
58}
59
60pub fn resolve_normal(key: KeyEvent) -> Option<Action> {
62 let ctrl = key.modifiers.contains(KeyModifiers::CONTROL);
63 let shift = key.modifiers.contains(KeyModifiers::SHIFT);
64 let alt = key.modifiers.contains(KeyModifiers::ALT);
65
66 match key.code {
67 KeyCode::Enter if alt => Some(Action::FollowUp),
69 KeyCode::Enter if shift => Some(Action::NewLine),
70 KeyCode::Enter => Some(Action::Submit),
71 KeyCode::Char('j') if ctrl => Some(Action::NewLine),
72
73 KeyCode::Char('c') if ctrl => Some(Action::Cancel),
75 KeyCode::Esc => Some(Action::Cancel),
76
77 KeyCode::Char('l') if ctrl => Some(Action::SelectModel),
79 KeyCode::Char('p') if ctrl && shift => Some(Action::CycleModelBackward),
80 KeyCode::Char('p') if ctrl => Some(Action::CycleModelForward),
81 KeyCode::Char('o') if ctrl => Some(Action::OpenSelectedReadFile),
82 KeyCode::BackTab => Some(Action::CycleThinking),
83
84 KeyCode::Tab => Some(Action::SidebarToggle),
86
87 KeyCode::Left if ctrl => Some(Action::WordLeft),
89 KeyCode::Right if ctrl => Some(Action::WordRight),
90 KeyCode::Left => Some(Action::CursorLeft),
91 KeyCode::Right => Some(Action::CursorRight),
92 KeyCode::Up if ctrl => Some(Action::ToolFocusPrev),
93 KeyCode::Down if ctrl => Some(Action::ToolFocusNext),
94 KeyCode::Up => Some(Action::CursorUp),
95 KeyCode::Down => Some(Action::CursorDown),
96 KeyCode::Home => Some(Action::CursorHome),
97 KeyCode::End => Some(Action::CursorEnd),
98
99 KeyCode::Char('a') if ctrl => Some(Action::CursorHome),
101 KeyCode::Char('e') if ctrl => Some(Action::CursorEnd),
102 KeyCode::Char('w') if ctrl => Some(Action::DeleteWordBack),
103 KeyCode::Char('u') if ctrl => Some(Action::DeleteToStart),
104 KeyCode::Char('k') if ctrl => Some(Action::DeleteToEnd),
105
106 KeyCode::Backspace => Some(Action::Backspace),
108 KeyCode::Delete => Some(Action::Delete),
109
110 KeyCode::PageUp => Some(Action::PageUp),
112 KeyCode::PageDown => Some(Action::PageDown),
113 KeyCode::Char('b') if ctrl => Some(Action::PageUp),
114 KeyCode::Char('f') if ctrl => Some(Action::PageDown),
115
116 KeyCode::Char(c) => Some(Action::InsertChar(c)),
118
119 _ => None,
120 }
121}
122
123pub fn resolve_overlay(key: KeyEvent) -> Option<Action> {
125 let ctrl = key.modifiers.contains(KeyModifiers::CONTROL);
126
127 match key.code {
128 KeyCode::Up => Some(Action::OverlayUp),
129 KeyCode::Down => Some(Action::OverlayDown),
130 KeyCode::Tab => Some(Action::OverlayDown),
131 KeyCode::BackTab => Some(Action::OverlayUp),
132 KeyCode::Char('n') if ctrl => Some(Action::OverlayDown),
133 KeyCode::Char('p') if ctrl => Some(Action::OverlayUp),
134 KeyCode::Enter => Some(Action::OverlaySelect),
135 KeyCode::Esc => Some(Action::OverlayDismiss),
136 KeyCode::Backspace => Some(Action::OverlayBackspace),
137 KeyCode::Char(c) => Some(Action::OverlayFilter(c)),
138 _ => None,
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 #[test]
147 fn ctrl_p_cycles_model_forward() {
148 assert_eq!(
149 resolve_normal(KeyEvent::new(KeyCode::Char('p'), KeyModifiers::CONTROL)),
150 Some(Action::CycleModelForward)
151 );
152 }
153
154 #[test]
155 fn ctrl_shift_p_cycles_model_backward() {
156 assert_eq!(
157 resolve_normal(KeyEvent::new(
158 KeyCode::Char('p'),
159 KeyModifiers::CONTROL | KeyModifiers::SHIFT
160 )),
161 Some(Action::CycleModelBackward)
162 );
163 }
164
165 #[test]
166 fn tab_toggles_sidebar() {
167 assert_eq!(
168 resolve_normal(KeyEvent::new(KeyCode::Tab, KeyModifiers::empty())),
169 Some(Action::SidebarToggle)
170 );
171 }
172
173 #[test]
174 fn ctrl_p_no_longer_toggles_sidebar() {
175 assert_ne!(
176 resolve_normal(KeyEvent::new(KeyCode::Char('p'), KeyModifiers::CONTROL)),
177 Some(Action::SidebarToggle)
178 );
179 }
180}