Skip to main content

duat_core/
lib.rs

1//! The core of Duat, for use by Duat's built-in plugins.
2//!
3//! This crate isn't really meant for public use, since it is used
4//! only by a select few plugins. Configuration crates and plugins
5//! should make use of the [duat] crate.
6//!
7//! One thing to note about this "builti-in plugins" thing though, is
8//! that the api of `duat` is a superset of `duat-core`'s api, the
9//! only reason why this distinction exists is so I can include some
10//! other plugins in `duat`'s api, like `duat-base`,
11//! `duat-treesitter`, and `duat-lsp`.
12//!
13//! [duat]: https://crates.io/duat
14#![warn(rustdoc::unescaped_backticks)]
15#![allow(clippy::single_range_in_vec_init)]
16
17// This is because of the weird Strs pointer trickery that I'm doing,
18// usize _must_ be u64
19#[cfg(target_pointer_width = "16")]
20compile_error!("This crate does not support 16-bit systems.");
21#[cfg(target_pointer_width = "32")]
22compile_error!("This crate does not support 32-bit systems.");
23
24use std::{any::TypeId, sync::Mutex};
25
26#[allow(unused_imports)]
27use dirs_next::cache_dir;
28
29pub use self::ranges::Ranges;
30
31pub mod buffer;
32pub mod cmd;
33pub mod context;
34pub mod data;
35pub mod form;
36pub mod hook;
37pub mod mode;
38pub mod opts;
39mod ranges;
40#[doc(hidden)]
41pub mod session;
42pub mod text;
43pub mod ui;
44pub mod utils;
45
46/// A plugin for Duat.
47///
48/// Plugins should mostly follow the builder pattern, but you can use
49/// fields if you wish to. When calling [`Plugin::plug`], the plugin's
50/// settings should be taken into account, and all of its setup should
51/// be done:
52///
53/// ```rust
54/// # use duat_core::{Plugin, Plugins};
55/// // It's not a supertrait of Plugin, but you must implement
56/// // Default in order to use the plugin.
57/// #[derive(Default)]
58/// struct MyPlugin(bool);
59///
60/// impl Plugin for MyPlugin {
61///     // With the Plugins struct, you can require other plugins
62///     // within your plugin
63///     fn plug(self, plugins: &Plugins) {
64///         //..
65///     }
66/// }
67///
68/// impl MyPlugin {
69///     /// Returns a new instance of the [`MyPlugin`] plugin
70///     pub fn new() -> Self {
71///         Self(false)
72///     }
73///
74///     /// Modifies [`MyPlugin`]
75///     pub fn modify(self) -> Self {
76///         //..
77/// #       self
78///     }
79/// }
80/// ```
81///
82/// [plugged]: Plugin::plug
83/// [`PhantomData`]: std::marker::PhantomData
84pub trait Plugin: 'static {
85    /// Sets up the [`Plugin`]
86    fn plug(self, plugins: &Plugins);
87}
88
89static PLUGINS: Plugins = Plugins(Mutex::new(Vec::new()));
90
91/// A struct for [`Plugin`]s to declare dependencies on other
92/// [`Plugin`]s.
93pub struct Plugins(Mutex<Vec<(PluginFn, TypeId)>>);
94
95impl Plugins {
96    /// Returnss a new instance of [`Plugins`].
97    ///
98    /// **FOR USE BY THE DUAT EXECUTABLE ONLY**
99    #[doc(hidden)]
100    pub fn _new() -> &'static Self {
101        &PLUGINS
102    }
103
104    /// Require that a [`Plugin`] be added.
105    ///
106    /// This plugin may have already been added, or it might be added
107    /// by this call.
108    ///
109    /// For built-in [`Plugin`]s, if they are required by some
110    /// `Plugin`, then they will be added before that `Plugin` is
111    /// added. Otherwise, they will be added at the end of the `setup`
112    /// function.
113    pub fn require<P: Plugin + Default>(&self) {
114        // SAFETY: This function can only push new elements to the list, not
115        // accessing the !Send functions within.
116        let mut plugins = self.0.lock().unwrap();
117        if !plugins.iter().any(|(_, ty)| *ty == TypeId::of::<P>()) {
118            plugins.push((
119                Some(Box::new(|plugins| P::default().plug(plugins))),
120                TypeId::of::<P>(),
121            ));
122        };
123    }
124}
125
126// SAFETY: The !Send functions are only accessed from the main thread
127unsafe impl Send for Plugins {}
128unsafe impl Sync for Plugins {}
129
130/// Functions defined in the application loading the config.
131///
132/// **FOR USE BY THE DUAT EXECUTABLE ONLY**
133#[doc(hidden)]
134pub struct MetaFunctions {
135    /// Cliboard functions.
136    pub clipboard_fns: clipboard::ClipboardFns,
137    /// File watching functions.
138    pub notify_fns: notify::NotifyFns,
139    /// Persistent process spawning functions.
140    pub process_fns: process::ProcessFns,
141    /// Persistent storage for structs.
142    pub storage_fns: storage::StorageFns,
143}
144
145pub mod clipboard {
146    //! Clipboard interaction for Duat.
147    //!
148    //! Just a regular clipboard, no image functionality.
149    use std::sync::OnceLock;
150
151    /// A clipboard for Duat, can be platform based, or local
152    ///
153    /// **FOR USE BY THE DUAT EXECUTABLE ONLY**
154    #[doc(hidden)]
155    #[derive(Clone, Copy)]
156    pub struct ClipboardFns {
157        /// The function to get the text of the clipboard.
158        pub get_text: fn() -> Option<String>,
159        /// The function to set the text of the clipboard.
160        pub set_text: fn(String),
161    }
162
163    static CLIPBOARD_FNS: OnceLock<&ClipboardFns> = OnceLock::new();
164
165    /// Gets a [`String`] from the clipboard.
166    ///
167    /// This can fail if the clipboard does not contain UTF-8 encoded
168    /// text.
169    pub fn get_text() -> Option<String> {
170        (CLIPBOARD_FNS.get().unwrap().get_text)()
171    }
172
173    /// Sets a [`String`] to the clipboard.
174    pub fn set_text(text: impl std::fmt::Display) {
175        (CLIPBOARD_FNS.get().unwrap().set_text)(text.to_string())
176    }
177
178    pub(crate) fn set_clipboard(clipb: &'static ClipboardFns) {
179        CLIPBOARD_FNS
180            .set(clipb)
181            .map_err(|_| {})
182            .expect("Setup ran twice");
183    }
184}
185
186pub mod notify {
187    //! File watching utility for Duat.
188    //!
189    //! Provides a simplified interface over the [`notify`] crate.
190    //!
191    //! [`notify`]: https://crates.io/crates/notify
192    use std::{
193        collections::HashMap,
194        path::{Path, PathBuf},
195        sync::{
196            LazyLock, Mutex, OnceLock,
197            atomic::{AtomicBool, AtomicUsize, Ordering::Relaxed},
198        },
199        time::Duration,
200    };
201
202    use notify_types::event::{AccessKind, AccessMode, Event, EventKind};
203    pub use notify_types::*;
204
205    static WATCHERS_DISABLED: AtomicBool = AtomicBool::new(false);
206    static WATCHER_COUNT: AtomicUsize = AtomicUsize::new(0);
207    static NOTIFY_FNS: OnceLock<&NotifyFns> = OnceLock::new();
208    static DUAT_WRITES: LazyLock<Mutex<HashMap<PathBuf, usize>>> = LazyLock::new(Mutex::default);
209
210    /// Wether an event came from Duat or not.
211    ///
212    /// This is only ever [`FromDuat::Yes`] if the event is a write
213    /// event resulting from [`Handle::<Buffer>::save`].
214    ///
215    /// This can be useful if you want to sort events based on
216    /// external or internal factors. For example, duat makes use of
217    /// this in order to calculate file diffs only if the file was
218    /// modified from outside of duat.
219    ///
220    /// [`Handle::<Buffer>::save`]: crate::context::Handle::save
221    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
222    pub enum FromDuat {
223        /// The event came from Duat, more specifically, it cam from a
224        /// function like [`Handle::<Buffer>::save`].
225        ///
226        /// Another thing to note is that this will only be this value
227        /// if _all_ the paths were written by Duat. If this is not
228        /// the case, then it will be [`FromDuat::No`]. This usually
229        /// isn't an issue, since the vast majority of events emmit
230        /// only one path, but it is something to keep in mind.
231        ///
232        /// [`Handle::<Buffer>::save`]: crate::context::Handle::save
233        Yes,
234        /// The event didn't come from Duat.
235        ///
236        /// Note that, even if the event actually came from Duat,
237        /// unless it is a write event, it will always be set to this.
238        No,
239    }
240
241    /// Functions for watching [`Path`]s.
242    ///
243    /// **FOR USE BY THE DUAT EXECUTABLE ONLY**
244    #[doc(hidden)]
245    #[derive(Debug)]
246    pub struct NotifyFns {
247        /// Spawn a new [`Watcher`], returning a unique identifier for
248        /// it.
249        pub spawn_watcher: fn(WatcherCallback) -> std::io::Result<usize>,
250        /// Watch a [`Path`] non recursively.
251        ///
252        /// The `usize` here is supposed to represent a unique
253        /// [`Watcher`], previously returned by `spawn_watcher`.
254        pub watch_path: fn(usize, &Path) -> std::io::Result<()>,
255        /// Watch a [`Path`] recursively.
256        ///
257        /// The `usize` here is supposed to represent a unique
258        /// [`Watcher`], previously returned by `spawn_watcher`.
259        pub watch_path_recursive: fn(usize, &Path) -> std::io::Result<()>,
260        /// Unwatch a [`Path`].
261        ///
262        /// The `usize` here is supposed to represent a unique
263        /// [`Watcher`], previously returned by `spawn_watcher`.
264        pub unwatch_path: fn(usize, &Path) -> std::io::Result<()>,
265        /// Unwatch all [`Path`]s.
266        ///
267        /// The `usize` here is supposed to represent a unique
268        /// [`Watcher`], previously returned by `spawn_watcher`.
269        pub unwatch_all: fn(usize),
270        /// Remove all [`Watcher`]s.
271        ///
272        /// This function is executed right as Duat is about to quit
273        /// or reload.
274        pub remove_all_watchers: fn(),
275    }
276
277    /// A [`Path`] watcher.
278    ///
279    /// If this struct is [`drop`]ped, the `Path`s it was watching
280    /// will no longer be watched by it.
281    pub struct Watcher(usize);
282
283    impl Watcher {
284        /// Spawn a new `Watcher`, with a callback function
285        ///
286        /// You can add paths to watch through [`Watcher::watch`] and
287        /// [`Watcher::watch_recursive`].
288        pub fn new(
289            mut callback: impl FnMut(std::io::Result<Event>, FromDuat) + Send + 'static,
290        ) -> std::io::Result<Self> {
291            if WATCHERS_DISABLED.load(Relaxed) {
292                return Err(std::io::Error::other(
293                    "Since Duat is poised to reload, no new Watchers are allowed to be created",
294                ));
295            }
296
297            let callback = WatcherCallback {
298                callback: Box::new(move |event| {
299                    use FromDuat::*;
300                    let from_duat = if let Ok(Event {
301                        kind: EventKind::Access(AccessKind::Close(AccessMode::Write)),
302                        paths,
303                        ..
304                    }) = &event
305                        && !paths.is_empty()
306                    {
307                        let mut duat_writes = DUAT_WRITES.lock().unwrap();
308                        let mut all_are_from_duat = true;
309
310                        for path in paths {
311                            if let Some(count) = duat_writes.get_mut(path)
312                                && *count > 0
313                            {
314                                *count -= 1;
315                            } else {
316                                all_are_from_duat = false;
317                            }
318                        }
319
320                        if all_are_from_duat { Yes } else { No }
321                    } else {
322                        No
323                    };
324
325                    callback(event, from_duat);
326                }),
327                drop: || _ = WATCHER_COUNT.fetch_sub(1, Relaxed),
328            };
329
330            match (NOTIFY_FNS.get().unwrap().spawn_watcher)(callback) {
331                Ok(id) => {
332                    WATCHER_COUNT.fetch_add(1, Relaxed);
333                    Ok(Self(id))
334                }
335                Err(err) => Err(err),
336            }
337        }
338
339        /// Watch a [`Path`] non-recursively.
340        pub fn watch(&self, path: &Path) -> std::io::Result<()> {
341            (NOTIFY_FNS.get().unwrap().watch_path)(self.0, path)
342        }
343
344        /// Watch a [`Path`] recursively.
345        pub fn watch_recursive(&self, path: &Path) -> std::io::Result<()> {
346            (NOTIFY_FNS.get().unwrap().watch_path_recursive)(self.0, path)
347        }
348
349        /// Stop watching a [`Path`].
350        pub fn unwatch(&self, path: &Path) -> std::io::Result<()> {
351            (NOTIFY_FNS.get().unwrap().unwatch_path)(self.0, path)
352        }
353    }
354
355    impl Drop for Watcher {
356        fn drop(&mut self) {
357            (NOTIFY_FNS.get().unwrap().unwatch_all)(self.0)
358        }
359    }
360
361    /// A callback for Watcher events.
362    ///
363    /// **FOR USE BY THE DUAT EXECUTABLE ONLY**
364    #[doc(hidden)]
365    pub struct WatcherCallback {
366        callback: Box<dyn FnMut(std::io::Result<Event>) + Send + 'static>,
367        // This is required, so the WATCHER_COUNT is from the loaded config, not the duat
368        // executable.
369        drop: fn(),
370    }
371
372    impl WatcherCallback {
373        /// Calls the callback.
374        pub fn call(&mut self, event: std::io::Result<Event>) {
375            (self.callback)(event)
376        }
377    }
378
379    impl Drop for WatcherCallback {
380        fn drop(&mut self) {
381            (self.drop)();
382        }
383    }
384
385    /// Declares that the next write event actually came from Duat,
386    /// for a given path.
387    pub(crate) fn set_next_write_as_from_duat(path: PathBuf) {
388        *DUAT_WRITES.lock().unwrap().entry(path).or_insert(0) += 1;
389    }
390
391    /// Declares that the next write event didn't come from Duat,
392    /// for a given path.
393    pub(crate) fn unset_next_write_as_from_duat(path: PathBuf) {
394        let mut duat_writes = DUAT_WRITES.lock().unwrap();
395        let count = duat_writes.entry(path).or_insert(0);
396        *count = count.saturating_sub(1);
397    }
398    /// Sets the functions for file watching.
399    pub(crate) fn set_notify_fns(notify_fns: &'static NotifyFns) {
400        NOTIFY_FNS.set(notify_fns).expect("Setup ran twice");
401    }
402
403    /// Removes all [`Watcher`]s.
404    pub(crate) fn remove_all_watchers() {
405        WATCHERS_DISABLED.store(true, Relaxed);
406        (NOTIFY_FNS.get().unwrap().remove_all_watchers)();
407
408        let mut watcher_count = WATCHER_COUNT.load(Relaxed);
409        while watcher_count > 0 {
410            watcher_count = WATCHER_COUNT.load(Relaxed);
411            std::thread::sleep(Duration::from_millis(5));
412        }
413    }
414}
415
416pub mod process {
417    //! Utilities for spawning processes that should outlive the
418    //! config.
419    use std::{
420        io::{BufWriter, Error},
421        process::{Child, ChildStderr, ChildStdin, ChildStdout, Command},
422        sync::OnceLock,
423    };
424
425    pub use interrupt_read::InterruptReader;
426
427    static PROCESS_FNS: OnceLock<&ProcessFns> = OnceLock::new();
428
429    /// Functions for spawning persistent processes
430    ///
431    /// **FOR USE BY THE DUAT EXECUTABLE ONLY**
432    #[doc(hidden)]
433    #[derive(Debug)]
434    pub struct ProcessFns {
435        /// Spawn a [`PersistentChild`], which can outlive this config
436        /// reload.
437        pub spawn: fn(&mut Command) -> std::io::Result<PersistentChild>,
438        /// Gets a spawned child.
439        pub get_child: fn(String) -> Option<PersistentChild>,
440        /// Store a spawned child.
441        pub store_child: fn(String, PersistentChild),
442        /// Interrupt all [`PersistentChild`]ren.
443        pub interrupt_all: fn(),
444        /// How many reader threads are currently spawned.
445        pub reader_thread_count: fn() -> usize,
446    }
447
448    /// Behaves nearly identically to a regular [`Child`], except the
449    /// `stdin` pipe is wrapped in a [`BufWriter`] and the `stdout`
450    /// and `stderr` pipes are wrapped in an /// [`InterruptReader`].
451    ///
452    /// The [`InterruptReader`] is similar to a [`BufReader`] in the
453    /// fact that it buffers the input bytes. However, it also comes
454    /// with the ability to be interrupted from another thread.
455    ///
456    /// In Duat, this will be done right before the [`ConfigUnloaded`]
457    /// hook is triggered, signaling that Duat is about to quit/unload
458    /// the config. This will also make it so [`context::will_unload`]
459    /// starts returning `true`, which can be used to stop other
460    /// threads on a loop.
461    ///
462    /// When you're doing your reading loop from the `stdout` and
463    /// `stderr`, you should add a check if the return type is
464    /// `Err(err) if is_interrupt(&err)` in order to check for that
465    /// possibility.
466    ///
467    /// The [`is_interrupt`] function just checks if the error was
468    /// sent because of the aforementioned reason.
469    ///
470    /// If the error is of that type, it is _your_ responsability to
471    /// `break` the reading loop and terminate the thread, so duat can
472    /// properly reload (duat won't reload until you do so.).
473    ///
474    /// [`BufReader`]: std::io::BufReader
475    /// [`ConfigUnloaded`]: crate::hook::ConfigUnloaded
476    /// [`context::will_unload`]: crate::context::will_unload
477    pub struct PersistentChild {
478        /// The [`Child`] that was spawned.
479        ///
480        /// It is guaranteed that `stdin`, `stdout` and `stderr` will
481        /// be [`None`], since those will have been moved to the
482        /// `PersistentChild`'s version of them.
483        pub child: Child,
484        /// The handle to a [`Child`]'s standard input, buffered so
485        /// you don't have to worry about unwrapping and dealing with
486        /// yet to be flushed bytes inbetween reloads
487        ///
488        /// If you wish to reuse the stdin and are running a writing
489        /// loop using the `loop` keyword, try to use `while
490        /// !context::will_unload()` instead, alongside a timeout
491        /// function for receiving the data that will be sent.
492        pub stdin: Option<BufWriter<ChildStdin>>,
493        /// A handle to the [`Child`]'s `stdout`, with buffering and
494        /// the ability to be interrupted through the
495        /// [`ConfigUnloaded`] hook.
496        ///
497        /// You should check if the reading was interrupted with
498        /// [`is_interrupt`]. If that is the case, you should end your
499        /// reading loop, as Duat is about to reload the
500        /// configuration.
501        ///
502        /// [`ConfigUnloaded`]: crate::hook::ConfigUnloaded
503        pub stdout: Option<InterruptReader<ChildStdout>>,
504        /// A handle to the [`Child`]'s `stderr`, with buffering and
505        /// the ability to be interrupted through the
506        /// [`ConfigUnloaded`] hook.
507        ///
508        /// You should check if the reading was interrupted with
509        /// [`is_interrupt`]. If that is the case, you should end your
510        /// reading loop, as Duat is about to reload the
511        /// configuration.
512        ///
513        /// [`ConfigUnloaded`]: crate::hook::ConfigUnloaded
514        pub stderr: Option<InterruptReader<ChildStderr>>,
515    }
516
517    /// Spawn a new `PersistentChild`, which can be reused in
518    /// future config reloads.
519    pub fn spawn(command: &mut Command) -> std::io::Result<PersistentChild> {
520        (PROCESS_FNS.get().unwrap().spawn)(command)
521    }
522
523    /// Get a [`Child`] process that might have been spawned in a
524    /// previous reload cycle.
525    ///
526    /// This function is useful if you want to access spawned
527    /// processes that outlive the presently loaded configuration.
528    ///
529    /// In order to get the `Child`, you must provide a type and a
530    /// name. The type is provided in order to prevent others from
531    /// accessing this child (it's not fully safe, but it takes effort
532    /// to break this), while the name is used to identify this
533    /// specific `Child`.
534    ///
535    /// For the type, you should create a new type specifically for
536    /// this, and this type should not be publicly available. If the
537    /// type is renamed in between reload cycles, the child will
538    /// become an inaccessible zombie.
539    pub fn get<KeyType: 'static>(keyname: impl std::fmt::Display) -> Option<PersistentChild> {
540        let key = format!("{keyname}{}", std::any::type_name::<KeyType>());
541        (PROCESS_FNS.get().unwrap().get_child)(key)
542    }
543
544    /// A combination of the [`spawn`] ang [`get`] commands, letting
545    /// you "lazyly" spawn processes.
546    ///
547    /// # Examples
548    ///
549    /// ```rust
550    /// # duat_core::doc_duat!(duat);
551    /// # fn do_stuff_with_line(string: String) {}
552    /// use std::{
553    ///     io::BufRead,
554    ///     mem,
555    ///     process::{Command, Stdio},
556    /// };
557    ///
558    /// use duat::{
559    ///     prelude::*,
560    ///     process::{self, is_interrupt},
561    /// };
562    ///
563    /// // Private key for processes.
564    /// struct Key;
565    /// struct PluginWithProcesses;
566    ///
567    /// impl Plugin for PluginWithProcesses {
568    ///     fn plug(self, _: &Plugins) {
569    ///         let pname = "process1";
570    ///         let mut child = process::get_or_spawn::<Key>(pname, || {
571    ///             let mut command = Command::new("your-command-name");
572    ///             command
573    ///                 .stdin(Stdio::piped())
574    ///                 .stdout(Stdio::piped())
575    ///                 .stderr(Stdio::piped());
576    ///             command
577    ///         })
578    ///         .unwrap();
579    ///
580    ///         let mut stdout = child.stdout.take().unwrap();
581    ///         let join_stdout = std::thread::spawn(move || {
582    ///             let mut line = String::new();
583    ///             loop {
584    ///                 match stdout.read_line(&mut line) {
585    ///                     // The Child has exited.
586    ///                     Ok(0) => break stdout,
587    ///                     Ok(_) => do_stuff_with_line(mem::take(&mut line)),
588    ///                     // Duat is about to reload.
589    ///                     Err(err) if is_interrupt(&err) => break stdout,
590    ///                     Err(err) => context::error!("{err}"),
591    ///                 }
592    ///             }
593    ///         });
594    ///
595    ///         let mut stderr = child.stderr.take().unwrap();
596    ///         let join_stderr = std::thread::spawn(move || {
597    ///             // Similar thing as above...
598    ///             stderr
599    ///         });
600    ///
601    ///         hook::add_once::<ConfigUnloaded>(move |_, _| {
602    ///             let stdout = join_stdout.join().unwrap();
603    ///             let stderr = join_stderr.join().unwrap();
604    ///
605    ///             child.stdout = Some(stdout);
606    ///             child.stderr = Some(stderr);
607    ///             process::store::<Key>(pname, child);
608    ///         });
609    ///     }
610    /// }
611    /// ```
612    pub fn get_or_spawn<KeyType: 'static>(
613        keyname: impl ToString,
614        spawn: impl FnOnce() -> Command,
615    ) -> std::io::Result<PersistentChild> {
616        let key = format!(
617            "{}{}",
618            keyname.to_string(),
619            std::any::type_name::<KeyType>()
620        );
621        let process_fns = PROCESS_FNS.get().unwrap();
622        match (process_fns.get_child)(key) {
623            Some(child) => Ok(child),
624            None => (process_fns.spawn)(&mut spawn()),
625        }
626    }
627
628    /// Store a [`PersistentChild`] process for retrieval on a future
629    /// reload.
630    ///
631    /// In order to store the `Child`, you must provide a type and a
632    /// name. The type is provided in order to prevent others from
633    /// accessing this child (it's not fully safe, but it takes effort
634    /// to break this), while the name is used to identify this
635    /// specific `Child`.
636    ///
637    /// For the type, you should create a new type specifically for
638    /// this, and this type should not be publicly available. If the
639    /// type is renamed in between reload cycles, the child will
640    /// become an inaccessible zombie.
641    ///
642    /// Returns [`Some`] if there was already a `PersistentChild` in
643    /// storage with the same key.
644    pub fn store<KeyType: 'static>(keyname: impl ToString, child: PersistentChild) {
645        let key = format!(
646            "{}{}",
647            keyname.to_string(),
648            std::any::type_name::<KeyType>()
649        );
650        (PROCESS_FNS.get().unwrap().store_child)(key, child)
651    }
652
653    /// Wether the [`std::io::Error`] in question is an interruption
654    /// triggered right before [`ConfigUnloaded`].
655    ///
656    /// You should use this as a second condition to end reading
657    /// loops, so you're able to restart them on the next reloading of
658    /// the config crate.
659    ///
660    /// [`ConfigUnloaded`]: crate::hook::ConfigUnloaded
661    pub fn is_interrupt(err: &Error) -> bool {
662        interrupt_read::is_interrupt(err) && crate::context::will_unload()
663    }
664
665    /// Interrupts all [`Interruptor`]s
666    ///
667    /// This is supposed to be done after
668    /// [`context::declare_will_unload`] is called.
669    ///
670    /// [`context::declare_will_unload`]: crate::context::declare_will_unload
671    pub(crate) fn interrupt_all() {
672        (PROCESS_FNS.get().unwrap().interrupt_all)()
673    }
674
675    /// How many reader threads are still running.
676    pub(crate) fn reader_thread_count() -> usize {
677        (PROCESS_FNS.get().unwrap().reader_thread_count)()
678    }
679
680    /// Sets the [`ProcessFns`].
681    pub(crate) fn set_process_fns(process_fns: &'static ProcessFns) {
682        PROCESS_FNS.set(process_fns).expect("Setup ran twice");
683    }
684}
685
686pub mod storage {
687    //! Utilities for storing items inbetween reloads.
688    use std::sync::OnceLock;
689
690    use bincode::{Decode, Encode, config::standard, error::EncodeError};
691
692    use crate::utils::duat_name;
693
694    static STORAGE_FNS: OnceLock<&StorageFns> = OnceLock::new();
695
696    /// Functions for storing persistent values.
697    ///
698    /// **FOR USE BY THE DUAT EXECUTABLE ONLY**
699    #[doc(hidden)]
700    #[derive(Debug)]
701    pub struct StorageFns {
702        /// Insert a new value into permanent storage.
703        pub insert: fn(String, Vec<u8>),
704        /// Get a value from permanent storage.
705        pub get_if: for<'f> fn(Box<dyn FnMut(&str, &[u8]) -> bool + 'f>) -> Option<Vec<u8>>,
706    }
707
708    /// Store a value across reload cycles.
709    ///
710    /// You can use this function if you want to store a value through
711    /// reload cycles, retrieving it after Duat reloads.
712    pub fn store<E: Encode + 'static>(value: E) -> Result<(), EncodeError> {
713        let value = bincode::encode_to_vec(value, standard())?;
714        (STORAGE_FNS.get().unwrap().insert)(duat_name::<E>().to_string(), value);
715        Ok(())
716    }
717
718    /// Retrieve a value that might have been stored on a previous
719    /// reload cycle.
720    ///
721    /// If a value of type `D` was stored with this key through
722    /// [`store`], then this function will return [`Some`] iff:
723    ///
724    /// - The type's name (through [`std::any::type_name`]) hasn't
725    ///   changed.
726    /// - The type's fields also haven't changed.
727    pub fn get_if<D: Decode<()> + 'static>(mut pred: impl FnMut(&D) -> bool) -> Option<D> {
728        let d_name = duat_name::<D>();
729
730        let value = (STORAGE_FNS.get().unwrap().get_if)(Box::new(move |duat_name, bytes| {
731            if d_name == duat_name
732                && let Some((value, _)) = bincode::decode_from_slice(bytes, standard()).ok()
733                && pred(&value)
734            {
735                true
736            } else {
737                false
738            }
739        }))?;
740
741        let (value, _) = bincode::decode_from_slice(&value, standard()).ok()?;
742        Some(value)
743    }
744
745    /// Sets the [`StorageFns`].
746    pub(crate) fn set_storage_fns(storage_fns: &'static StorageFns) {
747        STORAGE_FNS.set(storage_fns).expect("Setup ran twice");
748    }
749}
750
751////////// Text Builder macros (for pub/private bending)
752#[doc(hidden)]
753pub mod private_exports {
754    pub use format_like::format_like;
755}
756
757/// Converts a string to a valid priority
758#[doc(hidden)]
759pub const fn priority(priority: &str) -> u8 {
760    let mut bytes = priority.as_bytes();
761    let mut val = 0;
762
763    while let [byte, rest @ ..] = bytes {
764        assert!(b'0' <= *byte && *byte <= b'9', "invalid digit");
765        val = val * 10 + (*byte - b'0') as usize;
766        bytes = rest;
767    }
768
769    assert!(val <= 250, "priority cannot exceed 250");
770
771    val as u8
772}
773
774type PluginFn = Option<Box<dyn FnOnce(&Plugins)>>;
775
776/// Tries to evaluate a block that returns [`Result<T, Text>`]
777///
778/// If the block returns [`Ok`], this macro will resolve to `T`. If it
779/// returns [`Err`], it will log the error with [`context::error!`],
780/// then it will return from the function. As an example, this:
781///
782/// ```rust
783/// # duat_core::doc_duat!(duat);
784/// # fn test() {
785/// use duat::prelude::*;
786///
787/// let ret = try_or_log_err! {
788///     let value = result_fn()?;
789///     value
790/// };
791///
792/// fn result_fn() -> Result<usize, Text> {
793///     Err(txt!(":("))
794/// }
795/// # }
796/// ```
797///
798/// Will expand into:
799///
800/// ```rust
801/// # duat_core::doc_duat!(duat);
802/// # fn test() {
803/// use duat::prelude::*;
804///
805/// let ret = match (|| -> Result<_, Text> { Ok(result_fn()?) })() {
806///     Ok(ret) => ret,
807///     Err(err) => {
808///         context::error!("{err}");
809///         return;
810///     }
811/// };
812///
813/// fn result_fn() -> Result<usize, Text> {
814///     Err(txt!(":("))
815/// }
816/// # }
817/// ```
818///
819/// Note the [`Ok`] wrapping the tokens, so it works like the `try`
820/// keyword in that regard.
821#[macro_export]
822macro_rules! try_or_log_err {
823    { $($tokens:tt)* } => {
824        match (|| -> Result<_, $crate::text::Text> { Ok({ $($tokens)* }) })() {
825             Ok(ret) => ret,
826             Err(err) => {
827                 $crate::context::error!("{err}");
828                 return;
829             }
830        }
831    }
832}