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