Skip to main content

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