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