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