1#![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
44pub trait Plugin: 'static {
83 fn plug(self, plugins: &Plugins);
85}
86
87static PLUGINS: Plugins = Plugins(MainThreadOnly::new(Mutex::new(Vec::new())));
88
89pub struct Plugins(MainThreadOnly<Mutex<Vec<(PluginFn, TypeId)>>>);
92
93impl Plugins {
94 #[doc(hidden)]
98 pub fn _new() -> &'static Self {
99 &PLUGINS
100 }
101
102 pub fn require<P: Plugin + Default>(&self) {
112 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 #[derive(Default)]
130 #[doc(hidden)]
131 pub struct MainThreadOnly<T>(T);
132
133 impl<T> MainThreadOnly<T> {
134 pub const fn new(value: T) -> Self {
136 Self(value)
137 }
138
139 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 use std::sync::{Mutex, OnceLock};
163
164 #[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 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 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
245mod 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#[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)>>;