1use crokey::KeyCombination;
2use crossterm::event::{KeyModifiers, MouseEventKind};
3use serde::{Deserializer, Serialize, ser};
4use std::cmp::Ordering;
5use std::collections::BTreeMap;
6use std::{fmt, str::FromStr};
7
8use crossterm::event::MouseButton;
9use serde::de::{self, Visitor};
10
11use crate::config::TomlColorConfig;
12use crate::{action::Actions, message::Event};
13
14pub type BindMap = BTreeMap<Trigger, Actions>;
15
16#[derive(Debug, Hash, PartialEq, Eq, Clone)]
17pub enum Trigger {
18 Key(KeyCombination),
19 Mouse(MouseEvent),
20 Event(Event),
21}
22
23impl Ord for Trigger {
24 fn cmp(&self, other: &Self) -> Ordering {
25 use Trigger::*;
26
27 match (self, other) {
28 (Key(a), Key(b)) => a.to_string().cmp(&b.to_string()),
29 (Mouse(a), Mouse(b)) => mouse_event_kind_as_str(a.kind).cmp(mouse_event_kind_as_str(b.kind)),
30 (Event(a), Event(b)) => a.to_string().cmp(&b.to_string()),
31
32 (Key(_), _) => Ordering::Less,
34 (Mouse(_), Key(_)) => Ordering::Greater,
35 (Mouse(_), Event(_)) => Ordering::Less,
36 (Event(_), _) => Ordering::Greater,
37 }
38 }
39}
40
41impl PartialOrd for Trigger {
42 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
43 Some(self.cmp(other))
44 }
45}
46
47#[derive(Debug, Hash, PartialEq, Eq, Clone)]
48pub struct MouseEvent {
49 pub kind: MouseEventKind,
50 pub modifiers: KeyModifiers,
51}
52
53impl From<crossterm::event::MouseEvent> for Trigger {
55 fn from(e: crossterm::event::MouseEvent) -> Self {
56 Trigger::Mouse(MouseEvent {
57 kind: e.kind,
58 modifiers: e.modifiers,
59 })
60 }
61}
62
63impl From<KeyCombination> for Trigger {
64 fn from(key: KeyCombination) -> Self {
65 Trigger::Key(key)
66 }
67}
68
69impl From<Event> for Trigger {
70 fn from(event: Event) -> Self {
71 Trigger::Event(event)
72 }
73}
74impl ser::Serialize for Trigger {
77 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
78 where
79 S: ser::Serializer,
80 {
81 match self {
82 Trigger::Key(key) => serializer.serialize_str(&key.to_string()),
83 Trigger::Mouse(event) => {
84 let mut s = String::new();
85 if event.modifiers.contains(KeyModifiers::SHIFT) {
86 s.push_str("shift+");
87 }
88 if event.modifiers.contains(KeyModifiers::CONTROL) {
89 s.push_str("ctrl+");
90 }
91 if event.modifiers.contains(KeyModifiers::ALT) {
92 s.push_str("alt+");
93 }
94 if event.modifiers.contains(KeyModifiers::SUPER) {
95 s.push_str("super+");
96 }
97 if event.modifiers.contains(KeyModifiers::HYPER) {
98 s.push_str("hyper+");
99 }
100 if event.modifiers.contains(KeyModifiers::META) {
101 s.push_str("meta+");
102 }
103 s.push_str(mouse_event_kind_as_str(event.kind));
104 serializer.serialize_str(&s)
105 }
106 Trigger::Event(event) => serializer.serialize_str(&event.to_string()),
107 }
108 }
109}
110
111pub fn mouse_event_kind_as_str(kind: MouseEventKind) -> &'static str {
112 match kind {
113 MouseEventKind::Down(MouseButton::Left) => "left",
114 MouseEventKind::Down(MouseButton::Middle) => "middle",
115 MouseEventKind::Down(MouseButton::Right) => "right",
116 MouseEventKind::ScrollDown => "scrolldown",
117 MouseEventKind::ScrollUp => "scrollup",
118 MouseEventKind::ScrollLeft => "scrollleft",
119 MouseEventKind::ScrollRight => "scrollright",
120 _ => "", }
122}
123
124impl<'de> serde::Deserialize<'de> for Trigger {
125 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
126 where
127 D: Deserializer<'de>,
128 {
129 struct TriggerVisitor;
130
131 impl<'de> Visitor<'de> for TriggerVisitor {
132 type Value = Trigger;
133
134 fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
135 write!(f, "a string representing a Trigger")
136 }
137
138 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
139 where
140 E: de::Error,
141 {
142 if let Ok(key) = KeyCombination::from_str(value) {
144 return Ok(Trigger::Key(key));
145 }
146
147 let parts: Vec<&str> = value.split('+').collect();
149 if let Some(last) = parts.last()
150 && let Some(kind) = match last.to_lowercase().as_str() {
151 "left" => Some(MouseEventKind::Down(MouseButton::Left)),
152 "middle" => Some(MouseEventKind::Down(MouseButton::Middle)),
153 "right" => Some(MouseEventKind::Down(MouseButton::Right)),
154 "scrolldown" => Some(MouseEventKind::ScrollDown),
155 "scrollup" => Some(MouseEventKind::ScrollUp),
156 "scrollleft" => Some(MouseEventKind::ScrollLeft),
157 "scrollright" => Some(MouseEventKind::ScrollRight),
158 _ => None,
159 } {
160 let mut modifiers = KeyModifiers::empty();
161 for m in &parts[..parts.len() - 1] {
162 match m.to_lowercase().as_str() {
163 "shift" => modifiers |= KeyModifiers::SHIFT,
164 "ctrl" => modifiers |= KeyModifiers::CONTROL,
165 "alt" => modifiers |= KeyModifiers::ALT,
166 "super" => modifiers |= KeyModifiers::SUPER,
167 "hyper" => modifiers |= KeyModifiers::HYPER,
168 "meta" => modifiers |= KeyModifiers::META,
169 "none" => {}
170 unknown => {
171 return Err(E::custom(format!(
172 "Unknown modifier: {}",
173 unknown
174 )));
175 }
176 }
177 }
178 return Ok(Trigger::Mouse(MouseEvent { kind, modifiers }));
179 }
180
181 if let Ok(evt) = value.parse::<Event>() {
183 return Ok(Trigger::Event(evt));
184 }
185
186 Err(E::custom(format!(
187 "failed to parse trigger from '{}'",
188 value
189 )))
190 }
191 }
192
193 deserializer.deserialize_str(TriggerVisitor)
194 }
195}
196
197#[derive(Serialize)]
198struct BindFmtWrapper<'a> {
199 binds: &'a BindMap
200}
201use ratatui::style::{Style};
202use ratatui::text::{Line, Span, Text};
203use regex::Regex;
204
205
206
207pub fn display_binds(binds: &BindMap, cfg: Option<&TomlColorConfig>) -> Text<'static> {
209 let toml_string = toml::to_string(&BindFmtWrapper { binds }).unwrap();
210
211 let Some(cfg) = cfg else {
212 return Text::from(toml_string);
213 };
214
215 let section_re = Regex::new(r"^\s*\[.*\]").unwrap();
216 let key_re = Regex::new(r"^(\s*[\w_-]+)(\s*=\s*)").unwrap();
217 let string_re = Regex::new(r#""[^"]*""#).unwrap();
218 let number_re = Regex::new(r"\b\d+(\.\d+)?\b").unwrap();
219
220 let mut text = Text::default();
221
222 for line in toml_string.lines() {
223 if section_re.is_match(line) {
224 let mut style = Style::default().fg(cfg.section);
225 if cfg.section_bold {
226 style = style.add_modifier(ratatui::style::Modifier::BOLD);
227 }
228 text.extend(Text::from(Span::styled(line.to_string(), style)));
229 } else {
230 let mut spans = vec![];
231 let mut remainder = line.to_string();
232
233 if let Some(cap) = key_re.captures(&remainder) {
235 let key = &cap[1];
236 let eq = &cap[2];
237 spans.push(Span::styled(key.to_string(), Style::default().fg(cfg.key)));
238 spans.push(Span::raw(eq.to_string()));
239 remainder = remainder[cap[0].len()..].to_string();
240 }
241
242 let mut last_idx = 0;
244 for m in string_re.find_iter(&remainder) {
245 if m.start() > last_idx {
246 spans.push(Span::raw(remainder[last_idx..m.start()].to_string()));
247 }
248 spans.push(Span::styled(
249 m.as_str().to_string(),
250 Style::default().fg(cfg.string),
251 ));
252 last_idx = m.end();
253 }
254
255 let remainder = &remainder[last_idx..];
257 let mut last_idx = 0;
258 for m in number_re.find_iter(remainder) {
259 if m.start() > last_idx {
260 spans.push(Span::raw(remainder[last_idx..m.start()].to_string()));
261 }
262 spans.push(Span::styled(
263 m.as_str().to_string(),
264 Style::default().fg(cfg.number),
265 ));
266 last_idx = m.end();
267 }
268
269 if last_idx < remainder.len() {
270 spans.push(Span::raw(remainder[last_idx..].to_string()));
271 }
272
273 text.extend(Text::from(Line::from(spans)));
274 }
275 }
276
277 text
278}