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}