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 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#[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 fn log_err_with_backtrace(self) -> Option<Self::Ok>
223 where
224 E: std::fmt::Debug;
225 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 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
339struct 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
366pub 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 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#[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 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 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 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}