1#![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
35pub trait Plugin: 'static {
74 fn plug(self, plugins: &Plugins);
76}
77
78static PLUGINS: Plugins = Plugins(MainThreadOnly::new(Mutex::new(Vec::new())));
79
80pub struct Plugins(MainThreadOnly<Mutex<Vec<(PluginFn, TypeId)>>>);
83
84impl Plugins {
85 #[doc(hidden)]
89 pub fn _new() -> &'static Self {
90 &PLUGINS
91 }
92
93 pub fn require<P: Plugin + Default>(&self) {
103 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 #[derive(Default)]
121 #[doc(hidden)]
122 pub struct MainThreadOnly<T>(T);
123
124 impl<T> MainThreadOnly<T> {
125 pub const fn new(value: T) -> Self {
127 Self(value)
128 }
129
130 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 use std::sync::{Mutex, OnceLock};
154
155 #[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 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 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#[doc(hidden)]
238pub mod private_exports {
239 pub use format_like::format_like;
240}
241
242#[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)>>;