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