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//! [duat]: https://crates.io/duat
8#![warn(rustdoc::unescaped_backticks)]
9#![allow(clippy::single_range_in_vec_init)]
10
11use std::any::TypeId;
12
13#[allow(unused_imports)]
14use dirs_next::cache_dir;
15pub use lender;
16use parking_lot::Mutex;
17
18pub use self::{main_thread_only::MainThreadOnly, ranges::Ranges};
19
20pub mod buffer;
21pub mod cmd;
22pub mod context;
23pub mod data;
24pub mod form;
25pub mod hook;
26pub mod mode;
27pub mod opts;
28mod ranges;
29#[doc(hidden)]
30pub mod session;
31pub mod text;
32pub mod ui;
33pub mod utils;
34
35/// A plugin for Duat
36///
37/// Plugins should mostly follow the builder pattern, but you can use
38/// fields if you wish to. When calling [`Plugin::plug`], the plugin's
39/// settings should be taken into account, and all of its setup should
40/// be done:
41///
42/// ```rust
43/// # use duat_core::{Plugin, Plugins};
44/// // It's not a supertrait of Plugin, but you must implement
45/// // Default in order to use the plugin.
46/// #[derive(Default)]
47/// struct MyPlugin(bool);
48///
49/// impl Plugin for MyPlugin {
50///     // With the Plugins struct, you can require other plugins
51///     // within your plugin
52///     fn plug(self, plugins: &Plugins) {
53///         //..
54///     }
55/// }
56///
57/// impl MyPlugin {
58///     /// Returns a new instance of the [`MyPlugin`] plugin
59///     pub fn new() -> Self {
60///         Self(false)
61///     }
62///
63///     /// Modifies [`MyPlugin`]
64///     pub fn modify(self) -> Self {
65///         //..
66/// #       self
67///     }
68/// }
69/// ```
70///
71/// [plugged]: Plugin::plug
72/// [`PhantomData`]: std::marker::PhantomData
73pub trait Plugin: 'static {
74    /// Sets up the [`Plugin`]
75    fn plug(self, plugins: &Plugins);
76}
77
78static PLUGINS: Plugins = Plugins(MainThreadOnly::new(Mutex::new(Vec::new())));
79
80/// A struct for [`Plugin`]s to declare dependencies on other
81/// [`Plugin`]s
82pub struct Plugins(MainThreadOnly<Mutex<Vec<(PluginFn, TypeId)>>>);
83
84impl Plugins {
85    /// Returnss a new instance of [`Plugins`]
86    ///
87    /// **FOR USE BY THE DUAT EXECUTABLE ONLY**
88    #[doc(hidden)]
89    pub fn _new() -> &'static Self {
90        &PLUGINS
91    }
92
93    /// Require that a [`Plugin`] be added
94    ///
95    /// This plugin may have already been added, or it might be added
96    /// by this call.
97    ///
98    /// For built-in [`Plugin`]s, if they are required by some
99    /// `Plugin`, then they will be added before that `Plugin` is
100    /// added. Otherwise, they will be added at the end of the `setup`
101    /// function.
102    pub fn require<P: Plugin + Default>(&self) {
103        // SAFETY: This function can only push new elements to the list, not
104        // accessing the !Send functions within.
105        let mut plugins = unsafe { self.0.get() }.lock();
106        if !plugins.iter().any(|(_, ty)| *ty == TypeId::of::<P>()) {
107            plugins.push((
108                Some(Box::new(|plugins| P::default().plug(plugins))),
109                TypeId::of::<P>(),
110            ));
111        };
112    }
113}
114
115mod main_thread_only {
116    /// A container meant for access in only the main thread
117    ///
118    /// Use this if you want a static value that is not
119    /// [`Send`]/[`Sync`].
120    #[derive(Default)]
121    #[doc(hidden)]
122    pub struct MainThreadOnly<T>(T);
123
124    impl<T> MainThreadOnly<T> {
125        /// Returns a new [`MainThreadOnly`]
126        pub const fn new(value: T) -> Self {
127            Self(value)
128        }
129
130        /// Acquires the inner value.
131        ///
132        /// # Safety
133        ///
134        /// You must ensure that this operation is taking place in the
135        /// main thread of execution, although this function might
136        /// take a [`Pass`] parameter later on, in order to
137        /// lift that requirement.
138        ///
139        /// [`Pass`]: crate::data::Pass
140        pub unsafe fn get(&self) -> &T {
141            &self.0
142        }
143    }
144
145    unsafe impl<T> Send for MainThreadOnly<T> {}
146    unsafe impl<T> Sync for MainThreadOnly<T> {}
147}
148
149pub mod clipboard {
150    //! Clipboard interaction for Duat
151    //!
152    //! Just a regular clipboard, no image functionality.
153    use std::sync::{Mutex, OnceLock};
154
155    /// A clipboard for Duat, can be platform based, or local
156    #[doc(hidden)]
157    #[allow(private_interfaces)]
158    pub enum Clipboard {
159        #[cfg(target_os = "android")]
160        Platform,
161        #[cfg(not(target_os = "android"))]
162        Platform(arboard::Clipboard, &'static ClipboardFunctions),
163        Local(String),
164    }
165
166    impl Default for Clipboard {
167        fn default() -> Self {
168            #[cfg(not(target_os = "android"))]
169            match arboard::Clipboard::new() {
170                Ok(clipb) => Self::Platform(clipb, ClipboardFunctions::new()),
171                Err(_) => Self::Local(String::new()),
172            }
173
174            #[cfg(target_os = "android")]
175            Self::Platform
176        }
177    }
178
179    static CLIPB: OnceLock<&'static Mutex<Clipboard>> = OnceLock::new();
180
181    /// Gets a [`String`] from the clipboard
182    ///
183    /// This can fail if the clipboard does not contain UTF-8 encoded
184    /// text.
185    ///
186    /// Or if there is no clipboard I guess
187    pub fn get_text() -> Option<String> {
188        let mut clipb = CLIPB.get().unwrap().lock().unwrap();
189        match &mut *clipb {
190            #[cfg(target_os = "android")]
191            Clipboard::Platform => clipboard::get_text()
192                .map_err(|err| crate::context::error!("{err}"))
193                .ok(),
194            #[cfg(not(target_os = "android"))]
195            Clipboard::Platform(clipb, fns) => (fns.get_text)(clipb),
196            Clipboard::Local(clipb) => Some(clipb.clone()).filter(String::is_empty),
197        }
198    }
199
200    /// Sets a [`String`] to the clipboard
201    pub fn set_text(text: impl std::fmt::Display) {
202        let mut clipb = CLIPB.get().unwrap().lock().unwrap();
203        match &mut *clipb {
204            #[cfg(target_os = "android")]
205            Clipboard::Platform => {
206                if let Err(err) = clipboard::set_text(text.to_string()) {
207                    crate::context::error!("{err}");
208                }
209            }
210            #[cfg(not(target_os = "android"))]
211            Clipboard::Platform(clipb, fns) => (fns.set_text)(clipb, text.to_string()),
212            Clipboard::Local(clipb) => *clipb = text.to_string(),
213        }
214    }
215
216    #[cfg(not(target_os = "android"))]
217    struct ClipboardFunctions {
218        get_text: fn(&mut arboard::Clipboard) -> Option<String>,
219        set_text: fn(&mut arboard::Clipboard, String),
220    }
221
222    impl ClipboardFunctions {
223        fn new() -> &'static Self {
224            &Self {
225                get_text: |clipb| clipb.get_text().ok(),
226                set_text: |clipb, text| clipb.set_text(text).unwrap(),
227            }
228        }
229    }
230
231    pub(crate) fn set_clipboard(clipb: &'static Mutex<Clipboard>) {
232        CLIPB.set(clipb).map_err(|_| {}).expect("Setup ran twice");
233    }
234}
235
236////////// Text Builder macros (for pub/private bending)
237#[doc(hidden)]
238pub mod private_exports {
239    pub use format_like::format_like;
240}
241
242/// Converts a string to a valid priority
243#[doc(hidden)]
244pub const fn priority(priority: &str) -> u8 {
245    let mut bytes = priority.as_bytes();
246    let mut val = 0;
247
248    while let [byte, rest @ ..] = bytes {
249        assert!(b'0' <= *byte && *byte <= b'9', "invalid digit");
250        val = val * 10 + (*byte - b'0') as usize;
251        bytes = rest;
252    }
253
254    assert!(val <= 250, "priority cannot exceed 250");
255
256    val as u8
257}
258
259type PluginFn = Option<Box<dyn FnOnce(&Plugins)>>;