Skip to main content

ytil_noxi/
window.rs

1//! Provides functions for Neovim window operations.
2
3use nvim_oxi::api::Buffer;
4use nvim_oxi::api::Window;
5use nvim_oxi::api::opts::OptionOptsBuilder;
6use nvim_oxi::conversion::FromObject;
7
8use crate::buffer::BufferExt;
9
10/// Sets the specified window as the current window in Neovim.
11///
12/// On failure, notifies Neovim of the error and returns `None`.
13///
14/// # Errors
15/// - Setting the current window fails.
16pub fn set_current(window: &Window) -> Option<()> {
17    nvim_oxi::api::set_current_win(window)
18        .inspect_err(|err| {
19            crate::notify::error(format!(
20                "error setting current window | window={window:?}, error={err:?}"
21            ));
22        })
23        .ok()?;
24    Some(())
25}
26
27/// Retrieves the buffer associated with the specified window.
28///
29/// On failure, notifies Neovim of the error and returns `None`.
30///
31/// # Errors
32/// - Retrieving the buffer fails.
33pub fn get_buffer(window: &Window) -> Option<Buffer> {
34    window
35        .get_buf()
36        .inspect_err(|err| {
37            crate::notify::error(format!(
38                "error getting window buffer | window={window:?}, error={err:?}"
39            ));
40        })
41        .ok()
42}
43
44pub fn find_with_buffer(buffer_type: &str) -> Option<(Window, Buffer)> {
45    nvim_oxi::api::list_wins().find_map(|win| {
46        if let Some(buffer) = get_buffer(&win)
47            && buffer.get_buf_type().is_some_and(|bt| bt == buffer_type)
48        {
49            Some((win, buffer))
50        } else {
51            None
52        }
53    })
54}
55
56/// Returns the first focusable floating window whose buffer filetype is in
57/// `allowed_filetypes`, if any.
58///
59/// This prevents notification floats (e.g. fidget) from capturing focus;
60/// only interactive floats (e.g. fzf-lua picker, filetype `"fzf"`) qualify.
61///
62/// Uses `call_function("nvim_win_get_config", ...)` returning a raw
63/// [`nvim_oxi::Dictionary`] instead of [`Window::get_config()`] to
64/// avoid full `WindowConfig` deserialization which fails when Neovim
65/// returns non-string `border` values.
66pub fn find_focusable_float(allowed_filetypes: &[&str]) -> Option<Window> {
67    for win in nvim_oxi::api::list_wins() {
68        if !is_floating(&win) {
69            continue;
70        }
71
72        let is_focusable =
73            nvim_oxi::api::call_function::<_, nvim_oxi::Dictionary>("nvim_win_get_config", (win.clone(),))
74                .ok()
75                .and_then(|cfg| cfg.get("focusable").cloned())
76                .and_then(|obj| bool::from_object(obj).ok())
77                .unwrap_or(true);
78
79        if !is_focusable {
80            continue;
81        }
82
83        let Some(buf) = get_buffer(&win) else {
84            continue;
85        };
86
87        let opts = OptionOptsBuilder::default().buf(buf).build();
88        let has_allowed_ft = nvim_oxi::api::get_option_value::<String>("filetype", &opts)
89            .ok()
90            .is_some_and(|ft| allowed_filetypes.contains(&ft.as_str()));
91
92        if has_allowed_ft {
93            return Some(win);
94        }
95    }
96
97    None
98}
99
100pub fn is_floating(window: &Window) -> bool {
101    nvim_oxi::api::call_function::<_, nvim_oxi::Dictionary>("nvim_win_get_config", (window.clone(),))
102        .ok()
103        .and_then(|cfg| cfg.get("relative").cloned())
104        .and_then(|obj| String::from_object(obj).ok())
105        .is_some_and(|s| !s.is_empty())
106}
107
108pub fn get_number(win: &Window) -> Option<u32> {
109    win.get_number()
110        .inspect_err(|err| crate::notify::error(format!("error getting window number | window={win:?} error={err:?}")))
111        .ok()
112}