1use 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 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#[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 fn log_err_with_backtrace(self) -> Option<Self::Ok>
227 where
228 E: std::fmt::Debug;
229 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 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
343struct 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
370pub 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 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#[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 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 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 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}