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