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