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