oo_ide/operation.rs
1use std::borrow::Cow;
2use std::collections::HashMap;
3use std::path::PathBuf;
4
5use crate::prelude::*;
6
7use app_state::ScreenKind;
8use commands::ArgValue;
9use editor::position::Position;
10use crate::issue_registry::{IssueId, NewIssue, Severity};
11use crate::persistent_issues::PersistentNewIssue;
12use crate::task_registry::{TaskId, TaskKey, TaskStatus, TaskTrigger};
13
14#[derive(Debug, Clone)]
15pub enum ExtensionConfigOp {
16 MoveUp,
17 MoveDown,
18 Select(usize),
19 SetStatus(usize),
20}
21// View-local operations for FileSelector — defined here to avoid a circular
22// dependency between operation.rs and views/file_selector.rs.
23
24#[derive(Debug, Clone)]
25pub enum FileSelectorOp {
26 FilterInput(crate::widgets::input_field::InputFieldOp),
27 SwitchPane,
28 CollapseOrLeft,
29 ExpandOrRight,
30 ToggleDir,
31 /// Delivered by the async search task with the scored+sorted results.
32 /// The `generation` field is used to discard results from superseded
33 /// queries (user typed again before this result arrived).
34 SetResults {
35 generation: u64,
36 paths: Vec<PathBuf>,
37 },
38 /// Delivered by the async directory-read task when a lazy directory
39 /// expansion completes. Each entry is `(absolute_path, is_dir)`.
40 DirLoaded {
41 path: PathBuf,
42 entries: Vec<(PathBuf, bool)>,
43 },
44}
45
46// View-local operations for LogView.
47
48#[derive(Debug, Clone)]
49pub enum LogViewOp {
50 FilterInput(crate::widgets::input_field::InputFieldOp),
51}
52
53// View-local operations for TaskArchiveView.
54
55#[derive(Debug, Clone)]
56pub enum TaskArchiveOp {
57 FilterInput(crate::widgets::input_field::InputFieldOp),
58 /// Open the log for the highlighted task.
59 OpenSelected,
60 /// Cancel the highlighted running task.
61 CancelSelected,
62 /// Sent by app.rs when the task list changes so the view can clamp its cursor.
63 TaskUpdated { new_len: usize },
64 /// Close the inline detail pane and return to the list.
65 CloseDetail,
66 /// Populate the inline detail pane with loaded log lines.
67 ShowDetail {
68 task_id: TaskId,
69 title: String,
70 status: TaskStatus,
71 lines: Vec<String>,
72 },
73}
74
75// ---------------------------------------------------------------------------
76// View-local operations for IssueView.
77// ---------------------------------------------------------------------------
78
79/// View-local operations for crate::views::issue_view::IssueView.
80#[derive(Debug, Clone)]
81pub enum IssueViewOp {
82 FilterInput(crate::widgets::input_field::InputFieldOp),
83 /// Enter — open detail for the selected issue.
84 OpenSelected,
85 /// `r` — resolve the currently selected issue.
86 ResolveSelected,
87 /// `d` — dismiss the currently selected issue.
88 DismissSelected,
89 /// `h` — toggle visibility of dismissed/resolved issues.
90 ToggleShowDismissed,
91 /// `0`–`4` — filter by severity; `None` = show all.
92 SetSeverityFilter(Option<Severity>),
93 /// Esc in Detail mode — back to List.
94 CloseDetail,
95 /// Sent when IssueAdded / IssueRemoved / IssueUpdated arrive, so the view
96 /// can clamp its cursor.
97 RegistryChanged,
98}
99
100// View-local operations for CommandRunner — same pattern as FileSelectorOp.
101
102#[derive(Debug, Clone)]
103pub enum CommandSelectorOp {
104 FilterInput(crate::widgets::input_field::InputFieldOp),
105 SwitchPane,
106 CollapseOrLeft,
107 ExpandOrRight,
108 /// Confirm selection (Enter) from the selector pane.
109 Confirm,
110 /// In the arg-form view: move focus to next field.
111 ArgNext,
112 /// In the arg-form view: move focus to previous field.
113 ArgPrev,
114 /// In the arg-form view: set the focused field's value.
115 ArgChanged(String),
116 /// Cancel the arg form and go back to selector.
117 ArgCancel,
118}
119
120// ---------------------------------------------------------------------------
121// View-local operations for CommitWindow.
122// ---------------------------------------------------------------------------
123
124#[derive(Debug, Clone)]
125pub enum CommitOp {
126 // ── UI trigger ops (view → app → spawn_blocking) ─────────────────────
127 /// Switch between Commit and Diff tabs.
128 SwitchTab,
129 // Commit tab
130 /// Cycle focus: Staged → Unstaged → Message.
131 SwitchPane,
132 /// Stage or unstage the selected file.
133 ToggleFileStage,
134 /// Set the commit message text programmatically (e.g., clear or load a draft).
135 MessageChanged(String),
136 /// Keystroke editing ops for the message field.
137 MessageInsertChar(char),
138 MessageInsertNewline,
139 MessageBackspace,
140 MessageCursorLeft,
141 MessageCursorRight,
142 MessageCursorUp,
143 MessageCursorDown,
144 /// Open the new-branch input (clears any previous value).
145 BranchInputOpen,
146 /// Cancel the branch-name input without creating a branch.
147 BranchInputCancel,
148 /// Set the branch-name input text.
149 BranchChanged(String),
150 /// Confirm new branch from branch_input.
151 BranchCreate,
152 /// Open the branch selector popup (shows existing branches).
153 BranchSelectorOpen,
154 /// Close the branch selector popup without action.
155 BranchSelectorCancel,
156 /// Checkout the given branch (handled by app.rs with git ops).
157 BranchCheckout(String),
158 /// Execute commit (guarded: message must be non-empty).
159 Confirm,
160 // Diff tab
161 /// Toggle focus between file list and diff lines panels in the diff tab.
162 DiffFocusToggle,
163 /// Toggle staged_diff_lines for the selected diff line (local, no git call).
164 /// On a hunk header row this toggles fold instead.
165 ToggleLineStage,
166 /// Revert the selected diff line.
167 RevertLine,
168 /// Request diff for a file.
169 LoadDiff {
170 path: PathBuf,
171 staged: bool,
172 },
173 /// Async result ops (spawn_blocking → op_tx → handle_operation)
174 StatusLoaded {
175 staged: Vec<crate::vcs::StatusEntry>,
176 unstaged: Vec<crate::vcs::StatusEntry>,
177 branches: Vec<String>,
178 current_branch: String,
179 },
180 DiffLoaded {
181 path: PathBuf,
182 /// Whether this diff is staged (HEAD↔index) or unstaged (index↔workdir).
183 staged: bool,
184 lines: Vec<crate::vcs::DiffLine>,
185 },
186 GitOpSuccess(String),
187 GitOpError(String),
188}
189
190// LSP-related types and view-local operations for the editor.
191
192#[derive(Debug, Clone)]
193pub struct LspCompletionItem {
194 pub label: String,
195 pub kind: Option<u8>,
196 pub detail: Option<String>,
197 pub insert_text: Option<String>,
198}
199
200#[derive(Debug, Clone)]
201pub struct LspLocation {
202 pub path: PathBuf,
203 pub row: usize,
204 pub col: usize,
205}
206
207#[derive(Debug, Clone)]
208pub enum ClipOp {
209 /// Copy active selection (or current line if empty) to clipboard + history.
210 Copy,
211 /// Cut active selection (or current line if empty) to clipboard + history.
212 Cut,
213 /// Paste from system clipboard at cursor.
214 Paste,
215 /// Open the clipboard history picker dialog.
216 OpenHistory,
217 /// Navigate history picker up.
218 HistoryUp,
219 /// Navigate history picker down.
220 HistoryDown,
221 /// Paste the selected history item and close picker.
222 HistoryConfirm,
223 /// Close the history picker without pasting.
224 HistoryClose,
225}
226
227#[derive(Debug, Clone)]
228pub enum SelectionOp {
229 /// Extend active selection head (Shift+arrow).
230 Extend { head: Position },
231 /// Select the entire buffer.
232 SelectAll,
233 /// Clear selection (Esc).
234 Clear,
235}
236
237#[derive(Debug, Clone)]
238pub enum GoToLineOp {
239 /// Open the go-to-line bar (or focus it if already open).
240 Open,
241 /// Close the bar without committing.
242 Close,
243 /// Commit the current line number and close.
244 Confirm,
245 /// Feed a key to the line-number input field.
246 Input(crate::widgets::input_field::InputFieldOp),
247}
248
249#[derive(Debug, Clone)]
250pub enum SearchOp {
251 Open {
252 replace: bool,
253 },
254 Close,
255 QueryInput(crate::widgets::input_field::InputFieldOp),
256 ReplacementInput(crate::widgets::input_field::InputFieldOp),
257 ToggleIgnoreCase,
258 ToggleRegex,
259 ToggleSmartCase,
260 /// Toggle query ↔ replacement input focus.
261 FocusSwitch,
262 NextMatch,
263 PrevMatch,
264 ReplaceOne,
265 ReplaceAll,
266 /// Stream one project-wide file result into the editor search state.
267 /// Sent by the async project-search task for every file that contains matches.
268 AddProjectResult { file: PathBuf, result: MatchSpan },
269 /// Clear all project-wide file results — sent before a new search starts.
270 ClearProjectResults,
271 /// Select a file in the project-search file list (0-based index into `files`).
272 SelectFile(usize),
273 /// Scroll the match-results panel by N rows (positive = down, negative = up).
274 ScrollMatchPanel(i8),
275 /// Keystroke routed to the include-glob filter field (Expanded mode only).
276 IncludeGlobInput(crate::widgets::input_field::InputFieldOp),
277 /// Keystroke routed to the exclude-glob filter field (Expanded mode only).
278 ExcludeGlobInput(crate::widgets::input_field::InputFieldOp),
279}
280
281#[derive(Debug, Clone)]
282pub enum LspOp {
283 CompletionResponse {
284 items: Vec<LspCompletionItem>,
285 trigger: Option<Position>,
286 version: Option<i32>,
287 },
288 HoverResponse(Option<String>),
289 CompletionMoveUp,
290 CompletionMoveDown,
291 CompletionConfirm,
292 CompletionDismiss,
293 HoverDismiss,
294}
295
296// ---------------------------------------------------------------------------
297// Project-wide search & replace
298// ---------------------------------------------------------------------------
299
300/// A single match span within a file line.
301#[derive(Debug, Clone)]
302pub struct MatchSpan {
303 /// 0-based line index.
304 pub line: usize,
305 /// Byte offset of match start within the line.
306 pub byte_start: usize,
307 /// Byte offset of match end within the line.
308 pub byte_end: usize,
309 /// Full text of the line (no newline).
310 pub line_text: String,
311}
312
313/// All matches found in one file.
314#[derive(Debug, Clone)]
315pub struct FileMatchResult {
316 pub path: PathBuf,
317 pub matches: Vec<MatchSpan>,
318}
319
320// ---------------------------------------------------------------------------
321// Git History Viewer view-local operations
322// ---------------------------------------------------------------------------
323
324#[derive(Debug, Clone)]
325pub enum GitHistoryOp {
326 /// Keystroke routed to the filter input field.
327 FilterInput(crate::widgets::input_field::InputFieldOp),
328 /// Async result: commits loaded for the current filter + generation.
329 CommitsLoaded {
330 generation: u64,
331 commits: Vec<crate::vcs::CommitInfo>,
332 },
333 /// User confirmed a revision selection (Enter on revision list).
334 /// Intercepted by app.rs which spawns file-list + diff loading.
335 SelectRevision,
336 /// Async result: file list for the selected revision.
337 FilesLoaded {
338 oid: String,
339 files: Vec<crate::vcs::FileChange>,
340 },
341 /// User navigated to or confirmed a file selection.
342 /// Intercepted by app.rs which spawns diff loading.
343 SelectFile,
344 /// Async result: diff for the selected revision + file.
345 DiffLoaded {
346 lines: Vec<crate::vcs::DiffLine>,
347 },
348 /// An async git task failed.
349 LoadError(String),
350}
351
352// ---------------------------------------------------------------------------
353// Terminal view-local operations
354// ---------------------------------------------------------------------------
355
356#[derive(Debug, Clone)]
357pub enum TerminalOp {
358 /// Raw bytes received from the PTY process (sent by the async read task).
359 Output { id: u64, data: Vec<u8> },
360 /// Completed styled lines produced from PTY output by the async read task.
361 ///
362 /// Sent alongside `Output` so the main thread can maintain a styled
363 /// scrollback buffer per tab (enabling state persistence and reuse of
364 /// `DiagnosticsExtractor::extract_from_line`).
365 AppendScrollback { id: u64, lines: Vec<crate::vt_parser::StyledLine> },
366 /// Open a new tab. `command` defaults to the configured shell if `None`.
367 NewTab { command: Option<String> },
368 /// Close a tab by ID. If it was the last tab, a new shell is spawned.
369 CloseTab { id: u64 },
370 /// Switch to the tab at the given index (clamped to valid range).
371 SwitchToTab { index: usize },
372 /// Switch to the next tab (wraps around).
373 NextTab,
374 /// Switch to the previous tab (wraps around).
375 PrevTab,
376 /// Reset scroll to the bottom of the active tab.
377 ScrollReset,
378 /// Open the context menu for the given tab.
379 OpenMenu { id: u64 },
380 /// Close the context menu without taking action.
381 CloseMenu,
382 /// Confirm the highlighted context-menu item.
383 MenuConfirm,
384 /// Open the inline rename input for the given tab directly.
385 OpenRename { id: u64 },
386 /// Clear the screen of the given tab (sends VT clear sequence to PTY).
387 ClearTab { id: u64 },
388 /// Set the rename input text.
389 RenameChanged(String),
390 /// Commit the rename.
391 RenameConfirm,
392 /// Cancel the rename.
393 RenameCancel,
394 /// Open the tab selector popup (keyboard/mouse-navigable list of all tabs).
395 OpenTabSelector,
396 /// Terminal dimensions changed — resize all PTY tabs.
397 Resize { cols: u16, rows: u16 },
398 /// The PTY child process has exited.
399 ProcessExited { id: u64 },
400}
401
402#[derive(Debug, Clone)]
403pub enum SearchReplaceOp {
404 // Input field changes
405 QueryChanged(String),
406 ReplaceChanged(String),
407 IncludeGlobChanged(String),
408 ExcludeGlobChanged(String),
409 // Toggle options
410 ToggleRegex,
411 ToggleCase,
412 ToggleSmartCase,
413 // Tree navigation
414 TreeExpandOrEnter,
415 TreeCollapseOrLeave,
416 // Replace actions
417 ReplaceCurrentFile,
418 ReplaceAll,
419 // Undo / redo
420 Undo,
421 Redo,
422 // Async results
423 SearchResultsReady {
424 results: Vec<FileMatchResult>,
425 generation: u64,
426 },
427 ReplaceComplete {
428 path: PathBuf,
429 replaced_count: usize,
430 },
431 AsyncError(String),
432 ProgressUpdate {
433 scanned: usize,
434 total: usize,
435 },
436}
437
438#[derive(Debug, Clone)]
439pub enum Operation {
440 // Buffer mutations
441 InsertText {
442 path: Option<PathBuf>,
443 #[allow(dead_code)]
444 cursor: Position,
445 text: String,
446 },
447 /// Bracketed paste (or fallback-heuristic detected paste) delivered to the
448 /// active editor. Unlike `InsertText`, the full multi-line string is inserted
449 /// at once via `insert_text_raw` and also pushed to the clipboard register.
450 PasteText {
451 path: Option<PathBuf>,
452 text: String,
453 },
454 DeleteText {
455 path: Option<PathBuf>,
456 #[allow(dead_code)]
457 cursor: Position,
458 #[allow(dead_code)]
459 len: usize,
460 },
461 DeleteForward {
462 path: Option<PathBuf>,
463 #[allow(dead_code)]
464 cursor: Position,
465 },
466 DeleteWordBackward {
467 path: Option<PathBuf>,
468 },
469 DeleteWordForward {
470 path: Option<PathBuf>,
471 },
472 IndentLines {
473 path: Option<PathBuf>,
474 },
475 UnindentLines {
476 path: Option<PathBuf>,
477 },
478 DeleteLine {
479 path: Option<PathBuf>,
480 },
481 /// Replace the text from `start` to `end` (same line, byte offsets) with `text`.
482 /// Used by CompletionConfirm to delete the filter prefix before inserting.
483 ReplaceRange {
484 path: Option<PathBuf>,
485 start: Position,
486 end: Position,
487 text: String,
488 },
489 MoveCursor {
490 path: Option<PathBuf>,
491 cursor: Position,
492 },
493 Undo {
494 path: Option<PathBuf>,
495 },
496 Redo {
497 path: Option<PathBuf>,
498 },
499 ToggleFold {
500 path: Option<PathBuf>,
501 line: usize,
502 },
503 ToggleMarker {
504 path: Option<PathBuf>,
505 line: usize,
506 },
507 ToggleWordWrap,
508
509 // File lifecycle
510 OpenFile {
511 path: PathBuf,
512 },
513 /// Open an external URL in the system default browser.
514 OpenUrl {
515 url: String,
516 },
517 SaveFile {
518 path: PathBuf,
519 },
520 ReloadFromDisk {
521 path: PathBuf,
522 accept_external: bool,
523 },
524
525 // App / navigation
526 SwitchScreen {
527 to: ScreenKind,
528 },
529 /// Close the currently open modal/temporary view.
530 ///
531 /// If the current view is already primary, this is a no-op.
532 CloseModal,
533 Quit,
534
535 // Focus navigation — handled by whichever view is active
536 Focus(crate::widgets::focusable::FocusOp),
537
538 // Generic list/tree/scroll navigation — dispatched to the active view.
539 // Each view interprets these according to its own focus state.
540 NavigateUp,
541 NavigateDown,
542 NavigatePageUp,
543 NavigatePageDown,
544 NavigateHome,
545 NavigateEnd,
546
547 // View-local ops that don't cross view boundaries
548 FileSelectorLocal(FileSelectorOp),
549 CommandSelectorLocal(CommandSelectorOp),
550 LogViewLocal(LogViewOp),
551 CommitLocal(CommitOp),
552 GitHistoryLocal(GitHistoryOp),
553 ExtensionConfig(ExtensionConfigOp),
554
555 // LSP trigger ops (handled in app.rs, forwarded to LspClient)
556 LspTriggerCompletion {
557 #[allow(dead_code)]
558 path: Option<PathBuf>,
559 },
560 LspTriggerHover {
561 #[allow(dead_code)]
562 path: Option<PathBuf>,
563 },
564 LspGoToDefinition {
565 #[allow(dead_code)]
566 path: Option<PathBuf>,
567 },
568 /// Result from a go-to-definition request, dispatched back into the queue.
569 LspGoToDefinitionResult(Vec<LspLocation>),
570 /// LSP editor-local ops (dropdown navigation, response delivery).
571 LspLocal(LspOp),
572 /// Search/replace bar ops (editor-local).
573 SearchLocal(SearchOp),
574 /// Go-to-line bar ops (editor-local).
575 GoToLineLocal(GoToLineOp),
576 /// Selection management ops (editor-local).
577 SelectionLocal(SelectionOp),
578 /// Clipboard ops (editor-local).
579 ClipboardLocal(ClipOp),
580 /// Project-wide search & replace ops.
581 SearchReplaceLocal(SearchReplaceOp),
582
583 /// Execute a registered command by id (with pre-filled args).
584 RunCommand {
585 id: crate::commands::CommandId,
586 args: HashMap<String, ArgValue>,
587 },
588
589 /// Open a generic context menu at `(x, y)` with the given items.
590 ///
591 /// Items are `(label, command_id)` pairs. `app.rs` stores the menu on
592 /// [`crate::app_state::AppState`], intercepts input, and dispatches the selected command.
593 OpenContextMenu {
594 // Each item: (label, command_id, enabled_override)
595 // - enabled_override: `Some(bool)` to explicitly set enabled state,
596 // or `None` to let the runtime compute a sensible default.
597 items: Vec<(String, crate::commands::CommandId, Option<bool>)>,
598 x: u16,
599 y: u16,
600 },
601
602 /// Close the currently open context menu without selecting any item.
603 CloseContextMenu,
604
605 // Escape hatch for plugins
606 #[allow(dead_code)]
607 Generic {
608 source: Cow<'static, str>,
609 name: Cow<'static, str>,
610 payload: HashMap<String, ArgValue>,
611 },
612
613 // Extension system
614 /// Broadcast a named event to all active extensions.
615 ExtensionEvent {
616 name: String,
617 payload: HashMap<String, String>,
618 },
619 /// Display a user-visible notification (shown in status bar).
620 ShowNotification {
621 message: String,
622 level: NotificationLevel,
623 },
624 /// Update the IDE status bar slot with custom text.
625 UpdateStatusBar {
626 text: String,
627 #[allow(unused)]
628 slot: StatusSlot,
629 },
630 /// Request a terminal be created running the given command.
631 CreateTerminal {
632 command: String,
633 },
634 /// Send raw bytes to the PTY of the terminal tab with the given ID.
635 TerminalInput {
636 id: u64,
637 data: Vec<u8>,
638 },
639 /// Terminal view-local operations.
640 TerminalLocal(TerminalOp),
641 /// Show a picker (like command palette) populated with arbitrary items.
642 ShowPicker {
643 title: String,
644 items: Vec<PickerItem>,
645 },
646
647 /// Deliver pre-computed syntax-highlight spans for an editor buffer.
648 ///
649 /// Sent by the background highlight task; handled in `app.rs`, which stores
650 /// the result in `crate::views::editor::EditorView::highlight_cache` when the version still
651 /// matches the live buffer. The next frame then renders without blocking.
652 SetEditorHighlights {
653 path: Option<std::path::PathBuf>,
654 version: editor::buffer::Version,
655 start_line: usize,
656 generation: u64,
657 spans: Vec<Vec<editor::highlight::StyledSpan>>,
658 },
659
660 /// Incremental highlight update for multiple lines.
661 ///
662 /// Unlike SetEditorHighlights, this operation updates individual lines
663 /// without clearing existing highlights. This prevents highlights from
664 /// disappearing while typing or scrolling.
665 SetEditorHighlightsChunk {
666 path: Option<std::path::PathBuf>,
667 version: editor::buffer::Version,
668 generation: u64,
669 spans: Vec<(usize, Vec<editor::highlight::StyledSpan>)>,
670 },
671
672 // -----------------------------------------------------------------------
673 // Issue registry — ephemeral commands + startup loads
674 // (no disk I/O; produced by async tasks and the persistent-load path)
675 // -----------------------------------------------------------------------
676
677 /// Add an issue to the registry (registry-only, no disk write).
678 ///
679 /// `issue.marker = Some(m)` → ephemeral (cleared via `ClearIssuesByMarker`).
680 /// `issue.marker = None` → used only for startup loads by the Persistent
681 /// Component. User-initiated persistent issues
682 /// must go through `PersistentIssueLocal(Add)`.
683 AddIssue { issue: NewIssue },
684
685 /// Remove an issue by ID (registry-only). Emits `IssueRemoved` on success.
686 RemoveIssue { id: IssueId },
687
688 /// Mark an issue as resolved (registry-only). Emits `IssueUpdated` on success.
689 ResolveIssue { id: IssueId },
690
691 /// Mark an issue as dismissed (registry-only). Emits `IssueUpdated` on success.
692 DismissIssue { id: IssueId },
693
694 /// Remove all ephemeral issues belonging to `marker`. Emits one
695 /// `IssueRemoved` per removed issue.
696 ClearIssuesByMarker { marker: String },
697
698 // -----------------------------------------------------------------------
699 // Persistent issue operations — update registry AND write to disk
700 // -----------------------------------------------------------------------
701
702 /// Persistent-issue commands: each variant updates the in-memory
703 /// [`IssueRegistry`] **and** atomically rewrites `.oo/issues.yaml`.
704 ///
705 /// [`IssueRegistry`]: crate::issue_registry::IssueRegistry
706 PersistentIssueLocal(PersistentIssueOp),
707
708 // -----------------------------------------------------------------------
709 // Issue registry — events (enqueued by apply_operation after mutations)
710 // -----------------------------------------------------------------------
711
712 /// An issue was added.
713 IssueAdded { id: IssueId },
714
715 /// An issue was removed.
716 IssueRemoved { id: IssueId },
717
718 /// An issue's `resolved` or `dismissed` flag was toggled.
719 IssueUpdated { id: IssueId },
720
721 // -----------------------------------------------------------------------
722 // Task system lifecycle events
723 // -----------------------------------------------------------------------
724
725 /// A task was created and either started immediately or added to its queue.
726 TaskScheduled(TaskId),
727
728 /// A task transitioned from `Pending` to `Running`.
729 TaskStarted(TaskId),
730
731 /// A task reached a terminal status (`Success`, `Warning`, or `Error`).
732 ///
733 /// Sent by [`crate::task_executor::TaskExecutor`] when the spawned process
734 /// exits. Handled by `apply_operation` in `app.rs`, which calls
735 /// [`crate::task_registry::TaskRegistry::mark_finished`] and starts the
736 /// next queued task if one exists.
737 TaskFinished {
738 id: TaskId,
739 status: TaskStatus,
740 },
741
742 /// A task was cancelled (running or queued).
743 TaskCancelled(TaskId),
744
745 // -----------------------------------------------------------------------
746 // Task system commands
747 // -----------------------------------------------------------------------
748
749 /// Schedule a new task. Handled by `apply_operation`, which calls
750 /// [`crate::task_registry::TaskRegistry::schedule_task`] and immediately
751 /// spawns the executor if the queue was idle.
752 ScheduleTask {
753 key: TaskKey,
754 trigger: TaskTrigger,
755 /// The shell command to execute (passed through to the process).
756 command: String,
757 },
758
759 /// Cancel a running or queued task by its ID. Handled by `apply_operation`,
760 /// which calls [`crate::task_registry::TaskRegistry::cancel`] and starts the
761 /// next queued task if one exists.
762 CancelTask(TaskId),
763
764 /// Open the log view and pre-populate its filter with the task's label so
765 /// the user can quickly inspect task-related entries.
766 OpenLogForTask { task_id: TaskId },
767
768 /// Open the Task Archive View.
769 OpenTaskArchive,
770
771 /// Delivered by app.rs after it asynchronously loads task output from the
772 /// log store. Switches to `TaskOutputView` and populates it with the lines.
773 ShowTaskOutput {
774 task_id: TaskId,
775 title: String,
776 status: TaskStatus,
777 lines: Vec<String>,
778 },
779
780 /// View-local ops for `crate::views::task_archive::TaskArchiveView`.
781 TaskArchiveLocal(TaskArchiveOp),
782
783 /// Open the Issue List View.
784 OpenIssueList,
785
786 /// View-local ops for `crate::views::issue_view::IssueView`.
787 IssueViewLocal(IssueViewOp),
788}
789
790// ---------------------------------------------------------------------------
791// Persistent issue sub-operations
792// ---------------------------------------------------------------------------
793
794/// Commands that manipulate **persistent** issues.
795///
796/// Each variant is handled by `apply_operation` in `app.rs`, which:
797/// 1. Calls the corresponding [`IssueRegistry`] mutation method.
798/// 2. Enqueues the matching `IssueAdded` / `IssueRemoved` / `IssueUpdated`
799/// event operation.
800/// 3. Spawns a fire-and-forget [`crate::persistent_issues::save_atomic`] task.
801///
802/// [`IssueRegistry`]: crate::issue_registry::IssueRegistry
803#[derive(Debug, Clone)]
804pub enum PersistentIssueOp {
805 /// Add a new persistent issue and write the updated list to disk.
806 Add { issue: PersistentNewIssue },
807 /// Remove a persistent issue by ID and write the updated list to disk.
808 Remove { id: IssueId },
809 /// Mark a persistent issue as resolved and write the updated list to disk.
810 Resolve { id: IssueId },
811 /// Mark a persistent issue as dismissed and write the updated list to disk.
812 Dismiss { id: IssueId },
813}
814
815#[derive(Debug, Clone, PartialEq, Eq)]
816pub enum NotificationLevel {
817 Info,
818 Warn,
819 Error,
820}
821
822#[derive(Debug, Clone, PartialEq, Eq)]
823pub enum StatusSlot {
824 Left,
825 Center,
826 Right,
827}
828
829#[derive(Debug, Clone)]
830#[allow(unused)]
831pub struct PickerItem {
832 #[allow(dead_code)]
833 pub label: String,
834 #[allow(dead_code)]
835 pub value: String,
836}
837
838#[derive(Debug, Clone)]
839pub struct Event {
840 /// Which subsystem produced the operation: "editor", "file_selector", "plugin.git"
841 #[allow(dead_code)]
842 pub source: Cow<'static, str>,
843 #[allow(dead_code)]
844 pub kind: EventKind,
845}
846
847#[derive(Debug, Clone)]
848pub enum EventKind {
849 #[allow(dead_code)]
850 Applied(Operation),
851}
852
853impl Event {
854 pub fn applied(source: impl Into<Cow<'static, str>>, op: Operation) -> Self {
855 Self {
856 source: source.into(),
857 kind: EventKind::Applied(op),
858 }
859 }
860}