util.rs

   1pub mod arc_cow;
   2pub mod command;
   3pub mod fs;
   4pub mod markdown;
   5pub mod paths;
   6pub mod serde;
   7#[cfg(any(test, feature = "test-support"))]
   8pub mod test;
   9
  10use anyhow::Result;
  11use futures::Future;
  12use itertools::Either;
  13use regex::Regex;
  14use std::sync::{LazyLock, OnceLock};
  15use std::{
  16    borrow::Cow,
  17    cmp::{self, Ordering},
  18    env,
  19    ops::{AddAssign, Range, RangeInclusive},
  20    panic::Location,
  21    pin::Pin,
  22    task::{Context, Poll},
  23    time::Instant,
  24};
  25use unicase::UniCase;
  26
  27#[cfg(unix)]
  28use anyhow::{anyhow, Context as _};
  29
  30pub use take_until::*;
  31#[cfg(any(test, feature = "test-support"))]
  32pub use util_macros::{line_endings, separator, uri};
  33
  34#[macro_export]
  35macro_rules! debug_panic {
  36    ( $($fmt_arg:tt)* ) => {
  37        if cfg!(debug_assertions) {
  38            panic!( $($fmt_arg)* );
  39        } else {
  40            let backtrace = std::backtrace::Backtrace::capture();
  41            log::error!("{}\n{:?}", format_args!($($fmt_arg)*), backtrace);
  42        }
  43    };
  44}
  45
  46/// A macro to add "C:" to the beginning of a path literal on Windows, and replace all
  47/// the separator from `/` to `\`.
  48/// But on non-Windows platforms, it will return the path literal as is.
  49///
  50/// # Examples
  51/// ```rust
  52/// use util::path;
  53///
  54/// let path = path!("/Users/user/file.txt");
  55/// #[cfg(target_os = "windows")]
  56/// assert_eq!(path, "C:\\Users\\user\\file.txt");
  57/// #[cfg(not(target_os = "windows"))]
  58/// assert_eq!(path, "/Users/user/file.txt");
  59/// ```
  60#[cfg(all(any(test, feature = "test-support"), target_os = "windows"))]
  61#[macro_export]
  62macro_rules! path {
  63    ($path:literal) => {
  64        concat!("C:", util::separator!($path))
  65    };
  66}
  67
  68/// A macro to add "C:" to the beginning of a path literal on Windows, and replace all
  69/// the separator from `/` to `\`.
  70/// But on non-Windows platforms, it will return the path literal as is.
  71///
  72/// # Examples
  73/// ```rust
  74/// use util::path;
  75///
  76/// let path = path!("/Users/user/file.txt");
  77/// #[cfg(target_os = "windows")]
  78/// assert_eq!(path, "C:\\Users\\user\\file.txt");
  79/// #[cfg(not(target_os = "windows"))]
  80/// assert_eq!(path, "/Users/user/file.txt");
  81/// ```
  82#[cfg(all(any(test, feature = "test-support"), not(target_os = "windows")))]
  83#[macro_export]
  84macro_rules! path {
  85    ($path:literal) => {
  86        $path
  87    };
  88}
  89
  90pub fn truncate(s: &str, max_chars: usize) -> &str {
  91    match s.char_indices().nth(max_chars) {
  92        None => s,
  93        Some((idx, _)) => &s[..idx],
  94    }
  95}
  96
  97/// Removes characters from the end of the string if its length is greater than `max_chars` and
  98/// appends "..." to the string. Returns string unchanged if its length is smaller than max_chars.
  99pub fn truncate_and_trailoff(s: &str, max_chars: usize) -> String {
 100    debug_assert!(max_chars >= 5);
 101
 102    // If the string's byte length is <= max_chars, walking the string can be skipped since the
 103    // number of chars is <= the number of bytes.
 104    if s.len() <= max_chars {
 105        return s.to_string();
 106    }
 107    let truncation_ix = s.char_indices().map(|(i, _)| i).nth(max_chars);
 108    match truncation_ix {
 109        Some(index) => s[..index].to_string() + "",
 110        _ => s.to_string(),
 111    }
 112}
 113
 114/// Removes characters from the front of the string if its length is greater than `max_chars` and
 115/// prepends the string with "...". Returns string unchanged if its length is smaller than max_chars.
 116pub fn truncate_and_remove_front(s: &str, max_chars: usize) -> String {
 117    debug_assert!(max_chars >= 5);
 118
 119    // If the string's byte length is <= max_chars, walking the string can be skipped since the
 120    // number of chars is <= the number of bytes.
 121    if s.len() <= max_chars {
 122        return s.to_string();
 123    }
 124    let suffix_char_length = max_chars.saturating_sub(1);
 125    let truncation_ix = s
 126        .char_indices()
 127        .map(|(i, _)| i)
 128        .nth_back(suffix_char_length);
 129    match truncation_ix {
 130        Some(index) if index > 0 => "".to_string() + &s[index..],
 131        _ => s.to_string(),
 132    }
 133}
 134
 135/// Takes only `max_lines` from the string and, if there were more than `max_lines-1`, appends a
 136/// a newline and "..." to the string, so that `max_lines` are returned.
 137/// Returns string unchanged if its length is smaller than max_lines.
 138pub fn truncate_lines_and_trailoff(s: &str, max_lines: usize) -> String {
 139    let mut lines = s.lines().take(max_lines).collect::<Vec<_>>();
 140    if lines.len() > max_lines - 1 {
 141        lines.pop();
 142        lines.join("\n") + "\n"
 143    } else {
 144        lines.join("\n")
 145    }
 146}
 147
 148pub fn post_inc<T: From<u8> + AddAssign<T> + Copy>(value: &mut T) -> T {
 149    let prev = *value;
 150    *value += T::from(1);
 151    prev
 152}
 153
 154/// Extend a sorted vector with a sorted sequence of items, maintaining the vector's sort order and
 155/// enforcing a maximum length. This also de-duplicates items. Sort the items according to the given callback. Before calling this,
 156/// both `vec` and `new_items` should already be sorted according to the `cmp` comparator.
 157pub fn extend_sorted<T, I, F>(vec: &mut Vec<T>, new_items: I, limit: usize, mut cmp: F)
 158where
 159    I: IntoIterator<Item = T>,
 160    F: FnMut(&T, &T) -> Ordering,
 161{
 162    let mut start_index = 0;
 163    for new_item in new_items {
 164        if let Err(i) = vec[start_index..].binary_search_by(|m| cmp(m, &new_item)) {
 165            let index = start_index + i;
 166            if vec.len() < limit {
 167                vec.insert(index, new_item);
 168            } else if index < vec.len() {
 169                vec.pop();
 170                vec.insert(index, new_item);
 171            }
 172            start_index = index;
 173        }
 174    }
 175}
 176
 177pub fn truncate_to_bottom_n_sorted_by<T, F>(items: &mut Vec<T>, limit: usize, compare: &F)
 178where
 179    F: Fn(&T, &T) -> Ordering,
 180{
 181    if limit == 0 {
 182        items.truncate(0);
 183    }
 184    if items.len() <= limit {
 185        items.sort_by(compare);
 186        return;
 187    }
 188    // When limit is near to items.len() it may be more efficient to sort the whole list and
 189    // truncate, rather than always doing selection first as is done below. It's hard to analyze
 190    // where the threshold for this should be since the quickselect style algorithm used by
 191    // `select_nth_unstable_by` makes the prefix partially sorted, and so its work is not wasted -
 192    // the expected number of comparisons needed by `sort_by` is less than it is for some arbitrary
 193    // unsorted input.
 194    items.select_nth_unstable_by(limit, compare);
 195    items.truncate(limit);
 196    items.sort_by(compare);
 197}
 198
 199#[cfg(unix)]
 200pub fn load_shell_from_passwd() -> Result<()> {
 201    let buflen = match unsafe { libc::sysconf(libc::_SC_GETPW_R_SIZE_MAX) } {
 202        n if n < 0 => 1024,
 203        n => n as usize,
 204    };
 205    let mut buffer = Vec::with_capacity(buflen);
 206
 207    let mut pwd: std::mem::MaybeUninit<libc::passwd> = std::mem::MaybeUninit::uninit();
 208    let mut result: *mut libc::passwd = std::ptr::null_mut();
 209
 210    let uid = unsafe { libc::getuid() };
 211    let status = unsafe {
 212        libc::getpwuid_r(
 213            uid,
 214            pwd.as_mut_ptr(),
 215            buffer.as_mut_ptr() as *mut libc::c_char,
 216            buflen,
 217            &mut result,
 218        )
 219    };
 220    let entry = unsafe { pwd.assume_init() };
 221
 222    anyhow::ensure!(
 223        status == 0,
 224        "call to getpwuid_r failed. uid: {}, status: {}",
 225        uid,
 226        status
 227    );
 228    anyhow::ensure!(!result.is_null(), "passwd entry for uid {} not found", uid);
 229    anyhow::ensure!(
 230        entry.pw_uid == uid,
 231        "passwd entry has different uid ({}) than getuid ({}) returned",
 232        entry.pw_uid,
 233        uid,
 234    );
 235
 236    let shell = unsafe { std::ffi::CStr::from_ptr(entry.pw_shell).to_str().unwrap() };
 237    if env::var("SHELL").map_or(true, |shell_env| shell_env != shell) {
 238        log::info!(
 239            "updating SHELL environment variable to value from passwd entry: {:?}",
 240            shell,
 241        );
 242        env::set_var("SHELL", shell);
 243    }
 244
 245    Ok(())
 246}
 247
 248#[cfg(unix)]
 249pub fn load_login_shell_environment() -> Result<()> {
 250    let marker = "ZED_LOGIN_SHELL_START";
 251    let shell = env::var("SHELL").context(
 252        "SHELL environment variable is not assigned so we can't source login environment variables",
 253    )?;
 254
 255    // If possible, we want to `cd` in the user's `$HOME` to trigger programs
 256    // such as direnv, asdf, mise, ... to adjust the PATH. These tools often hook
 257    // into shell's `cd` command (and hooks) to manipulate env.
 258    // We do this so that we get the env a user would have when spawning a shell
 259    // in home directory.
 260    let shell_cmd_prefix = std::env::var_os("HOME")
 261        .and_then(|home| home.into_string().ok())
 262        .map(|home| format!("cd '{home}';"));
 263
 264    // The `exit 0` is the result of hours of debugging, trying to find out
 265    // why running this command here, without `exit 0`, would mess
 266    // up signal process for our process so that `ctrl-c` doesn't work
 267    // anymore.
 268    // We still don't know why `$SHELL -l -i -c '/usr/bin/env -0'`  would
 269    // do that, but it does, and `exit 0` helps.
 270    let shell_cmd = format!(
 271        "{}printf '%s' {marker}; /usr/bin/env; exit 0;",
 272        shell_cmd_prefix.as_deref().unwrap_or("")
 273    );
 274
 275    let output = std::process::Command::new(&shell)
 276        .args(["-l", "-i", "-c", &shell_cmd])
 277        .output()
 278        .context("failed to spawn login shell to source login environment variables")?;
 279    if !output.status.success() {
 280        Err(anyhow!("login shell exited with error"))?;
 281    }
 282
 283    let stdout = String::from_utf8_lossy(&output.stdout);
 284
 285    if let Some(env_output_start) = stdout.find(marker) {
 286        let env_output = &stdout[env_output_start + marker.len()..];
 287
 288        parse_env_output(env_output, |key, value| env::set_var(key, value));
 289
 290        log::info!(
 291            "set environment variables from shell:{}, path:{}",
 292            shell,
 293            env::var("PATH").unwrap_or_default(),
 294        );
 295    }
 296
 297    Ok(())
 298}
 299
 300/// Parse the result of calling `usr/bin/env` with no arguments
 301pub fn parse_env_output(env: &str, mut f: impl FnMut(String, String)) {
 302    let mut current_key: Option<String> = None;
 303    let mut current_value: Option<String> = None;
 304
 305    for line in env.split_terminator('\n') {
 306        if let Some(separator_index) = line.find('=') {
 307            if !line[..separator_index].is_empty() {
 308                if let Some((key, value)) = Option::zip(current_key.take(), current_value.take()) {
 309                    f(key, value)
 310                }
 311                current_key = Some(line[..separator_index].to_string());
 312                current_value = Some(line[separator_index + 1..].to_string());
 313                continue;
 314            };
 315        }
 316        if let Some(value) = current_value.as_mut() {
 317            value.push('\n');
 318            value.push_str(line);
 319        }
 320    }
 321    if let Some((key, value)) = Option::zip(current_key.take(), current_value.take()) {
 322        f(key, value)
 323    }
 324}
 325
 326pub fn merge_json_value_into(source: serde_json::Value, target: &mut serde_json::Value) {
 327    use serde_json::Value;
 328
 329    match (source, target) {
 330        (Value::Object(source), Value::Object(target)) => {
 331            for (key, value) in source {
 332                if let Some(target) = target.get_mut(&key) {
 333                    merge_json_value_into(value, target);
 334                } else {
 335                    target.insert(key, value);
 336                }
 337            }
 338        }
 339
 340        (Value::Array(source), Value::Array(target)) => {
 341            for value in source {
 342                target.push(value);
 343            }
 344        }
 345
 346        (source, target) => *target = source,
 347    }
 348}
 349
 350pub fn merge_non_null_json_value_into(source: serde_json::Value, target: &mut serde_json::Value) {
 351    use serde_json::Value;
 352    if let Value::Object(source_object) = source {
 353        let target_object = if let Value::Object(target) = target {
 354            target
 355        } else {
 356            *target = Value::Object(Default::default());
 357            target.as_object_mut().unwrap()
 358        };
 359        for (key, value) in source_object {
 360            if let Some(target) = target_object.get_mut(&key) {
 361                merge_non_null_json_value_into(value, target);
 362            } else if !value.is_null() {
 363                target_object.insert(key, value);
 364            }
 365        }
 366    } else if !source.is_null() {
 367        *target = source
 368    }
 369}
 370
 371pub fn measure<R>(label: &str, f: impl FnOnce() -> R) -> R {
 372    static ZED_MEASUREMENTS: OnceLock<bool> = OnceLock::new();
 373    let zed_measurements = ZED_MEASUREMENTS.get_or_init(|| {
 374        env::var("ZED_MEASUREMENTS")
 375            .map(|measurements| measurements == "1" || measurements == "true")
 376            .unwrap_or(false)
 377    });
 378
 379    if *zed_measurements {
 380        let start = Instant::now();
 381        let result = f();
 382        let elapsed = start.elapsed();
 383        eprintln!("{}: {:?}", label, elapsed);
 384        result
 385    } else {
 386        f()
 387    }
 388}
 389
 390pub fn iterate_expanded_and_wrapped_usize_range(
 391    range: Range<usize>,
 392    additional_before: usize,
 393    additional_after: usize,
 394    wrap_length: usize,
 395) -> impl Iterator<Item = usize> {
 396    let start_wraps = range.start < additional_before;
 397    let end_wraps = wrap_length < range.end + additional_after;
 398    if start_wraps && end_wraps {
 399        Either::Left(0..wrap_length)
 400    } else if start_wraps {
 401        let wrapped_start = (range.start + wrap_length).saturating_sub(additional_before);
 402        if wrapped_start <= range.end {
 403            Either::Left(0..wrap_length)
 404        } else {
 405            Either::Right((0..range.end + additional_after).chain(wrapped_start..wrap_length))
 406        }
 407    } else if end_wraps {
 408        let wrapped_end = range.end + additional_after - wrap_length;
 409        if range.start <= wrapped_end {
 410            Either::Left(0..wrap_length)
 411        } else {
 412            Either::Right((0..wrapped_end).chain(range.start - additional_before..wrap_length))
 413        }
 414    } else {
 415        Either::Left((range.start - additional_before)..(range.end + additional_after))
 416    }
 417}
 418
 419pub trait ResultExt<E> {
 420    type Ok;
 421
 422    fn log_err(self) -> Option<Self::Ok>;
 423    /// Assert that this result should never be an error in development or tests.
 424    fn debug_assert_ok(self, reason: &str) -> Self;
 425    fn warn_on_err(self) -> Option<Self::Ok>;
 426    fn log_with_level(self, level: log::Level) -> Option<Self::Ok>;
 427    fn anyhow(self) -> anyhow::Result<Self::Ok>
 428    where
 429        E: Into<anyhow::Error>;
 430}
 431
 432impl<T, E> ResultExt<E> for Result<T, E>
 433where
 434    E: std::fmt::Debug,
 435{
 436    type Ok = T;
 437
 438    #[track_caller]
 439    fn log_err(self) -> Option<T> {
 440        self.log_with_level(log::Level::Error)
 441    }
 442
 443    #[track_caller]
 444    fn debug_assert_ok(self, reason: &str) -> Self {
 445        if let Err(error) = &self {
 446            debug_panic!("{reason} - {error:?}");
 447        }
 448        self
 449    }
 450
 451    #[track_caller]
 452    fn warn_on_err(self) -> Option<T> {
 453        self.log_with_level(log::Level::Warn)
 454    }
 455
 456    #[track_caller]
 457    fn log_with_level(self, level: log::Level) -> Option<T> {
 458        match self {
 459            Ok(value) => Some(value),
 460            Err(error) => {
 461                log_error_with_caller(*Location::caller(), error, level);
 462                None
 463            }
 464        }
 465    }
 466
 467    fn anyhow(self) -> anyhow::Result<T>
 468    where
 469        E: Into<anyhow::Error>,
 470    {
 471        self.map_err(Into::into)
 472    }
 473}
 474
 475fn log_error_with_caller<E>(caller: core::panic::Location<'_>, error: E, level: log::Level)
 476where
 477    E: std::fmt::Debug,
 478{
 479    #[cfg(not(target_os = "windows"))]
 480    let file = caller.file();
 481    #[cfg(target_os = "windows")]
 482    let file = caller.file().replace('\\', "/");
 483    // In this codebase, the first segment of the file path is
 484    // the 'crates' folder, followed by the crate name.
 485    let target = file.split('/').nth(1);
 486
 487    log::logger().log(
 488        &log::Record::builder()
 489            .target(target.unwrap_or(""))
 490            .module_path(target)
 491            .args(format_args!("{:?}", error))
 492            .file(Some(caller.file()))
 493            .line(Some(caller.line()))
 494            .level(level)
 495            .build(),
 496    );
 497}
 498
 499pub fn log_err<E: std::fmt::Debug>(error: &E) {
 500    log_error_with_caller(*Location::caller(), error, log::Level::Warn);
 501}
 502
 503pub trait TryFutureExt {
 504    fn log_err(self) -> LogErrorFuture<Self>
 505    where
 506        Self: Sized;
 507
 508    fn log_tracked_err(self, location: core::panic::Location<'static>) -> LogErrorFuture<Self>
 509    where
 510        Self: Sized;
 511
 512    fn warn_on_err(self) -> LogErrorFuture<Self>
 513    where
 514        Self: Sized;
 515    fn unwrap(self) -> UnwrapFuture<Self>
 516    where
 517        Self: Sized;
 518}
 519
 520impl<F, T, E> TryFutureExt for F
 521where
 522    F: Future<Output = Result<T, E>>,
 523    E: std::fmt::Debug,
 524{
 525    #[track_caller]
 526    fn log_err(self) -> LogErrorFuture<Self>
 527    where
 528        Self: Sized,
 529    {
 530        let location = Location::caller();
 531        LogErrorFuture(self, log::Level::Error, *location)
 532    }
 533
 534    fn log_tracked_err(self, location: core::panic::Location<'static>) -> LogErrorFuture<Self>
 535    where
 536        Self: Sized,
 537    {
 538        LogErrorFuture(self, log::Level::Error, location)
 539    }
 540
 541    #[track_caller]
 542    fn warn_on_err(self) -> LogErrorFuture<Self>
 543    where
 544        Self: Sized,
 545    {
 546        let location = Location::caller();
 547        LogErrorFuture(self, log::Level::Warn, *location)
 548    }
 549
 550    fn unwrap(self) -> UnwrapFuture<Self>
 551    where
 552        Self: Sized,
 553    {
 554        UnwrapFuture(self)
 555    }
 556}
 557
 558#[must_use]
 559pub struct LogErrorFuture<F>(F, log::Level, core::panic::Location<'static>);
 560
 561impl<F, T, E> Future for LogErrorFuture<F>
 562where
 563    F: Future<Output = Result<T, E>>,
 564    E: std::fmt::Debug,
 565{
 566    type Output = Option<T>;
 567
 568    fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
 569        let level = self.1;
 570        let location = self.2;
 571        let inner = unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().0) };
 572        match inner.poll(cx) {
 573            Poll::Ready(output) => Poll::Ready(match output {
 574                Ok(output) => Some(output),
 575                Err(error) => {
 576                    log_error_with_caller(location, error, level);
 577                    None
 578                }
 579            }),
 580            Poll::Pending => Poll::Pending,
 581        }
 582    }
 583}
 584
 585pub struct UnwrapFuture<F>(F);
 586
 587impl<F, T, E> Future for UnwrapFuture<F>
 588where
 589    F: Future<Output = Result<T, E>>,
 590    E: std::fmt::Debug,
 591{
 592    type Output = T;
 593
 594    fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
 595        let inner = unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().0) };
 596        match inner.poll(cx) {
 597            Poll::Ready(result) => Poll::Ready(result.unwrap()),
 598            Poll::Pending => Poll::Pending,
 599        }
 600    }
 601}
 602
 603pub struct Deferred<F: FnOnce()>(Option<F>);
 604
 605impl<F: FnOnce()> Deferred<F> {
 606    /// Drop without running the deferred function.
 607    pub fn abort(mut self) {
 608        self.0.take();
 609    }
 610}
 611
 612impl<F: FnOnce()> Drop for Deferred<F> {
 613    fn drop(&mut self) {
 614        if let Some(f) = self.0.take() {
 615            f()
 616        }
 617    }
 618}
 619
 620/// Run the given function when the returned value is dropped (unless it's cancelled).
 621#[must_use]
 622pub fn defer<F: FnOnce()>(f: F) -> Deferred<F> {
 623    Deferred(Some(f))
 624}
 625
 626#[cfg(any(test, feature = "test-support"))]
 627mod rng {
 628    use rand::{seq::SliceRandom, Rng};
 629    pub struct RandomCharIter<T: Rng> {
 630        rng: T,
 631        simple_text: bool,
 632    }
 633
 634    impl<T: Rng> RandomCharIter<T> {
 635        pub fn new(rng: T) -> Self {
 636            Self {
 637                rng,
 638                simple_text: std::env::var("SIMPLE_TEXT").map_or(false, |v| !v.is_empty()),
 639            }
 640        }
 641
 642        pub fn with_simple_text(mut self) -> Self {
 643            self.simple_text = true;
 644            self
 645        }
 646    }
 647
 648    impl<T: Rng> Iterator for RandomCharIter<T> {
 649        type Item = char;
 650
 651        fn next(&mut self) -> Option<Self::Item> {
 652            if self.simple_text {
 653                return if self.rng.gen_range(0..100) < 5 {
 654                    Some('\n')
 655                } else {
 656                    Some(self.rng.gen_range(b'a'..b'z' + 1).into())
 657                };
 658            }
 659
 660            match self.rng.gen_range(0..100) {
 661                // whitespace
 662                0..=19 => [' ', '\n', '\r', '\t'].choose(&mut self.rng).copied(),
 663                // two-byte greek letters
 664                20..=32 => char::from_u32(self.rng.gen_range(('α' as u32)..('ω' as u32 + 1))),
 665                // // three-byte characters
 666                33..=45 => ['✋', '✅', '❌', '❎', '⭐']
 667                    .choose(&mut self.rng)
 668                    .copied(),
 669                // // four-byte characters
 670                46..=58 => ['🍐', '🏀', '🍗', '🎉'].choose(&mut self.rng).copied(),
 671                // ascii letters
 672                _ => Some(self.rng.gen_range(b'a'..b'z' + 1).into()),
 673            }
 674        }
 675    }
 676}
 677#[cfg(any(test, feature = "test-support"))]
 678pub use rng::RandomCharIter;
 679/// Get an embedded file as a string.
 680pub fn asset_str<A: rust_embed::RustEmbed>(path: &str) -> Cow<'static, str> {
 681    match A::get(path).expect(path).data {
 682        Cow::Borrowed(bytes) => Cow::Borrowed(std::str::from_utf8(bytes).unwrap()),
 683        Cow::Owned(bytes) => Cow::Owned(String::from_utf8(bytes).unwrap()),
 684    }
 685}
 686
 687/// Expands to an immediately-invoked function expression. Good for using the ? operator
 688/// in functions which do not return an Option or Result.
 689///
 690/// Accepts a normal block, an async block, or an async move block.
 691#[macro_export]
 692macro_rules! maybe {
 693    ($block:block) => {
 694        (|| $block)()
 695    };
 696    (async $block:block) => {
 697        (|| async $block)()
 698    };
 699    (async move $block:block) => {
 700        (|| async move $block)()
 701    };
 702}
 703
 704pub trait RangeExt<T> {
 705    fn sorted(&self) -> Self;
 706    fn to_inclusive(&self) -> RangeInclusive<T>;
 707    fn overlaps(&self, other: &Range<T>) -> bool;
 708    fn contains_inclusive(&self, other: &Range<T>) -> bool;
 709}
 710
 711impl<T: Ord + Clone> RangeExt<T> for Range<T> {
 712    fn sorted(&self) -> Self {
 713        cmp::min(&self.start, &self.end).clone()..cmp::max(&self.start, &self.end).clone()
 714    }
 715
 716    fn to_inclusive(&self) -> RangeInclusive<T> {
 717        self.start.clone()..=self.end.clone()
 718    }
 719
 720    fn overlaps(&self, other: &Range<T>) -> bool {
 721        self.start < other.end && other.start < self.end
 722    }
 723
 724    fn contains_inclusive(&self, other: &Range<T>) -> bool {
 725        self.start <= other.start && other.end <= self.end
 726    }
 727}
 728
 729impl<T: Ord + Clone> RangeExt<T> for RangeInclusive<T> {
 730    fn sorted(&self) -> Self {
 731        cmp::min(self.start(), self.end()).clone()..=cmp::max(self.start(), self.end()).clone()
 732    }
 733
 734    fn to_inclusive(&self) -> RangeInclusive<T> {
 735        self.clone()
 736    }
 737
 738    fn overlaps(&self, other: &Range<T>) -> bool {
 739        self.start() < &other.end && &other.start <= self.end()
 740    }
 741
 742    fn contains_inclusive(&self, other: &Range<T>) -> bool {
 743        self.start() <= &other.start && &other.end <= self.end()
 744    }
 745}
 746
 747/// A way to sort strings with starting numbers numerically first, falling back to alphanumeric one,
 748/// case-insensitive.
 749///
 750/// This is useful for turning regular alphanumerically sorted sequences as `1-abc, 10, 11-def, .., 2, 21-abc`
 751/// into `1-abc, 2, 10, 11-def, .., 21-abc`
 752#[derive(Debug, PartialEq, Eq)]
 753pub struct NumericPrefixWithSuffix<'a>(Option<u64>, &'a str);
 754
 755impl<'a> NumericPrefixWithSuffix<'a> {
 756    pub fn from_numeric_prefixed_str(str: &'a str) -> Self {
 757        let i = str.chars().take_while(|c| c.is_ascii_digit()).count();
 758        let (prefix, remainder) = str.split_at(i);
 759
 760        let prefix = prefix.parse().ok();
 761        Self(prefix, remainder)
 762    }
 763}
 764
 765/// When dealing with equality, we need to consider the case of the strings to achieve strict equality
 766/// to handle cases like "a" < "A" instead of "a" == "A".
 767impl Ord for NumericPrefixWithSuffix<'_> {
 768    fn cmp(&self, other: &Self) -> Ordering {
 769        match (self.0, other.0) {
 770            (None, None) => UniCase::new(self.1)
 771                .cmp(&UniCase::new(other.1))
 772                .then_with(|| self.1.cmp(other.1).reverse()),
 773            (None, Some(_)) => Ordering::Greater,
 774            (Some(_), None) => Ordering::Less,
 775            (Some(a), Some(b)) => a.cmp(&b).then_with(|| {
 776                UniCase::new(self.1)
 777                    .cmp(&UniCase::new(other.1))
 778                    .then_with(|| self.1.cmp(other.1).reverse())
 779            }),
 780        }
 781    }
 782}
 783
 784impl PartialOrd for NumericPrefixWithSuffix<'_> {
 785    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
 786        Some(self.cmp(other))
 787    }
 788}
 789
 790/// Capitalizes the first character of a string.
 791///
 792/// This function takes a string slice as input and returns a new `String` with the first character
 793/// capitalized.
 794///
 795/// # Examples
 796///
 797/// ```
 798/// use util::capitalize;
 799///
 800/// assert_eq!(capitalize("hello"), "Hello");
 801/// assert_eq!(capitalize("WORLD"), "WORLD");
 802/// assert_eq!(capitalize(""), "");
 803/// ```
 804pub fn capitalize(str: &str) -> String {
 805    let mut chars = str.chars();
 806    match chars.next() {
 807        None => String::new(),
 808        Some(first_char) => first_char.to_uppercase().collect::<String>() + chars.as_str(),
 809    }
 810}
 811
 812fn emoji_regex() -> &'static Regex {
 813    static EMOJI_REGEX: LazyLock<Regex> =
 814        LazyLock::new(|| Regex::new("(\\p{Emoji}|\u{200D})").unwrap());
 815    &EMOJI_REGEX
 816}
 817
 818/// Returns true if the given string consists of emojis only.
 819/// E.g. "👨‍👩‍👧‍👧👋" will return true, but "👋!" will return false.
 820pub fn word_consists_of_emojis(s: &str) -> bool {
 821    let mut prev_end = 0;
 822    for capture in emoji_regex().find_iter(s) {
 823        if capture.start() != prev_end {
 824            return false;
 825        }
 826        prev_end = capture.end();
 827    }
 828    prev_end == s.len()
 829}
 830
 831pub fn default<D: Default>() -> D {
 832    Default::default()
 833}
 834
 835#[cfg(test)]
 836mod tests {
 837    use super::*;
 838
 839    #[test]
 840    fn test_extend_sorted() {
 841        let mut vec = vec![];
 842
 843        extend_sorted(&mut vec, vec![21, 17, 13, 8, 1, 0], 5, |a, b| b.cmp(a));
 844        assert_eq!(vec, &[21, 17, 13, 8, 1]);
 845
 846        extend_sorted(&mut vec, vec![101, 19, 17, 8, 2], 8, |a, b| b.cmp(a));
 847        assert_eq!(vec, &[101, 21, 19, 17, 13, 8, 2, 1]);
 848
 849        extend_sorted(&mut vec, vec![1000, 19, 17, 9, 5], 8, |a, b| b.cmp(a));
 850        assert_eq!(vec, &[1000, 101, 21, 19, 17, 13, 9, 8]);
 851    }
 852
 853    #[test]
 854    fn test_truncate_to_bottom_n_sorted_by() {
 855        let mut vec: Vec<u32> = vec![5, 2, 3, 4, 1];
 856        truncate_to_bottom_n_sorted_by(&mut vec, 10, &u32::cmp);
 857        assert_eq!(vec, &[1, 2, 3, 4, 5]);
 858
 859        vec = vec![5, 2, 3, 4, 1];
 860        truncate_to_bottom_n_sorted_by(&mut vec, 5, &u32::cmp);
 861        assert_eq!(vec, &[1, 2, 3, 4, 5]);
 862
 863        vec = vec![5, 2, 3, 4, 1];
 864        truncate_to_bottom_n_sorted_by(&mut vec, 4, &u32::cmp);
 865        assert_eq!(vec, &[1, 2, 3, 4]);
 866
 867        vec = vec![5, 2, 3, 4, 1];
 868        truncate_to_bottom_n_sorted_by(&mut vec, 1, &u32::cmp);
 869        assert_eq!(vec, &[1]);
 870
 871        vec = vec![5, 2, 3, 4, 1];
 872        truncate_to_bottom_n_sorted_by(&mut vec, 0, &u32::cmp);
 873        assert!(vec.is_empty());
 874    }
 875
 876    #[test]
 877    fn test_iife() {
 878        fn option_returning_function() -> Option<()> {
 879            None
 880        }
 881
 882        let foo = maybe!({
 883            option_returning_function()?;
 884            Some(())
 885        });
 886
 887        assert_eq!(foo, None);
 888    }
 889
 890    #[test]
 891    fn test_truncate_and_trailoff() {
 892        assert_eq!(truncate_and_trailoff("", 5), "");
 893        assert_eq!(truncate_and_trailoff("aaaaaa", 7), "aaaaaa");
 894        assert_eq!(truncate_and_trailoff("aaaaaa", 6), "aaaaaa");
 895        assert_eq!(truncate_and_trailoff("aaaaaa", 5), "aaaaa…");
 896        assert_eq!(truncate_and_trailoff("èèèèèè", 7), "èèèèèè");
 897        assert_eq!(truncate_and_trailoff("èèèèèè", 6), "èèèèèè");
 898        assert_eq!(truncate_and_trailoff("èèèèèè", 5), "èèèèè…");
 899    }
 900
 901    #[test]
 902    fn test_truncate_and_remove_front() {
 903        assert_eq!(truncate_and_remove_front("", 5), "");
 904        assert_eq!(truncate_and_remove_front("aaaaaa", 7), "aaaaaa");
 905        assert_eq!(truncate_and_remove_front("aaaaaa", 6), "aaaaaa");
 906        assert_eq!(truncate_and_remove_front("aaaaaa", 5), "…aaaaa");
 907        assert_eq!(truncate_and_remove_front("èèèèèè", 7), "èèèèèè");
 908        assert_eq!(truncate_and_remove_front("èèèèèè", 6), "èèèèèè");
 909        assert_eq!(truncate_and_remove_front("èèèèèè", 5), "…èèèèè");
 910    }
 911
 912    #[test]
 913    fn test_numeric_prefix_str_method() {
 914        let target = "1a";
 915        assert_eq!(
 916            NumericPrefixWithSuffix::from_numeric_prefixed_str(target),
 917            NumericPrefixWithSuffix(Some(1), "a")
 918        );
 919
 920        let target = "12ab";
 921        assert_eq!(
 922            NumericPrefixWithSuffix::from_numeric_prefixed_str(target),
 923            NumericPrefixWithSuffix(Some(12), "ab")
 924        );
 925
 926        let target = "12_ab";
 927        assert_eq!(
 928            NumericPrefixWithSuffix::from_numeric_prefixed_str(target),
 929            NumericPrefixWithSuffix(Some(12), "_ab")
 930        );
 931
 932        let target = "1_2ab";
 933        assert_eq!(
 934            NumericPrefixWithSuffix::from_numeric_prefixed_str(target),
 935            NumericPrefixWithSuffix(Some(1), "_2ab")
 936        );
 937
 938        let target = "1.2";
 939        assert_eq!(
 940            NumericPrefixWithSuffix::from_numeric_prefixed_str(target),
 941            NumericPrefixWithSuffix(Some(1), ".2")
 942        );
 943
 944        let target = "1.2_a";
 945        assert_eq!(
 946            NumericPrefixWithSuffix::from_numeric_prefixed_str(target),
 947            NumericPrefixWithSuffix(Some(1), ".2_a")
 948        );
 949
 950        let target = "12.2_a";
 951        assert_eq!(
 952            NumericPrefixWithSuffix::from_numeric_prefixed_str(target),
 953            NumericPrefixWithSuffix(Some(12), ".2_a")
 954        );
 955
 956        let target = "12a.2_a";
 957        assert_eq!(
 958            NumericPrefixWithSuffix::from_numeric_prefixed_str(target),
 959            NumericPrefixWithSuffix(Some(12), "a.2_a")
 960        );
 961    }
 962
 963    #[test]
 964    fn test_numeric_prefix_with_suffix() {
 965        let mut sorted = vec!["1-abc", "10", "11def", "2", "21-abc"];
 966        sorted.sort_by_key(|s| NumericPrefixWithSuffix::from_numeric_prefixed_str(s));
 967        assert_eq!(sorted, ["1-abc", "2", "10", "11def", "21-abc"]);
 968
 969        for numeric_prefix_less in ["numeric_prefix_less", "aaa", "~™£"] {
 970            assert_eq!(
 971                NumericPrefixWithSuffix::from_numeric_prefixed_str(numeric_prefix_less),
 972                NumericPrefixWithSuffix(None, numeric_prefix_less),
 973                "String without numeric prefix `{numeric_prefix_less}` should not be converted into NumericPrefixWithSuffix"
 974            )
 975        }
 976    }
 977
 978    #[test]
 979    fn test_word_consists_of_emojis() {
 980        let words_to_test = vec![
 981            ("👨‍👩‍👧‍👧👋🥒", true),
 982            ("👋", true),
 983            ("!👋", false),
 984            ("👋!", false),
 985            ("👋 ", false),
 986            (" 👋", false),
 987            ("Test", false),
 988        ];
 989
 990        for (text, expected_result) in words_to_test {
 991            assert_eq!(word_consists_of_emojis(text), expected_result);
 992        }
 993    }
 994
 995    #[test]
 996    fn test_truncate_lines_and_trailoff() {
 997        let text = r#"Line 1
 998Line 2
 999Line 3"#;
1000
1001        assert_eq!(
1002            truncate_lines_and_trailoff(text, 2),
1003            r#"Line 1
1004…"#
1005        );
1006
1007        assert_eq!(
1008            truncate_lines_and_trailoff(text, 3),
1009            r#"Line 1
1010Line 2
1011…"#
1012        );
1013
1014        assert_eq!(
1015            truncate_lines_and_trailoff(text, 4),
1016            r#"Line 1
1017Line 2
1018Line 3"#
1019        );
1020    }
1021
1022    #[test]
1023    fn test_iterate_expanded_and_wrapped_usize_range() {
1024        // Neither wrap
1025        assert_eq!(
1026            iterate_expanded_and_wrapped_usize_range(2..4, 1, 1, 8).collect::<Vec<usize>>(),
1027            (1..5).collect::<Vec<usize>>()
1028        );
1029        // Start wraps
1030        assert_eq!(
1031            iterate_expanded_and_wrapped_usize_range(2..4, 3, 1, 8).collect::<Vec<usize>>(),
1032            ((0..5).chain(7..8)).collect::<Vec<usize>>()
1033        );
1034        // Start wraps all the way around
1035        assert_eq!(
1036            iterate_expanded_and_wrapped_usize_range(2..4, 5, 1, 8).collect::<Vec<usize>>(),
1037            (0..8).collect::<Vec<usize>>()
1038        );
1039        // Start wraps all the way around and past 0
1040        assert_eq!(
1041            iterate_expanded_and_wrapped_usize_range(2..4, 10, 1, 8).collect::<Vec<usize>>(),
1042            (0..8).collect::<Vec<usize>>()
1043        );
1044        // End wraps
1045        assert_eq!(
1046            iterate_expanded_and_wrapped_usize_range(3..5, 1, 4, 8).collect::<Vec<usize>>(),
1047            (0..1).chain(2..8).collect::<Vec<usize>>()
1048        );
1049        // End wraps all the way around
1050        assert_eq!(
1051            iterate_expanded_and_wrapped_usize_range(3..5, 1, 5, 8).collect::<Vec<usize>>(),
1052            (0..8).collect::<Vec<usize>>()
1053        );
1054        // End wraps all the way around and past the end
1055        assert_eq!(
1056            iterate_expanded_and_wrapped_usize_range(3..5, 1, 10, 8).collect::<Vec<usize>>(),
1057            (0..8).collect::<Vec<usize>>()
1058        );
1059        // Both start and end wrap
1060        assert_eq!(
1061            iterate_expanded_and_wrapped_usize_range(3..5, 4, 4, 8).collect::<Vec<usize>>(),
1062            (0..8).collect::<Vec<usize>>()
1063        );
1064    }
1065}