1use std::{
42 any::TypeId,
43 collections::HashMap,
44 ops::Range,
45 path::{Path, PathBuf},
46 sync::{LazyLock, OnceLock},
47};
48
49use parking_lot::RwLock;
50
51use crate::text::{Text, txt};
52
53pub fn duat_name<T: ?Sized + 'static>() -> &'static str {
66 fn duat_name_inner(type_id: TypeId, type_name: &str) -> &'static str {
67 static NAMES: LazyLock<RwLock<HashMap<TypeId, &'static str>>> =
68 LazyLock::new(RwLock::default);
69 let mut names = NAMES.write();
70
71 if let Some(name) = names.get(&type_id) {
72 name
73 } else {
74 let mut name = String::new();
75
76 for path in type_name.split_inclusive(['<', '>', ',', ' ']) {
77 for segment in path.split("::") {
78 let is_type = segment.chars().any(|c| c.is_uppercase());
79 let is_punct = segment.chars().all(|c| !c.is_alphanumeric());
80 let is_dyn = segment.starts_with("dyn");
81 if is_type || is_punct || is_dyn {
82 name.push_str(segment);
83 }
84 }
85 }
86
87 while let Some((i, len)) = None
88 .or_else(|| name.find("<Ui>").map(|i| (i, "<Ui>".len())))
89 .or_else(|| name.find("Ui, ").map(|i| (i, "Ui, ".len())))
90 .or_else(|| name.find("::<Ui>").map(|i| (i, "::<Ui>".len())))
91 {
92 unsafe {
93 name.as_mut_vec().splice(i..(i + len), []);
94 }
95 }
96
97 names.insert(type_id, name.leak());
98 names.get(&type_id).unwrap()
99 }
100 }
101
102 duat_name_inner(TypeId::of::<T>(), std::any::type_name::<T>())
103}
104
105pub fn src_crate<T: ?Sized + 'static>() -> &'static str {
107 fn src_crate_inner(type_id: TypeId, type_name: &'static str) -> &'static str {
108 static CRATES: LazyLock<RwLock<HashMap<TypeId, &'static str>>> =
109 LazyLock::new(|| RwLock::new(HashMap::new()));
110 let mut crates = CRATES.write();
111
112 if let Some(src_crate) = crates.get(&type_id) {
113 src_crate
114 } else {
115 let src_crate = type_name.split([' ', ':']).find(|w| *w != "dyn").unwrap();
116
117 crates.insert(type_id, src_crate);
118 crates.get(&type_id).unwrap()
119 }
120 }
121
122 src_crate_inner(TypeId::of::<T>(), std::any::type_name::<T>())
123}
124
125static CRATE_DIR: OnceLock<Option<&Path>> = OnceLock::new();
127static PROFILE: OnceLock<&str> = OnceLock::new();
129
130#[doc(hidden)]
134pub fn set_crate_dir_and_profile(dir: Option<&'static Path>, profile: &'static str) {
135 CRATE_DIR
136 .set(dir)
137 .expect("Crate directory set multiple times.");
138 PROFILE.set(profile).expect("Profile set multiple times.");
139}
140
141pub fn crate_dir() -> Result<&'static Path, Text> {
143 CRATE_DIR
144 .get()
145 .expect("Config not set yet")
146 .ok_or_else(|| txt!("Config directory is [a]undefined"))
147}
148
149pub fn profile() -> &'static str {
154 PROFILE.get().expect("Profile not set yet")
155}
156
157pub fn plugin_dir(plugin: &str) -> Result<PathBuf, Text> {
168 assert_ne!(plugin, "", "Can't have an empty plugin name");
169
170 static PLUGIN_DIR: LazyLock<Option<&Path>> = LazyLock::new(|| {
171 dirs_next::data_local_dir().map(|local_dir| {
172 let path: &'static str = local_dir
173 .join("duat")
174 .join("plugins")
175 .to_string_lossy()
176 .to_string()
177 .leak();
178
179 Path::new(path)
180 })
181 });
182
183 let plugin_dir = (*PLUGIN_DIR)
184 .ok_or_else(|| txt!("Local directory is [a]undefined"))?
185 .join(plugin);
186 std::fs::create_dir_all(&plugin_dir)?;
187
188 Ok(plugin_dir)
189}
190
191#[track_caller]
193pub fn get_ends(range: impl std::ops::RangeBounds<usize>, max: usize) -> (usize, usize) {
194 let start = match range.start_bound() {
195 std::ops::Bound::Included(start) => *start,
196 std::ops::Bound::Excluded(start) => *start + 1,
197 std::ops::Bound::Unbounded => 0,
198 };
199 let end = match range.end_bound() {
200 std::ops::Bound::Included(end) => *end + 1,
201 std::ops::Bound::Excluded(end) => *end,
202 std::ops::Bound::Unbounded => max,
203 };
204 assert!(
205 start <= max,
206 "start out of bounds: the len is {max}, but the index is {start}",
207 );
208 assert!(
209 end <= max,
210 "end out of bounds: the len is {max}, but the index is {end}",
211 );
212
213 (start, end)
214}
215
216pub fn add_shifts(lhs: [i32; 3], rhs: [i32; 3]) -> [i32; 3] {
218 let b = lhs[0] + rhs[0];
219 let c = lhs[1] + rhs[1];
220 let l = lhs[2] + rhs[2];
221 [b, c, l]
222}
223
224pub fn merging_range_by_guess_and_lazy_shift<T, U: Copy + Ord + std::fmt::Debug, V: Copy>(
231 (container, len): (&impl std::ops::Index<usize, Output = T>, usize),
232 (guess_i, [start, end]): (usize, [U; 2]),
233 (shift_from, shift, zero_shift, shift_fn): (usize, V, V, fn(U, V) -> U),
234 (start_fn, end_fn): (fn(&T) -> U, fn(&T) -> U),
235) -> Range<usize> {
236 let sh = |n: usize| if n >= shift_from { shift } else { zero_shift };
237 let start_of = |i: usize| shift_fn(start_fn(&container[i]), sh(i));
238 let end_of = |i: usize| shift_fn(end_fn(&container[i]), sh(i));
239 let search = |n: usize, t: &T| shift_fn(start_fn(t), sh(n));
240
241 let mut m_range = if let Some(prev_i) = guess_i.checked_sub(1)
242 && (prev_i < len && start_of(prev_i) <= start && start <= end_of(prev_i))
243 {
244 prev_i..guess_i
245 } else {
246 match binary_search_by_key_and_index(container, len, start, search) {
247 Ok(i) => i..i + 1,
248 Err(i) => {
249 if let Some(prev_i) = i.checked_sub(1)
250 && start <= end_of(prev_i)
251 {
252 prev_i..i
253 } else {
254 i..i
255 }
256 }
257 }
258 };
259
260 while m_range.start > 0 && start <= end_of(m_range.start - 1) {
262 m_range.start -= 1;
263 }
264
265 if m_range.end < len && end >= start_of(m_range.end) {
267 m_range.end = match binary_search_by_key_and_index(container, len, end, search) {
268 Ok(i) => i + 1,
269 Err(i) => i,
270 }
271 }
272
273 while m_range.end + 1 < len && end >= start_of(m_range.end + 1) {
274 m_range.end += 1;
275 }
276
277 m_range
278}
279
280pub fn binary_search_by_key_and_index<T, K>(
287 container: &(impl std::ops::Index<usize, Output = T> + ?Sized),
288 len: usize,
289 key: K,
290 f: impl Fn(usize, &T) -> K,
291) -> std::result::Result<usize, usize>
292where
293 K: PartialEq + Eq + PartialOrd + Ord,
294{
295 let mut size = len;
296 let mut left = 0;
297 let mut right = size;
298
299 while left < right {
300 let mid = left + size / 2;
301
302 let k = f(mid, &container[mid]);
303
304 match k.cmp(&key) {
305 std::cmp::Ordering::Less => left = mid + 1,
306 std::cmp::Ordering::Equal => return Ok(mid),
307 std::cmp::Ordering::Greater => right = mid,
308 }
309
310 size = right - left;
311 }
312
313 Err(left)
314}
315
316#[doc(hidden)]
318#[rustfmt::skip]
319#[macro_export]
320macro_rules! doc_duat {
321 ($duat:ident) => {
322 #[allow(unused, missing_docs)]
323 mod $duat {
324 pub mod cursor {
325 pub use ::duat_core::form::{
326 extra_cursor as get_extra, id_of, main_cursor as get_main,
327 set_extra_cursor as set_extra, set_main_cursor as set_main,
328 unset_cursors as unset, unset_extra_cursor as unset_extra,
329 unset_main_cursor as unset_main,
330 };
331 }
332
333 pub mod opts {
334 use super::prelude::*;
335 pub use ::duat_core::opts::{self, PrintOpts};
336 pub fn set(set: impl FnOnce(PrintOpts) -> PrintOpts) {}
337 pub fn set_lines<T>(set: T) {}
338 pub fn set_status<T>(set: impl FnMut(&mut Pass) -> T) {}
339 pub fn set_notifs<T>(set: T) {}
340 pub fn set_logs<T>(set: T) {}
341 pub fn one_line_footer() {}
342 }
343
344 pub mod data {
345 pub use ::duat_core::data::*;
346 }
347
348 pub mod state {
349 use super::prelude::*;
350 pub fn name_txt(buffer: &Buffer) -> Text { Text::default() }
351 pub fn path_txt(buffer: &Buffer) -> Text { Text::default() }
352 pub fn mode_name() -> data::DataMap<&'static str, &'static str> { todo!() }
353 pub fn mode_txt() -> data::DataMap<&'static str, Text> { todo!() }
354 pub fn main_byte(buffer: &Buffer) -> usize { 0 }
355 pub fn main_char(buffer: &Buffer) -> usize { 0 }
356 pub fn main_line(buffer: &Buffer) -> usize { 0 }
357 pub fn main_col(buffer: &Buffer, area: &ui::Area) -> usize { 0 }
358 pub fn main_txt(buffer: &Buffer, area: &ui::Area) -> Text { Text::default() }
359 pub fn selections(buffer: &Buffer) -> usize { 0 }
360 pub fn sels_txt(buffer: &Buffer) -> Text { Text::default() }
361 pub fn cur_map_txt() -> data::DataMap<(Vec<KeyEvent>, bool), Text> { todo!() }
362 pub fn last_key() -> data::RwData<String> { todo!() }
363 }
364
365 pub mod prelude {
366 pub use std::ops::Range;
367
368 pub use ::duat_core::{
369 Plugin, Plugins,
370 buffer::{Buffer, BufferTracker, Parser},
371 clipboard, cmd,
372 context::{self, Handle},
373 data::{self, Pass},
374 form::{self, CursorShape, Form},
375 hook::{
376 self, BufferWritten, ColorSchemeSet, ConfigLoaded, ConfigUnloaded,
377 ExitedDuat, FocusChanged, FocusedOnDuat, FormSet, Hookable, KeysSent,
378 KeysSentTo, ModeSet, ModeSwitched, UnfocusedFrom, UnfocusedFromDuat,
379 WidgetCreated, WindowCreated,
380 },
381 lender::{Lender, DoubleEndedLender, ExactSizeLender},
382 mode::{
383 self, KeyCode, KeyEvent, Mode, User, alias, alt, ctrl, event,
384 map, shift,
385 },
386 text::{
387 self, AlignCenter, AlignLeft, AlignRight, Builder, Conceal, Ghost, Spacer,
388 SpawnTag, Tagger, Text, txt, Point, Searcher
389 },
390 ui::{self, Area, Widget},
391 };
392
393 pub use super::{
394 cursor::*, state::*, modes::*, widgets::*, PassFileType, FileType, opts, plug
395 };
396
397 #[macro_export]
398 macro_rules! setup_duat{ ($setup:ident) => {} }
399 }
400
401 pub mod widgets {
402 use std::fmt::Alignment;
403
404 pub struct LineNumbers {
405 buffer: Handle,
406 text: Text,
407 pub relative: bool,
408 pub align: Alignment,
409 pub main_align: Alignment,
410 pub show_wraps: bool,
411 }
412 impl LineNumbers {
413 pub fn builder() -> LineNumbersOpts { LineNumbersOpts {
414 relative: false,
415 align: Alignment::Right,
416 main_align: Alignment::Right,
417 show_wraps: false,
418 on_the_right: false
419 }}
420 }
421 impl Widget for LineNumbers {
422 fn update(pa: &mut Pass, handle: &Handle<Self>) {}
423 fn needs_update(&self, pa: &Pass) -> bool { false }
424 fn text(&self) -> &Text { &self.text }
425 fn text_mut(&mut self) -> &mut Text { &mut self.text }
426 }
427 #[derive(Clone, Copy, Debug)]
428 pub struct LineNumbersOpts {
429 pub relative: bool,
430 pub align: Alignment,
431 pub main_align: Alignment,
432 pub show_wraps: bool,
433 pub on_the_right: bool,
434 }
435 impl LineNumbersOpts {
436 pub fn push_on(self, _: &mut Pass, _: &Handle) {}
437 }
438
439 #[macro_export]
440 macro_rules! status{ ($str: literal) => { $duat::widgets::StatusLine } }
441 pub struct StatusLine;
442 impl StatusLine {
443 pub fn above(self) -> Self { Self }
444 pub fn push_on(self, _: &mut Pass, _: &impl ::duat_core::ui::PushTarget) {}
445 }
446
447 use super::prelude::*;
448 pub struct VertRule;
449 impl VertRule {
450 pub fn builder() -> VertRuleBuilder { VertRuleBuilder }
451 }
452 pub struct VertRuleBuilder;
453 impl VertRuleBuilder {
454 pub fn push_on(self, _: &mut Pass, _: &impl ::duat_core::ui::PushTarget) {}
455 pub fn on_the_right(self) -> Self { self }
456 }
457 }
458
459 pub mod modes {
460 use super::prelude::*;
461 #[derive(Clone)]
462 pub struct Pager;
463 impl ::duat_core::mode::Mode for Pager {
464 type Widget = ::duat_core::buffer::Buffer;
465 fn send_key(
466 &mut self,
467 _: &mut Pass,
468 _: ::duat_core::mode::KeyEvent,
469 _: Handle<Self::Widget>,
470 ) {
471 }
472 }
473
474 #[derive(Clone)]
475 pub struct Prompt;
476 impl ::duat_core::mode::Mode for Prompt {
477 type Widget = ::duat_core::buffer::Buffer;
478 fn send_key(
479 &mut self,
480 _: &mut Pass,
481 _: ::duat_core::mode::KeyEvent,
482 _: Handle<Self::Widget>,
483 ) {
484 }
485 }
486 }
487
488 pub trait FileType {
489 fn filetype(&self) -> Option<&'static str> { None }
490 }
491
492 impl FileType for prelude::Buffer {}
493 impl FileType for String {}
494 impl FileType for &'_ str {}
495 impl FileType for std::path::PathBuf {}
496 impl FileType for &'_ std::path::Path {}
497
498 pub trait PassFileType {
499 fn filetype(&self, _: &prelude::Pass) -> Option<&'static str> { None }
500 }
501 impl PassFileType for prelude::data::RwData<prelude::Buffer> {}
502 impl PassFileType for prelude::Handle {}
503
504 pub fn plug<P: $crate::Plugin>(plugin: P) {}
505 }
506 }
507}