1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
use std::{collections::HashSet, fmt, hash::Hash};

use crossterm::event::KeyCode;
use key_parse::keymap::*;
use serde::{Deserialize, Serialize};

// toml can't serialize enum
macro_rules! actions {
    ($( ($key:ident, $val:literal) ); *) => {
        $(pub const $key: &str = $val;)*
    };
}
actions!(
    (PANEL_UP,          "panel_up");
    (PANEL_DOWN,        "panel_down");
    (PANEL_RIGHT,       "panel_right");
    (PANEL_LEFT,        "panel_left");

    (UP,                "up");
    (DOWN,              "down");
    (RIGHT,             "right");
    (LEFT,              "left");

    (TOGGLE_CURSOR,     "toggle");

    (TOP,               "top");
    (BOTTOM,            "bottom");

    (REDRAW,            "redraw");
    (EXIT,              "exit");

    (EDIT_CODE_EDITOR,  "edit_code");
    (EDIT_IN_TUI,       "edit_code_tui");

    (TOGGLE_SUBMIT_RES, "toggle_submit_res");
    (TOGGLE_TEST_RES,   "toggle_test_res");
    (TOGGLE_MENU,       "toggle_menu");

    (RE_QS_DETAIL,      "re_get_qs");

    (NEXT_TAB,          "next_tab");
    (PREV_TAB,          "prev_tab");

    (HEAD,              "head");
    (TAIL,              "tail");

    (SYNC_INDEX,        "sync_index");

    (ESCAPE,            "escape");

    (ADD_TEST_CASE,     "add_test_case")
);

#[derive(Clone)]
#[derive(Debug)]
#[derive(Default)]
#[derive(Eq)]
#[derive(Serialize, Deserialize)]
pub struct KeyMap {
    pub keys: Keys,
    pub action: String,
    #[serde(default)]
    pub desc: String,
}

impl fmt::Display for KeyMap {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let res = toml::to_string(self).unwrap_or_else(|_| "unknown keymap\n\n".to_owned());
        let mut a = res.split('\n');
        format!(
            "{:20}, {:30}, {}",
            a.next().unwrap_or_default(),
            a.next().unwrap_or_default(),
            a.next().unwrap_or_default()
        )
        .fmt(f)
    }
}

impl PartialEq for KeyMap {
    fn eq(&self, other: &Self) -> bool {
        self.action == other.action
    }
}

impl Hash for KeyMap {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        self.action.hash(state);
    }
}

#[derive(Serialize, Deserialize)]
#[derive(PartialEq, Eq)]
#[derive(Clone)]
#[derive(Debug)]
pub struct TuiKeyMap {
    #[serde(default)]
    #[serde(rename = "keymap")]
    pub map_set: HashSet<KeyMap>,
}

impl TuiKeyMap {
    /// Add extra keymap
    pub fn add_keymap(&mut self, add: HashSet<KeyMap>) {
        for ele in add {
            self.map_set.replace(ele);
        }
    }
}

macro_rules! keymaps {
    ($( ($keys:expr, $action:expr, $desc:literal) ); *) => {
        [
            $(
                KeyMap {
                    keys:   Keys($keys),
                    action: $action.to_owned(),
                    desc:   $desc.to_owned(),
                },
            )*
        ]
    };
}

const fn kc(ch: char) -> KeyCode {
    KeyCode::Char(ch)
}

impl Default for TuiKeyMap {
    #[allow(non_snake_case)]
    fn default() -> Self {
        let (tab, backtab) = (KeyCode::Tab, KeyCode::BackTab);
        let esc = KeyCode::Esc;
        let enter = KeyCode::Enter;

        let g = Key::new(NO_CONTROL, kc('g'));

        let mps = keymaps!(
            (vec![g, g],                          TOP,               "Go to top");
            (vec![Key::new(SHIFT,      kc('G'))], BOTTOM,            "Go to bottom");

            (vec![Key::new(SHIFT,      kc('H'))], HEAD,              "To the first column of the panel content.");
            (vec![Key::new(SHIFT,      kc('L'))], TAIL,              "To the last column of the panel content.");

            (vec![Key::new(NO_CONTROL, kc('h'))], LEFT,              "Panel content move left");
            (vec![Key::new(NO_CONTROL, kc('j'))], DOWN,              "Panel content move down");
            (vec![Key::new(NO_CONTROL, kc('k'))], UP,                "Panel content move up");
            (vec![Key::new(NO_CONTROL, kc('l'))], RIGHT,             "Panel content move right");

            (vec![Key::new(ALT,        kc('h'))], PANEL_LEFT,        "Switch to left panel(topic tags)");
            (vec![Key::new(ALT,        kc('j'))], PANEL_DOWN,        "Switch to down panel(topic tags)");
            (vec![Key::new(ALT,        kc('k'))], PANEL_UP,          "Switch to up panel(topic tags)");
            (vec![Key::new(ALT,        kc('l'))], PANEL_RIGHT,       "Switch to right panel(topic tags)");

            (vec![Key::new(NO_CONTROL,     tab)], NEXT_TAB,          "Next tab");
            (vec![Key::new(SHIFT,      backtab)], PREV_TAB,          "Prev tab");

            (vec![Key::new(NO_CONTROL, kc('o'))], EDIT_CODE_EDITOR,  "Edit cursor question(or current question) with your editor");
            (vec![Key::new(NO_CONTROL, kc('e'))], EDIT_IN_TUI,       "Enter input block");

            (vec![Key::new(NO_CONTROL,     esc)], ESCAPE,            "Close some float panel");
            (vec![Key::new(CTRL,       kc('l'))], REDRAW,            "Redraw ui");
            (vec![Key::new(CTRL,       kc('q'))], EXIT,              "Exit lcode");
            (vec![Key::new(SHIFT,      kc('S'))], SYNC_INDEX,        "Sync question index (select tab and topic_tags tab)");
            (vec![Key::new(CTRL,       kc('r'))], RE_QS_DETAIL,      "Re get question detail (reference tab0/select cursor question info)");

            (vec![Key::new(CTRL,       kc('p'))], TOGGLE_MENU,       "Show or hide menu(only edit)");
            (vec![Key::new(CTRL,       kc('t'))], TOGGLE_TEST_RES,   "Show or hide test result (only tab1/edit)");
            (vec![Key::new(CTRL,       kc('s'))], TOGGLE_SUBMIT_RES, "Show or hide submit result (only tab1/edit)");
            (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)");

            (vec![Key::new(NO_CONTROL, kc('a'))], ADD_TEST_CASE,     "When submit error can add test case. (tab1/edit)")

        );
        let keymap = HashSet::from(mps);

        Self { map_set: keymap }
    }
}