sql_cli/handlers/
navigation.rs1use crate::ui::input::actions::{Action, NavigateAction};
5use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
6
7pub struct NavigationHandler;
8
9impl Default for NavigationHandler {
10 fn default() -> Self {
11 Self::new()
12 }
13}
14
15impl NavigationHandler {
16 #[must_use]
17 pub fn new() -> Self {
18 NavigationHandler
19 }
20
21 #[must_use]
24 pub fn handle_key(&self, key: KeyEvent, mode: &crate::buffer::AppMode) -> Option<Action> {
25 use crate::buffer::AppMode;
26
27 if !matches!(mode, AppMode::Results) {
29 return None;
30 }
31
32 match key.code {
33 KeyCode::Char('h') if !key.modifiers.contains(KeyModifiers::SHIFT) => {
35 Some(Action::Navigate(NavigateAction::Left(1)))
36 }
37 KeyCode::Char('j') if !key.modifiers.contains(KeyModifiers::CONTROL) => {
38 Some(Action::Navigate(NavigateAction::Down(1)))
39 }
40 KeyCode::Char('k') if !key.modifiers.contains(KeyModifiers::CONTROL) => {
41 Some(Action::Navigate(NavigateAction::Up(1)))
42 }
43 KeyCode::Char('l') => Some(Action::Navigate(NavigateAction::Right(1))),
44
45 KeyCode::Up if !key.modifiers.contains(KeyModifiers::ALT) => {
47 Some(Action::Navigate(NavigateAction::Up(1)))
48 }
49 KeyCode::Down if !key.modifiers.contains(KeyModifiers::ALT) => {
50 Some(Action::Navigate(NavigateAction::Down(1)))
51 }
52 KeyCode::Left
53 if !key.modifiers.contains(KeyModifiers::SHIFT)
54 && !key.modifiers.contains(KeyModifiers::CONTROL) =>
55 {
56 Some(Action::Navigate(NavigateAction::Left(1)))
57 }
58 KeyCode::Right
59 if !key.modifiers.contains(KeyModifiers::SHIFT)
60 && !key.modifiers.contains(KeyModifiers::CONTROL) =>
61 {
62 Some(Action::Navigate(NavigateAction::Right(1)))
63 }
64
65 KeyCode::PageUp => Some(Action::Navigate(NavigateAction::PageUp)),
67 KeyCode::PageDown => Some(Action::Navigate(NavigateAction::PageDown)),
68 KeyCode::Char('f') if key.modifiers.contains(KeyModifiers::CONTROL) => {
69 Some(Action::Navigate(NavigateAction::PageDown))
70 }
71 KeyCode::Char('b') if key.modifiers.contains(KeyModifiers::CONTROL) => {
72 Some(Action::Navigate(NavigateAction::PageUp))
73 }
74
75 KeyCode::Home if !key.modifiers.contains(KeyModifiers::SHIFT) => {
77 Some(Action::Navigate(NavigateAction::Home))
78 }
79 KeyCode::End if !key.modifiers.contains(KeyModifiers::SHIFT) => {
80 Some(Action::Navigate(NavigateAction::End))
81 }
82
83 KeyCode::Char('g') if !key.modifiers.contains(KeyModifiers::CONTROL) => {
85 Some(Action::Navigate(NavigateAction::Home))
86 }
87 KeyCode::Char('G') => Some(Action::Navigate(NavigateAction::End)),
88
89 KeyCode::Char('H') if key.modifiers.contains(KeyModifiers::SHIFT) => {
91 Some(Action::NavigateToViewportTop)
92 }
93 KeyCode::Char('M') => Some(Action::NavigateToViewportMiddle),
94 KeyCode::Char('L') if key.modifiers.contains(KeyModifiers::SHIFT) => {
95 Some(Action::NavigateToViewportBottom)
96 }
97
98 KeyCode::Char('^') => Some(Action::Navigate(NavigateAction::FirstColumn)),
100 KeyCode::Char('$') => Some(Action::Navigate(NavigateAction::LastColumn)),
101
102 KeyCode::Tab if !key.modifiers.contains(KeyModifiers::SHIFT) => {
104 Some(Action::NextColumn)
105 }
106 KeyCode::BackTab | KeyCode::Tab if key.modifiers.contains(KeyModifiers::SHIFT) => {
107 Some(Action::PreviousColumn)
108 }
109
110 KeyCode::Char('x') => Some(Action::ToggleCursorLock),
112 KeyCode::Char(' ') if key.modifiers.contains(KeyModifiers::CONTROL) => {
113 Some(Action::ToggleViewportLock)
114 }
115
116 _ => None,
117 }
118 }
119
120 #[must_use]
122 pub fn is_navigation_key(&self, key: &KeyEvent, mode: &crate::buffer::AppMode) -> bool {
123 self.handle_key(*key, mode).is_some()
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130 use crate::buffer::AppMode;
131 use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
132
133 #[test]
134 fn test_vim_navigation() {
135 let handler = NavigationHandler::new();
136 let mode = AppMode::Results;
137
138 let h_key = KeyEvent::new(KeyCode::Char('h'), KeyModifiers::empty());
140 assert_eq!(
141 handler.handle_key(h_key, &mode),
142 Some(Action::Navigate(NavigateAction::Left(1)))
143 );
144
145 let j_key = KeyEvent::new(KeyCode::Char('j'), KeyModifiers::empty());
146 assert_eq!(
147 handler.handle_key(j_key, &mode),
148 Some(Action::Navigate(NavigateAction::Down(1)))
149 );
150
151 let k_key = KeyEvent::new(KeyCode::Char('k'), KeyModifiers::empty());
152 assert_eq!(
153 handler.handle_key(k_key, &mode),
154 Some(Action::Navigate(NavigateAction::Up(1)))
155 );
156
157 let l_key = KeyEvent::new(KeyCode::Char('l'), KeyModifiers::empty());
158 assert_eq!(
159 handler.handle_key(l_key, &mode),
160 Some(Action::Navigate(NavigateAction::Right(1)))
161 );
162 }
163
164 #[test]
165 fn test_arrow_keys() {
166 let handler = NavigationHandler::new();
167 let mode = AppMode::Results;
168
169 let up_key = KeyEvent::new(KeyCode::Up, KeyModifiers::empty());
170 assert_eq!(
171 handler.handle_key(up_key, &mode),
172 Some(Action::Navigate(NavigateAction::Up(1)))
173 );
174
175 let down_key = KeyEvent::new(KeyCode::Down, KeyModifiers::empty());
176 assert_eq!(
177 handler.handle_key(down_key, &mode),
178 Some(Action::Navigate(NavigateAction::Down(1)))
179 );
180 }
181
182 #[test]
183 fn test_page_navigation() {
184 let handler = NavigationHandler::new();
185 let mode = AppMode::Results;
186
187 let pageup_key = KeyEvent::new(KeyCode::PageUp, KeyModifiers::empty());
188 assert_eq!(
189 handler.handle_key(pageup_key, &mode),
190 Some(Action::Navigate(NavigateAction::PageUp))
191 );
192
193 let pagedown_key = KeyEvent::new(KeyCode::PageDown, KeyModifiers::empty());
194 assert_eq!(
195 handler.handle_key(pagedown_key, &mode),
196 Some(Action::Navigate(NavigateAction::PageDown))
197 );
198 }
199
200 #[test]
201 fn test_not_in_results_mode() {
202 let handler = NavigationHandler::new();
203 let mode = AppMode::Command;
204
205 let h_key = KeyEvent::new(KeyCode::Char('h'), KeyModifiers::empty());
206 assert_eq!(handler.handle_key(h_key, &mode), None);
207 }
208}