duat_core/
context.rs

1//! Access to widgets and other other parts of the state of Duat
2//!
3//! This module lets you access and mutate some things:
4//!
5//! # Files
6use std::{any::TypeId, sync::Arc};
7
8use parking_lot::Mutex;
9
10pub use self::global::*;
11use crate::{
12    data::{ReadDataGuard, RwData, WriteDataGuard},
13    mode::Cursors,
14    text::Text,
15    ui::{Area, Ui},
16    widgets::{File, Node, Related, Widget},
17};
18
19mod global {
20    use std::{
21        any::Any,
22        path::PathBuf,
23        sync::{
24            LazyLock, OnceLock,
25            atomic::{AtomicBool, AtomicUsize, Ordering},
26        },
27    };
28
29    use parking_lot::{Mutex, RwLock};
30
31    use super::{CurFile, CurWidget, DynamicFile, FileParts, FixedFile, Notifications};
32    use crate::{
33        data::RwData,
34        duat_name,
35        mode::Regular,
36        text::{Text, err},
37        ui::{Ui, Window},
38        widgets::Node,
39    };
40
41    static MODE_NAME: LazyLock<RwData<&str>> =
42        LazyLock::new(|| RwData::new(duat_name::<Regular>()));
43    static CUR_FILE: OnceLock<&(dyn Any + Send + Sync)> = OnceLock::new();
44    static CUR_WIDGET: OnceLock<&(dyn Any + Send + Sync)> = OnceLock::new();
45    static CUR_WINDOW: AtomicUsize = AtomicUsize::new(0);
46    static WINDOWS: OnceLock<&(dyn Any + Send + Sync)> = OnceLock::new();
47    static NOTIFICATIONS: LazyLock<RwData<Vec<Text>>> = LazyLock::new(RwData::default);
48    static WILL_RELOAD_OR_QUIT: AtomicBool = AtomicBool::new(false);
49    static CUR_DIR: OnceLock<Mutex<PathBuf>> = OnceLock::new();
50
51    // pub(crate) in order to keep just the status one public
52    pub(crate) fn mode_name() -> &'static RwData<&'static str> {
53        &MODE_NAME
54    }
55
56    pub fn file_named<U: Ui>(name: impl ToString) -> Result<FixedFile<U>, Text> {
57        let windows = windows::<U>().read();
58        let name = name.to_string();
59        let (.., node) = crate::file_entry(&windows, &name)?;
60
61        let (widget, area, related) = node.parts();
62        let file = widget.try_downcast().unwrap();
63
64        Ok(FixedFile((
65            file,
66            area.clone(),
67            related.as_ref().unwrap().clone(),
68        )))
69    }
70
71    pub fn fixed_file<U: Ui>() -> Result<FixedFile<U>, Text> {
72        Ok(cur_file()?.fixed_file())
73    }
74
75    pub fn dyn_file<U: Ui>() -> Result<DynamicFile<U>, Text> {
76        Ok(cur_file()?.dyn_file())
77    }
78
79    pub fn cur_window() -> usize {
80        CUR_WINDOW.load(Ordering::Relaxed)
81    }
82
83    pub fn cur_dir() -> PathBuf {
84        CUR_DIR
85            .get_or_init(|| Mutex::new(std::env::current_dir().unwrap()))
86            .lock()
87            .clone()
88    }
89
90    pub fn notifications() -> Notifications {
91        Notifications(NOTIFICATIONS.clone())
92    }
93
94    pub fn notify(msg: Text) {
95        NOTIFICATIONS.write().push(msg)
96    }
97
98    /// Returns `true` if Duat is about to reload
99    pub fn will_reload_or_quit() -> bool {
100        WILL_RELOAD_OR_QUIT.load(Ordering::Relaxed)
101    }
102
103    pub(crate) fn set_cur<U: Ui>(
104        parts: Option<FileParts<U>>,
105        node: Node<U>,
106    ) -> Option<(FileParts<U>, Node<U>)> {
107        let prev = parts.and_then(|p| inner_cur_file().0.write().replace(p));
108
109        prev.zip(inner_cur_widget().0.write().replace(node))
110    }
111
112    pub(crate) fn cur_widget<U: Ui>() -> Result<&'static CurWidget<U>, Text> {
113        let cur_widget = inner_cur_widget();
114        cur_widget.0.read().as_ref().ok_or(err!("No widget yet"))?;
115        Ok(cur_widget)
116    }
117
118    pub(crate) fn set_windows<U: Ui>(wins: Vec<Window<U>>) -> &'static AtomicUsize {
119        *windows().write() = wins;
120        &CUR_WINDOW
121    }
122
123    pub(crate) fn windows<U: Ui>() -> &'static RwLock<Vec<Window<U>>> {
124        WINDOWS.get().unwrap().downcast_ref().expect("1 Ui only")
125    }
126
127    pub(crate) fn inner_cur_file<U: Ui>() -> &'static CurFile<U> {
128        CUR_FILE.get().unwrap().downcast_ref().expect("1 Ui only")
129    }
130
131    /// Orders to quit Duat
132    pub(crate) fn order_reload_or_quit() {
133        WILL_RELOAD_OR_QUIT.store(true, Ordering::Relaxed);
134        while crate::thread::still_running() {
135            std::thread::sleep(std::time::Duration::from_micros(500));
136        }
137    }
138
139    fn cur_file<U: Ui>() -> Result<&'static CurFile<U>, Text> {
140        let cur_file = inner_cur_file();
141        cur_file.0.read().as_ref().ok_or(err!("No file yet"))?;
142        Ok(cur_file)
143    }
144
145    fn inner_cur_widget<U: Ui>() -> &'static CurWidget<U> {
146        CUR_WIDGET.get().unwrap().downcast_ref().expect("1 Ui only")
147    }
148
149    #[doc(hidden)]
150    pub fn setup_non_statics<U: Ui>(
151        cur_file: &'static CurFile<U>,
152        cur_widget: &'static CurWidget<U>,
153        cur_window: usize,
154        windows: &'static RwLock<Vec<Window<U>>>,
155    ) {
156        CUR_FILE.set(cur_file).expect("setup ran twice");
157        CUR_WIDGET.set(cur_widget).expect("setup ran twice");
158        CUR_WINDOW.store(cur_window, Ordering::Relaxed);
159        WINDOWS.set(windows).expect("setup ran twice");
160    }
161}
162
163pub struct CurFile<U: Ui>(RwData<Option<FileParts<U>>>);
164
165impl<U: Ui> CurFile<U> {
166    pub fn new() -> Self {
167        Self(RwData::new(None))
168    }
169
170    pub fn fixed_file(&self) -> FixedFile<U> {
171        let parts = self.0.raw_read();
172        let (file, area, related) = parts.clone().unwrap();
173
174        FixedFile((file, area, related))
175    }
176
177    pub fn dyn_file(&self) -> DynamicFile<U> {
178        let dyn_parts = self.0.clone();
179        // Here, I do regular read, so the dyn_parts checker doesn't keep
180        // returning true for all eternity.
181        let (file, area, related) = {
182            let parts = dyn_parts.read();
183            parts.clone().unwrap()
184        };
185        let checker = file.checker();
186
187        DynamicFile {
188            parts: (file, area, related),
189            dyn_parts,
190            checker: Arc::new(Mutex::new(Box::new(checker))),
191        }
192    }
193
194    pub(crate) fn mutate_related_widget<W: Widget<U>, R>(
195        &self,
196        f: impl FnOnce(&mut W, &U::Area) -> R,
197    ) -> Option<R> {
198        let f = move |widget: &mut W, area| {
199            let cfg = widget.print_cfg();
200            widget.text_mut().remove_cursors(area, cfg);
201
202            let ret = f(widget, area);
203
204            let cfg = widget.print_cfg();
205
206            if let Some(main) = widget.cursors().and_then(Cursors::get_main) {
207                area.scroll_around_point(widget.text(), main.caret(), widget.print_cfg());
208            }
209            widget.text_mut().add_cursors(area, cfg);
210            widget.update(area);
211            widget.print(area);
212
213            ret
214        };
215
216        let data = self.0.raw_read();
217        let (file, area, rel) = data.as_ref().unwrap();
218
219        let rel = rel.read();
220        if file.data_is::<W>() {
221            file.write_as().map(|mut w| f(&mut w, area))
222        } else {
223            rel.iter()
224                .find(|node| node.data_is::<W>())
225                .and_then(|node| {
226                    let (widget, area, _) = node.parts();
227                    widget.write_as().map(|mut w| f(&mut w, area))
228                })
229        }
230    }
231}
232
233impl<U: Ui> Default for CurFile<U> {
234    fn default() -> Self {
235        Self::new()
236    }
237}
238
239#[derive(Clone)]
240pub struct FixedFile<U: Ui>(FileParts<U>);
241
242impl<U: Ui> FixedFile<U> {
243    pub fn read(&mut self) -> (ReadDataGuard<File>, &U::Area) {
244        let (file, area, _) = &self.0;
245        (file.read(), area)
246    }
247
248    pub fn write(&mut self) -> (WriteFileGuard<U>, &U::Area) {
249        let (file, area, _) = &self.0;
250        let mut file = file.write();
251        let cfg = file.print_cfg();
252        file.text_mut().remove_cursors(area, cfg);
253
254        let guard = WriteFileGuard { file, area };
255        (guard, area)
256    }
257
258    pub fn has_changed(&self) -> bool {
259        self.0.0.has_changed()
260    }
261
262    pub fn checker(&self) -> impl Fn() -> bool + Send + Sync + 'static + use<U> {
263        self.0.0.checker()
264    }
265
266    // NOTE: Doesn't return result, since it is expected that widgets can
267    // only be created after the file exists.
268    pub fn file_ptr_eq(&self, other: &Node<U>) -> bool {
269        other.ptr_eq(&self.0.0)
270    }
271
272    pub(crate) fn get_related_widget<W>(&mut self) -> Option<(RwData<W>, U::Area)> {
273        let (.., related) = &self.0;
274        let related = related.read();
275        related.iter().find_map(|node| {
276            let (widget, area, _) = node.parts();
277            widget.try_downcast().map(|data| (data, area.clone()))
278        })
279    }
280
281    pub(crate) fn inspect_related<W: 'static, R>(
282        &mut self,
283        f: impl FnOnce(&W, &U::Area) -> R,
284    ) -> Option<R> {
285        let (file, area, related) = &self.0;
286
287        if file.data_is::<W>() {
288            file.read_as().map(|w| f(&w, area))
289        } else {
290            let related = related.read();
291            related
292                .iter()
293                .find(|node| node.data_is::<W>())
294                .and_then(|node| node.widget().read_as().map(|w| f(&w, node.area())))
295        }
296    }
297
298    pub(crate) fn related_widgets(&self) -> &RwData<Vec<Node<U>>> {
299        &self.0.2
300    }
301}
302
303pub struct DynamicFile<U: Ui> {
304    parts: FileParts<U>,
305    dyn_parts: RwData<Option<FileParts<U>>>,
306    checker: Arc<Mutex<Box<dyn Fn() -> bool + Send + Sync + 'static>>>,
307}
308
309impl<U: Ui> DynamicFile<U> {
310    pub fn read(&mut self) -> (ReadDataGuard<File>, &U::Area) {
311        if self.dyn_parts.has_changed() {
312            let (file, area, related) = self.dyn_parts.read().clone().unwrap();
313            *self.checker.lock() = Box::new(file.checker());
314            self.parts = (file, area, related);
315        }
316
317        let (file, area, _) = &self.parts;
318        (file.read(), area)
319    }
320
321    pub fn write(&mut self) -> (WriteFileGuard<U>, &U::Area) {
322        if self.dyn_parts.has_changed() {
323            let (file, area, related) = self.dyn_parts.read().clone().unwrap();
324            *self.checker.lock() = Box::new(file.checker());
325            self.parts = (file, area, related);
326        }
327
328        let (file, area, _) = &self.parts;
329        let mut file = file.write();
330        let cfg = file.print_cfg();
331        file.text_mut().remove_cursors(area, cfg);
332
333        let guard = WriteFileGuard { file, area };
334        (guard, area)
335    }
336
337    /// Wether the [`File`] within was switched to another
338    pub fn has_swapped(&self) -> bool {
339        self.dyn_parts.has_changed()
340    }
341
342    pub fn has_changed(&self) -> bool {
343        let has_swapped = self.dyn_parts.has_changed();
344        let (file, ..) = &self.parts;
345
346        file.has_changed() || has_swapped
347    }
348
349    pub fn checker(&self) -> impl Fn() -> bool + Send + Sync + 'static + use<U> {
350        let dyn_checker = self.dyn_parts.checker();
351        let checker = self.checker.clone();
352        move || checker.lock()() || { dyn_checker() }
353    }
354
355    // NOTE: Doesn't return result, since it is expected that widgets can
356    // only be created after the file exists.
357    pub fn file_ptr_eq(&self, other: &Node<U>) -> bool {
358        other.ptr_eq(&self.dyn_parts.raw_read().as_ref().unwrap().0)
359    }
360
361    pub(crate) fn inspect_related<W: 'static, R>(
362        &mut self,
363        f: impl FnOnce(&W, &U::Area) -> R,
364    ) -> Option<R> {
365        if self.dyn_parts.has_changed() {
366            let (file, area, related) = self.dyn_parts.read().clone().unwrap();
367            *self.checker.lock() = Box::new(file.checker());
368            self.parts = (file, area, related);
369        }
370        let (file, area, related) = &self.parts;
371
372        if file.data_is::<W>() {
373            file.read_as().map(|w| f(&w, area))
374        } else {
375            let related = related.read();
376            related
377                .iter()
378                .find(|node| node.data_is::<W>())
379                .and_then(|node| node.widget().read_as().map(|w| f(&w, node.area())))
380        }
381    }
382}
383
384impl<U: Ui> Clone for DynamicFile<U> {
385    fn clone(&self) -> Self {
386        let (file, area, related) = self.parts.clone();
387        let checker = file.checker();
388        Self {
389            parts: (file, area, related),
390            dyn_parts: self.dyn_parts.clone(),
391            checker: Arc::new(Mutex::new(Box::new(checker))),
392        }
393    }
394}
395
396pub struct WriteFileGuard<'a, U: Ui> {
397    file: WriteDataGuard<'a, File>,
398    area: &'a U::Area,
399}
400
401impl<U: Ui> std::ops::Deref for WriteFileGuard<'_, U> {
402    type Target = File;
403
404    fn deref(&self) -> &Self::Target {
405        &self.file
406    }
407}
408
409impl<U: Ui> std::ops::DerefMut for WriteFileGuard<'_, U> {
410    fn deref_mut(&mut self) -> &mut Self::Target {
411        &mut self.file
412    }
413}
414
415// TODO: Make this not update the RwData if the File hasn't changed
416impl<U: Ui> Drop for WriteFileGuard<'_, U> {
417    fn drop(&mut self) {
418        let cfg = self.file.print_cfg();
419
420        if let Some(main) = self.file.cursors().get_main() {
421            self.area
422                .scroll_around_point(self.file.text(), main.caret(), cfg);
423        }
424        self.file.text_mut().add_cursors(self.area, cfg);
425
426        <File as Widget<U>>::update(&mut self.file, self.area);
427        <File as Widget<U>>::print(&mut self.file, self.area);
428    }
429}
430
431/// The notifications sent to Duat.
432///
433/// This can include command results, failed mappings, recompilation
434/// messages, and any other thing that you want to [push] to be
435/// notified.
436///
437/// [push]: Notifications::push
438#[derive(Clone)]
439pub struct Notifications(RwData<Vec<Text>>);
440
441impl Notifications {
442    /// Reads the notifications that were sent to Duat
443    pub fn read(&mut self) -> ReadDataGuard<Vec<Text>> {
444        self.0.read()
445    }
446
447    /// Wether there are new notifications or not
448    pub fn has_changed(&self) -> bool {
449        self.0.has_changed()
450    }
451
452    /// Checker for new notifications
453    pub fn checker(&self) -> impl Fn() -> bool + Send + Sync + use<> {
454        self.0.checker()
455    }
456
457    /// Pushes a new [notification] to Duat
458    ///
459    /// [notification]: Text
460    pub fn push(&mut self, text: Text) {
461        self.0.write().push(text)
462    }
463}
464
465#[doc(hidden)]
466pub struct CurWidget<U: Ui>(RwData<Option<Node<U>>>);
467
468impl<U: Ui> CurWidget<U> {
469    pub fn new() -> Self {
470        Self(RwData::new(None))
471    }
472
473    pub fn type_id(&self) -> TypeId {
474        self.0.type_id
475    }
476
477    pub fn inspect<R>(&self, f: impl FnOnce(&dyn Widget<U>, &U::Area) -> R) -> R {
478        let data = self.0.raw_read();
479        let (widget, area, _) = data.as_ref().unwrap().parts();
480        let widget = widget.read();
481
482        f(&*widget, area)
483    }
484
485    pub fn inspect_as<W: Widget<U>, R>(&self, f: impl FnOnce(&W, &U::Area) -> R) -> Option<R> {
486        let data = self.0.raw_read();
487        let (widget, area, _) = data.as_ref().unwrap().parts();
488
489        widget.read_as().map(|widget| f(&widget, area))
490    }
491
492    pub(crate) fn mutate_data<R>(
493        &self,
494        f: impl FnOnce(&RwData<dyn Widget<U>>, &U::Area, &Related<U>) -> R,
495    ) -> R {
496        let data = self.0.read();
497        let (widget, area, related) = data.as_ref().unwrap().parts();
498
499        f(widget, area, related)
500    }
501
502    pub(crate) fn mutate_data_as<W: Widget<U>, R>(
503        &self,
504        f: impl FnOnce(&RwData<W>, &U::Area, &Related<U>) -> R,
505    ) -> Option<R> {
506        let data = self.0.read();
507        let (widget, area, related) = data.as_ref().unwrap().parts();
508
509        Some(f(&widget.try_downcast::<W>()?, area, related))
510    }
511
512    pub(crate) fn node(&self) -> Node<U> {
513        self.0.read().as_ref().unwrap().clone()
514    }
515}
516
517impl<U: Ui> Default for CurWidget<U> {
518    fn default() -> Self {
519        Self::new()
520    }
521}
522
523pub(crate) type FileParts<U> = (RwData<File>, <U as Ui>::Area, RwData<Vec<Node<U>>>);