oxi_types/
string.rs

1//! This module contains the binding to Neovim's `String` type.
2
3use core::{ffi, slice};
4use std::borrow::Cow;
5
6use oxi_luajit as lua;
7
8use crate::NonOwning;
9
10/// Binding to the string type used by Neovim.
11///
12/// Unlike Rust's `String`, this type is not guaranteed to contain valid UTF-8
13/// byte sequences, it can contain null bytes, and it is null-terminated.
14//
15// https://github.com/neovim/neovim/blob/v0.9.0/src/nvim/api/private/defs.h#L79-L82
16#[derive(Eq, Ord, PartialOrd)]
17#[repr(C)]
18pub struct String {
19    pub(super) data: *mut ffi::c_char,
20    pub(super) size: usize,
21}
22
23impl Default for String {
24    #[inline]
25    fn default() -> Self {
26        Self::new()
27    }
28}
29
30impl core::fmt::Debug for String {
31    #[inline]
32    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
33        self.to_string_lossy().as_ref().fmt(f)
34    }
35}
36
37impl core::fmt::Display for String {
38    #[inline]
39    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
40        self.to_string_lossy().as_ref().fmt(f)
41    }
42}
43
44impl String {
45    #[inline]
46    pub fn as_bytes(&self) -> &[u8] {
47        if self.data.is_null() {
48            &[]
49        } else {
50            assert!(self.len() <= isize::MAX as usize);
51            unsafe { slice::from_raw_parts(self.data as *const u8, self.size) }
52        }
53    }
54
55    /// Returns a pointer to the `String`'s buffer.
56    #[inline]
57    pub fn as_ptr(&self) -> *const ffi::c_char {
58        self.data as _
59    }
60
61    /// Creates a `String` from a byte slice by allocating `bytes.len() + 1`
62    /// bytes of memory and copying the contents of `bytes` into it, followed
63    /// by a null byte.
64    #[inline]
65    pub fn from_bytes(bytes: &[u8]) -> Self {
66        let data =
67            unsafe { libc::malloc(bytes.len() + 1) as *mut ffi::c_char };
68
69        unsafe {
70            libc::memcpy(
71                data as *mut _,
72                bytes.as_ptr() as *const _,
73                bytes.len(),
74            )
75        };
76
77        unsafe { *data.add(bytes.len()) = 0 };
78
79        Self { data: data as *mut _, size: bytes.len() }
80    }
81
82    /// Returns `true` if the `String` has a length of zero.
83    #[inline]
84    pub fn is_empty(&self) -> bool {
85        self.len() == 0
86    }
87
88    /// Returns the length of the `String`, *not* including the final null byte.
89    #[inline]
90    pub fn len(&self) -> usize {
91        self.size
92    }
93
94    /// Creates a new, empty `String`.
95    #[inline]
96    pub fn new() -> Self {
97        Self { data: core::ptr::null_mut(), size: 0 }
98    }
99
100    /// Makes a non-owning version of this `String`.
101    #[inline]
102    #[doc(hidden)]
103    pub fn non_owning(&self) -> NonOwning<'_, String> {
104        NonOwning::new(Self { ..*self })
105    }
106
107    /// Converts the `String` into Rust's `std::string::String`. If it already
108    /// holds a valid UTF-8 byte sequence no allocation is made. If it doesn't
109    /// the `String` is copied and all invalid sequences are replaced with `�`.
110    #[inline]
111    pub fn to_string_lossy(&self) -> Cow<'_, str> {
112        std::string::String::from_utf8_lossy(self.as_bytes())
113    }
114}
115
116impl Clone for String {
117    #[inline]
118    fn clone(&self) -> Self {
119        Self::from_bytes(self.as_bytes())
120    }
121}
122
123impl Drop for String {
124    fn drop(&mut self) {
125        // There's no way to know if the pointer we get from Neovim
126        // points to some `malloc`ed memory or to a static/borrowed string.
127        //
128        // TODO: we're leaking memory here if the pointer points to allocated
129        // memory.
130    }
131}
132
133impl From<&str> for String {
134    #[inline]
135    fn from(s: &str) -> Self {
136        Self::from_bytes(s.as_bytes())
137    }
138}
139
140impl From<char> for String {
141    #[inline]
142    fn from(ch: char) -> Self {
143        ch.to_string().as_str().into()
144    }
145}
146
147impl From<&std::path::Path> for String {
148    #[inline]
149    fn from(path: &std::path::Path) -> Self {
150        path.display().to_string().as_str().into()
151    }
152}
153
154#[cfg(not(windows))]
155impl From<String> for std::path::PathBuf {
156    #[inline]
157    fn from(nstr: String) -> Self {
158        use std::os::unix::ffi::OsStrExt;
159        std::ffi::OsStr::from_bytes(nstr.as_bytes()).to_owned().into()
160    }
161}
162
163#[cfg(windows)]
164impl From<String> for std::path::PathBuf {
165    #[inline]
166    fn from(nstr: String) -> Self {
167        std::string::String::from_utf8_lossy(nstr.as_bytes())
168            .into_owned()
169            .into()
170    }
171}
172
173impl PartialEq<Self> for String {
174    #[inline]
175    fn eq(&self, other: &Self) -> bool {
176        self.as_bytes() == other.as_bytes()
177    }
178}
179
180impl PartialEq<str> for String {
181    #[inline]
182    fn eq(&self, other: &str) -> bool {
183        self.as_bytes() == other.as_bytes()
184    }
185}
186
187impl PartialEq<String> for str {
188    #[inline]
189    fn eq(&self, other: &String) -> bool {
190        other == self
191    }
192}
193
194impl PartialEq<&str> for String {
195    #[inline]
196    fn eq(&self, other: &&str) -> bool {
197        self.as_bytes() == other.as_bytes()
198    }
199}
200
201impl PartialEq<String> for &str {
202    #[inline]
203    fn eq(&self, other: &String) -> bool {
204        other == self
205    }
206}
207
208impl PartialEq<std::string::String> for String {
209    #[inline]
210    fn eq(&self, other: &std::string::String) -> bool {
211        self.as_bytes() == other.as_bytes()
212    }
213}
214
215impl core::hash::Hash for String {
216    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
217        self.as_bytes().hash(state);
218        self.size.hash(state);
219    }
220}
221
222impl lua::Pushable for String {
223    #[inline]
224    unsafe fn push(
225        self,
226        lstate: *mut lua::ffi::lua_State,
227    ) -> Result<ffi::c_int, lua::Error> {
228        lua::ffi::lua_pushlstring(lstate, self.as_ptr(), self.len());
229        Ok(1)
230    }
231}
232
233impl lua::Poppable for String {
234    #[inline]
235    unsafe fn pop(
236        lstate: *mut lua::ffi::lua_State,
237    ) -> Result<Self, lua::Error> {
238        use lua::ffi::*;
239
240        if lua_gettop(lstate) < 0 {
241            return Err(lua::Error::PopEmptyStack);
242        }
243
244        let ty = lua_type(lstate, -1);
245
246        if ty != LUA_TSTRING && ty != LUA_TNUMBER {
247            return Err(lua::Error::pop_wrong_type::<Self>(LUA_TSTRING, ty));
248        }
249
250        let mut len = 0;
251        let ptr = lua_tolstring(lstate, -1, &mut len);
252
253        // The pointer shouldn't be null if the type value at the top of thr
254        // stack is a string or a number, but we'll check anyway.
255        assert!(!ptr.is_null());
256
257        let slice = std::slice::from_raw_parts(ptr as *const u8, len);
258        let s = String::from_bytes(slice);
259
260        lua_pop(lstate, 1);
261
262        Ok(s)
263    }
264}
265
266#[cfg(feature = "serde")]
267mod serde {
268    use std::fmt;
269
270    use serde::de::{self, Deserialize, Deserializer, Visitor};
271
272    impl<'de> Deserialize<'de> for super::String {
273        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
274        where
275            D: Deserializer<'de>,
276        {
277            struct StringVisitor;
278
279            impl<'de> Visitor<'de> for StringVisitor {
280                type Value = crate::String;
281
282                fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
283                    f.write_str("either a string of a byte vector")
284                }
285
286                fn visit_bytes<E>(self, b: &[u8]) -> Result<Self::Value, E>
287                where
288                    E: de::Error,
289                {
290                    Ok(crate::String::from_bytes(b))
291                }
292
293                fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
294                where
295                    E: de::Error,
296                {
297                    Ok(crate::String::from(s))
298                }
299            }
300
301            deserializer.deserialize_str(StringVisitor)
302        }
303    }
304}
305
306#[cfg(test)]
307mod tests {
308    use super::*;
309
310    #[test]
311    fn as_bytes() {
312        let s = String::from("hello");
313        assert_eq!(s.as_bytes(), &[b'h', b'e', b'l', b'l', b'o'][..]);
314    }
315
316    #[test]
317    fn partial_eq() {
318        let lhs = String::from("foo bar baz");
319        let rhs = String::from("foo bar baz");
320        assert_eq!(lhs, rhs);
321
322        let lhs = String::from("foo bar baz");
323        let rhs = std::string::String::from("bar foo baz");
324        assert_ne!(lhs, rhs);
325
326        let lhs = String::from("€");
327        let rhs = "€";
328        assert_eq!(lhs, rhs);
329    }
330
331    #[test]
332    fn clone() {
333        let lhs = String::from("abc");
334        let rhs = lhs.clone();
335
336        assert_eq!(lhs, rhs);
337    }
338
339    #[test]
340    fn from_string() {
341        let foo = std::string::String::from("foo bar baz");
342
343        let lhs = String::from(foo.as_str());
344        let rhs = String::from(foo.as_str());
345
346        assert_eq!(lhs, rhs);
347    }
348}