1use nvim_oxi::Dictionary;
2use nvim_oxi::api::Buffer;
3use nvim_oxi::api::opts::CreateAutocmdOpts;
4use serde::Deserialize;
5use ytil_noxi::mru_buffers::BufferKind;
6
7pub fn dict() -> Dictionary {
9 dict! {
10 "focus_vsplit": fn_from!(focus_vsplit),
11 "smart_close_buffer": fn_from!(smart_close_buffer),
12 "toggle_alternate_buffer": fn_from!(toggle_alternate_buffer),
13 }
14}
15
16pub fn create_autocmd() {
17 crate::cmds::create_autocmd(
18 ["BufEnter", "WinEnter", "TermOpen"],
19 "TerminalAutoInsertMode",
20 CreateAutocmdOpts::builder()
21 .patterns(["term://*"])
22 .command("startinsert"),
23 );
24}
25
26#[derive(Clone, Copy, Deserialize)]
27#[serde(rename_all(deserialize = "snake_case"))]
28pub enum SplitKind {
29 Term,
30 Buffer,
31}
32
33impl SplitKind {
34 pub const fn is_term(self) -> bool {
35 match self {
36 Self::Term => true,
37 Self::Buffer => false,
38 }
39 }
40}
41
42ytil_noxi::impl_nvim_deserializable!(SplitKind);
43
44fn focus_vsplit((split_kind, width_perc): (SplitKind, i32)) -> Option<()> {
45 let mru_bufs = ytil_noxi::mru_buffers::get().unwrap_or_default();
47 let term_bufs: Vec<Buffer> = mru_bufs.iter().filter(|b| b.is_term()).map(Buffer::from).collect();
48
49 let current_buf = nvim_oxi::api::get_current_buf();
50
51 let is_term = split_kind.is_term();
52
53 if (is_term && term_bufs.contains(¤t_buf)) || (!is_term && !term_bufs.contains(¤t_buf)) {
55 ytil_noxi::common::exec_vim_script("only", None)?;
56 return Some(());
57 }
58
59 if !is_term && let Some(float_win) = ytil_noxi::window::find_focusable_float(&["fzf"]) {
61 ytil_noxi::window::set_current(&float_win)?;
62 return Some(());
63 }
64
65 if let Some(win) = nvim_oxi::api::list_wins().find(|win| {
68 !ytil_noxi::window::is_floating(win)
69 && ytil_noxi::window::get_buffer(win)
70 .is_some_and(|buf| (is_term && term_bufs.contains(&buf)) || (!is_term && !term_bufs.contains(&buf)))
71 }) {
72 ytil_noxi::window::set_current(&win)?;
73 return Some(());
74 }
75
76 let width = compute_width(width_perc)?;
77 let (leftabove, split_new_cmd) = if is_term { ("leftabove ", "term") } else { ("", "enew") };
78
79 if let Some(mru_buf) = mru_bufs
81 .iter()
82 .find(|b| (is_term && b.is_term()) || (!is_term && matches!(b.kind, BufferKind::Path | BufferKind::NoName)))
83 {
84 ytil_noxi::common::exec_vim_script(
85 &format!("{leftabove}vsplit | vertical resize {width} | buffer {}", mru_buf.id),
86 None,
87 );
88 return Some(());
89 }
90
91 ytil_noxi::common::exec_vim_script(
93 &format!("{leftabove}vsplit | vertical resize {width} | {split_new_cmd}"),
94 None,
95 );
96
97 Some(())
98}
99
100fn toggle_alternate_buffer(_: ()) -> Option<()> {
101 let mru_bufs = ytil_noxi::mru_buffers::get().unwrap_or_default();
104
105 if let Some(target) = mru_bufs.iter().skip(1).find(|b| matches!(b.kind, BufferKind::Path)) {
106 ytil_noxi::buffer::set_current(&Buffer::from(target))?;
107 }
108
109 Some(())
110}
111
112fn smart_close_buffer(force_close: Option<bool>) -> Option<()> {
113 let mru_buffers = ytil_noxi::mru_buffers::get()?;
114
115 let Some(current_buffer) = mru_buffers.first() else {
116 return Some(());
117 };
118
119 let force = if force_close.is_some_and(std::convert::identity) {
120 "!"
121 } else {
122 ""
123 };
124
125 match current_buffer.kind {
126 BufferKind::Term | BufferKind::NoName => return Some(()),
127 BufferKind::GrugFar => {}
128 BufferKind::Path => {
129 let new_current_buffer = if let Some(mru_buffer) = mru_buffers.get(1)
130 && matches!(mru_buffer.kind, BufferKind::Path | BufferKind::NoName)
131 {
132 Buffer::from(mru_buffer.id)
133 } else {
134 ytil_noxi::buffer::create()?
135 };
136
137 ytil_noxi::buffer::set_current(&new_current_buffer)?;
138 }
139 }
140
141 ytil_noxi::common::exec_vim_script(&format!("bd{force} {}", current_buffer.id), Option::default())?;
142
143 Some(())
144}
145
146pub fn compute_width(perc: i32) -> Option<i32> {
147 let total_width: i32 = crate::vim_opts::get("columns", &crate::vim_opts::global_scope())?;
148 Some((total_width.saturating_mul(perc)) / 100)
149}