nvrim/
layout.rs

1use nvim_oxi::Dictionary;
2use nvim_oxi::api::Buffer;
3use nvim_oxi::api::opts::CreateAutocmdOpts;
4use ytil_noxi::buffer::BufferExt;
5use ytil_noxi::mru_buffers::BufferKind;
6
7/// [`Dictionary`] of Rust tests utilities.
8pub fn dict() -> Dictionary {
9    dict! {
10        "focus_term": fn_from!(focus_term),
11        "focus_buffer": fn_from!(focus_buffer),
12        "smart_close_buffer": fn_from!(smart_close_buffer),
13        "toggle_alternate_buffer": fn_from!(toggle_alternate_buffer),
14    }
15}
16
17pub fn create_autocmd() {
18    crate::cmds::create_autocmd(
19        ["BufEnter", "WinEnter", "TermOpen"],
20        "TerminalAutoInsertMode",
21        CreateAutocmdOpts::builder()
22            .patterns(["term://*"])
23            .command("startinsert"),
24    );
25}
26
27fn focus_term(width_perc: i32) -> Option<()> {
28    let current_buffer = nvim_oxi::api::get_current_buf();
29
30    // If current buffer IS terminal.
31    if current_buffer.is_terminal() {
32        ytil_noxi::common::exec_vim_script("only", None)?;
33        return Some(());
34    }
35
36    // If current buffer IS NOT terminal.
37    let maybe_terminal_window = ytil_noxi::window::find_with_buffer("terminal");
38
39    // If there is a VISIBLE terminal buffer.
40    if let Some((win, _)) = maybe_terminal_window {
41        ytil_noxi::window::set_current(&win)?;
42        return Some(());
43    }
44
45    let width = compute_width(width_perc)?;
46
47    // If there is NO VISIBLE terminal buffer.
48    if let Some(terminal_buffer) = nvim_oxi::api::list_bufs().find(BufferExt::is_terminal) {
49        ytil_noxi::common::exec_vim_script(&format!("leftabove vsplit | vertical resize {width}"), None);
50        ytil_noxi::buffer::set_current(&terminal_buffer)?;
51        return Some(());
52    }
53
54    // If there is NO terminal buffer at all.
55    ytil_noxi::common::exec_vim_script(&format!("leftabove vsplit | vertical resize {width} | term"), None);
56
57    Some(())
58}
59
60fn focus_buffer(width_perc: i32) -> Option<()> {
61    let current_buffer = nvim_oxi::api::get_current_buf();
62
63    // If current buffer IS NOT terminal.
64    if !current_buffer.is_terminal() {
65        ytil_noxi::common::exec_vim_script("only", None)?;
66        return Some(());
67    }
68
69    // If current buffer IS terminal.
70    let maybe_buffer_window = ytil_noxi::window::find_with_buffer("");
71
72    // If there is a visible file buffer.
73    if let Some((win, _)) = maybe_buffer_window {
74        ytil_noxi::window::set_current(&win)?;
75        return Some(());
76    }
77
78    // If there is NO visible file buffer.
79    let width = compute_width(width_perc)?;
80
81    // Using ytil_noxi::common::exec2 because nvim_oxi::api::open_win fails with split left.
82    ytil_noxi::common::exec_vim_script(&format!("vsplit | vertical resize {width}"), None)?;
83
84    let buffer = if let Some(mru_buffer) = ytil_noxi::mru_buffers::get()?
85        .iter()
86        .find(|b| matches!(b.kind, BufferKind::Path | BufferKind::NoName))
87    {
88        Buffer::from(mru_buffer)
89    } else {
90        ytil_noxi::buffer::create()?
91    };
92
93    ytil_noxi::buffer::set_current(&buffer)?;
94
95    Some(())
96}
97
98fn toggle_alternate_buffer(_: ()) -> Option<()> {
99    let alt_buf_id = nvim_oxi::api::call_function::<_, i32>("bufnr", ("#",))
100        .inspect_err(|err| ytil_noxi::notify::error(format!("error getting alternate buffer | error={err:?}")))
101        .ok()?;
102
103    if alt_buf_id != -1
104        && let alt_buf = Buffer::from(alt_buf_id)
105        && alt_buf.is_loaded()
106        && !alt_buf.is_terminal()
107    {
108        ytil_noxi::buffer::set_current(&alt_buf)?;
109        return Some(());
110    }
111
112    let current_buf = Buffer::current();
113    for buf in nvim_oxi::api::list_bufs().rev() {
114        if buf != current_buf
115            && buf.is_loaded()
116            && !buf.is_terminal()
117            && buf.get_buf_type().is_some_and(|bt| bt.is_empty())
118            && buf
119                .get_name()
120                .inspect_err(|err| {
121                    ytil_noxi::notify::error(format!("error getting buffer name | buffer={buf:?} error={err:?}"));
122                })
123                .ok()
124                .is_some_and(|bn| !bn.is_empty())
125        {
126            ytil_noxi::buffer::set_current(&buf)?;
127            return Some(());
128        }
129    }
130
131    Some(())
132}
133
134fn smart_close_buffer(force_close: Option<bool>) -> Option<()> {
135    let mru_buffers = ytil_noxi::mru_buffers::get()?;
136
137    let Some(current_buffer) = mru_buffers.first() else {
138        return Some(());
139    };
140
141    let force = if force_close.is_some_and(std::convert::identity) {
142        "!"
143    } else {
144        ""
145    };
146
147    match current_buffer.kind {
148        BufferKind::Term | BufferKind::NoName => return Some(()),
149        BufferKind::GrugFar => {}
150        BufferKind::Path => {
151            let new_current_buffer = if let Some(mru_buffer) = mru_buffers.get(1)
152                && !matches!(mru_buffer.kind, BufferKind::Term)
153            {
154                Buffer::from(mru_buffer.id)
155            } else {
156                ytil_noxi::buffer::create()?
157            };
158
159            ytil_noxi::buffer::set_current(&new_current_buffer)?;
160        }
161    }
162
163    ytil_noxi::common::exec_vim_script(&format!("bd{force} {}", current_buffer.id), Option::default())?;
164
165    Some(())
166}
167
168pub fn compute_width(perc: i32) -> Option<i32> {
169    let total_width: i32 = crate::vim_opts::get("columns", &crate::vim_opts::global_scope())?;
170    Some((total_width.saturating_mul(perc)) / 100)
171}