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    MoveDirection {
150        direction: NavigateDirection,
151    },
152    Resize {
153        direction: SplitDirection,
154        delta: i16,
155    },
156    Equalize,
157    /// Swap current window with window in the given direction
158    SwapDirection {
159        direction: NavigateDirection,
160    },
161}
162
163#[derive(Debug)]
164pub enum TabAction {
165    New { filename: Option<String> },
166    Close,
167    Next,
168    Prev,
169    Goto { index: usize },
170}
171
172/// Buffer navigation actions
173#[derive(Debug)]
174pub enum BufferAction {
175    /// Go to previous buffer
176    Prev,
177    /// Go to next buffer
178    Next,
179    /// Delete current buffer
180    Delete { force: bool },
181}
182
183/// File operations that require Runtime access
184///
185/// Used by plugins (e.g., Explorer) to request file system operations
186/// that need Runtime access for buffer management.
187#[derive(Debug)]
188pub enum FileAction {
189    /// Open a file into a buffer
190    Open { path: String },
191    /// Create a new file or directory
192    Create { path: String, is_dir: bool },
193    /// Delete a file or directory
194    Delete { path: String },
195    /// Rename/move a file or directory
196    Rename { from: String, to: String },
197}
198
199/// Operator + motion action (e.g., dw, yj, c$)
200#[derive(Debug)]
201pub enum OperatorMotionAction {
202    /// Delete with motion (d + motion)
203    Delete {
204        motion: crate::motion::Motion,
205        count: usize,
206    },
207    /// Yank with motion (y + motion)
208    Yank {
209        motion: crate::motion::Motion,
210        count: usize,
211    },
212    /// Change with motion (c + motion)
213    Change {
214        motion: crate::motion::Motion,
215        count: usize,
216    },
217    /// Delete text object (di(, da{, etc.)
218    DeleteTextObject {
219        text_object: crate::textobject::TextObject,
220    },
221    /// Yank text object (yi(, ya{, etc.)
222    YankTextObject {
223        text_object: crate::textobject::TextObject,
224    },
225    /// Change text object (ci(, ca{, etc.)
226    ChangeTextObject {
227        text_object: crate::textobject::TextObject,
228    },
229    /// Delete semantic text object (dif, dac, etc.) - uses treesitter
230    DeleteSemanticTextObject {
231        text_object: crate::textobject::SemanticTextObjectSpec,
232    },
233    /// Yank semantic text object (yif, yac, etc.) - uses treesitter
234    YankSemanticTextObject {
235        text_object: crate::textobject::SemanticTextObjectSpec,
236    },
237    /// Change semantic text object (cif, cac, etc.) - uses treesitter
238    ChangeSemanticTextObject {
239        text_object: crate::textobject::SemanticTextObjectSpec,
240    },
241    /// Delete word text object (diw, daw, diW, daW)
242    DeleteWordTextObject {
243        text_object: crate::textobject::WordTextObject,
244    },
245    /// Yank word text object (yiw, yaw, yiW, yaW)
246    YankWordTextObject {
247        text_object: crate::textobject::WordTextObject,
248    },
249    /// Change word text object (ciw, caw, ciW, caW)
250    ChangeWordTextObject {
251        text_object: crate::textobject::WordTextObject,
252    },
253    /// Change entire line (cc) - clears line content and enters insert mode
254    ChangeLine,
255}
256
257/// Command line mode actions
258#[derive(Debug)]
259pub enum CommandLineAction {
260    /// Insert a character into command line
261    InsertChar(char),
262    /// Delete character (backspace)
263    Backspace,
264    /// Execute the command line
265    Execute,
266    /// Cancel command line mode
267    Cancel,
268    /// Trigger completion (Tab key)
269    Complete,
270    /// Select previous completion (Shift-Tab)
271    CompletePrev,
272    /// Apply the selected completion text
273    ApplyCompletion {
274        /// Text to insert
275        text: String,
276        /// Position where to start replacing
277        replace_start: usize,
278    },
279}
280
281/// The core command trait - all commands must implement this
282///
283/// Commands are the fundamental unit of editor actions. They receive
284/// an execution context containing the buffer and metadata, and return
285/// a result indicating what action the runtime should take.
286pub trait CommandTrait: Debug + Send + Sync {
287    /// Unique identifier for this command (e.g., `cursor_up`, `enter_insert_mode`)
288    fn name(&self) -> &'static str;
289
290    /// Human-readable description for help/documentation
291    fn description(&self) -> &'static str;
292
293    /// Execute the command on the given buffer
294    ///
295    /// # Arguments
296    /// * `ctx` - Execution context containing buffer and metadata
297    ///
298    /// # Returns
299    /// Result indicating what action the runtime should take
300    fn execute(&self, ctx: &mut ExecutionContext) -> CommandResult;
301
302    /// Clone into a boxed trait object
303    ///
304    /// This is required because `Clone` is not object-safe
305    fn clone_box(&self) -> Box<dyn CommandTrait>;
306
307    /// Downcast support for type inspection
308    fn as_any(&self) -> &dyn Any;
309
310    /// Optional: which modes this command is valid in (None = all modes)
311    fn valid_modes(&self) -> Option<Vec<ModeState>> {
312        None
313    }
314
315    /// Optional: does this command support repeat count?
316    fn supports_count(&self) -> bool {
317        true
318    }
319
320    /// Whether this command is a "jump" that should be recorded in the jump list
321    ///
322    /// Commands like gg, G, search, etc. return true here so the cursor
323    /// position before execution is recorded in the jump list.
324    fn is_jump(&self) -> bool {
325        false
326    }
327
328    /// Whether this command modifies buffer text content
329    ///
330    /// Commands that insert, delete, or modify text return true here.
331    /// This is used to trigger treesitter reparsing after buffer modifications.
332    fn is_text_modifying(&self) -> bool {
333        false
334    }
335}
336
337impl Clone for Box<dyn CommandTrait> {
338    fn clone(&self) -> Self {
339        self.clone_box()
340    }
341}