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