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}