1use std::{cmp, io};
2
3use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
4
5use crate::{macros::*, Menu, Mode};
6
7pub(crate) struct KeyResponse(bool, bool);
8
9impl KeyResponse {
10 fn new(exit: bool, redraw: bool) -> Self {
11 KeyResponse(exit, redraw)
12 }
13
14 pub(crate) fn exit(&self) -> bool {
15 self.0
16 }
17
18 pub(crate) fn redraw(&self) -> bool {
19 self.1
20 }
21}
22
23impl<T: Send + Sync> Menu<T> {
24 pub(crate) fn dispatch_key(&mut self, key: KeyEvent) -> io::Result<KeyResponse> {
25 match key.modifiers {
26 KeyModifiers::NONE | KeyModifiers::SHIFT => self.dispatch_code(key.code),
27
28 KeyModifiers::CONTROL => {
29 if key.code == KeyCode::Char('c') {
30 return Ok(KeyResponse::new(true, false));
31 }
32 match key.code {
33 KeyCode::Char('n') => return self.key_down(),
34 KeyCode::Char('p') => return self.key_up(),
35 _ => {}
36 }
37 Ok(KeyResponse::new(false, false))
38 }
39
40 _ => Ok(KeyResponse::new(false, false)),
41 }
42 }
43
44 fn dispatch_code(&mut self, code: KeyCode) -> io::Result<KeyResponse> {
45 match self.mode {
46 Mode::Normal => self.dispatch_normal(code),
47 Mode::Query => self.dispatch_query(code),
48 }
49 }
50
51 fn dispatch_normal(&mut self, code: KeyCode) -> io::Result<KeyResponse> {
52 match code {
53 KeyCode::Up | KeyCode::Char('k') => return self.key_up(),
54
55 KeyCode::Down | KeyCode::Char('j') => return self.key_down(),
56
57 KeyCode::Esc => return self.key_esc(),
58
59 KeyCode::Enter => return self.key_enter(),
60
61 KeyCode::Char('/') => {
62 self.enter_query_mode()?;
63 }
64 _ => {}
65 };
66 Ok(KeyResponse::new(false, true))
67 }
68
69 fn dispatch_query(&mut self, code: KeyCode) -> io::Result<KeyResponse> {
70 match code {
71 KeyCode::Esc => return self.key_esc(),
72
73 KeyCode::Up => return self.key_up(),
74
75 KeyCode::Down => return self.key_down(),
76
77 KeyCode::Left => {
78 self.insert_idx = self.insert_idx.saturating_sub(1);
79 }
80
81 KeyCode::Right => {
82 self.insert_idx = cmp::min(self.insert_idx + 1, self.query.len());
83 }
84
85 KeyCode::Enter => return self.key_enter(),
86
87 KeyCode::Char(c) => {
88 let insert_pos = get_insert_pos!(&self.query, self.insert_idx);
89 self.query.insert(insert_pos, c);
90 self.insert_idx += 1;
91 self.fuzzy_match();
92 }
93
94 KeyCode::Backspace => {
95 if !self.query.is_empty() {
96 self.insert_idx = self.insert_idx.saturating_sub(1);
97 let pos = get_insert_pos!(&self.query, self.insert_idx);
98 self.query.remove(pos);
99 self.fuzzy_match();
100 }
101 }
102 _ => {}
103 }
104 Ok(KeyResponse(false, true))
105 }
106}
107
108impl<T: Send + Sync> Menu<T> {
109 fn key_up(&mut self) -> io::Result<KeyResponse> {
110 if self.selection_idx == 0 {
111 if self.scroll_offset == 0 {
112 return Ok(KeyResponse(false, false));
113 }
114 self.scroll_offset -= 1;
115 return Ok(KeyResponse(false, true));
116 }
117 self.selection_idx -= 1;
118 Ok(KeyResponse(false, true))
119 }
120
121 fn key_down(&mut self) -> io::Result<KeyResponse> {
122 let item_cnt = match self.mode {
123 Mode::Normal => self.item_list.len(),
124 Mode::Query => self.matched_item_indices.len(),
125 } as u16;
126 if self.selection_idx + self.scroll_offset == item_cnt - 1 {
127 return Ok(KeyResponse(false, false));
128 }
129 let (row, _) = self.cursor_abs_pos;
130 if self.selection_idx + row == self.max_row - 3 {
131 self.scroll_offset += 1;
132 return Ok(KeyResponse(false, true));
133 }
134 self.selection_idx += 1;
135 Ok(KeyResponse(false, true))
136 }
137
138 fn key_esc(&mut self) -> io::Result<KeyResponse> {
139 match self.mode {
140 Mode::Normal => Ok(KeyResponse(true, false)),
141 _ => {
142 self.enter_normal_mode()?;
143 Ok(KeyResponse(false, true))
144 }
145 }
146 }
147
148 fn key_enter(&mut self) -> io::Result<KeyResponse> {
149 match self.mode {
150 Mode::Normal => {
151 self.selected = true;
152 }
153 Mode::Query => {
154 if !self.matched_item_indices.is_empty() {
155 self.selected = true;
156 }
157 }
158 }
159 Ok(KeyResponse(true, false))
160 }
161}
162
163impl<T: Send + Sync> Menu<T> {
164 pub(crate) fn get_query_cursor_col(&self) -> u16 {
165 let mut col = 0;
166
167 let prefix = format!("{} /", self.get_title());
169 col += prefix
170 .chars()
171 .fold(0, |acc, c| acc + cmp::min(2, c.len_utf8() as u16));
173
174 col += self
176 .query
177 .chars()
178 .take(self.insert_idx)
179 .fold(0, |acc, c| acc + cmp::min(2, c.len_utf8() as u16));
180
181 col
182 }
183}