1pub mod arc_cow;
2pub mod command;
3pub mod fs;
4pub mod paths;
5pub mod serde;
6#[cfg(any(test, feature = "test-support"))]
7pub mod test;
8
9use futures::Future;
10
11use regex::Regex;
12use std::sync::OnceLock;
13use std::{
14 borrow::Cow,
15 cmp::{self, Ordering},
16 env,
17 ops::{AddAssign, Range, RangeInclusive},
18 panic::Location,
19 pin::Pin,
20 task::{Context, Poll},
21 time::Instant,
22};
23use unicase::UniCase;
24
25pub use take_until::*;
26
27#[macro_export]
28macro_rules! debug_panic {
29 ( $($fmt_arg:tt)* ) => {
30 if cfg!(debug_assertions) {
31 panic!( $($fmt_arg)* );
32 } else {
33 let backtrace = std::backtrace::Backtrace::capture();
34 log::error!("{}\n{:?}", format_args!($($fmt_arg)*), backtrace);
35 }
36 };
37}
38
39pub fn truncate(s: &str, max_chars: usize) -> &str {
40 match s.char_indices().nth(max_chars) {
41 None => s,
42 Some((idx, _)) => &s[..idx],
43 }
44}
45
46/// Removes characters from the end of the string if its length is greater than `max_chars` and
47/// appends "..." to the string. Returns string unchanged if its length is smaller than max_chars.
48pub fn truncate_and_trailoff(s: &str, max_chars: usize) -> String {
49 debug_assert!(max_chars >= 5);
50
51 let truncation_ix = s.char_indices().map(|(i, _)| i).nth(max_chars);
52 match truncation_ix {
53 Some(length) => s[..length].to_string() + "…",
54 None => s.to_string(),
55 }
56}
57
58/// Removes characters from the front of the string if its length is greater than `max_chars` and
59/// prepends the string with "...". Returns string unchanged if its length is smaller than max_chars.
60pub fn truncate_and_remove_front(s: &str, max_chars: usize) -> String {
61 debug_assert!(max_chars >= 5);
62
63 let truncation_ix = s.char_indices().map(|(i, _)| i).nth_back(max_chars);
64 match truncation_ix {
65 Some(length) => "…".to_string() + &s[length..],
66 None => s.to_string(),
67 }
68}
69
70/// Takes only `max_lines` from the string and, if there were more than `max_lines-1`, appends a
71/// a newline and "..." to the string, so that `max_lines` are returned.
72/// Returns string unchanged if its length is smaller than max_lines.
73pub fn truncate_lines_and_trailoff(s: &str, max_lines: usize) -> String {
74 let mut lines = s.lines().take(max_lines).collect::<Vec<_>>();
75 if lines.len() > max_lines - 1 {
76 lines.pop();
77 lines.join("\n") + "\n…"
78 } else {
79 lines.join("\n")
80 }
81}
82
83pub fn post_inc<T: From<u8> + AddAssign<T> + Copy>(value: &mut T) -> T {
84 let prev = *value;
85 *value += T::from(1);
86 prev
87}
88
89/// Extend a sorted vector with a sorted sequence of items, maintaining the vector's sort order and
90/// enforcing a maximum length. This also de-duplicates items. Sort the items according to the given callback. Before calling this,
91/// both `vec` and `new_items` should already be sorted according to the `cmp` comparator.
92pub fn extend_sorted<T, I, F>(vec: &mut Vec<T>, new_items: I, limit: usize, mut cmp: F)
93where
94 I: IntoIterator<Item = T>,
95 F: FnMut(&T, &T) -> Ordering,
96{
97 let mut start_index = 0;
98 for new_item in new_items {
99 if let Err(i) = vec[start_index..].binary_search_by(|m| cmp(m, &new_item)) {
100 let index = start_index + i;
101 if vec.len() < limit {
102 vec.insert(index, new_item);
103 } else if index < vec.len() {
104 vec.pop();
105 vec.insert(index, new_item);
106 }
107 start_index = index;
108 }
109 }
110}
111
112/// Parse the result of calling `usr/bin/env` with no arguments
113pub fn parse_env_output(env: &str, mut f: impl FnMut(String, String)) {
114 let mut current_key: Option<String> = None;
115 let mut current_value: Option<String> = None;
116
117 for line in env.split_terminator('\n') {
118 if let Some(separator_index) = line.find('=') {
119 if !line[..separator_index].is_empty() {
120 if let Some((key, value)) = Option::zip(current_key.take(), current_value.take()) {
121 f(key, value)
122 }
123 current_key = Some(line[..separator_index].to_string());
124 current_value = Some(line[separator_index + 1..].to_string());
125 continue;
126 };
127 }
128 if let Some(value) = current_value.as_mut() {
129 value.push('\n');
130 value.push_str(line);
131 }
132 }
133 if let Some((key, value)) = Option::zip(current_key.take(), current_value.take()) {
134 f(key, value)
135 }
136}
137
138pub fn merge_json_value_into(source: serde_json::Value, target: &mut serde_json::Value) {
139 use serde_json::Value;
140
141 match (source, target) {
142 (Value::Object(source), Value::Object(target)) => {
143 for (key, value) in source {
144 if let Some(target) = target.get_mut(&key) {
145 merge_json_value_into(value, target);
146 } else {
147 target.insert(key.clone(), value);
148 }
149 }
150 }
151
152 (Value::Array(source), Value::Array(target)) => {
153 for value in source {
154 target.push(value);
155 }
156 }
157
158 (source, target) => *target = source,
159 }
160}
161
162pub fn merge_non_null_json_value_into(source: serde_json::Value, target: &mut serde_json::Value) {
163 use serde_json::Value;
164 if let Value::Object(source_object) = source {
165 let target_object = if let Value::Object(target) = target {
166 target
167 } else {
168 *target = Value::Object(Default::default());
169 target.as_object_mut().unwrap()
170 };
171 for (key, value) in source_object {
172 if let Some(target) = target_object.get_mut(&key) {
173 merge_non_null_json_value_into(value, target);
174 } else if !value.is_null() {
175 target_object.insert(key.clone(), value);
176 }
177 }
178 } else if !source.is_null() {
179 *target = source
180 }
181}
182
183pub fn measure<R>(label: &str, f: impl FnOnce() -> R) -> R {
184 static ZED_MEASUREMENTS: OnceLock<bool> = OnceLock::new();
185 let zed_measurements = ZED_MEASUREMENTS.get_or_init(|| {
186 env::var("ZED_MEASUREMENTS")
187 .map(|measurements| measurements == "1" || measurements == "true")
188 .unwrap_or(false)
189 });
190
191 if *zed_measurements {
192 let start = Instant::now();
193 let result = f();
194 let elapsed = start.elapsed();
195 eprintln!("{}: {:?}", label, elapsed);
196 result
197 } else {
198 f()
199 }
200}
201
202pub trait ResultExt<E> {
203 type Ok;
204
205 fn log_err(self) -> Option<Self::Ok>;
206 /// Assert that this result should never be an error in development or tests.
207 fn debug_assert_ok(self, reason: &str) -> Self;
208 fn warn_on_err(self) -> Option<Self::Ok>;
209}
210
211impl<T, E> ResultExt<E> for Result<T, E>
212where
213 E: std::fmt::Debug,
214{
215 type Ok = T;
216
217 #[track_caller]
218 fn log_err(self) -> Option<T> {
219 match self {
220 Ok(value) => Some(value),
221 Err(error) => {
222 log_error_with_caller(*Location::caller(), error, log::Level::Error);
223 None
224 }
225 }
226 }
227
228 #[track_caller]
229 fn debug_assert_ok(self, reason: &str) -> Self {
230 if let Err(error) = &self {
231 debug_panic!("{reason} - {error:?}");
232 }
233 self
234 }
235
236 #[track_caller]
237 fn warn_on_err(self) -> Option<T> {
238 match self {
239 Ok(value) => Some(value),
240 Err(error) => {
241 log_error_with_caller(*Location::caller(), error, log::Level::Warn);
242 None
243 }
244 }
245 }
246}
247
248fn log_error_with_caller<E>(caller: core::panic::Location<'_>, error: E, level: log::Level)
249where
250 E: std::fmt::Debug,
251{
252 #[cfg(not(target_os = "windows"))]
253 let file = caller.file();
254 #[cfg(target_os = "windows")]
255 let file = caller.file().replace('\\', "/");
256 // In this codebase, the first segment of the file path is
257 // the 'crates' folder, followed by the crate name.
258 let target = file.split('/').nth(1);
259
260 log::logger().log(
261 &log::Record::builder()
262 .target(target.unwrap_or(""))
263 .module_path(target)
264 .args(format_args!("{:?}", error))
265 .file(Some(caller.file()))
266 .line(Some(caller.line()))
267 .level(level)
268 .build(),
269 );
270}
271
272pub trait TryFutureExt {
273 fn log_err(self) -> LogErrorFuture<Self>
274 where
275 Self: Sized;
276
277 fn log_tracked_err(self, location: core::panic::Location<'static>) -> LogErrorFuture<Self>
278 where
279 Self: Sized;
280
281 fn warn_on_err(self) -> LogErrorFuture<Self>
282 where
283 Self: Sized;
284 fn unwrap(self) -> UnwrapFuture<Self>
285 where
286 Self: Sized;
287}
288
289impl<F, T, E> TryFutureExt for F
290where
291 F: Future<Output = Result<T, E>>,
292 E: std::fmt::Debug,
293{
294 #[track_caller]
295 fn log_err(self) -> LogErrorFuture<Self>
296 where
297 Self: Sized,
298 {
299 let location = Location::caller();
300 LogErrorFuture(self, log::Level::Error, *location)
301 }
302
303 fn log_tracked_err(self, location: core::panic::Location<'static>) -> LogErrorFuture<Self>
304 where
305 Self: Sized,
306 {
307 LogErrorFuture(self, log::Level::Error, location)
308 }
309
310 #[track_caller]
311 fn warn_on_err(self) -> LogErrorFuture<Self>
312 where
313 Self: Sized,
314 {
315 let location = Location::caller();
316 LogErrorFuture(self, log::Level::Warn, *location)
317 }
318
319 fn unwrap(self) -> UnwrapFuture<Self>
320 where
321 Self: Sized,
322 {
323 UnwrapFuture(self)
324 }
325}
326
327#[must_use]
328pub struct LogErrorFuture<F>(F, log::Level, core::panic::Location<'static>);
329
330impl<F, T, E> Future for LogErrorFuture<F>
331where
332 F: Future<Output = Result<T, E>>,
333 E: std::fmt::Debug,
334{
335 type Output = Option<T>;
336
337 fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
338 let level = self.1;
339 let location = self.2;
340 let inner = unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().0) };
341 match inner.poll(cx) {
342 Poll::Ready(output) => Poll::Ready(match output {
343 Ok(output) => Some(output),
344 Err(error) => {
345 log_error_with_caller(location, error, level);
346 None
347 }
348 }),
349 Poll::Pending => Poll::Pending,
350 }
351 }
352}
353
354pub struct UnwrapFuture<F>(F);
355
356impl<F, T, E> Future for UnwrapFuture<F>
357where
358 F: Future<Output = Result<T, E>>,
359 E: std::fmt::Debug,
360{
361 type Output = T;
362
363 fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
364 let inner = unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().0) };
365 match inner.poll(cx) {
366 Poll::Ready(result) => Poll::Ready(result.unwrap()),
367 Poll::Pending => Poll::Pending,
368 }
369 }
370}
371
372pub struct Deferred<F: FnOnce()>(Option<F>);
373
374impl<F: FnOnce()> Deferred<F> {
375 /// Drop without running the deferred function.
376 pub fn abort(mut self) {
377 self.0.take();
378 }
379}
380
381impl<F: FnOnce()> Drop for Deferred<F> {
382 fn drop(&mut self) {
383 if let Some(f) = self.0.take() {
384 f()
385 }
386 }
387}
388
389/// Run the given function when the returned value is dropped (unless it's cancelled).
390#[must_use]
391pub fn defer<F: FnOnce()>(f: F) -> Deferred<F> {
392 Deferred(Some(f))
393}
394
395#[cfg(any(test, feature = "test-support"))]
396mod rng {
397 use rand::{seq::SliceRandom, Rng};
398 pub struct RandomCharIter<T: Rng> {
399 rng: T,
400 simple_text: bool,
401 }
402
403 impl<T: Rng> RandomCharIter<T> {
404 pub fn new(rng: T) -> Self {
405 Self {
406 rng,
407 simple_text: std::env::var("SIMPLE_TEXT").map_or(false, |v| !v.is_empty()),
408 }
409 }
410
411 pub fn with_simple_text(mut self) -> Self {
412 self.simple_text = true;
413 self
414 }
415 }
416
417 impl<T: Rng> Iterator for RandomCharIter<T> {
418 type Item = char;
419
420 fn next(&mut self) -> Option<Self::Item> {
421 if self.simple_text {
422 return if self.rng.gen_range(0..100) < 5 {
423 Some('\n')
424 } else {
425 Some(self.rng.gen_range(b'a'..b'z' + 1).into())
426 };
427 }
428
429 match self.rng.gen_range(0..100) {
430 // whitespace
431 0..=19 => [' ', '\n', '\r', '\t'].choose(&mut self.rng).copied(),
432 // two-byte greek letters
433 20..=32 => char::from_u32(self.rng.gen_range(('α' as u32)..('ω' as u32 + 1))),
434 // // three-byte characters
435 33..=45 => ['✋', '✅', '❌', '❎', '⭐']
436 .choose(&mut self.rng)
437 .copied(),
438 // // four-byte characters
439 46..=58 => ['🍐', '🏀', '🍗', '🎉'].choose(&mut self.rng).copied(),
440 // ascii letters
441 _ => Some(self.rng.gen_range(b'a'..b'z' + 1).into()),
442 }
443 }
444 }
445}
446#[cfg(any(test, feature = "test-support"))]
447pub use rng::RandomCharIter;
448/// Get an embedded file as a string.
449pub fn asset_str<A: rust_embed::RustEmbed>(path: &str) -> Cow<'static, str> {
450 match A::get(path).unwrap().data {
451 Cow::Borrowed(bytes) => Cow::Borrowed(std::str::from_utf8(bytes).unwrap()),
452 Cow::Owned(bytes) => Cow::Owned(String::from_utf8(bytes).unwrap()),
453 }
454}
455
456/// Expands to an immediately-invoked function expression. Good for using the ? operator
457/// in functions which do not return an Option or Result.
458///
459/// Accepts a normal block, an async block, or an async move block.
460#[macro_export]
461macro_rules! maybe {
462 ($block:block) => {
463 (|| $block)()
464 };
465 (async $block:block) => {
466 (|| async $block)()
467 };
468 (async move $block:block) => {
469 (|| async move $block)()
470 };
471}
472
473pub trait RangeExt<T> {
474 fn sorted(&self) -> Self;
475 fn to_inclusive(&self) -> RangeInclusive<T>;
476 fn overlaps(&self, other: &Range<T>) -> bool;
477 fn contains_inclusive(&self, other: &Range<T>) -> bool;
478}
479
480impl<T: Ord + Clone> RangeExt<T> for Range<T> {
481 fn sorted(&self) -> Self {
482 cmp::min(&self.start, &self.end).clone()..cmp::max(&self.start, &self.end).clone()
483 }
484
485 fn to_inclusive(&self) -> RangeInclusive<T> {
486 self.start.clone()..=self.end.clone()
487 }
488
489 fn overlaps(&self, other: &Range<T>) -> bool {
490 self.start < other.end && other.start < self.end
491 }
492
493 fn contains_inclusive(&self, other: &Range<T>) -> bool {
494 self.start <= other.start && other.end <= self.end
495 }
496}
497
498impl<T: Ord + Clone> RangeExt<T> for RangeInclusive<T> {
499 fn sorted(&self) -> Self {
500 cmp::min(self.start(), self.end()).clone()..=cmp::max(self.start(), self.end()).clone()
501 }
502
503 fn to_inclusive(&self) -> RangeInclusive<T> {
504 self.clone()
505 }
506
507 fn overlaps(&self, other: &Range<T>) -> bool {
508 self.start() < &other.end && &other.start <= self.end()
509 }
510
511 fn contains_inclusive(&self, other: &Range<T>) -> bool {
512 self.start() <= &other.start && &other.end <= self.end()
513 }
514}
515
516/// A way to sort strings with starting numbers numerically first, falling back to alphanumeric one,
517/// case-insensitive.
518///
519/// This is useful for turning regular alphanumerically sorted sequences as `1-abc, 10, 11-def, .., 2, 21-abc`
520/// into `1-abc, 2, 10, 11-def, .., 21-abc`
521#[derive(Debug, PartialEq, Eq)]
522pub struct NumericPrefixWithSuffix<'a>(Option<u64>, &'a str);
523
524impl<'a> NumericPrefixWithSuffix<'a> {
525 pub fn from_numeric_prefixed_str(str: &'a str) -> Self {
526 let i = str.chars().take_while(|c| c.is_ascii_digit()).count();
527 let (prefix, remainder) = str.split_at(i);
528
529 let prefix = prefix.parse().ok();
530 Self(prefix, remainder)
531 }
532}
533
534/// When dealing with equality, we need to consider the case of the strings to achieve strict equality
535/// to handle cases like "a" < "A" instead of "a" == "A".
536impl Ord for NumericPrefixWithSuffix<'_> {
537 fn cmp(&self, other: &Self) -> Ordering {
538 match (self.0, other.0) {
539 (None, None) => UniCase::new(self.1)
540 .cmp(&UniCase::new(other.1))
541 .then_with(|| self.1.cmp(other.1).reverse()),
542 (None, Some(_)) => Ordering::Greater,
543 (Some(_), None) => Ordering::Less,
544 (Some(a), Some(b)) => a.cmp(&b).then_with(|| {
545 UniCase::new(self.1)
546 .cmp(&UniCase::new(other.1))
547 .then_with(|| self.1.cmp(other.1).reverse())
548 }),
549 }
550 }
551}
552
553impl<'a> PartialOrd for NumericPrefixWithSuffix<'a> {
554 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
555 Some(self.cmp(other))
556 }
557}
558
559fn emoji_regex() -> &'static Regex {
560 static EMOJI_REGEX: OnceLock<Regex> = OnceLock::new();
561 EMOJI_REGEX.get_or_init(|| Regex::new("(\\p{Emoji}|\u{200D})").unwrap())
562}
563
564/// Returns true if the given string consists of emojis only.
565/// E.g. "👨👩👧👧👋" will return true, but "👋!" will return false.
566pub fn word_consists_of_emojis(s: &str) -> bool {
567 let mut prev_end = 0;
568 for capture in emoji_regex().find_iter(s) {
569 if capture.start() != prev_end {
570 return false;
571 }
572 prev_end = capture.end();
573 }
574 prev_end == s.len()
575}
576
577#[cfg(test)]
578mod tests {
579 use super::*;
580
581 #[test]
582 fn test_extend_sorted() {
583 let mut vec = vec![];
584
585 extend_sorted(&mut vec, vec![21, 17, 13, 8, 1, 0], 5, |a, b| b.cmp(a));
586 assert_eq!(vec, &[21, 17, 13, 8, 1]);
587
588 extend_sorted(&mut vec, vec![101, 19, 17, 8, 2], 8, |a, b| b.cmp(a));
589 assert_eq!(vec, &[101, 21, 19, 17, 13, 8, 2, 1]);
590
591 extend_sorted(&mut vec, vec![1000, 19, 17, 9, 5], 8, |a, b| b.cmp(a));
592 assert_eq!(vec, &[1000, 101, 21, 19, 17, 13, 9, 8]);
593 }
594
595 #[test]
596 fn test_iife() {
597 fn option_returning_function() -> Option<()> {
598 None
599 }
600
601 let foo = maybe!({
602 option_returning_function()?;
603 Some(())
604 });
605
606 assert_eq!(foo, None);
607 }
608
609 #[test]
610 fn test_truncate_and_trailoff() {
611 assert_eq!(truncate_and_trailoff("", 5), "");
612 assert_eq!(truncate_and_trailoff("èèèèèè", 7), "èèèèèè");
613 assert_eq!(truncate_and_trailoff("èèèèèè", 6), "èèèèèè");
614 assert_eq!(truncate_and_trailoff("èèèèèè", 5), "èèèèè…");
615 }
616
617 #[test]
618 fn test_numeric_prefix_str_method() {
619 let target = "1a";
620 assert_eq!(
621 NumericPrefixWithSuffix::from_numeric_prefixed_str(target),
622 NumericPrefixWithSuffix(Some(1), "a")
623 );
624
625 let target = "12ab";
626 assert_eq!(
627 NumericPrefixWithSuffix::from_numeric_prefixed_str(target),
628 NumericPrefixWithSuffix(Some(12), "ab")
629 );
630
631 let target = "12_ab";
632 assert_eq!(
633 NumericPrefixWithSuffix::from_numeric_prefixed_str(target),
634 NumericPrefixWithSuffix(Some(12), "_ab")
635 );
636
637 let target = "1_2ab";
638 assert_eq!(
639 NumericPrefixWithSuffix::from_numeric_prefixed_str(target),
640 NumericPrefixWithSuffix(Some(1), "_2ab")
641 );
642
643 let target = "1.2";
644 assert_eq!(
645 NumericPrefixWithSuffix::from_numeric_prefixed_str(target),
646 NumericPrefixWithSuffix(Some(1), ".2")
647 );
648
649 let target = "1.2_a";
650 assert_eq!(
651 NumericPrefixWithSuffix::from_numeric_prefixed_str(target),
652 NumericPrefixWithSuffix(Some(1), ".2_a")
653 );
654
655 let target = "12.2_a";
656 assert_eq!(
657 NumericPrefixWithSuffix::from_numeric_prefixed_str(target),
658 NumericPrefixWithSuffix(Some(12), ".2_a")
659 );
660
661 let target = "12a.2_a";
662 assert_eq!(
663 NumericPrefixWithSuffix::from_numeric_prefixed_str(target),
664 NumericPrefixWithSuffix(Some(12), "a.2_a")
665 );
666 }
667
668 #[test]
669 fn test_numeric_prefix_with_suffix() {
670 let mut sorted = vec!["1-abc", "10", "11def", "2", "21-abc"];
671 sorted.sort_by_key(|s| NumericPrefixWithSuffix::from_numeric_prefixed_str(s));
672 assert_eq!(sorted, ["1-abc", "2", "10", "11def", "21-abc"]);
673
674 for numeric_prefix_less in ["numeric_prefix_less", "aaa", "~™£"] {
675 assert_eq!(
676 NumericPrefixWithSuffix::from_numeric_prefixed_str(numeric_prefix_less),
677 NumericPrefixWithSuffix(None, numeric_prefix_less),
678 "String without numeric prefix `{numeric_prefix_less}` should not be converted into NumericPrefixWithSuffix"
679 )
680 }
681 }
682
683 #[test]
684 fn test_word_consists_of_emojis() {
685 let words_to_test = vec![
686 ("👨👩👧👧👋🥒", true),
687 ("👋", true),
688 ("!👋", false),
689 ("👋!", false),
690 ("👋 ", false),
691 (" 👋", false),
692 ("Test", false),
693 ];
694
695 for (text, expected_result) in words_to_test {
696 assert_eq!(word_consists_of_emojis(text), expected_result);
697 }
698 }
699
700 #[test]
701 fn test_truncate_lines_and_trailoff() {
702 let text = r#"Line 1
703Line 2
704Line 3"#;
705
706 assert_eq!(
707 truncate_lines_and_trailoff(text, 2),
708 r#"Line 1
709…"#
710 );
711
712 assert_eq!(
713 truncate_lines_and_trailoff(text, 3),
714 r#"Line 1
715Line 2
716…"#
717 );
718
719 assert_eq!(
720 truncate_lines_and_trailoff(text, 4),
721 r#"Line 1
722Line 2
723Line 3"#
724 );
725 }
726}