1use std::{collections::HashSet, fmt, hash::Hash};
2
3use crossterm::event::KeyCode;
4use key_parse::keymap::*;
5use serde::{Deserialize, Serialize};
6
7macro_rules! actions {
9 ($( ($key:ident, $val:literal) ); *) => {
10 $(pub const $key: &str = $val;)*
11 };
12}
13actions!(
14 (PANEL_UP, "panel_up");
15 (PANEL_DOWN, "panel_down");
16 (PANEL_RIGHT, "panel_right");
17 (PANEL_LEFT, "panel_left");
18
19 (UP, "up");
20 (DOWN, "down");
21 (RIGHT, "right");
22 (LEFT, "left");
23
24 (TOGGLE_CURSOR, "toggle");
25
26 (TOP, "top");
27 (BOTTOM, "bottom");
28
29 (REDRAW, "redraw");
30 (EXIT, "exit");
31
32 (EDIT_CODE_EDITOR, "edit_code");
33 (EDIT_IN_TUI, "edit_code_tui");
34
35 (TOGGLE_SUBMIT_RES, "toggle_submit_res");
36 (TOGGLE_TEST_RES, "toggle_test_res");
37 (TOGGLE_MENU, "toggle_menu");
38
39 (RE_QS_DETAIL, "re_get_qs");
40
41 (NEXT_TAB, "next_tab");
42 (PREV_TAB, "prev_tab");
43
44 (HEAD, "head");
45 (TAIL, "tail");
46
47 (SYNC_INDEX, "sync_index");
48
49 (ESCAPE, "escape");
50
51 (ADD_TEST_CASE, "add_test_case")
52);
53
54#[derive(Clone)]
55#[derive(Debug)]
56#[derive(Default)]
57#[derive(Eq)]
58#[derive(Serialize, Deserialize)]
59pub struct KeyMap {
60 pub keys: Keys,
61 pub action: String,
62 #[serde(default)]
63 pub desc: String,
64}
65
66impl fmt::Display for KeyMap {
67 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68 let res = toml::to_string(self).unwrap_or_else(|_| "unknown keymap\n\n".to_owned());
69 let mut a = res.split('\n');
70 format!(
71 "{:20}, {:30}, {}",
72 a.next().unwrap_or_default(),
73 a.next().unwrap_or_default(),
74 a.next().unwrap_or_default()
75 )
76 .fmt(f)
77 }
78}
79
80impl PartialEq for KeyMap {
81 fn eq(&self, other: &Self) -> bool {
82 self.action == other.action
83 }
84}
85
86impl Hash for KeyMap {
87 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
88 self.action.hash(state);
89 }
90}
91
92#[derive(Serialize, Deserialize)]
93#[derive(PartialEq, Eq)]
94#[derive(Clone)]
95#[derive(Debug)]
96pub struct TuiKeyMap {
97 #[serde(default)]
98 #[serde(rename = "keymap")]
99 pub map_set: HashSet<KeyMap>,
100}
101
102impl TuiKeyMap {
103 pub fn add_keymap(&mut self, add: HashSet<KeyMap>) {
105 for ele in add {
106 self.map_set.replace(ele);
107 }
108 }
109}
110
111macro_rules! keymaps {
112 ($( ($keys:expr, $action:expr, $desc:literal) ); *) => {
113 [
114 $(
115 KeyMap {
116 keys: Keys($keys),
117 action: $action.to_owned(),
118 desc: $desc.to_owned(),
119 },
120 )*
121 ]
122 };
123}
124
125const fn kc(ch: char) -> KeyCode {
126 KeyCode::Char(ch)
127}
128
129impl Default for TuiKeyMap {
130 fn default() -> Self {
131 let (tab, backtab) = (KeyCode::Tab, KeyCode::BackTab);
132 let esc = KeyCode::Esc;
133 let enter = KeyCode::Enter;
134
135 let g = Key::new(NO_CONTROL, kc('g'));
136
137 let mps = keymaps!(
138 (vec![g, g], TOP, "Go to top");
139 (vec![Key::new(SHIFT, kc('G'))], BOTTOM, "Go to bottom");
140
141 (vec![Key::new(SHIFT, kc('H'))], HEAD, "To the first column of the panel content.");
142 (vec![Key::new(SHIFT, kc('L'))], TAIL, "To the last column of the panel content.");
143
144 (vec![Key::new(NO_CONTROL, kc('h'))], LEFT, "Panel content move left");
145 (vec![Key::new(NO_CONTROL, kc('j'))], DOWN, "Panel content move down");
146 (vec![Key::new(NO_CONTROL, kc('k'))], UP, "Panel content move up");
147 (vec![Key::new(NO_CONTROL, kc('l'))], RIGHT, "Panel content move right");
148
149 (vec![Key::new(ALT, kc('h'))], PANEL_LEFT, "Switch to left panel(topic tags)");
150 (vec![Key::new(ALT, kc('j'))], PANEL_DOWN, "Switch to down panel(topic tags)");
151 (vec![Key::new(ALT, kc('k'))], PANEL_UP, "Switch to up panel(topic tags)");
152 (vec![Key::new(ALT, kc('l'))], PANEL_RIGHT, "Switch to right panel(topic tags)");
153
154 (vec![Key::new(NO_CONTROL, tab)], NEXT_TAB, "Next tab");
155 (vec![Key::new(SHIFT, backtab)], PREV_TAB, "Prev tab");
156
157 (vec![Key::new(NO_CONTROL, kc('o'))], EDIT_CODE_EDITOR, "Edit cursor question(or current question) with your editor");
158 (vec![Key::new(NO_CONTROL, kc('e'))], EDIT_IN_TUI, "Enter input block");
159
160 (vec![Key::new(NO_CONTROL, esc)], ESCAPE, "Close some float panel");
161 (vec![Key::new(CTRL, kc('l'))], REDRAW, "Redraw ui");
162 (vec![Key::new(CTRL, kc('q'))], EXIT, "Exit lcode");
163 (vec![Key::new(SHIFT, kc('S'))], SYNC_INDEX, "Sync question index (select tab and topic_tags tab)");
164 (vec![Key::new(CTRL, kc('r'))], RE_QS_DETAIL, "Re get question detail (reference tab0/select cursor question info)");
165
166 (vec![Key::new(CTRL, kc('p'))], TOGGLE_MENU, "Show or hide menu(only edit)");
167 (vec![Key::new(CTRL, kc('t'))], TOGGLE_TEST_RES, "Show or hide test result (only tab1/edit)");
168 (vec![Key::new(CTRL, kc('s'))], TOGGLE_SUBMIT_RES, "Show or hide submit result (only tab1/edit)");
169 (vec![Key::new(NO_CONTROL, enter)], TOGGLE_CURSOR, "Trigger cursor item, in edit pop menu will active button (add or rm topic_tags, or goto tab1/edit)");
170
171 (vec![Key::new(NO_CONTROL, kc('a'))], ADD_TEST_CASE, "When submit error can add test case. (tab1/edit)")
172
173 );
174 let keymap = HashSet::from(mps);
175
176 Self { map_set: keymap }
177 }
178}