nvim_api/
buffer.rs

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