duat_base/widgets/
mod.rs

1//! Core [`Widget`]s for usage in Duat
2//!
3//! The widgets defined in this module are expected to be used in
4//! pretty much every `config` of Duat, since they are Duat
5//! equivalents to what is commonly found in other text editors.
6//!
7//! [`Widget`]: duat_core::ui::Widget
8//! [`Text`]: duat_core::text::Text
9//! [`Buffer`]: duat_core::buffer::Buffer
10//! [`WindowCreated`]: duat_core::hook::WindowCreated
11//! [`duat-term`]: https://docs.rs/duat-term/latest/duat_term/
12//! [`VertRule`]: https://docs.rs/duat-term/latest/duat_term/struct.VertRule.html
13use duat_core::{
14    data::Pass,
15    hook::{self, FocusedOn, UnfocusedFrom},
16    ui::PushTarget,
17};
18
19pub use self::{
20    completions::{
21        Completions, CompletionsBuilder, CompletionsKind, CompletionsList, CompletionsProvider,
22        WordsCompletionParser,
23    },
24    line_numbers::{LineNumbers, LineNumbersOpts},
25    log_book::{LogBook, LogBookOpts},
26    notifications::{Notifications, NotificationsOpts},
27    prompt_line::{PromptLine, PromptLineBuilder},
28    status_line::{State, StatusLine, StatusLineFmt, status},
29};
30
31mod completions;
32mod line_numbers;
33mod log_book;
34mod notifications;
35mod prompt_line;
36mod status_line;
37
38/// A group of [`Widget`]s consisting of a [`StatusLine`],
39/// [`PromptLine`] and [`Notifications`] combo
40///
41/// These are the default [`Widget`]s placed in the footer position of
42/// Duat. By default, they will be placed [around the window], but
43/// they can also be placed around individual [`Buffer`]s:
44///
45/// ```rust
46/// # duat_core::doc_duat!(duat);
47/// # use duat_base::widgets::{FooterWidgets, status};
48/// setup_duat!(setup);
49/// use duat::prelude::*;
50///
51/// fn setup() {
52///     hook::add::<Buffer>(|pa, handle| {
53///         FooterWidgets::new(status!(
54///             "{name_txt}{Spacer}{} {sels_txt} {main_txt}",
55///             mode_txt()
56///         ))
57///         .push_on(pa, handle);
58///         Ok(())
59///     });
60/// }
61/// ```
62///
63/// This specific [`Widget`] configuration makes use of [hook]s in
64/// order to show the [`PromptLine`] only when it is in focus, showing
65/// [`Notifications`] whenever that is not the case. Like Vim/Neovim,
66/// it also takes up two lines on the screen, one for the
67/// [`StatusLine`] and one for the other two [`Widget`]s.
68///
69/// If you want a Kakoune-like one line footer, use
70/// the [`one_line`] method. You can also use the [`prompt`] and
71/// [`notifs`] methods in order to specify the [`PromptLine`] and
72/// [`Notifications`] [`Widget`]s, respectively.
73///
74/// Additionally, you can call the [`above`] method in order to place
75/// the footer as a header instead.
76///
77/// [around the window]: duat_core::hook::WindowCreated
78/// [`Buffer`]: duat_core::buffer::Buffer
79/// [`one_line`]: FooterWidgets::one_line
80/// [`prompt`]: FooterWidgets::prompt
81/// [`notifs`]: FooterWidgets::notifs
82/// [`above`]: FooterWidgets::above
83/// [`Widget`]: duat_core::ui::Widget
84pub struct FooterWidgets {
85    status: StatusLineFmt,
86    prompt: PromptLineBuilder,
87    notifs: NotificationsOpts,
88    one_line: bool,
89    above: bool,
90}
91
92impl FooterWidgets {
93    /// Adds footer [`Widget`]s
94    ///
95    /// [`Widget`]: duat_core::ui::Widget
96    pub fn push_on(mut self, pa: &mut Pass, push_target: &impl PushTarget) {
97        let prompt_line = if self.one_line {
98            self.prompt.request_width()
99        } else {
100            self.prompt
101        };
102        let prompt_line = if self.above {
103            prompt_line.above().hidden().push_on(pa, push_target)
104        } else {
105            prompt_line.below().hidden().push_on(pa, push_target)
106        };
107
108        if self.one_line {
109            self.status.right().push_on(pa, &prompt_line);
110        } else {
111            self.status.above().push_on(pa, &prompt_line);
112        };
113
114        let notifications = if self.one_line {
115            self.notifs.request_width();
116            self.notifs.push_on(pa, &prompt_line)
117        } else {
118            self.notifs.push_on(pa, &prompt_line)
119        };
120
121        hook::add::<FocusedOn<PromptLine>>({
122            let notifications = notifications.clone();
123            move |pa, (_, handle)| {
124                notifications.area().hide(pa)?;
125                handle.area().reveal(pa)?;
126                Ok(())
127            }
128        })
129        .filter(prompt_line.clone());
130
131        hook::add::<UnfocusedFrom<PromptLine>>({
132            move |pa, (handle, _)| {
133                notifications.area().reveal(pa)?;
134                handle.area().hide(pa)?;
135                Ok(())
136            }
137        })
138        .filter(prompt_line);
139    }
140
141    /// Returns a new [`FooterWidgets`], with a [`StatusLine`] and
142    /// default [`PromptLine`] and [`Notifications`]
143    ///
144    /// You can set those other two through the [`prompt`] and
145    /// [`notifs`] methods.
146    ///
147    /// [`prompt`]: FooterWidgets::prompt
148    /// [`notifs`]: FooterWidgets::notifs
149    pub fn new(status_cfg: StatusLineFmt) -> Self {
150        Self {
151            status: status_cfg,
152            prompt: PromptLine::builder(),
153            notifs: Notifications::builder(),
154            one_line: false,
155            above: false,
156        }
157    }
158
159    /// Turns this footer into a Kakoune-like one liner, as opposed to
160    /// a Neovim-like two liner
161    pub fn one_line(self) -> Self {
162        Self { one_line: true, ..self }
163    }
164
165    /// Changes this footer to a header
166    pub fn above(self) -> Self {
167        Self { above: true, ..self }
168    }
169
170    /// Sets the [`PromptLine`] to be used
171    pub fn prompt(self, prompt: PromptLineBuilder) -> Self {
172        Self { prompt, ..self }
173    }
174
175    /// Sets the [`Notifications`] to be used
176    pub fn notifs(self, notifs: NotificationsOpts) -> Self {
177        Self { notifs, ..self }
178    }
179}
180
181impl Default for FooterWidgets {
182    fn default() -> Self {
183        Self {
184            status: StatusLine::builder(),
185            prompt: PromptLine::builder(),
186            notifs: Notifications::builder(),
187            one_line: false,
188            above: false,
189        }
190    }
191}