Skip to main content

gpui_util/
lib.rs

1// FluentBuilder
2// pub use gpui_util::{FutureExt, Timeout, arc_cow::ArcCow};
3
4use std::{
5    env,
6    ffi::OsStr,
7    fmt,
8    ops::AddAssign,
9    panic::Location,
10    pin::Pin,
11    sync::OnceLock,
12    task::{Context, Poll},
13    time::Instant,
14};
15
16pub mod arc_cow;
17
18#[cfg(target_os = "windows")]
19const CREATE_NO_WINDOW: u32 = 0x0800_0000_u32;
20
21#[cfg(target_os = "windows")]
22pub fn new_std_command(program: impl AsRef<OsStr>) -> std::process::Command {
23    use std::os::windows::process::CommandExt;
24
25    let mut command = std::process::Command::new(program);
26    command.creation_flags(CREATE_NO_WINDOW);
27    command
28}
29
30#[cfg(not(target_os = "windows"))]
31pub fn new_std_command(program: impl AsRef<OsStr>) -> std::process::Command {
32    std::process::Command::new(program)
33}
34
35#[cfg(target_os = "windows")]
36pub fn get_powershell() -> Option<String> {
37    use std::path::PathBuf;
38
39    fn find_pwsh_in_programfiles(find_alternate: bool, find_preview: bool) -> Option<PathBuf> {
40        #[cfg(target_pointer_width = "64")]
41        let env_var = if find_alternate {
42            "ProgramFiles(x86)"
43        } else {
44            "ProgramFiles"
45        };
46
47        #[cfg(target_pointer_width = "32")]
48        let env_var = if find_alternate {
49            "ProgramW6432"
50        } else {
51            "ProgramFiles"
52        };
53
54        let install_base_dir = PathBuf::from(std::env::var_os(env_var)?).join("PowerShell");
55        install_base_dir
56            .read_dir()
57            .ok()?
58            .filter_map(Result::ok)
59            .filter(|entry| matches!(entry.file_type(), Ok(ft) if ft.is_dir()))
60            .filter_map(|entry| {
61                let dir_name = entry.file_name();
62                let dir_name = dir_name.to_string_lossy();
63
64                let version = if find_preview {
65                    let dash_index = dir_name.find('-')?;
66                    if &dir_name[dash_index + 1..] != "preview" {
67                        return None;
68                    };
69                    dir_name[..dash_index].parse::<u32>().ok()?
70                } else {
71                    dir_name.parse::<u32>().ok()?
72                };
73
74                let exe_path = entry.path().join("pwsh.exe");
75                if exe_path.is_file() {
76                    Some((version, exe_path))
77                } else {
78                    None
79                }
80            })
81            .max_by_key(|(version, _)| *version)
82            .map(|(_, path)| path)
83    }
84
85    fn find_pwsh_in_msix(find_preview: bool) -> Option<PathBuf> {
86        let msix_app_dir =
87            PathBuf::from(std::env::var_os("LOCALAPPDATA")?).join("Microsoft\\WindowsApps");
88        let package_family_name = if find_preview {
89            "Microsoft.PowerShellPreview_8wekyb3d8bbwe"
90        } else {
91            "Microsoft.PowerShell_8wekyb3d8bbwe"
92        };
93        let pwsh_exe = msix_app_dir.join(package_family_name).join("pwsh.exe");
94        pwsh_exe.exists().then_some(pwsh_exe)
95    }
96
97    fn find_pwsh_in_scoop() -> Option<PathBuf> {
98        // Scoop can be installed to a custom location; $SCOOP points at the
99        // scoop root in that case and defaults to %USERPROFILE%\scoop otherwise.
100        let scoop_dir = std::env::var_os("SCOOP").map(PathBuf::from).or_else(|| {
101            std::env::var_os("USERPROFILE").map(|home| PathBuf::from(home).join("scoop"))
102        })?;
103        let pwsh_exe = scoop_dir.join("shims").join("pwsh.exe");
104        pwsh_exe.is_file().then_some(pwsh_exe)
105    }
106
107    fn find_pwsh_in_dotnet_tools() -> Option<PathBuf> {
108        let pwsh_exe =
109            PathBuf::from(std::env::var_os("USERPROFILE")?).join(".dotnet\\tools\\pwsh.exe");
110        pwsh_exe.is_file().then_some(pwsh_exe)
111    }
112
113    fn find_windows_powershell() -> Option<PathBuf> {
114        let system_root = PathBuf::from(std::env::var_os("SystemRoot")?);
115        let powershell = system_root.join("System32\\WindowsPowerShell\\v1.0\\powershell.exe");
116        powershell.is_file().then_some(powershell)
117    }
118
119    static POWERSHELL: std::sync::LazyLock<Option<String>> = std::sync::LazyLock::new(|| {
120        let locations = [
121            || find_pwsh_in_programfiles(false, false),
122            || find_pwsh_in_programfiles(true, false),
123            || find_pwsh_in_msix(false),
124            || find_pwsh_in_programfiles(false, true),
125            || find_pwsh_in_msix(true),
126            || find_pwsh_in_programfiles(true, true),
127            || find_pwsh_in_scoop(),
128            || find_pwsh_in_dotnet_tools(),
129            || which::which_global("pwsh.exe").ok(),
130            || which::which_global("powershell.exe").ok(),
131            || find_windows_powershell(),
132        ];
133
134        locations
135            .into_iter()
136            .find_map(|f| f())
137            .map(|p| p.to_string_lossy().trim().to_owned())
138            .inspect(|shell| log::info!("Found powershell in: {}", shell))
139    });
140
141    (*POWERSHELL).clone()
142}
143
144#[cfg(target_os = "windows")]
145pub fn get_windows_system_shell() -> String {
146    static CMD: std::sync::LazyLock<String> = std::sync::LazyLock::new(|| {
147        log::warn!("Powershell not found, falling back to `cmd`");
148        let system_root = std::env::var_os("SystemRoot").unwrap_or_else(|| "C:\\Windows".into());
149        std::path::PathBuf::from(system_root)
150            .join("System32\\cmd.exe")
151            .to_string_lossy()
152            .into_owned()
153    });
154    get_powershell().unwrap_or_else(|| (*CMD).clone())
155}
156
157#[inline]
158pub const fn is_utf8_char_boundary(u8: u8) -> bool {
159    // This is bit magic equivalent to: b < 128 || b >= 192
160    (u8 as i8) >= -0x40
161}
162
163pub fn post_inc<T: From<u8> + AddAssign<T> + Copy>(value: &mut T) -> T {
164    let prev = *value;
165    *value += T::from(1);
166    prev
167}
168
169pub fn measure<R>(label: &str, f: impl FnOnce() -> R) -> R {
170    static ZED_MEASUREMENTS: OnceLock<bool> = OnceLock::new();
171    let zed_measurements = ZED_MEASUREMENTS.get_or_init(|| {
172        env::var("ZED_MEASUREMENTS")
173            .map(|measurements| measurements == "1" || measurements == "true")
174            .unwrap_or(false)
175    });
176
177    if *zed_measurements {
178        let start = Instant::now();
179        let result = f();
180        let elapsed = start.elapsed();
181        eprintln!("{}: {:?}", label, elapsed);
182        result
183    } else {
184        f()
185    }
186}
187
188#[macro_export]
189macro_rules! debug_panic {
190    ( $($fmt_arg:tt)* ) => {
191        if cfg!(debug_assertions) {
192            panic!( $($fmt_arg)* );
193        } else {
194            let backtrace = std::backtrace::Backtrace::capture();
195            log::error!("{}\n{:?}", format_args!($($fmt_arg)*), backtrace);
196        }
197    };
198}
199
200#[track_caller]
201pub fn some_or_debug_panic<T>(option: Option<T>) -> Option<T> {
202    #[cfg(debug_assertions)]
203    if option.is_none() {
204        panic!("Unexpected None");
205    }
206    option
207}
208
209/// Expands to an immediately-invoked function expression. Good for using the ? operator
210/// in functions which do not return an Option or Result.
211///
212/// Accepts a normal block, an async block, or an async move block.
213#[macro_export]
214macro_rules! maybe {
215    ($block:block) => {
216        (|| $block)()
217    };
218    (async $block:block) => {
219        (async || $block)()
220    };
221    (async move $block:block) => {
222        (async move || $block)()
223    };
224}
225pub trait ResultExt<E> {
226    type Ok;
227
228    fn log_err(self) -> Option<Self::Ok>;
229    /// Like [`ResultExt::log_err`], but uses `{:?}` formatting so `anyhow::Error` values emit their
230    /// full backtrace. Reach for this only when a backtrace is genuinely wanted — most call sites
231    /// should stick with `log_err` / `warn_on_err`, whose output is a single chained error message.
232    fn log_err_with_backtrace(self) -> Option<Self::Ok>
233    where
234        E: std::fmt::Debug;
235    /// Assert that this result should never be an error in development or tests.
236    fn debug_assert_ok(self, reason: &str) -> Self;
237    fn warn_on_err(self) -> Option<Self::Ok>;
238    fn log_with_level(self, level: log::Level) -> Option<Self::Ok>;
239    fn anyhow(self) -> anyhow::Result<Self::Ok>
240    where
241        E: Into<anyhow::Error>;
242}
243
244impl<T, E> ResultExt<E> for Result<T, E>
245where
246    E: std::fmt::Display,
247{
248    type Ok = T;
249
250    #[track_caller]
251    fn log_err(self) -> Option<T> {
252        self.log_with_level(log::Level::Error)
253    }
254
255    #[track_caller]
256    fn log_err_with_backtrace(self) -> Option<T>
257    where
258        E: std::fmt::Debug,
259    {
260        match self {
261            Ok(value) => Some(value),
262            Err(error) => {
263                log_error_with_caller(
264                    *Location::caller(),
265                    format_args!("{:#}", DebugAsDisplay(&error)),
266                    log::Level::Error,
267                );
268                None
269            }
270        }
271    }
272
273    #[track_caller]
274    fn debug_assert_ok(self, reason: &str) -> Self {
275        if let Err(error) = &self {
276            debug_panic!("{reason} - {error:#}");
277        }
278        self
279    }
280
281    #[track_caller]
282    fn warn_on_err(self) -> Option<T> {
283        self.log_with_level(log::Level::Warn)
284    }
285
286    #[track_caller]
287    fn log_with_level(self, level: log::Level) -> Option<T> {
288        match self {
289            Ok(value) => Some(value),
290            Err(error) => {
291                log_error_with_caller(*Location::caller(), format_args!("{error:#}"), level);
292                None
293            }
294        }
295    }
296
297    fn anyhow(self) -> anyhow::Result<T>
298    where
299        E: Into<anyhow::Error>,
300    {
301        self.map_err(Into::into)
302    }
303}
304
305#[inline(never)]
306fn log_error_with_caller(
307    caller: core::panic::Location<'_>,
308    arguments: fmt::Arguments<'_>,
309    level: log::Level,
310) {
311    #[cfg(not(windows))]
312    let file = caller.file();
313    #[cfg(windows)]
314    let file = caller.file().replace('\\', "/");
315    // In this codebase all crates reside in a `crates` directory,
316    // so discard the prefix up to that segment to find the crate name
317    let file = file.split_once("crates/");
318    let target = file.as_ref().and_then(|(_, s)| s.split_once("/src/"));
319
320    let module_path = target.map(|(krate, module)| {
321        if module.starts_with(krate) {
322            module.trim_end_matches(".rs").replace('/', "::")
323        } else {
324            krate.to_owned() + "::" + &module.trim_end_matches(".rs").replace('/', "::")
325        }
326    });
327    let file = file.map(|(_, file)| format!("crates/{file}"));
328    log::logger().log(
329        &log::Record::builder()
330            .target(module_path.as_deref().unwrap_or(""))
331            .module_path(file.as_deref())
332            .args(arguments)
333            .file(Some(caller.file()))
334            .line(Some(caller.line()))
335            .level(level)
336            .build(),
337    );
338}
339
340#[track_caller]
341pub fn log_err<E: std::fmt::Display>(error: &E) {
342    log_error_with_caller(
343        *Location::caller(),
344        format_args!("{error:#}"),
345        log::Level::Error,
346    );
347}
348
349// Forces `{:?}` formatting through a `Display`-bounded logging helper so `anyhow::Error` emits a
350// backtrace instead of the single-line chained message produced by its `Display`/`{:#}` forms.
351struct DebugAsDisplay<'a, E>(&'a E);
352
353impl<E: std::fmt::Debug> std::fmt::Display for DebugAsDisplay<'_, E> {
354    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
355        write!(f, "{:?}", self.0)
356    }
357}
358
359pub trait TryFutureExt {
360    fn log_err(self) -> LogErrorFuture<Self>
361    where
362        Self: Sized;
363
364    fn log_tracked_err(self, location: core::panic::Location<'static>) -> LogErrorFuture<Self>
365    where
366        Self: Sized;
367
368    fn warn_on_err(self) -> LogErrorFuture<Self>
369    where
370        Self: Sized;
371    fn unwrap(self) -> UnwrapFuture<Self>
372    where
373        Self: Sized;
374}
375
376/// `{:?}`-formatting companion to [`TryFutureExt`]; emits a backtrace for `anyhow::Error`. Prefer
377/// [`TryFutureExt`] unless a backtrace is genuinely wanted.
378pub trait TryFutureExtBacktrace {
379    fn log_err_with_backtrace(self) -> LogErrorWithBacktraceFuture<Self>
380    where
381        Self: Sized;
382
383    fn log_tracked_err_with_backtrace(
384        self,
385        location: core::panic::Location<'static>,
386    ) -> LogErrorWithBacktraceFuture<Self>
387    where
388        Self: Sized;
389}
390
391impl<F, T, E> TryFutureExt for F
392where
393    F: Future<Output = Result<T, E>>,
394    E: std::fmt::Display,
395{
396    #[track_caller]
397    fn log_err(self) -> LogErrorFuture<Self>
398    where
399        Self: Sized,
400    {
401        let location = Location::caller();
402        LogErrorFuture(self, log::Level::Error, *location)
403    }
404
405    fn log_tracked_err(self, location: core::panic::Location<'static>) -> LogErrorFuture<Self>
406    where
407        Self: Sized,
408    {
409        LogErrorFuture(self, log::Level::Error, location)
410    }
411
412    #[track_caller]
413    fn warn_on_err(self) -> LogErrorFuture<Self>
414    where
415        Self: Sized,
416    {
417        let location = Location::caller();
418        LogErrorFuture(self, log::Level::Warn, *location)
419    }
420
421    fn unwrap(self) -> UnwrapFuture<Self>
422    where
423        Self: Sized,
424    {
425        UnwrapFuture(self)
426    }
427}
428
429impl<F, T, E> TryFutureExtBacktrace for F
430where
431    F: Future<Output = Result<T, E>>,
432    E: std::fmt::Debug,
433{
434    #[track_caller]
435    fn log_err_with_backtrace(self) -> LogErrorWithBacktraceFuture<Self>
436    where
437        Self: Sized,
438    {
439        let location = Location::caller();
440        LogErrorWithBacktraceFuture(self, log::Level::Error, *location)
441    }
442
443    fn log_tracked_err_with_backtrace(
444        self,
445        location: core::panic::Location<'static>,
446    ) -> LogErrorWithBacktraceFuture<Self>
447    where
448        Self: Sized,
449    {
450        LogErrorWithBacktraceFuture(self, log::Level::Error, location)
451    }
452}
453
454#[must_use]
455pub struct LogErrorFuture<F>(F, log::Level, core::panic::Location<'static>);
456
457impl<F, T, E> Future for LogErrorFuture<F>
458where
459    F: Future<Output = Result<T, E>>,
460    E: std::fmt::Display,
461{
462    type Output = Option<T>;
463
464    fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
465        let level = self.1;
466        let location = self.2;
467        let inner = unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().0) };
468        match inner.poll(cx) {
469            Poll::Ready(output) => Poll::Ready(match output {
470                Ok(output) => Some(output),
471                Err(error) => {
472                    log_error_with_caller(location, format_args!("{error:#}"), level);
473                    None
474                }
475            }),
476            Poll::Pending => Poll::Pending,
477        }
478    }
479}
480
481#[must_use]
482pub struct LogErrorWithBacktraceFuture<F>(F, log::Level, core::panic::Location<'static>);
483
484impl<F, T, E> Future for LogErrorWithBacktraceFuture<F>
485where
486    F: Future<Output = Result<T, E>>,
487    E: std::fmt::Debug,
488{
489    type Output = Option<T>;
490
491    fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
492        let level = self.1;
493        let location = self.2;
494        let inner = unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().0) };
495        match inner.poll(cx) {
496            Poll::Ready(output) => Poll::Ready(match output {
497                Ok(output) => Some(output),
498                Err(error) => {
499                    log_error_with_caller(
500                        location,
501                        format_args!("{:#}", DebugAsDisplay(&error)),
502                        level,
503                    );
504                    None
505                }
506            }),
507            Poll::Pending => Poll::Pending,
508        }
509    }
510}
511
512pub struct UnwrapFuture<F>(F);
513
514impl<F, T, E> Future for UnwrapFuture<F>
515where
516    F: Future<Output = Result<T, E>>,
517    E: std::fmt::Debug,
518{
519    type Output = T;
520
521    fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
522        let inner = unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().0) };
523        match inner.poll(cx) {
524            Poll::Ready(result) => Poll::Ready(result.unwrap()),
525            Poll::Pending => Poll::Pending,
526        }
527    }
528}
529
530pub struct Deferred<F: FnOnce()>(Option<F>);
531
532impl<F: FnOnce()> Deferred<F> {
533    /// Drop without running the deferred function.
534    pub fn abort(mut self) {
535        self.0.take();
536    }
537}
538
539impl<F: FnOnce()> Drop for Deferred<F> {
540    fn drop(&mut self) {
541        if let Some(f) = self.0.take() {
542            f()
543        }
544    }
545}
546
547/// Run the given function when the returned value is dropped (unless it's cancelled).
548#[must_use]
549pub fn defer<F: FnOnce()>(f: F) -> Deferred<F> {
550    Deferred(Some(f))
551}
552
553#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
554pub struct TypeIdHashBuilder;
555
556impl std::hash::BuildHasher for TypeIdHashBuilder {
557    type Hasher = TypeIdHasher;
558
559    fn build_hasher(&self) -> Self::Hasher {
560        TypeIdHasher::default()
561    }
562}
563
564#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
565pub struct TypeIdHasher {
566    value: u64,
567}
568
569impl std::hash::Hasher for TypeIdHasher {
570    #[inline]
571    fn write(&mut self, bytes: &[u8]) {
572        // TypeId should only hash its first 8 bytes
573        if let Some(bytes) = bytes.get(..8) {
574            bytes
575                .as_array()
576                .map(|&array| self.value = u64::from_ne_bytes(array))
577                .unwrap_or_else(|| unreachable!("slice was sliced to 8 bytes"));
578        } else {
579            debug_panic!(
580                "expected a 64-bit value, did you use this hasher with something other than a TypeId?"
581            );
582        }
583    }
584
585    #[inline]
586    fn finish(&self) -> u64 {
587        self.value
588    }
589}
590
591#[test]
592fn type_id_hasher() {
593    use core::any::TypeId;
594    use core::hash::{Hash, Hasher};
595    fn verify_hashing_with(type_id: TypeId) {
596        let mut hasher = TypeIdHasher::default();
597        type_id.hash(&mut hasher);
598        assert_ne!(hasher.finish(), 0);
599    }
600    // Pick a variety of types, just to demonstrate it’s all sane. Normal, zero-sized, unsized, &c.
601    verify_hashing_with(TypeId::of::<usize>());
602    verify_hashing_with(TypeId::of::<()>());
603    verify_hashing_with(TypeId::of::<str>());
604    verify_hashing_with(TypeId::of::<&str>());
605    verify_hashing_with(TypeId::of::<Vec<u8>>());
606}
607
608pub fn truncate_to_bottom_n_sorted_by<T, F>(items: &mut Vec<T>, limit: usize, compare: &F)
609where
610    F: Fn(&T, &T) -> std::cmp::Ordering,
611{
612    if limit == 0 {
613        items.clear();
614    }
615    if items.len() <= limit {
616        items.sort_by(compare);
617        return;
618    }
619    // When limit is near to items.len() it may be more efficient to sort the whole list and
620    // truncate, rather than always doing selection first as is done below. It's hard to analyze
621    // where the threshold for this should be since the quickselect style algorithm used by
622    // `select_nth_unstable_by` makes the prefix partially sorted, and so its work is not wasted -
623    // the expected number of comparisons needed by `sort_by` is less than it is for some arbitrary
624    // unsorted input.
625    items.select_nth_unstable_by(limit, compare);
626    items.truncate(limit);
627    items.sort_by(compare);
628}
629
630#[cfg(test)]
631mod logging_tests {
632    use super::{ResultExt, TryFutureExt, TryFutureExtBacktrace};
633    use log::{Level, Log, Metadata, Record};
634    use std::{
635        cell::RefCell,
636        future::ready,
637        pin::pin,
638        task::{Context, Poll, Waker},
639    };
640
641    #[test]
642    fn logging_preserves_diagnostics_and_callers() {
643        log::set_logger(&TestLogger).expect("failed to install test logger");
644        let error = anyhow::anyhow!("root failure")
645            .context("inner context")
646            .context("outer context");
647        let display = "outer context: inner context: root failure";
648        let debug = format!("{error:?}");
649        let line = line!() + 1;
650        assert_eq!(Err::<(), _>(&error).log_err(), None);
651        assert_logged(line, display);
652        let line = line!() + 1;
653        assert_eq!(Err::<(), _>(&error).log_err_with_backtrace(), None);
654        assert_logged(line, &debug);
655        let mut context = Context::from_waker(Waker::noop());
656        let line = line!() + 1;
657        let mut future = pin!(ready(Err::<(), _>(&error)).log_err());
658        assert_eq!(future.as_mut().poll(&mut context), Poll::Ready(None));
659        assert_logged(line, display);
660        let line = line!() + 1;
661        let mut future = pin!(ready(Err::<(), _>(&error)).log_err_with_backtrace());
662        assert_eq!(future.as_mut().poll(&mut context), Poll::Ready(None));
663        assert_logged(line, &debug);
664    }
665
666    thread_local! {
667        static RECORDS: RefCell<Vec<(Option<u32>, String)>> = const { RefCell::new(Vec::new()) };
668    }
669
670    struct TestLogger;
671
672    impl Log for TestLogger {
673        fn enabled(&self, _: &Metadata<'_>) -> bool {
674            true
675        }
676
677        fn log(&self, record: &Record<'_>) {
678            assert_eq!(record.target(), "gpui_util::lib");
679            assert_eq!(record.module_path(), Some("crates/gpui_util/src/lib.rs"));
680            assert_eq!(record.file(), Some(file!()));
681            assert_eq!(record.level(), Level::Error);
682            RECORDS.with_borrow_mut(|records| {
683                records.push((record.line(), record.args().to_string()));
684            });
685        }
686
687        fn flush(&self) {}
688    }
689
690    fn assert_logged(line: u32, message: &str) {
691        assert_eq!(RECORDS.take(), [(Some(line), message.to_owned())]);
692    }
693}