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