nvim_oxi_types/
str.rs

1use core::ffi::{self, CStr};
2use core::marker::PhantomData;
3use core::str::Utf8Error;
4use core::{cmp, fmt, hash, slice};
5use std::borrow::Cow;
6
7use crate::String as NvimString;
8
9/// A borrowed version of [`NvimString`].
10///
11/// Values of this type can be created by calling
12/// [`as_nvim_str`](NvimString::as_nvim_str) on a [`NvimString`] or by
13/// converting a [`CStr`].
14#[derive(Copy, Clone)]
15#[repr(C)]
16pub struct NvimStr<'a> {
17    data: *const ffi::c_char,
18    len: usize,
19    _lifetime: PhantomData<&'a ()>,
20}
21
22impl<'a> NvimStr<'a> {
23    /// Converts the [`NvimStr`] into a byte slice, *not* including the final
24    /// null byte.
25    ///
26    /// If you want the final null byte to be included in the slice, use
27    /// [`as_bytes_with_nul`](Self::as_bytes_with_nul) instead.
28    #[inline]
29    pub const fn as_bytes(&self) -> &'a [u8] {
30        self.as_bytes_inner(false)
31    }
32
33    /// Converts the [`NvimStr`] into a byte slice, including the final
34    /// null byte.
35    ///
36    /// If you don't want the final null byte to be included in the slice, use
37    /// [`as_bytes`](Self::as_bytes) instead.
38    #[inline]
39    pub const fn as_bytes_with_nul(&self) -> &'a [u8] {
40        self.as_bytes_inner(false)
41    }
42
43    /// Returns a raw pointer to the [`NvimStr`]'s buffer.
44    #[inline]
45    pub const fn as_ptr(&self) -> *const ffi::c_char {
46        self.data as *const ffi::c_char
47    }
48
49    /// Creates an `NvimStr` from a pointer to the underlying data and a
50    /// length.
51    ///
52    /// # Safety
53    ///
54    /// The caller must ensure that the pointer is valid for `len + 1`
55    /// elements and that the last element is a null byte.
56    #[inline]
57    pub unsafe fn from_raw_parts(
58        data: *const ffi::c_char,
59        len: usize,
60    ) -> Self {
61        Self { data, len, _lifetime: PhantomData }
62    }
63
64    /// Returns `true` if the [`NvimStr`] has a length of zero, not including
65    /// the final null byte.
66    #[inline]
67    pub fn is_empty(&self) -> bool {
68        self.len() == 0
69    }
70
71    /// Returns the length of the [`NvimStr`], *not* including the final null
72    /// byte.
73    #[inline]
74    pub const fn len(&self) -> usize {
75        self.len
76    }
77
78    /// Returns the length of the [`NvimStr`], *not* including the final null
79    /// byte.
80    ///
81    /// # Safety
82    ///
83    /// The caller must ensure that the bytes at `old_len..new_len` are
84    /// initialized.
85    #[inline]
86    pub const unsafe fn set_len(&mut self, new_len: usize) {
87        self.len = new_len;
88    }
89
90    /// Yields a string slice if the [`NvimStr`]'s contents are valid UTF-8.
91    #[inline]
92    pub fn to_str(&self) -> Result<&str, Utf8Error> {
93        str::from_utf8(self.as_bytes())
94    }
95
96    /// Converts the [`NvimStr`] into a [`String`].
97    ///
98    /// If it already holds a valid UTF-8 byte sequence no allocation is made.
99    /// If it doesn't, the contents of the [`NvimStr`] are is copied and all
100    /// invalid sequences are replaced with `�`.
101    #[inline]
102    pub fn to_string_lossy(&self) -> Cow<'_, str> {
103        std::string::String::from_utf8_lossy(self.as_bytes())
104    }
105
106    #[inline]
107    pub(crate) fn reborrow(&self) -> NvimStr<'_> {
108        NvimStr { ..*self }
109    }
110
111    #[inline]
112    const fn as_bytes_inner(&self, with_nul: bool) -> &'a [u8] {
113        if self.data.is_null() {
114            &[]
115        } else {
116            unsafe {
117                slice::from_raw_parts(
118                    self.as_ptr() as *const u8,
119                    self.len + with_nul as usize,
120                )
121            }
122        }
123    }
124}
125
126impl fmt::Debug for NvimStr<'_> {
127    #[inline]
128    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129        fmt::Debug::fmt(&*self.to_string_lossy(), f)
130    }
131}
132
133impl fmt::Display for NvimStr<'_> {
134    #[inline]
135    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136        fmt::Display::fmt(&*self.to_string_lossy(), f)
137    }
138}
139
140impl hash::Hash for NvimStr<'_> {
141    #[inline]
142    fn hash<H: hash::Hasher>(&self, state: &mut H) {
143        self.as_bytes_with_nul().hash(state);
144    }
145}
146
147impl PartialEq for NvimStr<'_> {
148    #[inline]
149    fn eq(&self, other: &Self) -> bool {
150        self.cmp(other) == cmp::Ordering::Equal
151    }
152}
153
154impl PartialEq<&str> for NvimStr<'_> {
155    #[inline]
156    fn eq(&self, other: &&str) -> bool {
157        self.as_bytes() == other.as_bytes()
158    }
159}
160
161impl Eq for NvimStr<'_> {}
162
163impl PartialOrd for NvimStr<'_> {
164    #[inline]
165    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
166        Some(self.cmp(other))
167    }
168}
169
170impl Ord for NvimStr<'_> {
171    #[inline]
172    fn cmp(&self, other: &Self) -> cmp::Ordering {
173        self.as_bytes_with_nul().cmp(other.as_bytes_with_nul())
174    }
175}
176
177impl<'a> From<&'a NvimString> for NvimStr<'a> {
178    #[inline]
179    fn from(string: &'a NvimString) -> Self {
180        string.as_nvim_str()
181    }
182}
183
184impl<'a> From<&'a CStr> for NvimStr<'a> {
185    #[inline]
186    fn from(cstr: &'a CStr) -> Self {
187        Self {
188            data: cstr.as_ptr(),
189            len: cstr.to_bytes().len(),
190            _lifetime: PhantomData,
191        }
192    }
193}
194
195impl PartialEq<NvimStr<'_>> for &str {
196    #[inline]
197    fn eq(&self, other: &NvimStr<'_>) -> bool {
198        self.as_bytes() == other.as_bytes()
199    }
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205
206    #[test]
207    fn from_cstr() {
208        let c_str = c"Hello, World!";
209        let nvim_str = NvimStr::from(c_str);
210        assert_eq!(nvim_str, "Hello, World!");
211    }
212}