nvim_oxi_types/
string.rs

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