Skip to main content

reovim_module_bufferline/
commands.rs

1//! Command handlers for the bufferline module.
2
3use {
4    reovim_driver_command::{Command, CommandHandler, CommandResult},
5    reovim_driver_command_types::CommandContext,
6    reovim_driver_session::{BufferApi, ExtensionApi, SessionRuntime},
7    reovim_kernel::api::v1::CommandId,
8};
9
10use crate::{ids, state::BufferlineState};
11
12// ============================================================================
13// PinBuffer — toggle pin for active buffer
14// ============================================================================
15
16/// Toggle pin state for the active buffer.
17#[derive(Debug, Clone, Copy, Default)]
18pub struct PinBuffer;
19
20impl Command for PinBuffer {
21    fn id(&self) -> CommandId {
22        ids::PIN_BUFFER
23    }
24
25    fn description(&self) -> &'static str {
26        "Toggle pin for active buffer"
27    }
28}
29
30impl CommandHandler for PinBuffer {
31    #[cfg_attr(coverage_nightly, coverage(off))]
32    fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
33        let Some(buf_id) = runtime.active_buffer() else {
34            return CommandResult::Success;
35        };
36        let id = buf_id.as_usize() as u64;
37
38        let Some(state) = runtime.shared_ext_mut::<BufferlineState>() else {
39            return CommandResult::Success;
40        };
41
42        if state.is_pinned(id) {
43            state.unpin(id);
44        } else {
45            state.pin(id);
46        }
47
48        CommandResult::Success
49    }
50}
51
52// ============================================================================
53// UnpinBuffer — explicit unpin
54// ============================================================================
55
56/// Explicitly unpin the active buffer.
57#[derive(Debug, Clone, Copy, Default)]
58pub struct UnpinBuffer;
59
60impl Command for UnpinBuffer {
61    fn id(&self) -> CommandId {
62        ids::UNPIN_BUFFER
63    }
64
65    fn description(&self) -> &'static str {
66        "Unpin active buffer"
67    }
68}
69
70impl CommandHandler for UnpinBuffer {
71    #[cfg_attr(coverage_nightly, coverage(off))]
72    fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
73        let Some(buf_id) = runtime.active_buffer() else {
74            return CommandResult::Success;
75        };
76        let id = buf_id.as_usize() as u64;
77
78        if let Some(state) = runtime.shared_ext_mut::<BufferlineState>() {
79            state.unpin(id);
80        }
81
82        CommandResult::Success
83    }
84}
85
86// ============================================================================
87// CloseBuffer — close active buffer and remove from pins
88// ============================================================================
89
90/// Close the active buffer.
91#[derive(Debug, Clone, Copy, Default)]
92pub struct CloseBuffer;
93
94impl Command for CloseBuffer {
95    fn id(&self) -> CommandId {
96        ids::CLOSE_BUFFER
97    }
98
99    fn description(&self) -> &'static str {
100        "Close active buffer"
101    }
102}
103
104impl CommandHandler for CloseBuffer {
105    #[cfg_attr(coverage_nightly, coverage(off))]
106    fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
107        let Some(buf_id) = runtime.active_buffer() else {
108            return CommandResult::Success;
109        };
110        let id = buf_id.as_usize() as u64;
111
112        // Remove from pin list if pinned.
113        if let Some(state) = runtime.shared_ext_mut::<BufferlineState>() {
114            state.unpin(id);
115        }
116
117        // Delegate to kernel buffer close (deletes the buffer).
118        let _ = runtime.kernel().buffers.unregister(buf_id);
119
120        CommandResult::Success
121    }
122}
123
124// ============================================================================
125// NextBuffer — switch to next buffer in buffer list
126// ============================================================================
127
128/// Switch to the next buffer.
129#[derive(Debug, Clone, Copy, Default)]
130pub struct NextBuffer;
131
132impl Command for NextBuffer {
133    fn id(&self) -> CommandId {
134        ids::NEXT_BUFFER
135    }
136
137    fn description(&self) -> &'static str {
138        "Switch to next buffer"
139    }
140}
141
142impl CommandHandler for NextBuffer {
143    #[cfg_attr(coverage_nightly, coverage(off))]
144    fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
145        cycle_buffer(runtime, &Direction::Next)
146    }
147}
148
149// ============================================================================
150// PrevBuffer — switch to previous buffer in buffer list
151// ============================================================================
152
153/// Switch to the previous buffer.
154#[derive(Debug, Clone, Copy, Default)]
155pub struct PrevBuffer;
156
157impl Command for PrevBuffer {
158    fn id(&self) -> CommandId {
159        ids::PREV_BUFFER
160    }
161
162    fn description(&self) -> &'static str {
163        "Switch to previous buffer"
164    }
165}
166
167impl CommandHandler for PrevBuffer {
168    #[cfg_attr(coverage_nightly, coverage(off))]
169    fn execute(&self, runtime: &mut SessionRuntime<'_>, _args: &CommandContext) -> CommandResult {
170        cycle_buffer(runtime, &Direction::Prev)
171    }
172}
173
174// ============================================================================
175// Shared buffer cycling logic
176// ============================================================================
177
178enum Direction {
179    Next,
180    Prev,
181}
182
183#[cfg_attr(coverage_nightly, coverage(off))]
184fn cycle_buffer(runtime: &mut SessionRuntime<'_>, direction: &Direction) -> CommandResult {
185    let Some(current) = runtime.active_buffer() else {
186        return CommandResult::Success;
187    };
188
189    let mut buf_ids = runtime.kernel().buffers.list();
190    if buf_ids.len() < 2 {
191        return CommandResult::Success;
192    }
193    buf_ids.sort_unstable();
194
195    let current_idx = buf_ids.iter().position(|&id| id == current).unwrap_or(0);
196
197    let next_idx = match direction {
198        Direction::Next => (current_idx + 1) % buf_ids.len(),
199        Direction::Prev => {
200            if current_idx == 0 {
201                buf_ids.len() - 1
202            } else {
203                current_idx - 1
204            }
205        }
206    };
207
208    runtime.set_active_buffer(Some(buf_ids[next_idx]));
209    CommandResult::Success
210}
211
212// ============================================================================
213// Factory
214// ============================================================================
215
216/// Create all command handlers for the bufferline module.
217#[must_use]
218pub fn command_handlers() -> Vec<Box<dyn CommandHandler>> {
219    vec![
220        Box::new(PinBuffer),
221        Box::new(UnpinBuffer),
222        Box::new(CloseBuffer),
223        Box::new(NextBuffer),
224        Box::new(PrevBuffer),
225    ]
226}
227
228#[cfg(test)]
229#[path = "commands_tests.rs"]
230mod tests;