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}