duat_base/widgets/status_line/mod.rs
1//! A widget that shows general information, usually about a
2//! [`Buffer`]
3//!
4//! The [`StatusLine`] is a very convenient widget when the user
5//! simply wants to show some informatioon. The information, when
6//! relevant, can automatically be tied to the active buffer, saving
7//! some keystrokes for the user's configuration.
8//!
9//! There is also the [`status!`] macro, which is an extremely
10//! convenient way to modify the text of the status line, letting you
11//! place form, in the same way that [`text!`] does, and
12//! automatically recognizing a ton of different types of functions,
13//! that can read from the buffer, from other places, from [data]
14//! types, etc.
15//!
16//! [data]: crate::data
17//! [`Buffer`]: duat_core::buffer::Buffer
18use duat_core::{
19 context::{self, DynBuffer, Handle},
20 data::Pass,
21 text::{AlignRight, Builder, Spacer, Text},
22 ui::{PushSpecs, PushTarget, Side, Widget},
23};
24
25pub use self::state::State;
26use crate::state::{main_txt, mode_txt, name_txt, sels_txt};
27
28mod state;
29#[macro_use]
30mod macros;
31#[doc(inline)]
32pub use crate::__status__ as status;
33
34/// A widget to show information, usually about a [`Buffer`]
35///
36/// This widget is updated whenever any of its parts needs to be
37/// updated, and it also automatically adjusts to where it was pushed.
38/// For example, if you push it to a buffer (via
39/// `hook::add::<Buffer>`, for example), it's information will point
40/// to the [`Buffer`] to which it was pushed. However, if you push it
41/// with [`WindowCreated`], it will always point to the currently
42/// active [`Buffer`]:
43///
44/// ```rust
45/// # duat_core::doc_duat!(duat);
46/// # use duat_base::widgets::status;
47/// setup_duat!(setup);
48/// use duat::prelude::*;
49///
50/// fn setup() {
51/// opts::one_line_footer();
52/// opts::set_status(|pa| status!("{AlignRight}{} {sels_txt} {main_txt}", mode_txt()));
53///
54/// hook::add::<Buffer>(|pa, handle| {
55/// status!("{AlignCenter}{name_txt}")
56/// .above()
57/// .push_on(pa, handle);
58/// Ok(())
59/// });
60/// }
61/// ```
62///
63/// In the code above, I'm modifying the "global" `StatusLine` through
64/// [`opts::set_status`] (this can be done with [hooks] as well, but
65/// this method is added for convenience's sake). This is in
66/// conjunction with [`opts::one_line_footer`], which will place
67/// the [`PromptLine`] and `StatusLine` on the same line.
68///
69/// After that, I'm _also_ pushing a new `StatusLine` above every
70/// opened [`Buffer`], showing that `Buffer`]'s name, centered.
71///
72/// You will usually want to create `StatusLine`s via the
73/// [`status!`] macro, since that is how you can customize it.
74/// Although, if you want the regular status line, you can call
75/// [`StatusLine::builder`]:
76///
77/// ```rust
78/// # duat_core::doc_duat!(duat);
79/// # use duat_base::widgets::StatusLine;
80/// setup_duat!(setup);
81/// use duat::prelude::*;
82///
83/// fn setup() {
84/// hook::add::<Buffer>(|pa, handle| {
85/// StatusLine::builder().above().push_on(pa, handle);
86/// Ok(())
87/// });
88/// }
89/// ```
90///
91/// [`Buffer`]: duat_core::buffer::Buffer
92/// [`WindowCreated`]: duat_core::hook::WindowCreated
93/// [`PromptLine`]: super::PromptLine
94/// [`Notifications`]: super::Notifications
95/// [`FooterWidgets`]: super::FooterWidgets
96/// [`opts::set_status`]: https://docs.rs/duat/latest/duat/opts/fn.set_status.html
97/// [`opts::one_line_footer`]: https://docs.rs/duat/latest/duat/opts/fn.one_line_footer.html
98/// [hooks]: duat_core::hook
99pub struct StatusLine {
100 buffer_handle: BufferHandle,
101 text_fn: TextFn,
102 text: Text,
103 checker: Box<dyn Fn(&Pass) -> bool + Send>,
104}
105
106impl StatusLine {
107 fn new(builder: StatusLineFmt, buffer_handle: BufferHandle) -> Self {
108 let (builder_fn, checker) = if let Some((builder, checker)) = builder.fns {
109 (builder, checker)
110 } else {
111 let mode_txt = mode_txt();
112
113 let opts = match builder.specs.side {
114 Side::Above | Side::Below => {
115 status!("{mode_txt}{Spacer}{name_txt} {sels_txt} {main_txt}")
116 }
117 Side::Right => {
118 status!("{AlignRight}{name_txt} {mode_txt} {sels_txt} {main_txt}",)
119 }
120 Side::Left => unreachable!(),
121 };
122
123 opts.fns.unwrap()
124 };
125
126 Self {
127 buffer_handle,
128 text_fn: Box::new(move |pa, fh| {
129 let builder = Text::builder();
130 builder_fn(pa, builder, fh)
131 }),
132 text: Text::new(),
133 checker: Box::new(checker),
134 }
135 }
136
137 /// Replaces this `StatusLine` with a new one
138 pub fn fmt(&mut self, new: StatusLineFmt) {
139 let handle = self.buffer_handle.clone();
140 *self = StatusLine::new(new, handle);
141 }
142
143 /// Returns a [`StatusLineFmt`], which can be used to push
144 /// around `StatusLine`s
145 ///
146 /// The same can be done more conveniently with the [`status!`]
147 /// macro, which is imported by default in the configuration
148 /// crate.
149 pub fn builder() -> StatusLineFmt {
150 StatusLineFmt { fns: None, ..Default::default() }
151 }
152}
153
154impl Widget for StatusLine {
155 fn update(pa: &mut Pass, handle: &Handle<Self>) {
156 if let BufferHandle::Dynamic(dyn_file) = &mut handle.write(pa).buffer_handle {
157 dyn_file.swap_to_current();
158 }
159
160 let sl = handle.read(pa);
161
162 handle.write(pa).text = match &sl.buffer_handle {
163 BufferHandle::Fixed(buffer) => (sl.text_fn)(pa, buffer),
164 BufferHandle::Dynamic(dyn_file) => (sl.text_fn)(pa, dyn_file.handle()),
165 };
166
167 // Do this in case the Buffer is never read during Text construction
168 match &handle.read(pa).buffer_handle {
169 BufferHandle::Fixed(handle) => handle.declare_as_read(),
170 BufferHandle::Dynamic(dyn_file) => dyn_file.declare_as_read(),
171 }
172 }
173
174 fn needs_update(&self, pa: &Pass) -> bool {
175 let buffer_changed = match &self.buffer_handle {
176 BufferHandle::Fixed(handle) => handle.has_changed(pa),
177 BufferHandle::Dynamic(dyn_buf) => dyn_buf.has_changed(pa),
178 };
179 let checkered = (self.checker)(pa);
180
181 buffer_changed || checkered
182 }
183
184 fn text(&self) -> &Text {
185 &self.text
186 }
187
188 fn text_mut(&mut self) -> &mut Text {
189 &mut self.text
190 }
191}
192
193/// A builder for [`StatusLine`]s
194///
195/// This struct is created by the [`status!`] macro, and its purpose
196/// is mainly to allow formatting of the `StatusLine`.
197///
198/// There is also the [`StatusLineFmt::above`] method, which places
199/// the `StatusLine` above, rather than below.
200pub struct StatusLineFmt {
201 fns: Option<(BuilderFn, CheckerFn)>,
202 specs: PushSpecs,
203}
204
205impl StatusLineFmt {
206 /// Push the [`StatusLine`]
207 ///
208 /// If the handle's [`Widget`] is a [`Buffer`], then this
209 /// `StatusLine` will refer to it when printing information about
210 /// `Buffer`s. Otherwise, the `StatusLine` will print information
211 /// about the currently active `Buffer`.
212 ///
213 /// [`Buffer`]: duat_core::buffer::Buffer
214 pub fn push_on(self, pa: &mut Pass, push_target: &impl PushTarget) -> Handle<StatusLine> {
215 let specs = self.specs;
216 let status_line = StatusLine::new(
217 self,
218 match push_target.try_downcast() {
219 Some(handle) => BufferHandle::Fixed(handle),
220 None => BufferHandle::Dynamic(context::dynamic_buffer(pa)),
221 },
222 );
223
224 push_target.push_outer(pa, status_line, specs)
225 }
226
227 /// Returns a new `StatusLineFmt`, meant to be called only be the
228 /// [`status!`] macro
229 #[doc(hidden)]
230 pub fn new_with(fns: (BuilderFn, CheckerFn)) -> Self {
231 Self { fns: Some(fns), ..Default::default() }
232 }
233
234 /// Puts the [`StatusLine`] above, as opposed to below
235 pub fn above(self) -> Self {
236 Self {
237 specs: PushSpecs { side: Side::Above, ..self.specs },
238 ..self
239 }
240 }
241
242 /// Puts the [`StatusLine`] below, this is the default
243 pub fn below(self) -> Self {
244 Self {
245 specs: PushSpecs { side: Side::Below, ..self.specs },
246 ..self
247 }
248 }
249
250 /// Puts the [`StatusLine`] on the right
251 pub(crate) fn right(self) -> Self {
252 Self {
253 specs: PushSpecs { side: Side::Right, ..self.specs },
254 ..self
255 }
256 }
257}
258
259impl Default for StatusLineFmt {
260 fn default() -> Self {
261 Self {
262 fns: None,
263 specs: PushSpecs {
264 side: Side::Below,
265 height: Some(1.0),
266 ..Default::default()
267 },
268 }
269 }
270}
271
272type TextFn = Box<dyn Fn(&Pass, &Handle) -> Text + Send>;
273type BuilderFn = Box<dyn Fn(&Pass, Builder, &Handle) -> Text + Send>;
274type CheckerFn = Box<dyn Fn(&Pass) -> bool + Send>;
275
276#[derive(Clone)]
277enum BufferHandle {
278 Fixed(Handle),
279 Dynamic(DynBuffer),
280}