use std::sync::{Arc, LazyLock, mpsc};
use duat_core::{
context::{self, DynBuffer, Handle},
data::Pass,
hook::{self, BufferClosed, BufferUpdated},
text::{Builder, Spacer, Text, TextMut},
ui::{PushSpecs, PushTarget, Side, Widget},
};
pub use self::state::State;
use crate::state::{main_txt, mode_txt, name_txt, sels_txt};
mod state;
#[macro_use]
mod macros;
#[doc(inline)]
pub use crate::__status__ as status;
pub struct StatusLine {
buffer_handle: BufferHandle,
text_fn: TextFn,
text: Text,
checker: CheckerFn,
}
impl StatusLine {
fn new(builder: StatusLineFmt, buffer_handle: BufferHandle) -> Self {
let (builder_fn, checker) = if let Some((builder, checker)) = builder.fns {
(builder, checker)
} else {
let mode_txt = mode_txt();
let opts = match builder.specs.side {
Side::Above | Side::Below => {
status!("{mode_txt}{Spacer}{name_txt} {sels_txt} {main_txt}")
}
Side::Right => {
status!("{Spacer}{name_txt} {mode_txt} {sels_txt} {main_txt}",)
}
Side::Left => unreachable!(),
};
opts.fns.unwrap()
};
Self {
buffer_handle,
text_fn: Box::new(move |pa, fh| {
let builder = Text::builder();
builder_fn(pa, builder, fh)
}),
text: Text::new(),
checker,
}
}
pub fn fmt(&mut self, new: StatusLineFmt) {
let handle = self.buffer_handle.clone();
*self = StatusLine::new(new, handle);
}
pub fn builder() -> StatusLineFmt {
StatusLineFmt { fns: None, ..Default::default() }
}
fn update(pa: &mut Pass, handle: &Handle<Self>) {
if let BufferHandle::Dynamic(dyn_file) = &mut handle.write(pa).buffer_handle {
dyn_file.swap_to_current();
}
let sl = handle.read(pa);
handle.write(pa).text = match &sl.buffer_handle {
BufferHandle::Fixed(buffer) => (sl.text_fn)(pa, buffer),
BufferHandle::Dynamic(dyn_file) => (sl.text_fn)(pa, dyn_file.handle()),
};
match &handle.read(pa).buffer_handle {
BufferHandle::Fixed(handle) => handle.declare_as_read(),
BufferHandle::Dynamic(dyn_file) => dyn_file.declare_as_read(),
}
}
}
impl Widget for StatusLine {
fn text(&self) -> &Text {
&self.text
}
fn text_mut(&mut self) -> TextMut<'_> {
self.text.as_mut()
}
}
pub struct StatusLineFmt {
fns: Option<(BuilderFn, CheckerFn)>,
specs: PushSpecs,
}
impl StatusLineFmt {
pub fn push_on(self, pa: &mut Pass, push_target: &impl PushTarget) -> Handle<StatusLine> {
static SENDER: LazyLock<mpsc::Sender<StatusLineEvent>> = LazyLock::new(|| {
hook::add::<BufferUpdated>(|pa, buffer| {
let handles = buffer.get_related::<StatusLine>(pa);
for (statusline, _) in &handles {
StatusLine::update(pa, statusline);
}
let handles: Vec<Handle<StatusLine>> = context::current_window(pa)
.handles(pa)
.filter_map(Handle::try_downcast)
.filter(|statusline| !handles.iter().any(|(other, _)| other == statusline))
.collect();
for statusline in handles {
let sl = statusline.read(pa);
let buffer_changed = match &sl.buffer_handle {
BufferHandle::Fixed(handle) => handle.has_changed(pa),
BufferHandle::Dynamic(dyn_buf) => dyn_buf.has_changed(pa),
};
if buffer_changed || (sl.checker)() {
StatusLine::update(pa, &statusline);
}
}
});
hook::add::<BufferClosed>(|pa, buffer| {
for (statusline, _) in buffer.get_related::<StatusLine>(pa) {
SENDER.send(StatusLineEvent::Closed(statusline)).unwrap();
}
});
let (tx, rx) = mpsc::channel();
std::thread::spawn(move || {
let mut entries = Vec::new();
loop {
match rx.recv_timeout(std::time::Duration::from_millis(50)) {
Ok(StatusLineEvent::Opened(statusline, checker)) => {
entries.push((statusline, checker));
}
Ok(StatusLineEvent::Closed(rm_statusline)) => {
entries.retain(|(statusline, ..)| *statusline != rm_statusline);
}
Err(_) => {}
}
for (statusline, checker) in &entries {
if checker() {
let statusline = statusline.clone();
context::queue(move |pa| StatusLine::update(pa, &statusline))
}
}
}
});
tx
});
let specs = self.specs;
let statusline = StatusLine::new(self, match push_target.try_downcast() {
Some(handle) => BufferHandle::Fixed(handle),
None => BufferHandle::Dynamic(context::dynamic_buffer(pa)),
});
let checker = statusline.checker.clone();
let statusline = push_target.push_outer(pa, statusline, specs);
SENDER
.send(StatusLineEvent::Opened(statusline.clone(), checker))
.unwrap();
statusline
}
#[doc(hidden)]
pub fn new_with(fns: (BuilderFn, CheckerFn)) -> Self {
Self { fns: Some(fns), ..Default::default() }
}
pub fn above(self) -> Self {
Self {
specs: PushSpecs { side: Side::Above, ..self.specs },
..self
}
}
pub fn below(self) -> Self {
Self {
specs: PushSpecs { side: Side::Below, ..self.specs },
..self
}
}
pub(crate) fn right(self) -> Self {
Self {
specs: PushSpecs { side: Side::Right, ..self.specs },
..self
}
}
}
impl Default for StatusLineFmt {
fn default() -> Self {
Self {
fns: None,
specs: PushSpecs {
side: Side::Below,
height: Some(1.0),
..Default::default()
},
}
}
}
#[derive(Clone)]
enum BufferHandle {
Fixed(Handle),
Dynamic(DynBuffer),
}
enum StatusLineEvent {
Opened(Handle<StatusLine>, CheckerFn),
Closed(Handle<StatusLine>),
}
type TextFn = Box<dyn Fn(&Pass, &Handle) -> Text + Send>;
type BuilderFn = Box<dyn Fn(&Pass, Builder, &Handle) -> Text + Send>;
type CheckerFn = Arc<dyn Fn() -> bool + Send + Sync>;