rofi_mode/
string.rs

1/// A UTF-8-encoded growable string buffer suitable for FFI with Rofi.
2///
3/// In constrast to the standard library's [`std::string::String`] type,
4/// this string type:
5/// - Cannot contain any intermediary nul bytes.
6/// - Is always nul-terminated.
7/// - Is allocated using glib's allocator
8///     (`g_malloc`, `g_realloc` and `g_free`).
9///
10/// You can use our [`format!`](crate::format!) macro to format these strings,
11/// just like with the standard library.
12pub struct String {
13    ptr: ptr::NonNull<u8>,
14    // Doesn't include the nul terminator, so is always < capacity.
15    len: usize,
16    capacity: usize,
17}
18
19unsafe impl Send for String {}
20unsafe impl Sync for String {}
21
22impl String {
23    /// Create a new empty string.
24    #[must_use]
25    pub const fn new() -> Self {
26        Self {
27            ptr: unsafe { ptr::NonNull::new_unchecked(PTR_TO_NULL as *mut u8) },
28            len: 0,
29            capacity: 0,
30        }
31    }
32
33    /// Create a new empty string with at least the specified capacity.
34    #[must_use]
35    pub fn with_capacity(capacity: usize) -> Self {
36        let mut this = Self::new();
37        this.reserve(capacity);
38        this
39    }
40
41    /// Get the length of the string in bytes,
42    /// excluding the nul terminator.
43    #[must_use]
44    pub const fn len(&self) -> usize {
45        self.len
46    }
47
48    /// Retrieve whether the string is empty or not.
49    #[must_use]
50    pub const fn is_empty(&self) -> bool {
51        self.len() == 0
52    }
53
54    /// Obtain the current capacity of the string.
55    ///
56    /// If the value is zero, no allocation has been made yet.
57    #[must_use]
58    pub const fn capacity(&self) -> usize {
59        self.capacity
60    }
61
62    /// Extract a string slice containing the entire string,
63    /// excluding the nul terminator.
64    ///
65    /// This is equivalent to the `Deref` implementation.
66    #[must_use]
67    pub fn as_str(&self) -> &str {
68        unsafe { str::from_utf8_unchecked(slice::from_raw_parts(self.ptr.as_ptr(), self.len)) }
69    }
70
71    /// Extract a string slice containing the entire string,
72    /// including the nul terminator.
73    #[must_use]
74    pub fn as_str_nul(&self) -> &str {
75        unsafe { str::from_utf8_unchecked(slice::from_raw_parts(self.ptr.as_ptr(), self.len + 1)) }
76    }
77
78    /// Construct an owned, allocated string from its raw parts.
79    ///
80    /// # Safety
81    ///
82    /// - `capacity` must be nonzero.
83    /// - `len` must be < `capacity`.
84    /// - `ptr` must be non-null.
85    /// - `ptr` must point to the start of
86    ///     an allocation in the glib allocator
87    ///     of at least `capacity` bytes.
88    /// - `ptr` must have provenance over at least `capacity` bytes.
89    /// - The first `len` bytes at `*ptr` must be initialized and valid UTF-8,
90    ///     and not contain any nul characters.
91    /// - The byte at `ptr[len]` must be zero.
92    #[must_use]
93    pub unsafe fn from_raw_parts(ptr: *mut u8, len: usize, capacity: usize) -> Self {
94        debug_assert!(!ptr.is_null());
95        debug_assert_ne!(capacity, 0);
96        debug_assert!(len < capacity);
97        Self {
98            ptr: unsafe { ptr::NonNull::new_unchecked(ptr) },
99            len,
100            capacity,
101        }
102    }
103
104    /// Take ownership of the string,
105    /// giving back a raw pointer to its contents
106    /// that can be freed with `g_free`.
107    ///
108    /// This may allocate if the string has not allocated yet.
109    #[must_use]
110    pub fn into_raw(self) -> *mut u8 {
111        let this = ManuallyDrop::new(self);
112        if this.capacity == 0 {
113            unsafe { calloc(1, 1) }.cast()
114        } else {
115            this.ptr.as_ptr()
116        }
117    }
118
119    /// Reserve `n` bytes of free space in the string.
120    ///
121    /// `n` doesn't include the nul terminator,
122    /// meaning `.reserve(5)` will reserve enough capacity
123    /// to push five more bytes of content.
124    /// This means that `.reserve(0)` will allocate space for one byte on an empty string.
125    ///
126    /// # Panics
127    ///
128    /// Panics if the string length overflows a usize.
129    pub fn reserve(&mut self, n: usize) {
130        // no point in small strings
131        const MIN_NON_ZERO_CAP: usize = 8;
132
133        // Use less-than to take into account the nul byte.
134        if n < self.capacity - self.len {
135            return;
136        }
137
138        let min_capacity =
139            (|| self.len.checked_add(n)?.checked_add(1))().expect("string length overflowed");
140        let new_capacity = Ord::max(self.capacity * 2, min_capacity);
141        let new_capacity = Ord::max(MIN_NON_ZERO_CAP, new_capacity);
142
143        let ptr = if self.capacity == 0 {
144            let ptr = unsafe { malloc(new_capacity) }.cast::<u8>();
145            // Null-terminate the newly-allocated string
146            unsafe { *ptr = b'\0' };
147            ptr
148        } else {
149            unsafe { realloc(self.ptr.as_ptr().cast(), new_capacity) }.cast::<u8>()
150        };
151
152        // Glib allocation functions will only return NULL if `new_capacity = 0`, which it is not.
153        self.ptr = ptr::NonNull::new(ptr).expect("glib allocation returned NULL");
154
155        self.capacity = new_capacity;
156    }
157
158    /// Push a string onto the end of this string.
159    ///
160    /// # Panics
161    ///
162    /// Panics if the string contains intermediary nul bytes.
163    pub fn push_str(&mut self, s: &str) {
164        assert!(
165            !s.as_bytes().contains(&b'\0'),
166            "push_str called on string with nuls"
167        );
168
169        if s.is_empty() {
170            return;
171        }
172
173        self.reserve(s.len());
174        unsafe {
175            ptr::copy_nonoverlapping(s.as_ptr(), self.ptr.as_ptr().add(self.len), s.len());
176            self.len += s.len();
177            *self.ptr.as_ptr().add(self.len) = b'\0';
178        }
179    }
180
181    /// Shrinks the capacity of this string with a lower bound.
182    ///
183    /// The capacity will remain at least as large as both the length and the supplied value.
184    ///
185    /// If the current capacity is `<=` than the lower limit, this is a no-op.
186    #[allow(clippy::missing_panics_doc)]
187    pub fn shrink_to(&mut self, min_capacity: usize) {
188        let min_capacity = Ord::max(self.len + 1, min_capacity);
189        if self.capacity <= min_capacity {
190            return;
191        }
192
193        // At this point we know that we already have a heap allocation,
194        // because if `self.capacity` was 0 the above branch would be taken.
195
196        let ptr = unsafe { realloc(self.ptr.as_ptr().cast(), min_capacity) }.cast::<u8>();
197
198        // Glib allocation functions will only return NULL if `min_capacity = 0`, which it is not.
199        self.ptr = ptr::NonNull::new(ptr).expect("glib allocation returned NULL");
200
201        self.capacity = min_capacity;
202    }
203
204    /// Shrinks the capacity of this string to match its length,
205    /// plus one for the nul terminator.
206    pub fn shrink_to_fit(&mut self) {
207        self.shrink_to(0);
208    }
209
210    /// Truncate this string, removing all its contents.
211    ///
212    /// This does not touch the string's capacity.
213    pub fn clear(&mut self) {
214        if !self.is_empty() {
215            unsafe { *self.ptr.as_ptr() = b'\0' };
216            self.len = 0;
217        }
218    }
219
220    /// Append the given [`char`] to the end of this string.
221    pub fn push(&mut self, char: char) {
222        self.push_str(char.encode_utf8(&mut [0; 4]));
223    }
224
225    /// Shorten this string to the specified length.
226    ///
227    /// If `new_len` is greater or equal to than the current length,
228    /// this has no effect.
229    ///
230    /// The capacity of the string is untouched by this method.
231    ///
232    /// # Panics
233    ///
234    /// Panics if `new_len` does not lie on a [`char`] boundary.
235    pub fn truncate(&mut self, new_len: usize) {
236        if new_len < self.len() {
237            assert!(self.is_char_boundary(new_len));
238            self.len = new_len;
239            unsafe { *self.ptr.as_ptr().add(self.len) = b'\0' };
240        }
241    }
242
243    /// Remove and obtain the last character from the string.
244    ///
245    /// Returns [`None`] if the string is empty.
246    pub fn pop(&mut self) -> Option<char> {
247        let last = self.chars().next_back()?;
248        self.truncate(self.len() - last.len_utf8());
249        Some(last)
250    }
251}
252
253impl Drop for String {
254    fn drop(&mut self) {
255        if self.capacity != 0 {
256            unsafe { free(self.ptr.as_ptr().cast()) };
257        }
258    }
259}
260
261impl Deref for String {
262    type Target = str;
263    fn deref(&self) -> &Self::Target {
264        self.as_str()
265    }
266}
267
268// No `DerefMut` impl because users could write in nul bytes
269
270impl AsRef<str> for String {
271    fn as_ref(&self) -> &str {
272        self.as_str()
273    }
274}
275
276impl AsRef<CStr> for String {
277    fn as_ref(&self) -> &CStr {
278        let bytes = self.as_str_nul().as_bytes();
279        if cfg!(debug_assertions) {
280            CStr::from_bytes_with_nul(bytes).unwrap()
281        } else {
282            unsafe { CStr::from_bytes_with_nul_unchecked(bytes) }
283        }
284    }
285}
286
287impl Debug for String {
288    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
289        Debug::fmt(self.as_str(), f)
290    }
291}
292
293impl Display for String {
294    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
295        Display::fmt(self.as_str(), f)
296    }
297}
298
299impl fmt::Write for String {
300    fn write_str(&mut self, s: &str) -> fmt::Result {
301        self.push_str(s);
302        Ok(())
303    }
304}
305
306macro_rules! impl_from_stringlike {
307    ($($t:ty,)*) => { $(
308        impl From<$t> for String {
309            fn from(s: $t) -> Self {
310                let mut this = Self::new();
311                this.push_str(&*s);
312                this
313            }
314        }
315    )* };
316}
317impl_from_stringlike!(
318    &String,
319    &str,
320    &mut str,
321    std::string::String,
322    &std::string::String,
323    Cow<'_, str>,
324    &Cow<'_, str>,
325);
326
327macro_rules! impl_into_std_string {
328    ($($t:ty),*) => { $(
329        impl From<$t> for std::string::String {
330            fn from(string: $t) -> Self {
331                std::string::String::from(string.as_str())
332            }
333        }
334    )* }
335}
336impl_into_std_string!(String, &String, &mut String);
337
338impl From<GString> for String {
339    fn from(s: GString) -> Self {
340        let len = s.len();
341        // We don't know the actual capacity but it doesn't matter,
342        // since a lower value is always fine.
343        // We also add one for the nul teminator.
344        let capacity = len + 1;
345
346        unsafe { Self::from_raw_parts(s.into_glib_ptr().cast(), len, capacity) }
347    }
348}
349
350impl Default for String {
351    fn default() -> Self {
352        Self::new()
353    }
354}
355
356impl Clone for String {
357    fn clone(&self) -> Self {
358        Self::from(self.as_str())
359    }
360}
361
362impl PartialEq for String {
363    fn eq(&self, other: &Self) -> bool {
364        self.as_str() == other.as_str()
365    }
366}
367impl Eq for String {}
368
369impl PartialOrd for String {
370    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
371        Some(self.cmp(other))
372    }
373}
374impl Ord for String {
375    fn cmp(&self, other: &Self) -> cmp::Ordering {
376        self.as_str().cmp(other.as_str())
377    }
378}
379
380impl Hash for String {
381    fn hash<H: Hasher>(&self, state: &mut H) {
382        self.as_str().hash(state);
383    }
384}
385
386impl Borrow<str> for String {
387    fn borrow(&self) -> &str {
388        self.as_str()
389    }
390}
391
392impl Extend<char> for String {
393    fn extend<T: IntoIterator<Item = char>>(&mut self, iter: T) {
394        for c in iter {
395            self.push_str(c.encode_utf8(&mut [0; 4]));
396        }
397    }
398}
399
400impl<'a> Extend<&'a char> for String {
401    fn extend<T: IntoIterator<Item = &'a char>>(&mut self, iter: T) {
402        for c in iter {
403            self.push_str(c.encode_utf8(&mut [0; 4]));
404        }
405    }
406}
407
408impl<'a> Extend<&'a str> for String {
409    fn extend<T: IntoIterator<Item = &'a str>>(&mut self, iter: T) {
410        for s in iter {
411            self.push_str(s);
412        }
413    }
414}
415
416impl FromIterator<char> for String {
417    fn from_iter<T: IntoIterator<Item = char>>(iter: T) -> Self {
418        let mut this = Self::new();
419        this.extend(iter);
420        this
421    }
422}
423
424impl<'a> FromIterator<&'a char> for String {
425    fn from_iter<T: IntoIterator<Item = &'a char>>(iter: T) -> Self {
426        let mut this = Self::new();
427        this.extend(iter);
428        this
429    }
430}
431
432impl<'a> FromIterator<&'a str> for String {
433    fn from_iter<T: IntoIterator<Item = &'a str>>(iter: T) -> Self {
434        let mut this = Self::new();
435        this.extend(iter);
436        this
437    }
438}
439
440/// Format a Rofi [`String`] using interpolation of runtime expressions.
441///
442/// See the documentation of [`std::format!`] for more details.
443#[macro_export]
444macro_rules! format {
445    ($($tt:tt)*) => { $crate::format(::core::format_args!($($tt)*)) };
446}
447
448/// Format a Rofi [`String`] using a set of format arguments.
449///
450/// Usually you will want to use the [`format!`](crate::format!) macro instead of this function.
451///
452/// # Panics
453///
454/// Panics if `args` returns an error during formatting.
455#[must_use]
456pub fn format(args: fmt::Arguments<'_>) -> String {
457    let mut s = String::new();
458    s.write_fmt(args)
459        .expect("a formatting trait implementation returned an error");
460    s
461}
462
463const PTR_TO_NULL: *const u8 = &0;
464
465#[cfg(test)]
466mod tests {
467    #[test]
468    fn empty() {
469        let s = String::new();
470        assert_eq!(unsafe { *s.ptr.as_ptr() }, b'\0');
471        assert_eq!(s.len, 0);
472        assert_eq!(s.len(), 0);
473        assert!(s.is_empty());
474        assert_eq!(s.capacity, 0);
475        assert_eq!(s.capacity(), 0);
476        assert_eq!(s.as_str(), "");
477        assert_eq!(s.as_str_nul(), "\0");
478    }
479
480    #[test]
481    fn into_raw_allocates() {
482        unsafe { super::free(String::new().into_raw().cast()) };
483    }
484
485    #[test]
486    fn reserve_none() {
487        let mut s = String::new();
488        s.reserve(0);
489        assert!(s.is_empty());
490        assert_eq!(s.as_str_nul(), "\0");
491        assert_eq!(s.capacity(), 8);
492    }
493
494    #[test]
495    fn reserve() {
496        let mut s = String::new();
497        s.reserve(2);
498
499        assert!(s.is_empty());
500        assert_eq!(s.as_str_nul(), "\0");
501        assert_eq!(s.capacity(), 8);
502
503        s.reserve(7);
504        assert_eq!(s.as_str_nul(), "\0");
505        assert_eq!(s.capacity(), 8);
506
507        s.reserve(8);
508        assert_eq!(s.as_str_nul(), "\0");
509        assert_eq!(s.capacity(), 16);
510    }
511
512    #[test]
513    fn push_str() {
514        let mut s = String::new();
515
516        s.push_str("a");
517        assert_eq!(s.as_str_nul(), "a\0");
518        assert_eq!(s.capacity(), 8);
519
520        s.push_str("bcdefg");
521        assert_eq!(s.as_str_nul(), "abcdefg\0");
522        assert_eq!(s.capacity(), 8);
523
524        s.push_str("h");
525        assert_eq!(s.as_str_nul(), "abcdefgh\0");
526        assert_eq!(s.capacity(), 16);
527    }
528
529    #[test]
530    fn shrink() {
531        let mut s = String::new();
532        s.shrink_to_fit();
533        s.shrink_to(0);
534        s.shrink_to(400);
535        assert_eq!(s.capacity(), 0);
536
537        s.push_str("foo");
538
539        s.shrink_to(5);
540        assert_eq!(s.capacity(), 5);
541        assert_eq!(s.as_str_nul(), "foo\0");
542
543        s.shrink_to_fit();
544        assert_eq!(s.capacity(), 4);
545        assert_eq!(s.as_str_nul(), "foo\0");
546    }
547
548    #[test]
549    fn clear() {
550        let mut s = String::new();
551        assert_eq!(s.as_str_nul(), "\0");
552        s.clear();
553        assert_eq!(s.as_str_nul(), "\0");
554        assert_eq!(s.capacity(), 0);
555
556        s.push_str("hello world!");
557        s.clear();
558        assert_eq!(s.as_str_nul(), "\0");
559        assert_eq!(s.capacity(), 13);
560    }
561
562    #[test]
563    fn truncate() {
564        let mut s = String::new();
565
566        s.truncate(10);
567        s.truncate(0);
568
569        s.push_str("foobar");
570
571        s.truncate(10);
572        assert_eq!(s.as_str(), "foobar");
573        assert_eq!(s.as_str_nul(), "foobar\0");
574
575        s.truncate(6);
576        assert_eq!(s.as_str(), "foobar");
577        assert_eq!(s.as_str_nul(), "foobar\0");
578
579        s.truncate(3);
580        assert_eq!(s.as_str(), "foo");
581        assert_eq!(s.as_str_nul(), "foo\0");
582
583        s.truncate(0);
584        assert_eq!(s.as_str(), "");
585        assert_eq!(s.as_str_nul(), "\0");
586    }
587
588    #[test]
589    #[cfg(not(miri))]
590    fn from_gstring() {
591        let s = String::from(GString::from("hello world"));
592        assert_eq!(s.as_str(), "hello world");
593        assert_eq!(s.as_str_nul(), "hello world\0");
594    }
595
596    #[test]
597    fn formatting() {
598        assert_eq!(format!("PI = {}", 3).as_str_nul(), "PI = 3\0");
599    }
600
601    use super::String;
602    use cairo::glib::GString;
603}
604
605#[cfg(not(miri))]
606mod allocator {
607    pub(crate) use crate::glib_sys::g_free as free;
608    pub(crate) use crate::glib_sys::g_malloc as malloc;
609    pub(crate) use crate::glib_sys::g_malloc0_n as calloc;
610    pub(crate) use crate::glib_sys::g_realloc as realloc;
611}
612#[cfg(miri)]
613mod allocator {
614    pub(crate) use libc::calloc;
615    pub(crate) use libc::free;
616    pub(crate) use libc::malloc;
617    pub(crate) use libc::realloc;
618}
619use allocator::calloc;
620use allocator::free;
621use allocator::malloc;
622use allocator::realloc;
623
624use cairo::glib::translate::IntoGlibPtr;
625use cairo::glib::GString;
626use std::borrow::Borrow;
627use std::borrow::Cow;
628use std::cmp;
629use std::ffi::CStr;
630use std::fmt;
631use std::fmt::Debug;
632use std::fmt::Display;
633use std::fmt::Formatter;
634use std::fmt::Write as _;
635use std::hash::Hash;
636use std::hash::Hasher;
637use std::mem::ManuallyDrop;
638use std::ops::Deref;
639use std::ptr;
640use std::slice;
641use std::str;