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 OpenFileFinder,
44 OpenCommandPalette,
45 OverlayUp,
47 OverlayDown,
48 OverlaySelect,
49 OverlayDismiss,
50 OverlayFilter(char),
51 OverlayBackspace,
52 ToolFocusNext,
54 ToolFocusPrev,
55 OpenSelectedReadFile,
57 ToolToggle,
59}
60
61pub fn resolve_normal(key: KeyEvent) -> Option<Action> {
63 let ctrl = key.modifiers.contains(KeyModifiers::CONTROL);
64 let shift = key.modifiers.contains(KeyModifiers::SHIFT);
65 let alt = key.modifiers.contains(KeyModifiers::ALT);
66
67 match key.code {
68 KeyCode::Enter if alt => Some(Action::FollowUp),
70 KeyCode::Enter if shift => Some(Action::NewLine),
71 KeyCode::Enter => Some(Action::Submit),
72 KeyCode::Char('j') if ctrl => Some(Action::NewLine),
73
74 KeyCode::Char('c') if ctrl => Some(Action::Cancel),
76 KeyCode::Esc => Some(Action::Cancel),
77
78 KeyCode::Char('l') if ctrl => Some(Action::SelectModel),
80 KeyCode::Char('p') if ctrl && shift => Some(Action::CycleModelBackward),
81 KeyCode::Char('p') if ctrl => Some(Action::CycleModelForward),
82 KeyCode::Char('o') if ctrl => Some(Action::OpenSelectedReadFile),
83 KeyCode::BackTab => Some(Action::CycleThinking),
84
85 KeyCode::Tab => Some(Action::SidebarToggle),
87
88 KeyCode::Left if ctrl => Some(Action::WordLeft),
90 KeyCode::Right if ctrl => Some(Action::WordRight),
91 KeyCode::Left => Some(Action::CursorLeft),
92 KeyCode::Right => Some(Action::CursorRight),
93 KeyCode::Up if ctrl => Some(Action::ToolFocusPrev),
94 KeyCode::Down if ctrl => Some(Action::ToolFocusNext),
95 KeyCode::Up => Some(Action::CursorUp),
96 KeyCode::Down => Some(Action::CursorDown),
97 KeyCode::Home => Some(Action::CursorHome),
98 KeyCode::End => Some(Action::CursorEnd),
99
100 KeyCode::Char('a') if ctrl => Some(Action::CursorHome),
102 KeyCode::Char('e') if ctrl => Some(Action::CursorEnd),
103 KeyCode::Char('w') if ctrl => Some(Action::DeleteWordBack),
104 KeyCode::Char('u') if ctrl => Some(Action::DeleteToStart),
105 KeyCode::Char('k') if ctrl => Some(Action::DeleteToEnd),
106
107 KeyCode::Backspace => Some(Action::Backspace),
109 KeyCode::Delete => Some(Action::Delete),
110
111 KeyCode::PageUp => Some(Action::PageUp),
113 KeyCode::PageDown => Some(Action::PageDown),
114 KeyCode::Char('b') if ctrl => Some(Action::PageUp),
115 KeyCode::Char('f') if ctrl => Some(Action::PageDown),
116
117 KeyCode::Char(c) => Some(Action::InsertChar(c)),
119
120 _ => None,
121 }
122}
123
124pub fn resolve_overlay(key: KeyEvent) -> Option<Action> {
126 let ctrl = key.modifiers.contains(KeyModifiers::CONTROL);
127
128 match key.code {
129 KeyCode::Up => Some(Action::OverlayUp),
130 KeyCode::Down => Some(Action::OverlayDown),
131 KeyCode::Tab => Some(Action::OverlayDown),
132 KeyCode::BackTab => Some(Action::OverlayUp),
133 KeyCode::Char('n') if ctrl => Some(Action::OverlayDown),
134 KeyCode::Char('p') if ctrl => Some(Action::OverlayUp),
135 KeyCode::Enter => Some(Action::OverlaySelect),
136 KeyCode::Esc => Some(Action::OverlayDismiss),
137 KeyCode::Backspace => Some(Action::OverlayBackspace),
138 KeyCode::Char(c) => Some(Action::OverlayFilter(c)),
139 _ => None,
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[test]
148 fn ctrl_p_cycles_model_forward() {
149 assert_eq!(
150 resolve_normal(KeyEvent::new(KeyCode::Char('p'), KeyModifiers::CONTROL)),
151 Some(Action::CycleModelForward)
152 );
153 }
154
155 #[test]
156 fn ctrl_shift_p_cycles_model_backward() {
157 assert_eq!(
158 resolve_normal(KeyEvent::new(
159 KeyCode::Char('p'),
160 KeyModifiers::CONTROL | KeyModifiers::SHIFT
161 )),
162 Some(Action::CycleModelBackward)
163 );
164 }
165
166 #[test]
167 fn tab_toggles_sidebar() {
168 assert_eq!(
169 resolve_normal(KeyEvent::new(KeyCode::Tab, KeyModifiers::empty())),
170 Some(Action::SidebarToggle)
171 );
172 }
173
174 #[test]
175 fn ctrl_p_no_longer_toggles_sidebar() {
176 assert_ne!(
177 resolve_normal(KeyEvent::new(KeyCode::Char('p'), KeyModifiers::CONTROL)),
178 Some(Action::SidebarToggle)
179 );
180 }
181}