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}