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