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 /// Enter amend mode by loading the most recent commit message.
161 EnterAmendMode,
162 /// Confirm an amend (git commit --amend equivalent).
163 ConfirmAmend,
164 /// Cancel an in-progress amend — exit amend mode without committing.
165 CancelAmend,
166 // Diff tab
167 /// Toggle focus between file list and diff lines panels in the diff tab.
168 DiffFocusToggle,
169 /// Toggle staged_diff_lines for the selected diff line (local, no git call).
170 /// On a hunk header row this toggles fold instead.
171 ToggleLineStage,
172 /// Revert the selected diff line.
173 RevertLine,
174 /// Request diff for a file.
175 LoadDiff {
176 path: PathBuf,
177 staged: bool,
178 },
179 /// Async result ops (spawn_blocking → op_tx → handle_operation)
180 StatusLoaded {
181 staged: Vec<crate::vcs::StatusEntry>,
182 unstaged: Vec<crate::vcs::StatusEntry>,
183 branches: Vec<String>,
184 current_branch: String,
185 },
186 DiffLoaded {
187 path: PathBuf,
188 /// Whether this diff is staged (HEAD↔index) or unstaged (index↔workdir).
189 staged: bool,
190 lines: Vec<crate::vcs::DiffLine>,
191 },
192 /// Toggle or set amend mode in the view (delivered after async load).
193 AmendModeSet(bool, Option<String>),
194 GitOpSuccess(String),
195 GitOpError(String),
196 /// Async result: detected Git operation mode (rebase / merge / normal).
197 /// Delivered by the detection task spawned on CommitWindow open and refresh.
198 GitModeDetected {
199 mode: crate::vcs::GitMode,
200 has_conflicts: bool,
201 /// Pre-filled commit message for the current rebase step or merge.
202 message: String,
203 },
204 /// Abort an in-progress rebase (`git rebase --abort`).
205 AbortRebase,
206 /// Abort an in-progress merge (`git merge --abort`).
207 AbortMerge,
208}
209
210// LSP-related types and view-local operations for the editor.
211
212#[derive(Debug, Clone)]
213pub struct LspCompletionItem {
214 pub label: String,
215 pub kind: Option<u8>,
216 pub detail: Option<String>,
217 pub insert_text: Option<String>,
218}
219
220#[derive(Debug, Clone)]
221pub struct LspLocation {
222 pub path: PathBuf,
223 pub row: usize,
224 pub col: usize,
225}
226
227#[derive(Debug, Clone)]
228pub enum GoToTarget {
229 /// Request LSP definition at the current cursor (editor context).
230 LspRequest,
231 /// Concrete LSP location returned by server.
232 LspLocation(LspLocation),
233 /// Open file at line (0-based) and optional column.
234 FileLine {
235 path: std::path::PathBuf,
236 line: usize,
237 col: Option<usize>,
238 },
239 /// Placeholder for future diff/commit targets.
240 DiffLine {
241 repo: Option<std::path::PathBuf>,
242 commit: Option<String>,
243 path: std::path::PathBuf,
244 line: usize,
245 },
246 /// Symbol name with optional scope.
247 Symbol {
248 name: String,
249 scope: Option<String>,
250 },
251 /// Raw URI & range.
252 UriRange {
253 uri: String,
254 start_line: usize,
255 start_char: usize,
256 },
257}
258
259#[derive(Debug, Clone)]
260pub enum ClipOp {
261 /// Copy active selection (or current line if empty) to clipboard + history.
262 Copy,
263 /// Cut active selection (or current line if empty) to clipboard + history.
264 Cut,
265 /// Paste from system clipboard at cursor.
266 Paste,
267 /// Open the clipboard history picker dialog.
268 OpenHistory,
269 /// Navigate history picker up.
270 HistoryUp,
271 /// Navigate history picker down.
272 HistoryDown,
273 /// Paste the selected history item and close picker.
274 HistoryConfirm,
275 /// Close the history picker without pasting.
276 HistoryClose,
277}
278
279#[derive(Debug, Clone)]
280pub enum SelectionOp {
281 /// Extend active selection head (Shift+arrow).
282 Extend { head: Position },
283 /// Select the entire buffer.
284 SelectAll,
285 /// Clear selection (Esc).
286 Clear,
287}
288
289#[derive(Debug, Clone)]
290pub enum GoToLineOp {
291 /// Open the go-to-line bar (or focus it if already open).
292 Open,
293 /// Close the bar without committing.
294 Close,
295 /// Commit the current line number and close.
296 Confirm,
297 /// Feed a key to the line-number input field.
298 Input(crate::widgets::input_field::InputFieldOp),
299 /// Jump directly to a specific line (programmatic, e.g., from terminal link).
300 JumpTo { line: usize, column: usize },
301}
302
303#[derive(Debug, Clone)]
304pub enum SearchOp {
305 Open {
306 replace: bool,
307 },
308 Close,
309 QueryInput(crate::widgets::input_field::InputFieldOp),
310 ReplacementInput(crate::widgets::input_field::InputFieldOp),
311 ToggleIgnoreCase,
312 ToggleRegex,
313 ToggleSmartCase,
314 /// Toggle query ↔ replacement input focus.
315 FocusSwitch,
316 NextMatch,
317 PrevMatch,
318 ReplaceOne,
319 ReplaceAll,
320 /// Stream one project-wide file result into the editor search state.
321 /// Sent by the async project-search task for every file that contains matches.
322 /// `generation` is used to discard results from a superseded search.
323 AddProjectResult { file: PathBuf, result: MatchSpan, generation: u64 },
324 /// Clear all project-wide file results — sent before a new search starts.
325 /// `generation` identifies the search about to begin so the editor can
326 /// ignore any in-flight `AddProjectResult` ops from the previous search.
327 ClearProjectResults { generation: u64 },
328 /// Select a file in the project-search file list (0-based index into `files`).
329 SelectFile(usize),
330 /// Scroll the match-results panel by N rows (positive = down, negative = up).
331 ScrollMatchPanel(i8),
332 /// Keystroke routed to the include-glob filter field (Expanded mode only).
333 IncludeGlobInput(crate::widgets::input_field::InputFieldOp),
334 /// Keystroke routed to the exclude-glob filter field (Expanded mode only).
335 ExcludeGlobInput(crate::widgets::input_field::InputFieldOp),
336 /// Move cursor up in the search results tree.
337 TreeMoveUp,
338 /// Move cursor down in the search results tree.
339 TreeMoveDown,
340 /// Toggle expand/collapse on a file node, or activate a match entry.
341 TreeToggle,
342 /// Collapse current file node (or move to parent file when on a match row).
343 TreeCollapse,
344}
345
346#[derive(Debug, Clone)]
347pub enum LspOp {
348 CompletionResponse {
349 items: Vec<LspCompletionItem>,
350 trigger: Option<Position>,
351 version: Option<i32>,
352 },
353 /// Sent immediately when completion is triggered to show loading state
354 CompletionLoading {
355 trigger: Position,
356 },
357 HoverResponse(Option<String>),
358 CompletionMoveUp,
359 CompletionMoveDown,
360 CompletionConfirm,
361 CompletionDismiss,
362 HoverDismiss,
363}
364
365// ---------------------------------------------------------------------------
366// LSP rename
367// ---------------------------------------------------------------------------
368
369/// A single text replacement within one file, delivered as part of a
370/// workspace edit. Positions are 0-based (line, UTF-16 character offset).
371#[derive(Debug, Clone)]
372pub struct LspTextEdit {
373 pub start_line: u32,
374 pub start_char: u32,
375 pub end_line: u32,
376 pub end_char: u32,
377 pub new_text: String,
378}
379
380/// All text edits for a single file, as part of a `WorkspaceEdit` result.
381#[derive(Debug, Clone)]
382pub struct LspFileEdit {
383 pub path: std::path::PathBuf,
384 pub edits: Vec<LspTextEdit>,
385}
386
387/// View-local operations for the editor rename prompt.
388#[derive(Debug, Clone)]
389pub enum LspRenameOp {
390 /// Open the rename prompt pre-filled with the current symbol name.
391 OpenPrompt { current_name: String },
392 /// Update the text input field.
393 Input(crate::widgets::input_field::InputFieldOp),
394 /// Close the prompt without renaming.
395 Dismiss,
396}
397// Project-wide search & replace
398// ---------------------------------------------------------------------------
399
400/// A single match span within a file line.
401#[derive(Debug, Clone)]
402pub struct MatchSpan {
403 /// 0-based line index.
404 pub line: usize,
405 /// Byte offset of match start within the line.
406 pub byte_start: usize,
407 /// Byte offset of match end within the line.
408 pub byte_end: usize,
409 /// Full text of the line (no newline).
410 pub line_text: String,
411}
412
413/// All matches found in one file.
414#[derive(Debug, Clone)]
415pub struct FileMatchResult {
416 pub path: PathBuf,
417 pub matches: Vec<MatchSpan>,
418}
419
420// ---------------------------------------------------------------------------
421// Git History Viewer view-local operations
422// ---------------------------------------------------------------------------
423
424#[derive(Debug, Clone)]
425pub enum GitHistoryOp {
426 /// Keystroke routed to the filter input field.
427 FilterInput(crate::widgets::input_field::InputFieldOp),
428 /// Async result: commits loaded for the current filter + generation.
429 CommitsLoaded {
430 generation: u64,
431 commits: Vec<crate::vcs::CommitInfo>,
432 },
433 /// User confirmed a revision selection (Enter on revision list).
434 /// Intercepted by app.rs which spawns file-list + diff loading.
435 SelectRevision,
436 /// Async result: file list for the selected revision.
437 FilesLoaded {
438 oid: String,
439 files: Vec<crate::vcs::FileChange>,
440 },
441 /// User navigated to or confirmed a file selection.
442 /// Intercepted by app.rs which spawns diff loading.
443 SelectFile,
444 /// Async result: diff for the selected revision + file.
445 DiffLoaded {
446 lines: Vec<crate::vcs::DiffLine>,
447 },
448 /// An async git task failed.
449 LoadError(String),
450}
451
452// ---------------------------------------------------------------------------
453// Branch Management View view-local operations
454// ---------------------------------------------------------------------------
455
456#[derive(Debug, Clone)]
457pub enum BranchViewOp {
458 /// Async result: branch lists loaded on view open.
459 BranchesLoaded {
460 local: Vec<String>,
461 remote: Vec<String>,
462 current: String,
463 },
464 /// Async error while loading branches.
465 LoadError(String),
466 /// User pressed Enter — intercepted by app.rs to spawn a checkout.
467 CheckoutBranch,
468 /// Async result: checkout succeeded; carries the new current branch name.
469 CheckoutCompleted(String),
470 /// Async result: checkout failed.
471 CheckoutError(String),
472 /// User pressed 'n' — open the new-branch name input.
473 NewBranchOpen,
474 /// Keystroke routed to the new-branch name input field.
475 NewBranchInput(crate::widgets::input_field::InputFieldOp),
476 /// User confirmed the new branch name (Enter while input is open).
477 /// Intercepted by app.rs to spawn a create+checkout task.
478 NewBranchConfirm,
479 /// User cancelled the new-branch input (Esc).
480 NewBranchCancel,
481 /// Async result: branch created and checked out.
482 CreateCompleted(String),
483 /// Async result: branch creation failed.
484 CreateError(String),
485 /// User pressed Delete (first press) — app.rs checks merge status and replies with
486 /// DeleteConfirmNeeded if required or performs immediate deletion.
487 DeleteRequested,
488 /// App replies that confirmation is required: (branch, merged)
489 DeleteConfirmNeeded { branch: String, merged: bool },
490 /// User pressed Delete again to confirm (view converts to this when pending matches).
491 DeleteConfirmed,
492 /// Async result: deletion succeeded — carries the deleted branch name.
493 DeleteCompleted(String),
494 /// Async deletion failed.
495 DeleteError(String),
496}
497
498// ---------------------------------------------------------------------------
499// Git Pull view-local operations
500// ---------------------------------------------------------------------------
501
502/// View-local operations for the Git Pull workflow view.
503///
504/// The pull flow is:
505/// 1. Fetch remote (`FetchCompleted` / `FetchError`)
506/// 2a. Fast-forward if possible (`FastForwardCompleted` / `FastForwardError`)
507/// 2b. Present rebase / merge choice; user navigates + confirms
508/// 3. Apply selected strategy (`RebaseCompleted` / `MergeCompleted` / errors)
509#[derive(Debug, Clone)]
510pub enum GitPullOp {
511 // ── Async results ────────────────────────────────────────────────────────
512
513 /// Fetch succeeded.
514 ///
515 /// `can_fast_forward` is `true` when HEAD is an ancestor of the upstream
516 /// ref — i.e., a fast-forward merge is safe and will be applied
517 /// automatically. When `false`, the view shows the Rebase / Merge choice.
518 FetchCompleted {
519 can_fast_forward: bool,
520 /// Short name of the upstream tracking ref (e.g. `"origin/main"`).
521 upstream: String,
522 /// Name of the local branch being updated.
523 current_branch: String,
524 },
525 /// Fetch failed (network error, no remote configured, etc.).
526 FetchError(String),
527 /// Fast-forward completed successfully.
528 FastForwardCompleted,
529 /// Fast-forward failed (should be rare — upstream diverged since fetch).
530 FastForwardError(String),
531 /// Rebase on the upstream completed successfully.
532 RebaseCompleted,
533 /// Rebase failed (conflicts or other errors).
534 RebaseError(String),
535 /// Merge of the upstream completed successfully.
536 MergeCompleted,
537 /// Merge failed.
538 MergeError(String),
539
540 // ── User actions (choice prompt) ─────────────────────────────────────────
541
542 /// Move the highlighted choice up.
543 ChoiceUp,
544 /// Move the highlighted choice down.
545 ChoiceDown,
546 /// Confirm the currently highlighted choice (Rebase / Merge / Cancel).
547 /// Intercepted by `app.rs`; the view does not need to spawn tasks.
548 ChoiceConfirm,
549}
550
551// ---------------------------------------------------------------------------
552// Git History Editor view-local operations
553// ---------------------------------------------------------------------------
554
555#[derive(Debug, Clone)]
556pub enum HistoryEditorOp {
557 // --- Async results -------------------------------------------------------
558 /// Initial commit list loaded on view open.
559 CommitsLoaded(Vec<crate::vcs::CommitInfo>),
560 /// Async operation failed (generic).
561 OperationFailed(String),
562 /// A git operation (rebase / reset) completed successfully.
563 /// Carries the new commit list and optionally the pre-op SHA for undo recording.
564 /// `pre_sha` is `None` for undo operations (so they don't themselves get undone again).
565 OperationCompleted {
566 commits: Vec<crate::vcs::CommitInfo>,
567 pre_sha: Option<String>,
568 description: String,
569 },
570
571 // --- User actions (intercepted by app.rs) --------------------------------
572 /// `u` — move selected commit one step toward older commits.
573 MoveUp,
574 /// `d` — move selected commit one step toward newer commits.
575 MoveDown,
576 /// `Enter` — confirm pending move (run git rebase).
577 ConfirmMove,
578 /// `r` — open the commit message editor for rewording.
579 StartReword,
580 /// `s` — open the combined message editor for squashing with the commit at
581 /// the given index (the older commit; one index higher than cursor).
582 StartSquash { target_idx: usize },
583 /// `ctrl+s` in editor mode — apply the edited message (triggers git).
584 ConfirmEdit,
585 /// `Del` — drop the selected commit (may require a second press).
586 DeleteCommit,
587 /// `ctrl+z` — undo the last operation (runs `git reset --hard <sha>`).
588 UndoLast,
589
590 // --- Editor mode text input (emitted in EditorMode) ----------------------
591 /// Insert a single character into the message buffer.
592 EditorInsertChar(char),
593 /// Insert a newline into the message buffer.
594 EditorInsertNewline,
595 /// Backspace in the message buffer.
596 EditorBackspace,
597 /// Move cursor left in the message buffer.
598 EditorCursorLeft,
599 /// Move cursor right in the message buffer.
600 EditorCursorRight,
601}
602
603
604#[derive(Debug, Clone)]
605pub enum TerminalOp {
606 /// Raw bytes received from the PTY process (sent by the async read task).
607 Output { id: u64, data: Vec<u8> },
608 /// Completed styled lines produced from PTY output by the async read task.
609 ///
610 /// Sent alongside `Output` so the main thread can maintain a styled
611 /// scrollback buffer per tab (enabling state persistence and reuse of
612 /// `DiagnosticsExtractor::extract_from_line`).
613 AppendScrollback { id: u64, lines: Vec<crate::vt_parser::StyledLine> },
614 /// Open a new tab. `command` defaults to the configured shell if `None`.
615 NewTab { command: Option<String> },
616 /// Close a tab by ID. If it was the last tab, a new shell is spawned.
617 CloseTab { id: u64 },
618 /// Switch to the tab at the given index (clamped to valid range).
619 SwitchToTab { index: usize },
620 /// Switch to the next tab (wraps around).
621 NextTab,
622 /// Switch to the previous tab (wraps around).
623 PrevTab,
624 /// Reset scroll to the bottom of the active tab.
625 ScrollReset,
626 /// Enable/disable link highlight mode (triggered by holding Ctrl while using the mouse).
627 SetLinkHighlight { enabled: bool },
628 /// Open the context menu for the given tab.
629 OpenMenu { id: u64 },
630 /// Close the context menu without taking action.
631 CloseMenu,
632 /// Confirm the highlighted context-menu item.
633 MenuConfirm,
634 /// Open the inline rename input for the given tab directly.
635 OpenRename { id: u64 },
636 /// Clear the screen of the given tab (sends VT clear sequence to PTY).
637 ClearTab { id: u64 },
638 /// Set the rename input text.
639 RenameChanged(String),
640 /// Commit the rename.
641 RenameConfirm,
642 /// Cancel the rename.
643 RenameCancel,
644 /// Open the tab selector popup (keyboard/mouse-navigable list of all tabs).
645 OpenTabSelector,
646 /// Terminal dimensions changed — resize all PTY tabs.
647 Resize { cols: u16, rows: u16 },
648 /// The PTY child process has exited.
649 ProcessExited { id: u64 },
650}
651
652#[derive(Debug, Clone)]
653pub enum SearchReplaceOp {
654 // Input field changes
655 QueryChanged(String),
656 ReplaceChanged(String),
657 IncludeGlobChanged(String),
658 ExcludeGlobChanged(String),
659 // Toggle options
660 ToggleRegex,
661 ToggleCase,
662 ToggleSmartCase,
663 // Tree navigation
664 TreeExpandOrEnter,
665 TreeCollapseOrLeave,
666 // Replace actions
667 ReplaceCurrentFile,
668 ReplaceAll,
669 // Undo / redo
670 Undo,
671 Redo,
672 // Async results
673 SearchResultsReady {
674 results: Vec<FileMatchResult>,
675 generation: u64,
676 },
677 ReplaceComplete {
678 path: PathBuf,
679 replaced_count: usize,
680 },
681 AsyncError(String),
682 ProgressUpdate {
683 scanned: usize,
684 total: usize,
685 },
686}
687
688#[derive(Debug, Clone)]
689pub enum Operation {
690 // Buffer mutations
691 InsertText {
692 path: Option<PathBuf>,
693 #[allow(dead_code)]
694 cursor: Position,
695 text: String,
696 },
697 /// Bracketed paste (or fallback-heuristic detected paste) delivered to the
698 /// active editor. Unlike `InsertText`, the full multi-line string is inserted
699 /// at once via `insert_text_raw` and also pushed to the clipboard register.
700 PasteText {
701 path: Option<PathBuf>,
702 text: String,
703 },
704 DeleteText {
705 path: Option<PathBuf>,
706 #[allow(dead_code)]
707 cursor: Position,
708 #[allow(dead_code)]
709 len: usize,
710 },
711 DeleteForward {
712 path: Option<PathBuf>,
713 #[allow(dead_code)]
714 cursor: Position,
715 },
716 DeleteWordBackward {
717 path: Option<PathBuf>,
718 },
719 DeleteWordForward {
720 path: Option<PathBuf>,
721 },
722 IndentLines {
723 path: Option<PathBuf>,
724 },
725 UnindentLines {
726 path: Option<PathBuf>,
727 },
728 DeleteLine {
729 path: Option<PathBuf>,
730 },
731 /// Replace the text from `start` to `end` (same line, byte offsets) with `text`.
732 /// Used by CompletionConfirm to delete the filter prefix before inserting.
733 ReplaceRange {
734 path: Option<PathBuf>,
735 start: Position,
736 end: Position,
737 text: String,
738 },
739 MoveCursor {
740 path: Option<PathBuf>,
741 cursor: Position,
742 },
743 Undo {
744 path: Option<PathBuf>,
745 },
746 Redo {
747 path: Option<PathBuf>,
748 },
749 ToggleFold {
750 path: Option<PathBuf>,
751 line: usize,
752 },
753 ToggleMarker {
754 path: Option<PathBuf>,
755 line: usize,
756 },
757 ToggleWordWrap,
758
759 // File lifecycle
760 OpenFile {
761 path: PathBuf,
762 },
763 /// Navigate from the project search tree to a match.
764 /// Opens the file (if not already active) while preserving the search
765 /// state, then positions the cursor at the given line and column.
766 GoToProjectMatch {
767 file: PathBuf,
768 line: usize,
769 col: usize,
770 },
771 /// Open an external URL in the system default browser.
772 OpenUrl {
773 url: String,
774 },
775 SaveFile {
776 path: PathBuf,
777 },
778 ReloadFromDisk {
779 path: PathBuf,
780 accept_external: bool,
781 /// When true, suppress the "Reloaded from disk." status message.
782 silent: bool,
783 },
784
785 // App / navigation
786 SwitchScreen {
787 to: ScreenKind,
788 },
789 /// Close the currently active overlay, sub-view, or modal.
790 ///
791 /// The handler in `app.rs` walks a priority list to determine what to
792 /// close (completion menu, search bar, modal sub-views, etc.).
793 Close,
794 Quit,
795
796 // Focus navigation — handled by whichever view is active
797 Focus(crate::widgets::focusable::FocusOp),
798
799 // Generic list/tree/scroll navigation — dispatched to the active view.
800 // Each view interprets these according to its own focus state.
801 NavigateUp,
802 NavigateDown,
803 NavigatePageUp,
804 NavigatePageDown,
805 NavigateHome,
806 NavigateEnd,
807
808 // View-local ops that don't cross view boundaries
809 FileSelectorLocal(FileSelectorOp),
810 CommandSelectorLocal(CommandSelectorOp),
811 LogViewLocal(LogViewOp),
812 CommitLocal(CommitOp),
813 GitHistoryLocal(GitHistoryOp),
814 BranchViewLocal(BranchViewOp),
815 HistoryEditorLocal(HistoryEditorOp),
816 GitPullLocal(GitPullOp),
817 ExtensionConfig(ExtensionConfigOp),
818
819 // LSP trigger ops (handled in app.rs, forwarded to LspClient)
820 LspTriggerCompletion {
821 #[allow(dead_code)]
822 path: Option<PathBuf>,
823 /// The character(s) that triggered completion (for LSP context)
824 #[allow(dead_code)]
825 trigger_char: Option<String>,
826 },
827 LspTriggerHover {
828 #[allow(dead_code)]
829 path: Option<PathBuf>,
830 /// If `Some`, this trigger was generated by the idle debounce timer.
831 /// The value is the generation counter at spawn time; the handler
832 /// ignores this operation if the stored generation has advanced.
833 idle_generation: Option<u64>,
834 },
835 LspGoToDefinition {
836 #[allow(dead_code)]
837 path: Option<PathBuf>,
838 },
839 /// Result from a go-to-definition request, dispatched back into the queue.
840 LspGoToDefinitionResult(Vec<LspLocation>),
841 /// Generic go-to operation for cross-context navigation.
842 GoTo {
843 target: GoToTarget,
844 },
845 /// LSP server returned trigger characters for a language.
846 LspTriggerCharactersReceived {
847 lang_id: String,
848 trigger_chars: Vec<String>,
849 },
850 /// LSP editor-local ops (dropdown navigation, response delivery).
851 LspLocal(LspOp),
852 /// Trigger the LSP rename prompt for the symbol under the cursor.
853 LspTriggerRename {
854 #[allow(dead_code)]
855 path: Option<PathBuf>,
856 },
857 /// Sent by the rename prompt after the user confirms a new name.
858 LspTriggerRenameWith {
859 new_name: String,
860 /// Cursor position (line, col) at the time the prompt was opened.
861 row: u32,
862 col: u32,
863 },
864 /// Workspace edit returned by the server — apply across all affected files.
865 LspRenameResult(Vec<LspFileEdit>),
866 /// Editor-local rename prompt ops.
867 LspRenameLocal(LspRenameOp),
868 /// Search/replace bar ops (editor-local).
869 SearchLocal(SearchOp),
870 /// Go-to-line bar ops (editor-local).
871 GoToLineLocal(GoToLineOp),
872 /// Selection management ops (editor-local).
873 SelectionLocal(SelectionOp),
874 /// Clipboard ops (editor-local).
875 ClipboardLocal(ClipOp),
876 /// Project-wide search & replace ops.
877 SearchReplaceLocal(SearchReplaceOp),
878
879 /// Execute a registered command by id (with pre-filled args).
880 RunCommand {
881 id: crate::commands::CommandId,
882 args: HashMap<String, ArgValue>,
883 },
884
885 /// Open a generic context menu at `(x, y)` with the given items.
886 ///
887 /// Items are `(label, command_id)` pairs. `app.rs` stores the menu on
888 /// [`crate::app_state::AppState`], intercepts input, and dispatches the selected command.
889 OpenContextMenu {
890 // Each item: (label, command_id, enabled_override)
891 // - enabled_override: `Some(bool)` to explicitly set enabled state,
892 // or `None` to let the runtime compute a sensible default.
893 items: Vec<(String, crate::commands::CommandId, Option<bool>)>,
894 x: u16,
895 y: u16,
896 },
897
898 /// Close the currently open context menu without selecting any item.
899 CloseContextMenu,
900
901 // Escape hatch for plugins
902 #[allow(dead_code)]
903 Generic {
904 source: Cow<'static, str>,
905 name: Cow<'static, str>,
906 payload: HashMap<String, ArgValue>,
907 },
908
909 // Extension system
910 /// Broadcast a named event to all active extensions.
911 ExtensionEvent {
912 name: String,
913 payload: HashMap<String, String>,
914 },
915 /// Display a user-visible notification (shown in status bar).
916 ShowNotification {
917 message: String,
918 level: NotificationLevel,
919 },
920 /// Update the IDE status bar slot with custom text.
921 UpdateStatusBar {
922 text: String,
923 #[allow(unused)]
924 slot: StatusSlot,
925 },
926 /// Request a terminal be created running the given command.
927 CreateTerminal {
928 command: String,
929 },
930 /// Send raw bytes to the PTY of the terminal tab with the given ID.
931 TerminalInput {
932 id: u64,
933 data: Vec<u8>,
934 },
935 /// Terminal view-local operations.
936 TerminalLocal(TerminalOp),
937 /// Show a picker (like command palette) populated with arbitrary items.
938 ShowPicker {
939 title: String,
940 items: Vec<PickerItem>,
941 },
942
943 /// Deliver pre-computed syntax-highlight spans for an editor buffer.
944 ///
945 /// Sent by the background highlight task; handled in `app.rs`, which stores
946 /// the result in `crate::views::editor::EditorView::highlight_cache` when the version still
947 /// matches the live buffer. The next frame then renders without blocking.
948 SetEditorHighlights {
949 path: Option<std::path::PathBuf>,
950 version: editor::buffer::Version,
951 start_line: usize,
952 generation: u64,
953 spans: Vec<Vec<editor::highlight::StyledSpan>>,
954 },
955
956 /// Incremental highlight update for multiple lines.
957 ///
958 /// Unlike SetEditorHighlights, this operation updates individual lines
959 /// without clearing existing highlights. This prevents highlights from
960 /// disappearing while typing or scrolling.
961 SetEditorHighlightsChunk {
962 path: Option<std::path::PathBuf>,
963 version: editor::buffer::Version,
964 generation: u64,
965 spans: Vec<(usize, Vec<editor::highlight::StyledSpan>)>,
966 },
967
968 /// Deliver git-diff change line numbers for the editor gutter.
969 ///
970 /// Sent by a background task after a file is opened or saved.
971 /// Contains the set of 0-based line indices that are added or modified
972 /// in the workdir relative to the index.
973 SetEditorGitChanges {
974 path: std::path::PathBuf,
975 /// 0-based line indices that have been added or modified.
976 changed_lines: std::collections::HashSet<usize>,
977 },
978
979 // -----------------------------------------------------------------------
980 // Issue registry — ephemeral commands + startup loads
981 // (no disk I/O; produced by async tasks and the persistent-load path)
982 // -----------------------------------------------------------------------
983
984 /// Add an issue to the registry (registry-only, no disk write).
985 ///
986 /// `issue.marker = Some(m)` → ephemeral (cleared via `ClearIssuesByMarker`).
987 /// `issue.marker = None` → used only for startup loads by the Persistent
988 /// Component. User-initiated persistent issues
989 /// must go through `PersistentIssueLocal(Add)`.
990 AddIssue { issue: NewIssue },
991
992 /// Remove an issue by ID (registry-only). Emits `IssueRemoved` on success.
993 RemoveIssue { id: IssueId },
994
995 /// Mark an issue as resolved (registry-only). Emits `IssueUpdated` on success.
996 ResolveIssue { id: IssueId },
997
998 /// Mark an issue as dismissed (registry-only). Emits `IssueUpdated` on success.
999 DismissIssue { id: IssueId },
1000
1001 /// Remove all ephemeral issues belonging to `marker`. Emits one
1002 /// `IssueRemoved` per removed issue.
1003 ClearIssuesByMarker { marker: String },
1004
1005 // -----------------------------------------------------------------------
1006 // Persistent issue operations — update registry AND write to disk
1007 // -----------------------------------------------------------------------
1008
1009 /// Persistent-issue commands: each variant updates the in-memory
1010 /// [`IssueRegistry`] **and** atomically rewrites `.oo/issues.yaml`.
1011 ///
1012 /// [`IssueRegistry`]: crate::issue_registry::IssueRegistry
1013 PersistentIssueLocal(PersistentIssueOp),
1014
1015 // -----------------------------------------------------------------------
1016 // Issue registry — events (enqueued by apply_operation after mutations)
1017 // -----------------------------------------------------------------------
1018
1019 /// An issue was added.
1020 IssueAdded { id: IssueId },
1021
1022 /// An issue was removed.
1023 IssueRemoved { id: IssueId },
1024
1025 /// An issue's `resolved` or `dismissed` flag was toggled.
1026 IssueUpdated { id: IssueId },
1027
1028 // -----------------------------------------------------------------------
1029 // Task system lifecycle events
1030 // -----------------------------------------------------------------------
1031
1032 /// A task was created and either started immediately or added to its queue.
1033 TaskScheduled(TaskId),
1034
1035 /// A task transitioned from `Pending` to `Running`.
1036 TaskStarted(TaskId),
1037
1038 /// A task reached a terminal status (`Success`, `Warning`, or `Error`).
1039 ///
1040 /// Sent by [`crate::task_executor::TaskExecutor`] when the spawned process
1041 /// exits. Handled by `apply_operation` in `app.rs`, which calls
1042 /// [`crate::task_registry::TaskRegistry::mark_finished`] and starts the
1043 /// next queued task if one exists.
1044 TaskFinished {
1045 id: TaskId,
1046 status: TaskStatus,
1047 },
1048
1049 /// A task was cancelled (running or queued).
1050 TaskCancelled(TaskId),
1051
1052 // -----------------------------------------------------------------------
1053 // Task system commands
1054 // -----------------------------------------------------------------------
1055
1056 /// Schedule a new task. Handled by `apply_operation`, which calls
1057 /// [`crate::task_registry::TaskRegistry::schedule_task`] and immediately
1058 /// spawns the executor if the queue was idle.
1059 ScheduleTask {
1060 key: TaskKey,
1061 trigger: TaskTrigger,
1062 /// The shell command to execute (passed through to the process).
1063 command: String,
1064 },
1065
1066 /// Cancel a running or queued task by its ID. Handled by `apply_operation`,
1067 /// which calls [`crate::task_registry::TaskRegistry::cancel`] and starts the
1068 /// next queued task if one exists.
1069 CancelTask(TaskId),
1070
1071 /// Open the log view and pre-populate its filter with the task's label so
1072 /// the user can quickly inspect task-related entries.
1073 OpenLogForTask { task_id: TaskId },
1074
1075 /// Open the Task Archive View.
1076 OpenTaskArchive,
1077
1078 /// Delivered by app.rs after it asynchronously loads task output from the
1079 /// log store. Switches to `TaskOutputView` and populates it with the lines.
1080 ShowTaskOutput {
1081 task_id: TaskId,
1082 title: String,
1083 status: TaskStatus,
1084 lines: Vec<String>,
1085 },
1086
1087 /// View-local ops for `crate::views::task_archive::TaskArchiveView`.
1088 TaskArchiveLocal(TaskArchiveOp),
1089
1090 /// Open the Issue List View.
1091 OpenIssueList,
1092
1093 /// View-local ops for `crate::views::issue_view::IssueView`.
1094 IssueViewLocal(IssueViewOp),
1095}
1096
1097// ---------------------------------------------------------------------------
1098// Persistent issue sub-operations
1099// ---------------------------------------------------------------------------
1100
1101/// Commands that manipulate **persistent** issues.
1102///
1103/// Each variant is handled by `apply_operation` in `app.rs`, which:
1104/// 1. Calls the corresponding [`IssueRegistry`] mutation method.
1105/// 2. Enqueues the matching `IssueAdded` / `IssueRemoved` / `IssueUpdated`
1106/// event operation.
1107/// 3. Spawns a fire-and-forget [`crate::persistent_issues::save_atomic`] task.
1108///
1109/// [`IssueRegistry`]: crate::issue_registry::IssueRegistry
1110#[derive(Debug, Clone)]
1111pub enum PersistentIssueOp {
1112 /// Add a new persistent issue and write the updated list to disk.
1113 Add { issue: PersistentNewIssue },
1114 /// Remove a persistent issue by ID and write the updated list to disk.
1115 Remove { id: IssueId },
1116 /// Mark a persistent issue as resolved and write the updated list to disk.
1117 Resolve { id: IssueId },
1118 /// Mark a persistent issue as dismissed and write the updated list to disk.
1119 Dismiss { id: IssueId },
1120}
1121
1122#[derive(Debug, Clone, PartialEq, Eq)]
1123pub enum NotificationLevel {
1124 Info,
1125 Warn,
1126 Error,
1127}
1128
1129#[derive(Debug, Clone, PartialEq, Eq)]
1130pub enum StatusSlot {
1131 Left,
1132 Center,
1133 Right,
1134}
1135
1136#[derive(Debug, Clone)]
1137#[allow(unused)]
1138pub struct PickerItem {
1139 #[allow(dead_code)]
1140 pub label: String,
1141 #[allow(dead_code)]
1142 pub value: String,
1143}
1144
1145#[derive(Debug, Clone)]
1146pub struct Event {
1147 /// Which subsystem produced the operation: "editor", "file_selector", "plugin.git"
1148 #[allow(dead_code)]
1149 pub source: Cow<'static, str>,
1150 #[allow(dead_code)]
1151 pub kind: EventKind,
1152}
1153
1154#[derive(Debug, Clone)]
1155pub enum EventKind {
1156 #[allow(dead_code)]
1157 Applied(Operation),
1158}
1159
1160impl Event {
1161 pub fn applied(source: impl Into<Cow<'static, str>>, op: Operation) -> Self {
1162 Self {
1163 source: source.into(),
1164 kind: EventKind::Applied(op),
1165 }
1166 }
1167}