1use std::{
44 any::TypeId,
45 collections::HashMap,
46 ops::Range,
47 path::{Path, PathBuf},
48 sync::{LazyLock, Mutex, OnceLock, RwLock},
49};
50
51pub use shellexpand::full as expand_path;
52
53use crate::text::{Text, txt};
54
55pub struct Memoized<K: std::hash::Hash + std::cmp::Eq, V>(LazyLock<Mutex<HashMap<K, V>>>);
57
58impl<K: std::hash::Hash + std::cmp::Eq, V: Clone + 'static> Memoized<K, V> {
59 pub const fn new() -> Self {
61 Self(LazyLock::new(Mutex::default))
62 }
63
64 pub fn get_or_insert_with<Q>(&self, key: &Q, f: impl FnOnce() -> V) -> V
66 where
67 K: std::borrow::Borrow<Q>,
68 Q: std::hash::Hash + Eq + Clone + Into<K>,
69 {
70 let mut map = self.0.lock().unwrap_or_else(|err| err.into_inner());
71 match map.get(key) {
72 Some(value) => value.clone(),
73 None => map.entry(key.clone().into()).or_insert(f()).clone(),
74 }
75 }
76}
77
78impl<K: std::hash::Hash + std::cmp::Eq, V: Clone + 'static> Default for Memoized<K, V> {
79 fn default() -> Self {
80 Self::new()
81 }
82}
83
84pub fn duat_name<T: ?Sized + 'static>() -> &'static str {
90 fn duat_name_inner(type_id: TypeId, type_name: &str) -> &'static str {
91 static NAMES: LazyLock<RwLock<HashMap<TypeId, &'static str>>> =
92 LazyLock::new(RwLock::default);
93 let mut names = NAMES.write().unwrap();
94
95 if let Some(name) = names.get(&type_id) {
96 name
97 } else {
98 let mut name = String::new();
99
100 for path in type_name.split_inclusive(['<', '>', ',', ' ']) {
101 for segment in path.split("::") {
102 let is_type = segment.chars().any(|c| c.is_uppercase());
103 let is_punct = segment.chars().all(|c| !c.is_alphanumeric());
104 let is_dyn = segment.starts_with("dyn");
105 if is_type || is_punct || is_dyn {
106 name.push_str(segment);
107 }
108 }
109 }
110
111 names.insert(type_id, name.leak());
112 names.get(&type_id).unwrap()
113 }
114 }
115
116 duat_name_inner(TypeId::of::<T>(), std::any::type_name::<T>())
117}
118
119pub fn src_crate<T: ?Sized + 'static>() -> &'static str {
121 fn src_crate_inner(type_id: TypeId, type_name: &'static str) -> &'static str {
122 static CRATES: LazyLock<RwLock<HashMap<TypeId, &'static str>>> =
123 LazyLock::new(|| RwLock::new(HashMap::new()));
124 let mut crates = CRATES.write().unwrap();
125
126 if let Some(src_crate) = crates.get(&type_id) {
127 src_crate
128 } else {
129 let src_crate = type_name.split([' ', ':']).find(|w| *w != "dyn").unwrap();
130
131 crates.insert(type_id, src_crate);
132 crates.get(&type_id).unwrap()
133 }
134 }
135
136 src_crate_inner(TypeId::of::<T>(), std::any::type_name::<T>())
137}
138
139static CRATE_DIR: OnceLock<Option<&Path>> = OnceLock::new();
141static PROFILE: OnceLock<&str> = OnceLock::new();
143
144#[doc(hidden)]
148#[track_caller]
149pub(crate) fn set_crate_profile_and_dir(profile: String, dir: Option<String>) {
150 CRATE_DIR
151 .set(dir.map(|dir| Path::new(dir.leak())))
152 .expect("Crate directory set multiple times.");
153 PROFILE
154 .set(profile.leak())
155 .expect("Profile set multiple times.");
156}
157
158#[track_caller]
160pub fn crate_dir() -> Result<&'static Path, Text> {
161 CRATE_DIR
162 .get()
163 .expect("Config not set yet")
164 .ok_or_else(|| txt!("Config directory is [a]undefined"))
165}
166
167pub fn profile() -> &'static str {
172 PROFILE.get().expect("Profile not set yet")
173}
174
175pub fn plugin_dir(plugin: &str) -> Result<PathBuf, Text> {
186 assert_ne!(plugin, "", "Can't have an empty plugin name");
187
188 static PLUGIN_DIR: LazyLock<Option<&Path>> = LazyLock::new(|| {
189 dirs_next::data_local_dir().map(|local_dir| {
190 let path: &'static str = local_dir
191 .join("duat")
192 .join("plugins")
193 .to_string_lossy()
194 .to_string()
195 .leak();
196
197 Path::new(path)
198 })
199 });
200
201 let plugin_dir = (*PLUGIN_DIR)
202 .ok_or_else(|| txt!("Local directory is [a]undefined"))?
203 .join(plugin);
204 std::fs::create_dir_all(&plugin_dir)?;
205
206 Ok(plugin_dir)
207}
208
209pub fn try_get_range(range: impl std::ops::RangeBounds<usize>, max: usize) -> Option<Range<usize>> {
213 let start = match range.start_bound() {
214 std::ops::Bound::Included(start) => *start,
215 std::ops::Bound::Excluded(start) => *start + 1,
216 std::ops::Bound::Unbounded => 0,
217 };
218 let end = match range.end_bound() {
219 std::ops::Bound::Included(end) => *end + 1,
220 std::ops::Bound::Excluded(end) => *end,
221 std::ops::Bound::Unbounded => max,
222 };
223
224 if start > max || end > max || start > end {
225 None
226 } else {
227 Some(start..end)
228 }
229}
230
231#[track_caller]
235pub fn get_range(range: impl std::ops::RangeBounds<usize>, max: usize) -> Range<usize> {
236 let start = match range.start_bound() {
237 std::ops::Bound::Included(start) => *start,
238 std::ops::Bound::Excluded(start) => *start + 1,
239 std::ops::Bound::Unbounded => 0,
240 };
241 let end = match range.end_bound() {
242 std::ops::Bound::Included(end) => *end + 1,
243 std::ops::Bound::Excluded(end) => *end,
244 std::ops::Bound::Unbounded => max,
245 };
246
247 crate::ranges::assert_range(&(start..end));
248
249 assert!(
250 start <= max,
251 "start out of bounds: the len is {max}, but the index is {start}",
252 );
253 assert!(
254 end <= max,
255 "end out of bounds: the len is {max}, but the index is {end}",
256 );
257
258 start..end
259}
260
261pub fn add_shifts(lhs: [i32; 3], rhs: [i32; 3]) -> [i32; 3] {
263 let b = lhs[0] + rhs[0];
264 let c = lhs[1] + rhs[1];
265 let l = lhs[2] + rhs[2];
266 [b, c, l]
267}
268
269#[track_caller]
276pub fn merging_range_by_guess_and_lazy_shift<T, U: Copy + Ord + std::fmt::Debug, V: Copy>(
277 (container, len): (&impl std::ops::Index<usize, Output = T>, usize),
278 (guess_i, [start, end]): (usize, [U; 2]),
279 (shift_from, shift, zero_shift, shift_fn): (usize, V, V, fn(U, V) -> U),
280 (start_fn, end_fn): (fn(&T) -> U, fn(&T) -> U),
281) -> Range<usize> {
282 let sh = |n: usize| if n >= shift_from { shift } else { zero_shift };
283 let start_of = |i: usize| shift_fn(start_fn(&container[i]), sh(i));
284 let end_of = |i: usize| shift_fn(end_fn(&container[i]), sh(i));
285 let search = |n: usize, t: &T| shift_fn(start_fn(t), sh(n));
286
287 let mut m_range = if let Some(prev_i) = guess_i.checked_sub(1)
288 && (prev_i < len && start_of(prev_i) <= start && start <= end_of(prev_i))
289 {
290 prev_i..guess_i
291 } else {
292 match binary_search_by_key_and_index(container, len, start, search) {
293 Ok(i) => i..i + 1,
294 Err(i) => {
295 if let Some(prev_i) = i.checked_sub(1)
296 && start <= end_of(prev_i)
297 {
298 prev_i..i
299 } else {
300 i..i
301 }
302 }
303 }
304 };
305
306 while m_range.start > 0 && start <= end_of(m_range.start - 1) {
308 m_range.start -= 1;
309 }
310
311 if m_range.end < len && end >= start_of(m_range.end) {
313 m_range.end = match binary_search_by_key_and_index(container, len, end, search) {
314 Ok(i) => i + 1,
315 Err(i) => i,
316 }
317 }
318
319 while m_range.end + 1 < len && end >= start_of(m_range.end + 1) {
320 m_range.end += 1;
321 }
322
323 m_range
324}
325
326#[inline(always)]
333pub fn binary_search_by_key_and_index<T, K>(
334 container: &(impl std::ops::Index<usize, Output = T> + ?Sized),
335 len: usize,
336 key: K,
337 f: impl Fn(usize, &T) -> K,
338) -> std::result::Result<usize, usize>
339where
340 K: PartialEq + Eq + PartialOrd + Ord,
341{
342 let mut size = len;
343 let mut left = 0;
344 let mut right = size;
345
346 while left < right {
347 let mid = left + size / 2;
348
349 let k = f(mid, &container[mid]);
350
351 match k.cmp(&key) {
352 std::cmp::Ordering::Less => left = mid + 1,
353 std::cmp::Ordering::Equal => return Ok(mid),
354 std::cmp::Ordering::Greater => right = mid,
355 }
356
357 size = right - left;
358 }
359
360 Err(left)
361}
362
363pub fn catch_panic<R>(f: impl FnOnce() -> R) -> Option<R> {
372 std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)).ok()
373}
374
375#[macro_export]
378macro_rules! log_to_file {
379 ($($tokens:tt)*) => {{
380 use std::io::Write;
381 let mut log = std::fs::OpenOptions::new()
382 .append(true)
383 .create(true)
384 .open("log")
385 .unwrap();
386
387 writeln!(log, $($tokens)*).unwrap();
388 }};
389}
390
391#[doc(hidden)]
393#[rustfmt::skip]
394#[macro_export]
395macro_rules! doc_duat {
396 ($duat:ident) => {
397 #[allow(unused, missing_docs)]
398 mod $duat {
399 pub use $crate::{clipboard, notify, process};
400
401 pub struct Opts {
402 pub wrap_lines: bool,
403 pub wrap_on_word: bool,
404 pub wrapping_cap: Option<u32>,
405 pub indent_wraps: bool,
406 pub tabstop: u8,
407 pub print_new_line: bool,
408 pub scrolloff: $crate::opts::ScrollOff,
409 pub force_scrolloff: bool,
410 pub extra_word_chars: &'static [char],
411 pub show_ghosts: bool,
412 pub allow_overscroll: bool,
413 pub one_line_footer: bool,
414 pub footer_on_top: bool,
415 }
416
417 pub mod hook {
418 pub use $crate::hook::*;
419 }
420
421 pub mod text {
422 pub use $crate::text::*;
423 }
424
425 pub mod cursor {
426 pub use $crate::form::{
427 extra_cursor as get_extra, id_of, main_cursor as get_main,
428 set_extra_cursor as set_extra, set_main_cursor as set_main,
429 unset_cursors as unset, unset_extra_cursor as unset_extra,
430 unset_main_cursor as unset_main,
431 };
432 }
433
434 pub mod opts {
435 use super::prelude::*;
436 pub use $crate::opts::{self, PrintOpts};
437 pub fn set(set: impl FnOnce(&mut super::Opts)) {}
438 pub fn fmt_status<T>(set: impl FnMut(&mut Pass) -> T) {}
439 }
440
441 pub mod data {
442 pub use $crate::data::*;
443 }
444
445 pub mod state {
446 use super::prelude::*;
447 pub fn name_txt(buffer: &Buffer) -> Text { Text::default() }
448 pub fn path_txt(buffer: &Buffer) -> Text { Text::default() }
449 pub fn mode_name() -> data::DataMap<&'static str, &'static str> { todo!() }
450 pub fn mode_txt() -> data::DataMap<&'static str, Text> { todo!() }
451 pub fn main_byte(buffer: &Buffer) -> usize { 0 }
452 pub fn main_char(buffer: &Buffer) -> usize { 0 }
453 pub fn main_line(buffer: &Buffer) -> usize { 0 }
454 pub fn main_col(buffer: &Buffer, area: &ui::Area) -> usize { 0 }
455 pub fn main_txt(buffer: &Buffer, area: &ui::Area) -> Text { Text::default() }
456 pub fn selections(buffer: &Buffer) -> usize { 0 }
457 pub fn sels_txt(buffer: &Buffer) -> Text { Text::default() }
458 pub fn cur_map_txt() -> data::DataMap<(Vec<KeyEvent>, bool), Text> { todo!() }
459 pub fn last_key() -> data::RwData<String> { todo!() }
460 }
461
462 pub mod mode {
463 pub use $crate::mode::*;
464 pub use super::modes::*;
465 }
466
467 pub mod prelude {
468 pub use std::ops::Range;
469 pub use $crate::try_or_log_err;
470
471 pub use $crate::{
472 Ns, buffer::Buffer, cmd,
473 context::{self, Handle},
474 data::{self, Pass},
475 form::{self, CursorShape, Form},
476 hook::{
477 self, BufferOpened, BufferSaved, BufferUpdated, ColorschemeSet,
478 ConfigLoaded, ConfigUnloaded, FocusChanged, FocusedOn, FocusedOnDuat,
479 FormSet, Hookable, KeySent, ModeSwitched, UnfocusedFrom, UnfocusedFromDuat,
480 WidgetOpened, WindowOpened,
481 },
482 text::{
483 self, Builder, Conceal, Inlay, Spacer, Spawn, Strs, Text, txt, Point, Mask,
484 TextMut
485 },
486 ui::{self, Area, Widget},
487 };
488
489 pub use super::{
490 cursor::*,
491 mode::{
492 self, KeyCode, KeyEvent, Mode, Prompt, Pager, User, alias, alt, ctrl, event,
493 map, shift,
494 },
495 state::*, widgets::*, PassFileType, FileType, opts, Opts
496 };
497
498 #[macro_export]
499 macro_rules! setup_duat{ ($setup:ident) => {} }
500 }
501
502 pub mod widgets {
503 use std::fmt::Alignment;
504
505 pub struct LineNumbers {
506 buffer: Handle,
507 text: Text,
508 pub relative: bool,
509 pub align: Alignment,
510 pub main_align: Alignment,
511 pub show_wraps: bool,
512 }
513 impl LineNumbers {
514 pub fn builder() -> LineNumbersOpts { LineNumbersOpts {
515 relative: false,
516 align: Alignment::Right,
517 main_align: Alignment::Right,
518 show_wraps: false,
519 on_the_right: false
520 }}
521 }
522 impl Widget for LineNumbers {
523 fn text(&self) -> &Text { &self.text }
524 fn text_mut(&mut self) -> TextMut<'_> { self.text.as_mut() }
525 }
526 #[derive(Clone, Copy, Debug)]
527 pub struct LineNumbersOpts {
528 pub relative: bool,
529 pub align: Alignment,
530 pub main_align: Alignment,
531 pub show_wraps: bool,
532 pub on_the_right: bool,
533 }
534 impl LineNumbersOpts {
535 pub fn push_on(self, _: &mut Pass, _: &Handle) {}
536 }
537
538 #[macro_export]
539 macro_rules! status{ ($str: literal) => { $duat::widgets::StatusLine } }
540 pub struct StatusLine;
541 impl StatusLine {
542 pub fn above(self) -> Self { Self }
543 pub fn push_on(self, _: &mut Pass, _: &impl $crate::ui::PushTarget) {}
544 }
545
546 pub struct LogBook;
547 impl LogBook {
548 pub fn builder() -> LogBookOpts { LogBookOpts::default() }
549 pub fn push_on(self, _: &mut Pass, _: &impl $crate::ui::PushTarget) {}
550 }
551 #[derive(Default, Clone, Copy, Debug)]
552 pub struct LogBookOpts {
553 pub close_on_unfocus: bool,
554 pub hidden: bool,
555 pub side: $crate::ui::Side,
556 pub height: f32,
557 pub width: f32,
558 }
559 impl LogBookOpts {
560 pub fn push_on(self, _: &mut Pass, _: &impl $crate::ui::PushTarget) {}
561 }
562
563 use super::prelude::*;
564 pub struct VertRule;
565 impl VertRule {
566 pub fn builder() -> VertRuleBuilder { VertRuleBuilder }
567 }
568 pub struct VertRuleBuilder;
569 impl VertRuleBuilder {
570 pub fn push_on(self, _: &mut Pass, _: &impl $crate::ui::PushTarget) {}
571 pub fn on_the_right(self) -> Self { self }
572 }
573 }
574
575 pub mod modes {
576 use super::prelude::*;
577 #[derive(Clone)]
578 pub struct Pager;
579 impl $crate::mode::Mode for Pager {
580 type Widget = $crate::buffer::Buffer;
581 fn send_key(
582 &mut self,
583 _: &mut Pass,
584 _: $crate::mode::KeyEvent,
585 _: Handle<Self::Widget>,
586 ) {
587 }
588 }
589
590 #[derive(Clone)]
591 pub struct Prompt;
592 impl $crate::mode::Mode for Prompt {
593 type Widget = $crate::buffer::Buffer;
594 fn send_key(
595 &mut self,
596 _: &mut Pass,
597 _: $crate::mode::KeyEvent,
598 _: Handle<Self::Widget>,
599 ) {
600 }
601 }
602
603 #[derive(Clone)]
604 pub struct RunCommands;
605 impl RunCommands {
606 pub fn new() -> Prompt {
607 Prompt
608 }
609 pub fn new_with(initial: &str) -> Prompt {
610 Prompt
611 }
612 }
613 }
614
615 pub trait FileType {
616 fn filetype(&self) -> Option<&'static str> { None }
617 }
618
619 impl FileType for prelude::Buffer {}
620 impl FileType for String {}
621 impl FileType for &'_ str {}
622 impl FileType for std::path::PathBuf {}
623 impl FileType for &'_ std::path::Path {}
624
625 pub trait PassFileType {
626 fn filetype(&self, _: &prelude::Pass) -> Option<&'static str> { None }
627 }
628 impl PassFileType for prelude::data::RwData<prelude::Buffer> {}
629 impl PassFileType for prelude::Handle {}
630
631
632 }
633 }
634}