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