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}