nvim_oxi_api/
window.rs

1use std::error::Error as StdError;
2use std::fmt;
3use std::result::Result as StdResult;
4
5use luajit::{self as lua, Poppable, Pushable};
6use serde::{Deserialize, Serialize};
7use types::{
8    self as nvim,
9    Array,
10    Function,
11    Integer,
12    Object,
13    WinHandle,
14    conversion::{self, FromObject, ToObject},
15};
16
17use crate::Result;
18use crate::choose;
19use crate::ffi::window::*;
20use crate::opts::WinTextHeightOpts;
21use crate::types::WinTextHeightInfos;
22use crate::{Buffer, IntoResult, TabPage};
23
24/// A wrapper around a Neovim window handle.
25#[derive(Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
26pub struct Window(pub(crate) WinHandle);
27
28impl fmt::Debug for Window {
29    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
30        f.debug_tuple("Window").field(&self.0).finish()
31    }
32}
33
34impl fmt::Display for Window {
35    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
36        fmt::Debug::fmt(self, f)
37    }
38}
39
40impl<H: Into<WinHandle>> From<H> for Window {
41    fn from(handle: H) -> Self {
42        Self(handle.into())
43    }
44}
45
46impl From<Window> for Object {
47    fn from(win: Window) -> Self {
48        win.0.into()
49    }
50}
51
52impl From<&Window> for Object {
53    fn from(win: &Window) -> Self {
54        win.0.into()
55    }
56}
57
58impl FromObject for Window {
59    fn from_object(obj: Object) -> StdResult<Self, conversion::Error> {
60        Ok(WinHandle::from_object(obj)?.into())
61    }
62}
63
64impl Poppable for Window {
65    unsafe fn pop(
66        lstate: *mut lua::ffi::State,
67    ) -> std::result::Result<Self, lua::Error> {
68        WinHandle::pop(lstate).map(Into::into)
69    }
70}
71
72impl Pushable for Window {
73    unsafe fn push(
74        self,
75        lstate: *mut lua::ffi::State,
76    ) -> std::result::Result<std::ffi::c_int, lua::Error> {
77        self.0.push(lstate)
78    }
79}
80
81impl Window {
82    /// Shorthand for [`get_current_win`](crate::get_current_win).
83    #[inline(always)]
84    pub fn current() -> Self {
85        crate::get_current_win()
86    }
87
88    /// Retrieve window's underlying id/handle
89    #[inline(always)]
90    pub fn handle(&self) -> i32 {
91        self.0
92    }
93
94    /// Binding to [`nvim_win_call()`][1].
95    ///
96    /// Calls a function with this window as the temporary current window.
97    ///
98    /// [1]: https://neovim.io/doc/user/api.html#nvim_win_call()
99    pub fn call<F, Res, Ret>(&self, fun: F) -> Result<Ret>
100    where
101        F: FnOnce(()) -> Res + 'static,
102        Res: IntoResult<Ret>,
103        Res::Error: StdError + 'static,
104        Ret: Pushable + FromObject,
105    {
106        let fun = Function::from_fn_once(fun);
107        let mut err = nvim::Error::new();
108
109        let ref_or_nil =
110            unsafe { nvim_win_call(self.0, fun.lua_ref(), &mut err) };
111
112        let lua_ref = match ref_or_nil.kind() {
113            types::ObjectKind::LuaRef => unsafe {
114                ref_or_nil.as_luaref_unchecked()
115            },
116            types::ObjectKind::Nil => {
117                return Ret::from_object(Object::nil()).map_err(Into::into);
118            },
119            other => panic!("Unexpected object kind: {other:?}"),
120        };
121
122        let obj = unsafe {
123            lua::with_state(|lstate| {
124                lua::ffi::lua_rawgeti(
125                    lstate,
126                    lua::ffi::LUA_REGISTRYINDEX,
127                    lua_ref,
128                );
129                Object::pop(lstate)
130            })
131        }
132        .map_err(crate::Error::custom)?;
133
134        choose!(err, {
135            fun.remove_from_lua_registry();
136            Ok(Ret::from_object(obj)?)
137        })
138    }
139
140    /// Binding to [`nvim_win_close()`][1].
141    ///
142    /// Closes the window. Not allowed when
143    /// [`textlock`](https://neovim.io/doc/user/eval.html#textlock) is active.
144    ///
145    /// [1]: https://neovim.io/doc/user/api.html#nvim_win_close()
146    pub fn close(self, force: bool) -> Result<()> {
147        let mut err = nvim::Error::new();
148        unsafe { nvim_win_close(self.0, force, &mut err) };
149        choose!(err, ())
150    }
151
152    /// Binding to [`nvim_win_del_var()`][1].
153    ///
154    /// Removes a window-scoped (`w:`) variable.
155    ///
156    /// [1]: https://neovim.io/doc/user/api.html#nvim_win_del_var()
157    pub fn del_var(&mut self, name: &str) -> Result<()> {
158        let mut err = nvim::Error::new();
159        let name = nvim::String::from(name);
160        unsafe { nvim_win_del_var(self.0, name.as_nvim_str(), &mut err) };
161        choose!(err, ())
162    }
163
164    /// Binding to [`nvim_win_get_buf()`][1].
165    ///
166    /// Gets the current [`Buffer`] in the window.
167    ///
168    /// [1]: https://neovim.io/doc/user/api.html#nvim_win_get_buf()
169    pub fn get_buf(&self) -> Result<Buffer> {
170        let mut err = nvim::Error::new();
171        let handle = unsafe { nvim_win_get_buf(self.0, &mut err) };
172        choose!(err, Ok(handle.into()))
173    }
174
175    /// Binding to [`nvim_win_get_cursor()`][1].
176    ///
177    /// Gets the (1,0)-indexed cursor position in the window.
178    ///
179    /// [1]: https://neovim.io/doc/user/api.html#nvim_win_get_cursor()
180    pub fn get_cursor(&self) -> Result<(usize, usize)> {
181        let mut err = nvim::Error::new();
182        let arr =
183            unsafe { nvim_win_get_cursor(self.0, types::arena(), &mut err) };
184        choose!(err, {
185            let mut iter = arr.into_iter();
186            let line = usize::from_object(iter.next().unwrap())?;
187            let col = usize::from_object(iter.next().unwrap())?;
188            Ok((line, col))
189        })
190    }
191
192    /// Binding to [`nvim_win_get_height()`][1].
193    ///
194    /// Gets the window height as a count of rows.
195    ///
196    /// [1]: https://neovim.io/doc/user/api.html#nvim_win_get_height()
197    pub fn get_height(&self) -> Result<u32> {
198        let mut err = nvim::Error::new();
199        let height = unsafe { nvim_win_get_height(self.0, &mut err) };
200        choose!(err, Ok(height.try_into().expect("always positive")))
201    }
202
203    /// Binding to [`nvim_win_get_number()`][1].
204    ///
205    /// Gets the window number.
206    ///
207    /// [1]: https://neovim.io/doc/user/api.html#nvim_win_get_number()
208    pub fn get_number(&self) -> Result<u32> {
209        let mut err = nvim::Error::new();
210        let nr = unsafe { nvim_win_get_number(self.0, &mut err) };
211        choose!(err, Ok(nr.try_into().expect("always positive")))
212    }
213
214    /// Binding to [`nvim_win_get_position()`][1].
215    ///
216    /// Gets the window position in display cells.
217    ///
218    /// [1]: https://neovim.io/doc/user/api.html#nvim_win_get_position()
219    pub fn get_position(&self) -> Result<(usize, usize)> {
220        let mut err = nvim::Error::new();
221        let arr =
222            unsafe { nvim_win_get_position(self.0, types::arena(), &mut err) };
223        choose!(err, {
224            let mut iter = arr.into_iter();
225            let line = usize::from_object(iter.next().unwrap())?;
226            let col = usize::from_object(iter.next().unwrap())?;
227            Ok((line, col))
228        })
229    }
230
231    /// Binding to [`nvim_win_get_tabpage()`][1].
232    ///
233    /// Gets the window's `TabPage`.
234    ///
235    /// [1]: https://neovim.io/doc/user/api.html#nvim_win_get_tabpage()
236    pub fn get_tabpage(&self) -> Result<TabPage> {
237        let mut err = nvim::Error::new();
238        let handle = unsafe { nvim_win_get_tabpage(self.0, &mut err) };
239        choose!(err, Ok(handle.into()))
240    }
241
242    /// Binding to [`nvim_win_get_var()`][1].
243    ///
244    /// Gets a window-scoped (`w:`) variable.
245    ///
246    /// [1]: https://neovim.io/doc/user/api.html#nvim_win_get_var()
247    pub fn get_var<Var>(&self, name: &str) -> Result<Var>
248    where
249        Var: FromObject,
250    {
251        let mut err = nvim::Error::new();
252        let name = nvim::String::from(name);
253        let obj = unsafe {
254            nvim_win_get_var(
255                self.0,
256                name.as_nvim_str(),
257                types::arena(),
258                &mut err,
259            )
260        };
261        choose!(err, Ok(Var::from_object(obj)?))
262    }
263
264    /// Binding to [`nvim_win_get_width()`][1].
265    ///
266    /// Gets the window width as a number of columns.
267    ///
268    /// [1]: https://neovim.io/doc/user/api.html#nvim_win_get_width()
269    pub fn get_width(&self) -> Result<u32> {
270        let mut err = nvim::Error::new();
271        let width = unsafe { nvim_win_get_width(self.0, &mut err) };
272        choose!(err, Ok(width.try_into().expect("always positive")))
273    }
274
275    /// Binding to [`nvim_win_hide()`][1].
276    ///
277    /// Closes the window and hides the buffer it contains.
278    ///
279    /// [1]: https://neovim.io/doc/user/api.html#nvim_win_hide()
280    pub fn hide(self) -> Result<()> {
281        let mut err = nvim::Error::new();
282        unsafe { nvim_win_hide(self.0, &mut err) };
283        choose!(err, ())
284    }
285
286    /// Binding to [`nvim_win_is_valid()`][1].
287    ///
288    /// Checks if the window is valid.
289    ///
290    /// [1]: https://neovim.io/doc/user/api.html#nvim_win_is_valid()
291    pub fn is_valid(&self) -> bool {
292        unsafe { nvim_win_is_valid(self.0) }
293    }
294
295    /// Binding to [`nvim_win_set_buf()`][1].
296    ///
297    /// Sets `buffer` as the current buffer in the window.
298    ///
299    /// [1]: https://neovim.io/doc/user/api.html#nvim_win_set_buf()
300    pub fn set_buf(&mut self, buffer: &Buffer) -> Result<()> {
301        let mut err = nvim::Error::new();
302        unsafe { nvim_win_set_buf(self.0, buffer.0, &mut err) };
303        choose!(err, ())
304    }
305
306    /// Binding to [`nvim_win_set_cursor()`][1].
307    ///
308    /// Sets the (1,0)-indexed cursor in the window. This will scroll the
309    /// window even if it's not the current one.
310    ///
311    /// [1]: https://neovim.io/doc/user/api.html#nvim_win_set_cursor()
312    pub fn set_cursor(&mut self, line: usize, col: usize) -> Result<()> {
313        let mut err = nvim::Error::new();
314        let pos = Array::from_iter([line as Integer, col as Integer]);
315        unsafe { nvim_win_set_cursor(self.0, pos.non_owning(), &mut err) };
316        choose!(err, ())
317    }
318
319    /// Binding to [`nvim_win_set_height()`][1].
320    ///
321    /// Sets the window height.
322    ///
323    /// [1]: https://neovim.io/doc/user/api.html#nvim_win_set_height()
324    pub fn set_height(&mut self, height: u32) -> Result<()> {
325        let mut err = nvim::Error::new();
326        unsafe { nvim_win_set_height(self.0, height.into(), &mut err) };
327        choose!(err, ())
328    }
329
330    /// Binding to [`nvim_win_set_hl_ns()`][1].
331    ///
332    /// Sets the highlight namespace for this window. This will the highlights
333    /// defined with [`set_hl`](crate::set_hl) for the given namespace, but
334    /// fall back to global highlights (`ns_id = 0`) if those are missing.
335    ///
336    /// This takes precedence over the `winhighlight` option.
337    ///
338    /// [1]: https://neovim.io/doc/user/api.html#nvim_win_set_hl_ns()
339    pub fn set_hl_ns(&mut self, ns_id: u32) -> Result<()> {
340        let mut err = nvim::Error::new();
341        unsafe { nvim_win_set_hl_ns(self.0, ns_id.into(), &mut err) };
342        choose!(err, ())
343    }
344
345    /// Binding to [`nvim_win_set_var()`][1].
346    ///
347    /// Sets a window-scoped (`w:`) variable.
348    ///
349    /// [1]: https://neovim.io/doc/user/api.html#nvim_win_set_var()
350    pub fn set_var<Var>(&mut self, name: &str, value: Var) -> Result<()>
351    where
352        Var: ToObject,
353    {
354        let mut err = nvim::Error::new();
355        let name = nvim::String::from(name);
356        unsafe {
357            nvim_win_set_var(
358                self.0,
359                name.as_nvim_str(),
360                value.to_object()?.non_owning(),
361                &mut err,
362            )
363        };
364        choose!(err, ())
365    }
366
367    /// Binding to [`nvim_win_set_width()`][1].
368    ///
369    /// Sets the window width.
370    ///
371    /// [1]: https://neovim.io/doc/user/api.html#nvim_win_set_width()
372    pub fn set_width(&mut self, width: u32) -> Result<()> {
373        let mut err = nvim::Error::new();
374        unsafe { nvim_win_set_width(self.0, width.into(), &mut err) };
375        choose!(err, ())
376    }
377
378    /// Binding to [`nvim_win_text_height()`][1].
379    ///
380    /// Computes the number of screen lines occupied by a range of text in a
381    /// given window. Works for off-screen text and takes folds into account.
382    ///
383    /// [1]: https://neovim.io/doc/user/api.html#nvim_win_text_height()
384    pub fn text_height(
385        &self,
386        opts: &WinTextHeightOpts,
387    ) -> Result<WinTextHeightInfos> {
388        let mut err = nvim::Error::new();
389        let dict = unsafe {
390            nvim_win_text_height(self.0, opts, types::arena(), &mut err)
391        };
392        choose!(err, dict.try_into().map_err(Into::into))
393    }
394}