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}