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