nvim_types/
dictionary.rs

1use std::collections::HashMap;
2use std::ffi::c_int;
3use std::mem::ManuallyDrop;
4use std::{fmt, ptr};
5
6use luajit_bindings::{self as lua, ffi::*, Poppable, Pushable};
7
8use super::{KVec, Object, String};
9
10// https://github.com/neovim/neovim/blob/master/src/nvim/api/private/defs.h#L95
11//
12/// A vector of Neovim [`KeyValuePair`] s.
13pub type Dictionary = KVec<KeyValuePair>;
14
15// https://github.com/neovim/neovim/blob/master/src/nvim/api/private/defs.h#L122
16//
17/// A key-value pair mapping a Neovim [`String`] to a Neovim [`Object`].
18#[derive(Clone, PartialEq)]
19#[repr(C)]
20pub struct KeyValuePair {
21    pub(crate) key: String,
22    pub(crate) value: Object,
23}
24
25impl fmt::Debug for KeyValuePair {
26    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
27        fmt::Display::fmt(self, f)
28    }
29}
30
31impl fmt::Display for KeyValuePair {
32    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
33        write!(f, "{}: {}", self.key, self.value)
34    }
35}
36
37impl<K, V> From<(K, V)> for KeyValuePair
38where
39    K: Into<String>,
40    V: Into<Object>,
41{
42    fn from((k, v): (K, V)) -> Self {
43        Self { key: k.into(), value: v.into() }
44    }
45}
46
47impl Dictionary {
48    pub fn get<Q>(&self, query: &Q) -> Option<&Object>
49    where
50        String: PartialEq<Q>,
51    {
52        self.iter()
53            .find_map(|pair| (&pair.key == query).then_some(&pair.value))
54    }
55
56    pub fn get_mut<Q>(&mut self, query: &Q) -> Option<&mut Object>
57    where
58        String: PartialEq<Q>,
59    {
60        self.iter_mut()
61            .find_map(|pair| (&pair.key == query).then_some(&mut pair.value))
62    }
63}
64
65impl fmt::Debug for Dictionary {
66    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
67        f.debug_map()
68            .entries(self.iter().map(|pair| (&pair.key, &pair.value)))
69            .finish()
70    }
71}
72
73impl fmt::Display for Dictionary {
74    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
75        fmt::Debug::fmt(self, f)
76    }
77}
78
79impl<S: Into<String>> std::ops::Index<S> for Dictionary {
80    type Output = Object;
81
82    fn index(&self, index: S) -> &Self::Output {
83        self.get(&index.into()).unwrap()
84    }
85}
86
87impl<S: Into<String>> std::ops::IndexMut<S> for Dictionary {
88    fn index_mut(&mut self, index: S) -> &mut Self::Output {
89        self.get_mut(&index.into()).unwrap()
90    }
91}
92
93impl Pushable for Dictionary {
94    unsafe fn push(self, lstate: *mut lua_State) -> Result<c_int, lua::Error> {
95        lua::ffi::lua_createtable(lstate, 0, self.len() as _);
96
97        for (key, obj) in self {
98            lua::ffi::lua_pushlstring(lstate, key.as_ptr(), key.len());
99            obj.push(lstate)?;
100            lua::ffi::lua_rawset(lstate, -3);
101        }
102
103        Ok(1)
104    }
105}
106
107impl Poppable for Dictionary {
108    unsafe fn pop(lstate: *mut lua_State) -> Result<Self, lua::Error> {
109        <HashMap<crate::String, Object>>::pop(lstate).map(Into::into)
110    }
111}
112
113impl IntoIterator for Dictionary {
114    type IntoIter = DictIterator;
115    type Item = <DictIterator as Iterator>::Item;
116
117    #[inline]
118    fn into_iter(self) -> Self::IntoIter {
119        // Wrap `self` in `ManuallyDrop` to avoid running destructor.
120        let arr = ManuallyDrop::new(self);
121        let start = arr.items;
122        let end = unsafe { start.add(arr.len()) };
123
124        DictIterator { start, end }
125    }
126}
127
128/// An owning iterator over the ([`String`], [`Object`]) pairs of a Neovim
129/// [`Dictionary`].
130pub struct DictIterator {
131    start: *const KeyValuePair,
132    end: *const KeyValuePair,
133}
134
135impl Iterator for DictIterator {
136    type Item = (String, Object);
137
138    #[inline]
139    fn next(&mut self) -> Option<Self::Item> {
140        if self.start == self.end {
141            return None;
142        }
143        let current = self.start;
144        self.start = unsafe { self.start.offset(1) };
145        let KeyValuePair { key, value } = unsafe { ptr::read(current) };
146        Some((key, value))
147    }
148
149    #[inline]
150    fn size_hint(&self) -> (usize, Option<usize>) {
151        let exact = self.len();
152        (exact, Some(exact))
153    }
154
155    #[inline]
156    fn count(self) -> usize {
157        self.len()
158    }
159}
160
161impl ExactSizeIterator for DictIterator {
162    #[inline]
163    fn len(&self) -> usize {
164        unsafe { self.end.offset_from(self.start) as usize }
165    }
166}
167
168impl DoubleEndedIterator for DictIterator {
169    #[inline]
170    fn next_back(&mut self) -> Option<Self::Item> {
171        if self.start == self.end {
172            return None;
173        }
174        let current = self.end;
175        self.end = unsafe { self.end.offset(-1) };
176        let KeyValuePair { key, value } = unsafe { ptr::read(current) };
177        Some((key, value))
178    }
179}
180
181impl std::iter::FusedIterator for DictIterator {}
182
183impl Drop for DictIterator {
184    fn drop(&mut self) {
185        while self.start != self.end {
186            unsafe {
187                ptr::drop_in_place(self.start as *mut Object);
188                self.start = self.start.offset(1);
189            }
190        }
191    }
192}
193
194impl<K, V> FromIterator<(K, V)> for Dictionary
195where
196    K: Into<String>,
197    V: Into<Object>,
198{
199    fn from_iter<I: IntoIterator<Item = (K, V)>>(iter: I) -> Self {
200        iter.into_iter()
201            .map(|(k, v)| (k, v.into()))
202            .filter(|(_, obj)| obj.is_some())
203            .map(KeyValuePair::from)
204            .collect::<Vec<KeyValuePair>>()
205            .into()
206    }
207}
208
209impl<K, V> From<HashMap<K, V>> for Dictionary
210where
211    String: From<K>,
212    Object: From<V>,
213{
214    fn from(hashmap: HashMap<K, V>) -> Self {
215        hashmap.into_iter().collect()
216    }
217}
218
219#[cfg(test)]
220mod tests {
221    use super::{Dictionary, Object, String as NvimString};
222
223    #[test]
224    fn iter_basic() {
225        let dict = Dictionary::from_iter([
226            ("foo", "Foo"),
227            ("bar", "Bar"),
228            ("baz", "Baz"),
229        ]);
230
231        let mut iter = dict.into_iter();
232        assert_eq!(
233            Some((NvimString::from("foo"), Object::from("Foo"))),
234            iter.next()
235        );
236        assert_eq!(
237            Some((NvimString::from("bar"), Object::from("Bar"))),
238            iter.next()
239        );
240        assert_eq!(
241            Some((NvimString::from("baz"), Object::from("Baz"))),
242            iter.next()
243        );
244        assert_eq!(None, iter.next());
245    }
246
247    #[test]
248    fn drop_iter_halfway() {
249        let dict = Dictionary::from_iter([
250            ("foo", "Foo"),
251            ("bar", "Bar"),
252            ("baz", "Baz"),
253        ]);
254
255        let mut iter = dict.into_iter();
256        assert_eq!(
257            Some((NvimString::from("foo"), Object::from("Foo"))),
258            iter.next()
259        );
260    }
261
262    #[test]
263    fn debug_dict() {
264        let dict = Dictionary::from_iter([
265            ("a", Object::from(1)),
266            ("b", Object::from(true)),
267            ("c", Object::from("foobar")),
268        ]);
269
270        assert_eq!(
271            String::from("{a: 1, b: true, c: \"foobar\"}"),
272            format!("{dict}")
273        );
274    }
275
276    #[test]
277    fn debug_nested_dict() {
278        let dict = Dictionary::from_iter([(
279            "foo",
280            Object::from(Dictionary::from_iter([("a", 1)])),
281        )]);
282
283        assert_eq!(String::from("{foo: {a: 1}}"), format!("{dict}"));
284    }
285}