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