reovim_core/command/traits.rs
1//! Core command trait definitions for extensible command system
2
3use {
4 crate::{
5 buffer::Buffer,
6 event_bus::DynEvent,
7 modd::ModeState,
8 screen::{NavigateDirection, SplitDirection},
9 },
10 std::{any::Any, fmt::Debug},
11};
12
13/// Execution context passed to commands
14pub struct ExecutionContext<'a> {
15 /// The buffer to operate on
16 pub buffer: &'a mut Buffer,
17 /// Repeat count (e.g., 5j moves down 5 lines)
18 pub count: Option<usize>,
19 /// ID of the buffer being operated on
20 pub buffer_id: usize,
21 /// ID of the window containing the buffer
22 pub window_id: usize,
23}
24
25/// Result of command execution
26#[derive(Debug)]
27pub enum CommandResult {
28 /// Command succeeded, no further action needed
29 Success,
30 /// Command succeeded, screen needs re-render
31 NeedsRender,
32 /// Command triggers a mode change
33 ModeChange(ModeState),
34 /// Editor should quit
35 Quit,
36 /// Command produced text for clipboard (e.g., yank, delete)
37 /// `register` is the target register: None for unnamed, Some('a'-'z') for named, '+' for system
38 /// `mode_change` optionally specifies a mode to transition to after writing to clipboard
39 /// `yank_type` specifies whether this is a linewise or characterwise yank (defaults to characterwise if None)
40 /// `yank_range` optionally specifies the visual range to animate (start, end positions)
41 ClipboardWrite {
42 text: String,
43 register: Option<char>,
44 mode_change: Option<ModeState>,
45 yank_type: Option<crate::register::YankType>,
46 yank_range: Option<(crate::screen::Position, crate::screen::Position)>,
47 },
48 /// Command needs Runtime access (deferred execution)
49 ///
50 /// DEPRECATED: Use `Deferred(Box<dyn DeferredActionHandler>)` for new code.
51 /// This variant is kept for backward compatibility during plugin migration.
52 DeferToRuntime(DeferredAction),
53 /// Command needs Runtime access via trait-based handler
54 ///
55 /// This is the preferred way for plugins to defer actions to the runtime.
56 /// The handler receives a `RuntimeContext` and can perform any runtime operation.
57 Deferred(Box<dyn DeferredActionHandler>),
58 /// Command emits an event to the event bus
59 EmitEvent(DynEvent),
60 /// Command failed with error message
61 Error(String),
62}
63
64/// Trait for deferred action handlers
65///
66/// This trait allows plugins to define custom actions that require
67/// runtime access. Instead of adding variants to `DeferredAction`,
68/// plugins implement this trait.
69///
70/// # Example
71///
72/// ```ignore
73/// struct MyPluginAction { /* ... */ }
74///
75/// impl DeferredActionHandler for MyPluginAction {
76/// fn handle(&self, ctx: &mut dyn RuntimeContext) {
77/// // Access runtime via context
78/// ctx.with_state_mut::<MyState, _, _>(|state| {
79/// // Modify state
80/// });
81/// }
82///
83/// fn name(&self) -> &'static str {
84/// "my_plugin_action"
85/// }
86/// }
87/// ```
88pub trait DeferredActionHandler: Send + Sync {
89 /// Execute the deferred action with runtime context
90 fn handle(&self, ctx: &mut dyn crate::runtime::RuntimeContext);
91
92 /// Name of this action for debugging
93 fn name(&self) -> &'static str;
94}
95
96impl std::fmt::Debug for dyn DeferredActionHandler {
97 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98 write!(f, "DeferredActionHandler({})", self.name())
99 }
100}
101
102/// Actions that require Runtime-level access
103///
104/// For new features, prefer using:
105/// - `CommandResult::Deferred(Box<dyn DeferredActionHandler>)` for complex handlers
106/// - `CommandResult::EmitEvent(DynEvent)` for event-driven features
107#[derive(Debug)]
108pub enum DeferredAction {
109 Paste {
110 before: bool,
111 register: Option<char>,
112 },
113 CommandLine(CommandLineAction),
114 JumpOlder,
115 JumpNewer,
116 OperatorMotion(OperatorMotionAction),
117 Window(WindowAction),
118 Tab(TabAction),
119 Buffer(BufferAction),
120 File(FileAction),
121}
122
123#[derive(Debug)]
124pub enum WindowAction {
125 SplitHorizontal {
126 filename: Option<String>,
127 },
128 SplitVertical {
129 filename: Option<String>,
130 },
131 Close {
132 force: bool,
133 },
134 CloseOthers,
135 FocusDirection {
136 direction: NavigateDirection,
137 },
138 MoveDirection {
139 direction: NavigateDirection,
140 },
141 Resize {
142 direction: SplitDirection,
143 delta: i16,
144 },
145 Equalize,
146 /// Swap current window with window in the given direction
147 SwapDirection {
148 direction: NavigateDirection,
149 },
150}
151
152#[derive(Debug)]
153pub enum TabAction {
154 New { filename: Option<String> },
155 Close,
156 Next,
157 Prev,
158 Goto { index: usize },
159}
160
161/// Buffer navigation actions
162#[derive(Debug)]
163pub enum BufferAction {
164 /// Go to previous buffer
165 Prev,
166 /// Go to next buffer
167 Next,
168 /// Delete current buffer
169 Delete { force: bool },
170}
171
172/// File operations that require Runtime access
173///
174/// Used by plugins (e.g., Explorer) to request file system operations
175/// that need Runtime access for buffer management.
176#[derive(Debug)]
177pub enum FileAction {
178 /// Open a file into a buffer
179 Open { path: String },
180 /// Create a new file or directory
181 Create { path: String, is_dir: bool },
182 /// Delete a file or directory
183 Delete { path: String },
184 /// Rename/move a file or directory
185 Rename { from: String, to: String },
186}
187
188/// Operator + motion action (e.g., dw, yj, c$)
189#[derive(Debug)]
190pub enum OperatorMotionAction {
191 /// Delete with motion (d + motion)
192 Delete {
193 motion: crate::motion::Motion,
194 count: usize,
195 },
196 /// Yank with motion (y + motion)
197 Yank {
198 motion: crate::motion::Motion,
199 count: usize,
200 },
201 /// Change with motion (c + motion)
202 Change {
203 motion: crate::motion::Motion,
204 count: usize,
205 },
206 /// Delete text object (di(, da{, etc.)
207 DeleteTextObject {
208 text_object: crate::textobject::TextObject,
209 },
210 /// Yank text object (yi(, ya{, etc.)
211 YankTextObject {
212 text_object: crate::textobject::TextObject,
213 },
214 /// Change text object (ci(, ca{, etc.)
215 ChangeTextObject {
216 text_object: crate::textobject::TextObject,
217 },
218 /// Delete semantic text object (dif, dac, etc.) - uses treesitter
219 DeleteSemanticTextObject {
220 text_object: crate::textobject::SemanticTextObjectSpec,
221 },
222 /// Yank semantic text object (yif, yac, etc.) - uses treesitter
223 YankSemanticTextObject {
224 text_object: crate::textobject::SemanticTextObjectSpec,
225 },
226 /// Change semantic text object (cif, cac, etc.) - uses treesitter
227 ChangeSemanticTextObject {
228 text_object: crate::textobject::SemanticTextObjectSpec,
229 },
230 /// Delete word text object (diw, daw, diW, daW)
231 DeleteWordTextObject {
232 text_object: crate::textobject::WordTextObject,
233 },
234 /// Yank word text object (yiw, yaw, yiW, yaW)
235 YankWordTextObject {
236 text_object: crate::textobject::WordTextObject,
237 },
238 /// Change word text object (ciw, caw, ciW, caW)
239 ChangeWordTextObject {
240 text_object: crate::textobject::WordTextObject,
241 },
242 /// Change entire line (cc) - clears line content and enters insert mode
243 ChangeLine,
244}
245
246/// Command line mode actions
247#[derive(Debug)]
248pub enum CommandLineAction {
249 /// Insert a character into command line
250 InsertChar(char),
251 /// Delete character (backspace)
252 Backspace,
253 /// Execute the command line
254 Execute,
255 /// Cancel command line mode
256 Cancel,
257}
258
259/// The core command trait - all commands must implement this
260///
261/// Commands are the fundamental unit of editor actions. They receive
262/// an execution context containing the buffer and metadata, and return
263/// a result indicating what action the runtime should take.
264pub trait CommandTrait: Debug + Send + Sync {
265 /// Unique identifier for this command (e.g., `cursor_up`, `enter_insert_mode`)
266 fn name(&self) -> &'static str;
267
268 /// Human-readable description for help/documentation
269 fn description(&self) -> &'static str;
270
271 /// Execute the command on the given buffer
272 ///
273 /// # Arguments
274 /// * `ctx` - Execution context containing buffer and metadata
275 ///
276 /// # Returns
277 /// Result indicating what action the runtime should take
278 fn execute(&self, ctx: &mut ExecutionContext) -> CommandResult;
279
280 /// Clone into a boxed trait object
281 ///
282 /// This is required because `Clone` is not object-safe
283 fn clone_box(&self) -> Box<dyn CommandTrait>;
284
285 /// Downcast support for type inspection
286 fn as_any(&self) -> &dyn Any;
287
288 /// Optional: which modes this command is valid in (None = all modes)
289 fn valid_modes(&self) -> Option<Vec<ModeState>> {
290 None
291 }
292
293 /// Optional: does this command support repeat count?
294 fn supports_count(&self) -> bool {
295 true
296 }
297
298 /// Whether this command is a "jump" that should be recorded in the jump list
299 ///
300 /// Commands like gg, G, search, etc. return true here so the cursor
301 /// position before execution is recorded in the jump list.
302 fn is_jump(&self) -> bool {
303 false
304 }
305
306 /// Whether this command modifies buffer text content
307 ///
308 /// Commands that insert, delete, or modify text return true here.
309 /// This is used to trigger treesitter reparsing after buffer modifications.
310 fn is_text_modifying(&self) -> bool {
311 false
312 }
313}
314
315impl Clone for Box<dyn CommandTrait> {
316 fn clone(&self) -> Self {
317 self.clone_box()
318 }
319}