Skip to main content

duat_base/widgets/
whichkey.rs

1//! A `Widget` to show available bindings and remaps
2//!
3//! This widget is automatically displayed when deemed necessary.
4//! Normally, this is done on some modes, and when typing remapped key
5//! sequences, be them maps or aliases.
6use std::sync::Once;
7
8use duat_core::{
9    context::{self, Handle},
10    data::Pass,
11    form::{self, Form},
12    hook::{self, FocusChanged, KeyTyped, OnMouseEvent},
13    mode::{self, Description, MouseEventKind},
14    text::{Text, TextMut, txt},
15    ui::{DynSpawnSpecs, PushSpecs, Side, Widget},
16};
17use duat_term::Frame;
18
19/// Add the hooks for the [`WhichKey`].
20pub fn add_whichkey_hooks() {
21    hook::add::<OnMouseEvent<WhichKey>>(move |pa, event| {
22        use MouseEventKind::{ScrollDown, ScrollUp};
23        match event.kind {
24            ScrollDown | ScrollUp => {
25                let (wk, area) = event.handle.write_with_area(pa);
26                let scroll = if let ScrollDown = event.kind { 3 } else { -3 };
27                area.scroll_ver(&wk.0, scroll, wk.print_opts());
28
29                let whichkey_desc = wk.1.clone().unwrap();
30                let (wkd, area) = whichkey_desc.write_with_area(pa);
31                area.scroll_ver(&wkd.0, scroll, wkd.print_opts());
32            }
33            _ => {}
34        }
35    });
36
37    hook::add::<OnMouseEvent<WhichKeyDescriptions>>(move |pa, event| {
38        use MouseEventKind::{ScrollDown, ScrollUp};
39        match event.kind {
40            ScrollDown | ScrollUp => {
41                let (wkd, area) = event.handle.write_with_area(pa);
42                let scroll = if let MouseEventKind::ScrollDown = event.kind {
43                    3
44                } else {
45                    -3
46                };
47                area.scroll_ver(&wkd.0, scroll, wkd.print_opts());
48
49                let whichkey = wkd.1.clone().unwrap();
50                let (wk, area) = whichkey.write_with_area(pa);
51                area.scroll_ver(&wk.0, scroll, wk.print_opts());
52            }
53            _ => {}
54        }
55    });
56}
57
58/// A [`Widget`] to display what [keys] will do
59///
60/// [keys]: mode::KeyEvent
61pub struct WhichKey(Text, Option<Handle<WhichKeyDescriptions>>);
62
63impl WhichKey {
64    /// Open the `WhichKey` widget
65    ///
66    /// You can optionally pass an
67    #[allow(clippy::type_complexity)] // ??? where?
68    pub fn open(
69        pa: &mut Pass,
70        mut fmt: Option<Box<dyn FnMut(Description) -> Option<(Text, Text)>>>,
71        mut specs: DynSpawnSpecs,
72    ) {
73        static ONCE: Once = Once::new();
74        ONCE.call_once(|| {
75            form::set(
76                "default.WhichKeyDescriptions",
77                Form::mimic("default.WhichKey"),
78            );
79        });
80
81        let mut keys_builder = Text::builder();
82        let mut descs_builder = Text::builder();
83
84        let (title, descs) = mode::current_seq_descriptions(pa);
85        let title = title.cloned();
86        for desc in descs {
87            if let Some(fmt) = fmt.as_mut() {
88                if let Some((keys, desc)) = fmt(desc) {
89                    keys_builder.push(keys);
90                    descs_builder.push(desc);
91                }
92            } else if let Some(text) = desc.text
93                && !text.is_empty()
94            {
95                keys_builder.push(txt!("{}[colon.WhichKey]:", desc.keys.into_text()));
96                descs_builder.push(txt!("{text}"));
97            } else {
98                continue;
99            }
100
101            keys_builder.push('\n');
102            descs_builder.push('\n');
103        }
104
105        let keys = WhichKey(keys_builder.build_no_double_nl(), None);
106        let mut descs = WhichKeyDescriptions(descs_builder.build_no_double_nl(), None);
107
108        let handles: Vec<_> = context::windows()
109            .handles(pa)
110            .filter(|handle| {
111                handle.widget().is::<WhichKey>() || handle.widget().is::<WhichKeyDescriptions>()
112            })
113            .collect();
114        for handle in handles {
115            let _ = handle.close(pa);
116        }
117
118        if let Some(height) = specs.height.as_mut() {
119            *height = keys.text().end_point().line().min(*height as usize) as f32;
120        }
121
122        let keys_handle = context::current_buffer(pa)
123            .spawn_widget(pa, keys, specs)
124            .unwrap();
125        descs.1 = Some(keys_handle.clone());
126
127        let title = title.unwrap_or_else(|| txt!("{}", crate::state::mode_name().call(pa)));
128        if let Some(area) = keys_handle.area().write_as::<duat_term::Area>(pa) {
129            use duat_core::text::Spacer;
130
131            let mut frame = Frame {
132                left: true,
133                right: true,
134                above: true,
135                below: true,
136                ..Frame::default()
137            };
138            frame.set_text(Side::Above, move |_| {
139                txt!("{Spacer}[terminal.frame]┤[]{title}[terminal.frame]├{Spacer}")
140            });
141            area.set_frame(frame);
142        }
143
144        let (keys, area) = keys_handle.write_with_area(pa);
145        if let Ok(size) = area.size_of_text(keys.print_opts(), keys.text()) {
146            area.set_width(size.x + 1.0).unwrap();
147        }
148
149        let descs_handle = keys_handle.push_inner_widget(pa, descs, PushSpecs {
150            side: Side::Right,
151            ..Default::default()
152        });
153        keys_handle.write(pa).1 = Some(descs_handle.clone());
154
155        let (descs, area) = descs_handle.write_with_area(pa);
156        if let Ok(size) = area.size_of_text(descs.print_opts(), descs.text()) {
157            area.set_width(size.x).unwrap();
158        }
159
160        hook::add_once::<KeyTyped>({
161            let keys_handle = keys_handle.clone();
162            let descs_handle = descs_handle.clone();
163            move |pa, _| {
164                _ = keys_handle.close(pa);
165                _ = descs_handle.close(pa);
166            }
167        });
168
169        hook::add_once::<FocusChanged>(move |pa, _| {
170            _ = keys_handle.close(pa);
171            _ = descs_handle.close(pa);
172        });
173    }
174}
175
176impl Widget for WhichKey {
177    fn text(&self) -> &Text {
178        &self.0
179    }
180
181    fn text_mut(&mut self) -> TextMut<'_> {
182        self.0.as_mut()
183    }
184}
185
186struct WhichKeyDescriptions(Text, Option<Handle<WhichKey>>);
187
188impl Widget for WhichKeyDescriptions {
189    fn text(&self) -> &Text {
190        &self.0
191    }
192
193    fn text_mut(&mut self) -> TextMut<'_> {
194        self.0.as_mut()
195    }
196}