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#[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 #[inline]
29 pub const fn as_bytes(&self) -> &'a [u8] {
30 self.as_bytes_inner(false)
31 }
32
33 #[inline]
39 pub const fn as_bytes_with_nul(&self) -> &'a [u8] {
40 self.as_bytes_inner(false)
41 }
42
43 #[inline]
45 pub const fn as_ptr(&self) -> *const ffi::c_char {
46 self.data as *const ffi::c_char
47 }
48
49 #[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 #[inline]
67 pub fn is_empty(&self) -> bool {
68 self.len() == 0
69 }
70
71 #[inline]
74 pub const fn len(&self) -> usize {
75 self.len
76 }
77
78 #[inline]
86 pub const unsafe fn set_len(&mut self, new_len: usize) {
87 self.len = new_len;
88 }
89
90 #[inline]
92 pub fn to_str(&self) -> Result<&str, Utf8Error> {
93 str::from_utf8(self.as_bytes())
94 }
95
96 #[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}