1pub mod arc_cow;
2pub mod channel;
3pub mod fs;
4pub mod github;
5pub mod http;
6pub mod paths;
7#[cfg(any(test, feature = "test-support"))]
8pub mod test;
9
10use std::{
11 borrow::Cow,
12 cmp::{self, Ordering},
13 ops::{AddAssign, Range, RangeInclusive},
14 panic::Location,
15 pin::Pin,
16 task::{Context, Poll},
17};
18
19#[cfg(not(feature = "allow-multiple-gpui-versions"))]
20use std::sync::atomic::AtomicU32;
21
22pub use backtrace::Backtrace;
23use futures::Future;
24use rand::{seq::SliceRandom, Rng};
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 = $crate::Backtrace::new();
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
47/// Removes characters from the end of the string if it's length is greater than `max_chars` and
48/// appends "..." to the string. Returns string unchanged if it's length is smaller than max_chars.
49pub fn truncate_and_trailoff(s: &str, max_chars: usize) -> String {
50 debug_assert!(max_chars >= 5);
51
52 let truncation_ix = s.char_indices().map(|(i, _)| i).nth(max_chars);
53 match truncation_ix {
54 Some(length) => s[..length].to_string() + "…",
55 None => s.to_string(),
56 }
57}
58
59/// Removes characters from the front of the string if it's length is greater than `max_chars` and
60/// prepends the string with "...". Returns string unchanged if it's length is smaller than max_chars.
61pub fn truncate_and_remove_front(s: &str, max_chars: usize) -> String {
62 debug_assert!(max_chars >= 5);
63
64 let truncation_ix = s.char_indices().map(|(i, _)| i).nth_back(max_chars);
65 match truncation_ix {
66 Some(length) => "…".to_string() + &s[length..],
67 None => s.to_string(),
68 }
69}
70
71pub fn post_inc<T: From<u8> + AddAssign<T> + Copy>(value: &mut T) -> T {
72 let prev = *value;
73 *value += T::from(1);
74 prev
75}
76
77/// Extend a sorted vector with a sorted sequence of items, maintaining the vector's sort order and
78/// enforcing a maximum length. This also de-duplicates items. Sort the items according to the given callback. Before calling this,
79/// both `vec` and `new_items` should already be sorted according to the `cmp` comparator.
80pub fn extend_sorted<T, I, F>(vec: &mut Vec<T>, new_items: I, limit: usize, mut cmp: F)
81where
82 I: IntoIterator<Item = T>,
83 F: FnMut(&T, &T) -> Ordering,
84{
85 let mut start_index = 0;
86 for new_item in new_items {
87 if let Err(i) = vec[start_index..].binary_search_by(|m| cmp(m, &new_item)) {
88 let index = start_index + i;
89 if vec.len() < limit {
90 vec.insert(index, new_item);
91 } else if index < vec.len() {
92 vec.pop();
93 vec.insert(index, new_item);
94 }
95 start_index = index;
96 }
97 }
98}
99
100pub fn merge_json_value_into(source: serde_json::Value, target: &mut serde_json::Value) {
101 use serde_json::Value;
102
103 match (source, target) {
104 (Value::Object(source), Value::Object(target)) => {
105 for (key, value) in source {
106 if let Some(target) = target.get_mut(&key) {
107 merge_json_value_into(value, target);
108 } else {
109 target.insert(key.clone(), value);
110 }
111 }
112 }
113
114 (source, target) => *target = source,
115 }
116}
117
118pub fn merge_non_null_json_value_into(source: serde_json::Value, target: &mut serde_json::Value) {
119 use serde_json::Value;
120 if let Value::Object(source_object) = source {
121 let target_object = if let Value::Object(target) = target {
122 target
123 } else {
124 *target = Value::Object(Default::default());
125 target.as_object_mut().unwrap()
126 };
127 for (key, value) in source_object {
128 if let Some(target) = target_object.get_mut(&key) {
129 merge_non_null_json_value_into(value, target);
130 } else if !value.is_null() {
131 target_object.insert(key.clone(), value);
132 }
133 }
134 } else if !source.is_null() {
135 *target = source
136 }
137}
138
139pub trait ResultExt<E> {
140 type Ok;
141
142 fn log_err(self) -> Option<Self::Ok>;
143 fn warn_on_err(self) -> Option<Self::Ok>;
144 fn inspect_error(self, func: impl FnOnce(&E)) -> Self;
145}
146
147impl<T, E> ResultExt<E> for Result<T, E>
148where
149 E: std::fmt::Debug,
150{
151 type Ok = T;
152
153 #[track_caller]
154 fn log_err(self) -> Option<T> {
155 match self {
156 Ok(value) => Some(value),
157 Err(error) => {
158 let caller = Location::caller();
159 log::error!("{}:{}: {:?}", caller.file(), caller.line(), error);
160 None
161 }
162 }
163 }
164
165 fn warn_on_err(self) -> Option<T> {
166 match self {
167 Ok(value) => Some(value),
168 Err(error) => {
169 log::warn!("{:?}", error);
170 None
171 }
172 }
173 }
174
175 /// https://doc.rust-lang.org/std/result/enum.Result.html#method.inspect_err
176 fn inspect_error(self, func: impl FnOnce(&E)) -> Self {
177 if let Err(err) = &self {
178 func(err);
179 }
180
181 self
182 }
183}
184
185pub trait TryFutureExt {
186 fn log_err(self) -> LogErrorFuture<Self>
187 where
188 Self: Sized;
189
190 fn log_tracked_err(self, location: core::panic::Location<'static>) -> LogErrorFuture<Self>
191 where
192 Self: Sized;
193
194 fn warn_on_err(self) -> LogErrorFuture<Self>
195 where
196 Self: Sized;
197 fn unwrap(self) -> UnwrapFuture<Self>
198 where
199 Self: Sized;
200}
201
202impl<F, T, E> TryFutureExt for F
203where
204 F: Future<Output = Result<T, E>>,
205 E: std::fmt::Debug,
206{
207 #[track_caller]
208 fn log_err(self) -> LogErrorFuture<Self>
209 where
210 Self: Sized,
211 {
212 let location = Location::caller();
213 LogErrorFuture(self, log::Level::Error, *location)
214 }
215
216 fn log_tracked_err(self, location: core::panic::Location<'static>) -> LogErrorFuture<Self>
217 where
218 Self: Sized,
219 {
220 LogErrorFuture(self, log::Level::Error, location)
221 }
222
223 #[track_caller]
224 fn warn_on_err(self) -> LogErrorFuture<Self>
225 where
226 Self: Sized,
227 {
228 let location = Location::caller();
229 LogErrorFuture(self, log::Level::Warn, *location)
230 }
231
232 fn unwrap(self) -> UnwrapFuture<Self>
233 where
234 Self: Sized,
235 {
236 UnwrapFuture(self)
237 }
238}
239
240pub struct LogErrorFuture<F>(F, log::Level, core::panic::Location<'static>);
241
242impl<F, T, E> Future for LogErrorFuture<F>
243where
244 F: Future<Output = Result<T, E>>,
245 E: std::fmt::Debug,
246{
247 type Output = Option<T>;
248
249 fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
250 let level = self.1;
251 let location = self.2;
252 let inner = unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().0) };
253 match inner.poll(cx) {
254 Poll::Ready(output) => Poll::Ready(match output {
255 Ok(output) => Some(output),
256 Err(error) => {
257 log::log!(
258 level,
259 "{}:{}: {:?}",
260 location.file(),
261 location.line(),
262 error
263 );
264 None
265 }
266 }),
267 Poll::Pending => Poll::Pending,
268 }
269 }
270}
271
272pub struct UnwrapFuture<F>(F);
273
274impl<F, T, E> Future for UnwrapFuture<F>
275where
276 F: Future<Output = Result<T, E>>,
277 E: std::fmt::Debug,
278{
279 type Output = T;
280
281 fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
282 let inner = unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().0) };
283 match inner.poll(cx) {
284 Poll::Ready(result) => Poll::Ready(result.unwrap()),
285 Poll::Pending => Poll::Pending,
286 }
287 }
288}
289
290pub struct Deferred<F: FnOnce()>(Option<F>);
291
292impl<F: FnOnce()> Deferred<F> {
293 /// Drop without running the deferred function.
294 pub fn cancel(mut self) {
295 self.0.take();
296 }
297}
298
299impl<F: FnOnce()> Drop for Deferred<F> {
300 fn drop(&mut self) {
301 if let Some(f) = self.0.take() {
302 f()
303 }
304 }
305}
306
307/// Run the given function when the returned value is dropped (unless it's cancelled).
308pub fn defer<F: FnOnce()>(f: F) -> Deferred<F> {
309 Deferred(Some(f))
310}
311
312pub struct RandomCharIter<T: Rng> {
313 rng: T,
314 simple_text: bool,
315}
316
317impl<T: Rng> RandomCharIter<T> {
318 pub fn new(rng: T) -> Self {
319 Self {
320 rng,
321 simple_text: std::env::var("SIMPLE_TEXT").map_or(false, |v| !v.is_empty()),
322 }
323 }
324
325 pub fn with_simple_text(mut self) -> Self {
326 self.simple_text = true;
327 self
328 }
329}
330
331impl<T: Rng> Iterator for RandomCharIter<T> {
332 type Item = char;
333
334 fn next(&mut self) -> Option<Self::Item> {
335 if self.simple_text {
336 return if self.rng.gen_range(0..100) < 5 {
337 Some('\n')
338 } else {
339 Some(self.rng.gen_range(b'a'..b'z' + 1).into())
340 };
341 }
342
343 match self.rng.gen_range(0..100) {
344 // whitespace
345 0..=19 => [' ', '\n', '\r', '\t'].choose(&mut self.rng).copied(),
346 // two-byte greek letters
347 20..=32 => char::from_u32(self.rng.gen_range(('α' as u32)..('ω' as u32 + 1))),
348 // // three-byte characters
349 33..=45 => ['✋', '✅', '❌', '❎', '⭐']
350 .choose(&mut self.rng)
351 .copied(),
352 // // four-byte characters
353 46..=58 => ['🍐', '🏀', '🍗', '🎉'].choose(&mut self.rng).copied(),
354 // ascii letters
355 _ => Some(self.rng.gen_range(b'a'..b'z' + 1).into()),
356 }
357 }
358}
359
360/// Get an embedded file as a string.
361pub fn asset_str<A: rust_embed::RustEmbed>(path: &str) -> Cow<'static, str> {
362 match A::get(path).unwrap().data {
363 Cow::Borrowed(bytes) => Cow::Borrowed(std::str::from_utf8(bytes).unwrap()),
364 Cow::Owned(bytes) => Cow::Owned(String::from_utf8(bytes).unwrap()),
365 }
366}
367
368// copy unstable standard feature option unzip
369// https://github.com/rust-lang/rust/issues/87800
370// Remove when this ship in Rust 1.66 or 1.67
371pub fn unzip_option<T, U>(option: Option<(T, U)>) -> (Option<T>, Option<U>) {
372 match option {
373 Some((a, b)) => (Some(a), Some(b)),
374 None => (None, None),
375 }
376}
377
378/// Evaluates to an immediately invoked function expression. Good for using the ? operator
379/// in functions which do not return an Option or Result
380#[macro_export]
381macro_rules! maybe {
382 ($block:block) => {
383 (|| $block)()
384 };
385}
386
387/// Evaluates to an immediately invoked function expression. Good for using the ? operator
388/// in functions which do not return an Option or Result, but async.
389#[macro_export]
390macro_rules! async_maybe {
391 ($block:block) => {
392 (|| async move { $block })()
393 };
394}
395
396pub trait RangeExt<T> {
397 fn sorted(&self) -> Self;
398 fn to_inclusive(&self) -> RangeInclusive<T>;
399 fn overlaps(&self, other: &Range<T>) -> bool;
400 fn contains_inclusive(&self, other: &Range<T>) -> bool;
401}
402
403impl<T: Ord + Clone> RangeExt<T> for Range<T> {
404 fn sorted(&self) -> Self {
405 cmp::min(&self.start, &self.end).clone()..cmp::max(&self.start, &self.end).clone()
406 }
407
408 fn to_inclusive(&self) -> RangeInclusive<T> {
409 self.start.clone()..=self.end.clone()
410 }
411
412 fn overlaps(&self, other: &Range<T>) -> bool {
413 self.start < other.end && other.start < self.end
414 }
415
416 fn contains_inclusive(&self, other: &Range<T>) -> bool {
417 self.start <= other.start && other.end <= self.end
418 }
419}
420
421impl<T: Ord + Clone> RangeExt<T> for RangeInclusive<T> {
422 fn sorted(&self) -> Self {
423 cmp::min(self.start(), self.end()).clone()..=cmp::max(self.start(), self.end()).clone()
424 }
425
426 fn to_inclusive(&self) -> RangeInclusive<T> {
427 self.clone()
428 }
429
430 fn overlaps(&self, other: &Range<T>) -> bool {
431 self.start() < &other.end && &other.start <= self.end()
432 }
433
434 fn contains_inclusive(&self, other: &Range<T>) -> bool {
435 self.start() <= &other.start && &other.end <= self.end()
436 }
437}
438
439#[cfg(not(feature = "allow-multiple-gpui-versions"))]
440static GPUI_LOADED: AtomicU32 = AtomicU32::new(0);
441
442pub fn gpui2_loaded() {
443 #[cfg(not(feature = "allow-multiple-gpui-versions"))]
444 if GPUI_LOADED.fetch_add(2, std::sync::atomic::Ordering::SeqCst) != 0 {
445 panic!("=========\nYou are loading both GPUI1 and GPUI2 in the same build!\nFix Your Dependencies with cargo tree!\n=========")
446 }
447}
448
449pub fn gpui1_loaded() {
450 #[cfg(not(feature = "allow-multiple-gpui-versions"))]
451 if GPUI_LOADED.fetch_add(1, std::sync::atomic::Ordering::SeqCst) != 0 {
452 panic!("=========\nYou are loading both GPUI1 and GPUI2 in the same build!\nFix Your Dependencies with cargo tree!\n=========")
453 }
454}
455
456#[cfg(test)]
457mod tests {
458 use super::*;
459
460 #[test]
461 fn test_extend_sorted() {
462 let mut vec = vec![];
463
464 extend_sorted(&mut vec, vec![21, 17, 13, 8, 1, 0], 5, |a, b| b.cmp(a));
465 assert_eq!(vec, &[21, 17, 13, 8, 1]);
466
467 extend_sorted(&mut vec, vec![101, 19, 17, 8, 2], 8, |a, b| b.cmp(a));
468 assert_eq!(vec, &[101, 21, 19, 17, 13, 8, 2, 1]);
469
470 extend_sorted(&mut vec, vec![1000, 19, 17, 9, 5], 8, |a, b| b.cmp(a));
471 assert_eq!(vec, &[1000, 101, 21, 19, 17, 13, 9, 8]);
472 }
473
474 #[test]
475 fn test_iife() {
476 fn option_returning_function() -> Option<()> {
477 None
478 }
479
480 let foo = maybe!({
481 option_returning_function()?;
482 Some(())
483 });
484
485 assert_eq!(foo, None);
486 }
487
488 #[test]
489 fn test_trancate_and_trailoff() {
490 assert_eq!(truncate_and_trailoff("", 5), "");
491 assert_eq!(truncate_and_trailoff("èèèèèè", 7), "èèèèèè");
492 assert_eq!(truncate_and_trailoff("èèèèèè", 6), "èèèèèè");
493 assert_eq!(truncate_and_trailoff("èèèèèè", 5), "èèèèè…");
494 }
495}