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