nvim_oxi_api/
buffer.rs

1use core::ops::RangeBounds;
2use std::error::Error as StdError;
3use std::fmt;
4use std::path::{Path, PathBuf};
5use std::result::Result as StdResult;
6
7use luajit::{self as lua, Poppable, Pushable};
8use serde::{Deserialize, Serialize};
9use types::{
10    self as nvim,
11    Array,
12    BufHandle,
13    Function,
14    Integer,
15    Object,
16    conversion::{self, FromObject, ToObject},
17};
18
19use crate::LUA_INTERNAL_CALL;
20use crate::SuperIterator;
21use crate::choose;
22use crate::ffi::buffer::*;
23use crate::opts::*;
24use crate::types::{KeymapInfos, Mode};
25use crate::utils;
26use crate::{Error, IntoResult, Result};
27
28/// A wrapper around a Neovim buffer handle.
29#[derive(Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
30pub struct Buffer(pub(crate) BufHandle);
31
32impl fmt::Debug for Buffer {
33    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
34        f.debug_tuple("Buffer").field(&self.0).finish()
35    }
36}
37
38impl fmt::Display for Buffer {
39    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
40        fmt::Debug::fmt(self, f)
41    }
42}
43
44impl<H: Into<BufHandle>> From<H> for Buffer {
45    #[inline(always)]
46    fn from(handle: H) -> Self {
47        Self(handle.into())
48    }
49}
50
51impl From<Buffer> for Object {
52    #[inline(always)]
53    fn from(buf: Buffer) -> Self {
54        buf.0.into()
55    }
56}
57
58impl From<&Buffer> for Object {
59    #[inline(always)]
60    fn from(buf: &Buffer) -> Self {
61        buf.0.into()
62    }
63}
64
65impl FromObject for Buffer {
66    #[inline(always)]
67    fn from_object(obj: Object) -> StdResult<Self, conversion::Error> {
68        Ok(BufHandle::from_object(obj)?.into())
69    }
70}
71
72impl Poppable for Buffer {
73    unsafe fn pop(
74        lstate: *mut lua::ffi::State,
75    ) -> std::result::Result<Self, lua::Error> {
76        BufHandle::pop(lstate).map(Into::into)
77    }
78}
79
80impl Pushable for Buffer {
81    unsafe fn push(
82        self,
83        lstate: *mut lua::ffi::State,
84    ) -> std::result::Result<std::ffi::c_int, lua::Error> {
85        self.0.push(lstate)
86    }
87}
88
89impl Buffer {
90    /// Shorthand for [`get_current_buf`](crate::get_current_buf).
91    #[inline(always)]
92    pub fn current() -> Self {
93        crate::get_current_buf()
94    }
95
96    /// Retrieve buffer's underlying id/handle
97    #[inline(always)]
98    pub fn handle(&self) -> i32 {
99        self.0
100    }
101
102    /// Binding to [`nvim_buf_attach()`][1].
103    ///
104    /// Used to register a set of callbacks on specific buffer events.
105    ///
106    /// [1]: https://neovim.io/doc/user/api.html#nvim_buf_attach()
107    pub fn attach(
108        &self,
109        send_buffer: bool,
110        opts: &BufAttachOpts,
111    ) -> Result<()> {
112        let mut err = nvim::Error::new();
113
114        let has_attached = unsafe {
115            nvim_buf_attach(
116                LUA_INTERNAL_CALL,
117                self.0,
118                send_buffer,
119                opts,
120                &mut err,
121            )
122        };
123
124        choose!(
125            err,
126            match has_attached {
127                true => Ok(()),
128                _ => Err(Error::custom("Attaching to buffer failed")),
129            }
130        )
131    }
132
133    /// Binding to [`nvim_buf_call()`][1].
134    ///
135    /// Calls a function with this buffer as the temporary current buffer.
136    ///
137    /// [1]: https://neovim.io/doc/user/api.html#nvim_buf_call()
138    pub fn call<F, Res, Ret>(&self, fun: F) -> Result<Ret>
139    where
140        F: FnOnce(()) -> Res + 'static,
141        Res: IntoResult<Ret>,
142        Res::Error: StdError + 'static,
143        Ret: Pushable + FromObject,
144    {
145        let fun = Function::from_fn_once(fun);
146        let mut err = nvim::Error::new();
147
148        let ref_or_nil =
149            unsafe { nvim_buf_call(self.0, fun.lua_ref(), &mut err) };
150
151        let lua_ref = match ref_or_nil.kind() {
152            types::ObjectKind::LuaRef => unsafe {
153                ref_or_nil.as_luaref_unchecked()
154            },
155            types::ObjectKind::Nil => {
156                return Ret::from_object(Object::nil()).map_err(Into::into);
157            },
158            other => panic!("Unexpected object kind: {other:?}"),
159        };
160
161        let obj = unsafe {
162            lua::with_state(|lstate| {
163                lua::ffi::lua_rawgeti(
164                    lstate,
165                    lua::ffi::LUA_REGISTRYINDEX,
166                    lua_ref,
167                );
168                Object::pop(lstate)
169            })
170        }
171        .map_err(Error::custom)?;
172
173        choose!(err, {
174            fun.remove_from_lua_registry();
175            Ok(Ret::from_object(obj)?)
176        })
177    }
178
179    /// Binding to [`nvim_buf_del_keymap()`][1].
180    ///
181    /// Unmaps a buffer-local mapping for the given mode.
182    ///
183    /// [1]: https://neovim.io/doc/user/api.html#nvim_buf_del_keymap()
184    pub fn del_keymap(&mut self, mode: Mode, lhs: &str) -> Result<()> {
185        let mut err = nvim::Error::new();
186        let mode = nvim::String::from(mode);
187        let lhs = nvim::String::from(lhs);
188        unsafe {
189            nvim_buf_del_keymap(
190                LUA_INTERNAL_CALL,
191                self.0,
192                mode.as_nvim_str(),
193                lhs.as_nvim_str(),
194                &mut err,
195            )
196        };
197        choose!(err, ())
198    }
199
200    /// Binding to [`nvim_buf_del_mark()`][1].
201    ///
202    /// Deletes a named mark in the buffer.
203    ///
204    /// [1]: https://neovim.io/doc/user/api.html#nvim_buf_del_mark()
205    pub fn del_mark(&mut self, name: char) -> Result<()> {
206        let mut err = nvim::Error::new();
207        let name = nvim::String::from(name);
208        let was_deleted =
209            unsafe { nvim_buf_del_mark(self.0, name.as_nvim_str(), &mut err) };
210        choose!(
211            err,
212            match was_deleted {
213                true => Ok(()),
214
215                _ => Err(Error::custom("Couldn't delete mark")),
216            }
217        )
218    }
219
220    /// Binding to [`nvim_buf_del_var()`][1].
221    ///
222    /// Removes a buffer-scoped (`b:`) variable.
223    ///
224    /// [1]: https://neovim.io/doc/user/api.html#nvim_buf_del_var()
225    pub fn del_var(&mut self, name: &str) -> Result<()> {
226        let mut err = nvim::Error::new();
227        let name = nvim::String::from(name);
228        unsafe { nvim_buf_del_var(self.0, name.as_nvim_str(), &mut err) };
229        choose!(err, ())
230    }
231
232    /// Binding to [`nvim_buf_delete()`][1].
233    ///
234    /// Deletes the buffer (not allowed while
235    /// [`textlock`](https://neovim.io/doc/user/eval.html#textlock) is active).
236    ///
237    /// [1]: https://neovim.io/doc/user/api.html#nvim_buf_delete()
238    pub fn delete(self, opts: &BufDeleteOpts) -> Result<()> {
239        let mut err = nvim::Error::new();
240        unsafe { nvim_buf_delete(self.0, opts, &mut err) };
241        choose!(err, ())
242    }
243
244    /// Binding to [`nvim_buf_get_changedtick()`][1].
245    ///
246    /// [1]: https://neovim.io/doc/user/api.html#nvim_buf_get_changedtick()
247    pub fn get_changedtick(&self) -> Result<u32> {
248        let mut err = nvim::Error::new();
249        let ct = unsafe { nvim_buf_get_changedtick(self.0, &mut err) };
250        choose!(err, Ok(ct.try_into().expect("always positive")))
251    }
252
253    /// Binding to [`nvim_buf_get_keymap()`][1].
254    ///
255    /// [1]: https://neovim.io/doc/user/api.html#nvim_buf_get_keymap()
256    pub fn get_keymap(
257        &self,
258        mode: Mode,
259    ) -> Result<impl SuperIterator<KeymapInfos>> {
260        let mut err = nvim::Error::new();
261        let mode = nvim::String::from(mode);
262        let maps = unsafe {
263            nvim_buf_get_keymap(
264                self.0,
265                mode.as_nvim_str(),
266                types::arena(),
267                &mut err,
268            )
269        };
270        choose!(
271            err,
272            Ok({
273                maps.into_iter()
274                    .map(|obj| KeymapInfos::from_object(obj).unwrap())
275            })
276        )
277    }
278
279    /// Binding to [`nvim_buf_get_lines()`][1].
280    ///
281    /// Gets a line range from the buffer. Indexing is zero-based,
282    /// end-exclusive.
283    ///
284    /// [1]: https://neovim.io/doc/user/api.html#nvim_buf_get_lines()
285    pub fn get_lines<R>(
286        &self,
287        line_range: R,
288        strict_indexing: bool,
289    ) -> Result<impl SuperIterator<nvim::String>>
290    where
291        R: RangeBounds<usize>,
292    {
293        let mut err = nvim::Error::new();
294        let (start, end) = utils::range_to_limits(line_range);
295        let lines = unsafe {
296            nvim_buf_get_lines(
297                LUA_INTERNAL_CALL,
298                self.0,
299                start,
300                end,
301                strict_indexing,
302                types::arena(),
303                // The nvim_buf_get_lines() function returns no line if we use
304                // an actual lstate here.
305                core::ptr::null_mut(),
306                &mut err,
307            )
308        };
309        choose!(
310            err,
311            Ok({
312                lines
313                    .into_iter()
314                    .map(|line| nvim::String::from_object(line).unwrap())
315            })
316        )
317    }
318
319    /// Binding to [`nvim_buf_get_mark()`][1].
320    ///
321    /// Returns a (1-0) indexed `(row, col)` tuple representing the position
322    /// of the named mark.
323    ///
324    /// [1]: https://neovim.io/doc/user/api.html#nvim_buf_get_mark()
325    pub fn get_mark(&self, name: char) -> Result<(usize, usize)> {
326        let mut err = nvim::Error::new();
327        let name = nvim::String::from(name);
328        let mark = unsafe {
329            nvim_buf_get_mark(
330                self.0,
331                name.as_nvim_str(),
332                types::arena(),
333                &mut err,
334            )
335        };
336        choose!(err, {
337            let mut iter = mark.into_iter().map(usize::from_object);
338            let row = iter.next().expect("row is present")?;
339            let col = iter.next().expect("col is present")?;
340            Ok((row, col))
341        })
342    }
343
344    /// Binding to [`nvim_buf_get_name()`][1].
345    ///
346    /// Returns the full filepath of the buffer.
347    ///
348    /// [1]: https://neovim.io/doc/user/api.html#nvim_buf_get_name()
349    pub fn get_name(&self) -> Result<PathBuf> {
350        let mut err = nvim::Error::new();
351        let name =
352            unsafe { nvim_buf_get_name(self.0, types::arena(), &mut err) };
353        choose!(err, Ok(name.into()))
354    }
355
356    /// Binding to [`nvim_buf_get_offset()`][1].
357    ///
358    /// Returns the 0-indexed byte offset of a line.
359    ///
360    /// [1]: https://neovim.io/doc/user/api.html#nvim_buf_get_offset()
361    pub fn get_offset(&self, index: usize) -> Result<usize> {
362        let mut err = nvim::Error::new();
363        let offset =
364            unsafe { nvim_buf_get_offset(self.0, index as Integer, &mut err) };
365        choose!(err, Ok(offset.try_into().expect("offset is positive")))
366    }
367
368    /// Binding to [`nvim_buf_get_text()`][1].
369    ///
370    /// Gets a range from the buffer. This differs from `Buffer::get_lines` in
371    /// that it allows retrieving only portions of a line.
372    ///
373    /// Indexing is zero-based, with both row and column indices being
374    /// end-exclusive.
375    ///
376    /// [1]: https://neovim.io/doc/user/api.html#nvim_buf_get_text()
377    pub fn get_text<R>(
378        &self,
379        line_range: R,
380        start_col: usize,
381        end_col: usize,
382        opts: &GetTextOpts,
383    ) -> Result<impl SuperIterator<nvim::String>>
384    where
385        R: RangeBounds<usize>,
386    {
387        let mut err = nvim::Error::new();
388        let (start, end) = utils::range_to_limits(line_range);
389        let lines = unsafe {
390            nvim_buf_get_text(
391                LUA_INTERNAL_CALL,
392                self.0,
393                start,
394                start_col.try_into()?,
395                end,
396                end_col.try_into()?,
397                opts,
398                types::arena(),
399                // The nvim_buf_get_text() function returns no line if we use
400                // an actual lstate here
401                std::ptr::null_mut(),
402                &mut err,
403            )
404        };
405        choose!(
406            err,
407            Ok({
408                lines
409                    .into_iter()
410                    .map(|line| nvim::String::from_object(line).unwrap())
411            })
412        )
413    }
414
415    /// Binding to [`nvim_buf_get_var()`][1].
416    ///
417    /// Gets a buffer-scoped (`b:`) variable.
418    ///
419    /// [1]: https://neovim.io/doc/user/api.html#nvim_buf_get_var()
420    pub fn get_var<Var>(&self, name: &str) -> Result<Var>
421    where
422        Var: FromObject,
423    {
424        let mut err = nvim::Error::new();
425        let name = nvim::String::from(name);
426        let obj = unsafe {
427            nvim_buf_get_var(
428                self.0,
429                name.as_nvim_str(),
430                types::arena(),
431                &mut err,
432            )
433        };
434        choose!(err, Ok(Var::from_object(obj)?))
435    }
436
437    /// Binding to [`nvim_buf_is_loaded()`][1].
438    ///
439    /// Checks if a buffer is valid and loaded.
440    ///
441    /// [1]: https://neovim.io/doc/user/api.html#nvim_buf_is_loaded()
442    pub fn is_loaded(&self) -> bool {
443        unsafe { nvim_buf_is_loaded(self.0) }
444    }
445
446    /// Binding to [`nvim_buf_is_valid()`][1].
447    ///
448    /// Checks if a buffer is valid.
449    ///
450    /// [1]: https://neovim.io/doc/user/api.html#nvim_buf_is_valid()
451    pub fn is_valid(&self) -> bool {
452        unsafe { nvim_buf_is_valid(self.0) }
453    }
454
455    /// Binding to [`nvim_buf_line_count()`][1].
456    ///
457    /// Returns the number of lines in the given buffer.
458    ///
459    /// [1]: https://neovim.io/doc/user/api.html#nvim_buf_line_count()
460    pub fn line_count(&self) -> Result<usize> {
461        let mut err = nvim::Error::new();
462        let count = unsafe { nvim_buf_line_count(self.0, &mut err) };
463        choose!(err, Ok(count.try_into().expect("always positive")))
464    }
465
466    /// Binding to [`nvim_buf_set_keymap()`][1].
467    ///
468    /// Sets a buffer-local mapping for the given mode. To set a global mapping
469    /// use [`set_keymap`](crate::set_keymap) instead.
470    ///
471    /// [1]: https://neovim.io/doc/user/api.html#nvim_buf_set_keymap()
472    pub fn set_keymap(
473        &mut self,
474        mode: Mode,
475        lhs: &str,
476        rhs: &str,
477        opts: &SetKeymapOpts,
478    ) -> Result<()> {
479        let mode = nvim::String::from(mode);
480        let lhs = nvim::String::from(lhs);
481        let rhs = nvim::String::from(rhs);
482        let mut err = nvim::Error::new();
483        unsafe {
484            nvim_buf_set_keymap(
485                LUA_INTERNAL_CALL,
486                self.0,
487                mode.as_nvim_str(),
488                lhs.as_nvim_str(),
489                rhs.as_nvim_str(),
490                opts,
491                &mut err,
492            )
493        };
494        choose!(err, ())
495    }
496
497    /// Binding to [`nvim_buf_set_lines()`][1].
498    ///
499    /// Sets (replaces) a line-range in the buffer. Indexing is zero-based,
500    /// end-exclusive.
501    ///
502    /// [1]: https://neovim.io/doc/user/api.html#nvim_buf_set_lines()
503    pub fn set_lines<Line, Lines, R>(
504        &mut self,
505        line_range: R,
506        strict_indexing: bool,
507        replacement: Lines,
508    ) -> Result<()>
509    where
510        R: RangeBounds<usize>,
511        Lines: IntoIterator<Item = Line>,
512        Line: Into<nvim::String>,
513    {
514        let rpl = replacement.into_iter().map(Into::into).collect::<Array>();
515        let mut err = nvim::Error::new();
516        let (start, end) = utils::range_to_limits(line_range);
517        unsafe {
518            nvim_buf_set_lines(
519                LUA_INTERNAL_CALL,
520                self.0,
521                start,
522                end,
523                strict_indexing,
524                rpl.non_owning(),
525                types::arena(),
526                &mut err,
527            )
528        };
529        choose!(err, ())
530    }
531
532    /// Binding to [`nvim_buf_set_mark()`][1].
533    ///
534    /// Sets a named mark in the buffer. Marks are (1,0)-indexed, and passing 0
535    /// as `line` deletes the mark.
536    ///
537    /// [1]: https://neovim.io/doc/user/api.html#nvim_buf_set_mark()
538    pub fn set_mark(
539        &mut self,
540        name: char,
541        line: usize,
542        col: usize,
543        opts: &SetMarkOpts,
544    ) -> Result<()> {
545        let mut err = nvim::Error::new();
546        let name = nvim::String::from(name);
547        let mark_was_set = unsafe {
548            nvim_buf_set_mark(
549                self.0,
550                name.as_nvim_str(),
551                line.try_into()?,
552                col.try_into()?,
553                opts,
554                &mut err,
555            )
556        };
557        choose!(
558            err,
559            match mark_was_set {
560                true => Ok(()),
561                _ => Err(Error::custom("Couldn't set mark")),
562            }
563        )
564    }
565
566    /// Binding to [`nvim_buf_set_name()`][1].
567    ///
568    /// Sets the full file name for a buffer.
569    ///
570    /// [1]: https://neovim.io/doc/user/api.html#nvim_buf_set_name()
571    pub fn set_name<Name: AsRef<Path>>(&mut self, name: Name) -> Result<()> {
572        let name = nvim::String::from(name.as_ref());
573        let mut err = nvim::Error::new();
574        unsafe { nvim_buf_set_name(self.0, name.as_nvim_str(), &mut err) };
575        choose!(err, ())
576    }
577
578    /// Binding to [`nvim_buf_set_text()`][1].
579    ///
580    /// Sets (replaces) a range in the buffer. Indexing is zero-based, with
581    /// both row and column indices being end-exclusive.
582    ///
583    /// [1]: https://neovim.io/doc/user/api.html#nvim_buf_set_text()
584    pub fn set_text<Line, Lines, R>(
585        &mut self,
586        line_range: R,
587        start_col: usize,
588        end_col: usize,
589        replacement: Lines,
590    ) -> Result<()>
591    where
592        R: RangeBounds<usize>,
593        Lines: IntoIterator<Item = Line>,
594        Line: Into<nvim::String>,
595    {
596        let mut err = nvim::Error::new();
597        let (start, end) = utils::range_to_limits(line_range);
598        unsafe {
599            nvim_buf_set_text(
600                LUA_INTERNAL_CALL,
601                self.0,
602                start,
603                start_col.try_into()?,
604                end,
605                end_col.try_into()?,
606                replacement
607                    .into_iter()
608                    .map(|line| line.into())
609                    .collect::<Array>()
610                    .non_owning(),
611                types::arena(),
612                &mut err,
613            )
614        };
615        choose!(err, ())
616    }
617
618    /// Binding to [`nvim_buf_set_var()`][1].
619    ///
620    /// Sets a buffer-scoped (`b:`) variable.
621    ///
622    /// [1]: https://neovim.io/doc/user/api.html#nvim_buf_set_var()
623    pub fn set_var<V>(&mut self, name: &str, value: V) -> Result<()>
624    where
625        V: ToObject,
626    {
627        let mut err = nvim::Error::new();
628        let name = nvim::String::from(name);
629        unsafe {
630            nvim_buf_set_var(
631                self.0,
632                name.as_nvim_str(),
633                value.to_object()?.non_owning(),
634                &mut err,
635            )
636        };
637        choose!(err, ())
638    }
639}