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