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