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