gitkraft_gui/message.rs
1//! Unified message type for the entire GitKraft GUI application.
2//!
3//! Every user interaction, async result callback, and internal event is
4//! represented as a variant of [`Message`]. The top-level `update` function
5//! pattern-matches on these and delegates to the appropriate feature handler.
6
7use std::path::PathBuf;
8
9use gitkraft_core::{DiffFileEntry, DiffInfo, StashEntry};
10
11// ── Payload types ─────────────────────────────────────────────────────────────
12
13/// Payload returned after successfully opening / refreshing a repository.
14///
15/// This is now a re-export of the shared core type so both GUI and TUI use
16/// the same struct definition.
17pub type RepoPayload = gitkraft_core::RepoSnapshot;
18
19/// Payload returned after a staging operation (stage / unstage / discard).
20#[derive(Debug, Clone)]
21pub struct StagingPayload {
22 pub unstaged: Vec<DiffFileEntry>,
23 pub staged: Vec<DiffFileEntry>,
24}
25
26/// Payload returned by a lazy-load page request.
27#[derive(Debug, Clone)]
28pub struct CommitPage {
29 pub commits: Vec<gitkraft_core::CommitInfo>,
30 pub graph_rows: Vec<gitkraft_core::GraphRow>,
31}
32
33// ── Message enum ──────────────────────────────────────────────────────────────
34
35#[derive(Debug, Clone)]
36pub enum Message {
37 // ── Tabs ──────────────────────────────────────────────────────────────
38 /// User clicked a tab in the tab bar.
39 SwitchTab(usize),
40 /// User clicked the "+" button to open a new empty tab.
41 NewTab,
42 /// User clicked the close (×) button on a tab.
43 CloseTab(usize),
44
45 // ── Repository ────────────────────────────────────────────────────────
46 /// User clicked "Open Repository" — launch the folder picker.
47 OpenRepo,
48 /// User clicked "Init Repository" — launch the folder picker for init.
49 InitRepo,
50 /// Folder picker returned a path (or was cancelled → `None`).
51 RepoSelected(Option<PathBuf>),
52 /// Folder picker returned a path for init (or was cancelled → `None`).
53 RepoInitSelected(Option<PathBuf>),
54 /// Async repo-open completed.
55 RepoOpened(Result<RepoPayload, String>),
56 /// User requested a full refresh of the current repo.
57 RefreshRepo,
58 /// Async refresh completed.
59 RepoRefreshed(Result<RepoPayload, String>),
60
61 // ── Branches ──────────────────────────────────────────────────────────
62 /// User clicked a branch name → checkout that branch.
63 CheckoutBranch(String),
64 /// Async checkout completed.
65 BranchCheckedOut(Result<(), String>),
66 /// User submitted the new-branch form.
67 CreateBranch,
68 /// User is typing a new branch name.
69 NewBranchNameChanged(String),
70 /// Async branch creation completed.
71 BranchCreated(Result<(), String>),
72 /// User clicked the delete button next to a branch.
73 DeleteBranch(String),
74 /// Async branch deletion completed.
75 BranchDeleted(Result<(), String>),
76 /// Toggle visibility of the new-branch inline form.
77 ToggleBranchCreate,
78 /// Collapse or expand the Local branches section.
79 ToggleLocalBranches,
80 /// Collapse or expand the Remote branches section.
81 ToggleRemoteBranches,
82
83 // ── Commits ───────────────────────────────────────────────────────────
84 /// User clicked a commit row in the log.
85 SelectCommit(usize),
86 /// Async commit file-list load completed (lightweight — no diff content).
87 CommitFileListLoaded(Result<Vec<DiffFileEntry>, String>),
88 /// Async single-file diff load completed.
89 SingleFileDiffLoaded(Result<DiffInfo, String>),
90 /// Diff a file from a commit against the working tree.
91 DiffFileWithWorkingTree(String, String), // (oid, file_path)
92 /// Result of diffing a file with the working tree.
93 DiffWithWorkingTreeLoaded(Result<DiffInfo, String>),
94 /// The commit log scrollable was scrolled.
95 /// Carries `(absolute_y, relative_y)` — absolute for virtual-window
96 /// positioning, relative (0.0 = top, 1.0 = bottom) for load-more trigger.
97 CommitLogScrolled(f32, f32),
98 /// The diff viewer scrollable was scrolled — carries `absolute_y`.
99 DiffViewScrolled(f32),
100 /// A lazy-loaded page of additional commits was fetched from the background.
101 MoreCommitsLoaded(Result<CommitPage, String>),
102
103 // ── Staging ───────────────────────────────────────────────────────────
104 /// Stage a single file by its path.
105 StageFile(String),
106 /// Unstage a single file by its path.
107 UnstageFile(String),
108 /// Stage all unstaged files.
109 StageAll,
110 /// Unstage all staged files.
111 UnstageAll,
112 /// Discard working-directory changes for a file.
113 DiscardFile(String),
114 /// User confirmed the discard for a file.
115 ConfirmDiscard(String),
116 /// User cancelled a pending discard.
117 CancelDiscard,
118 /// Toggle selection of an unstaged file (Shift+Click).
119 ToggleSelectUnstaged(String),
120 /// Toggle selection of a staged file (Shift+Click).
121 ToggleSelectStaged(String),
122 /// Stage all currently selected unstaged files.
123 StageSelected,
124 /// Unstage all currently selected staged files.
125 UnstageSelected,
126 /// Discard all currently selected unstaged files.
127 DiscardSelected,
128 /// Discard a staged file (unstage + discard working dir changes).
129 DiscardStagedFile(String),
130 /// Async staging operation completed.
131 StagingUpdated(Result<StagingPayload, String>),
132
133 // ── Commit creation ───────────────────────────────────────────────────
134 /// User is typing in the commit-message input.
135 CommitMessageChanged(String),
136 /// User clicked "Commit".
137 CreateCommit,
138 /// Async commit creation completed.
139 CommitCreated(Result<(), String>),
140
141 // ── Stash ─────────────────────────────────────────────────────────────
142 /// Save the current working state as a stash.
143 StashSave,
144 /// Pop (apply + drop) a stash by index.
145 StashPop(usize),
146 /// Drop (delete) a stash by index without applying.
147 StashDrop(usize),
148 /// Pop (apply + drop) the most recent stash — shortcut from the staging area.
149 StashPopFirst,
150 /// Async stash operation completed.
151 StashUpdated(Result<Vec<StashEntry>, String>),
152 /// User is typing in the stash-message input.
153 StashMessageChanged(String),
154 /// User right-clicked a stash entry.
155 OpenStashContextMenu(usize),
156 /// User right-clicked a file in the commit diff file list.
157 OpenCommitFileContextMenu(String, String), // (oid, file_path)
158 /// User right-clicked an unstaged file.
159 OpenUnstagedFileContextMenu(String),
160 /// User right-clicked a staged file.
161 OpenStagedFileContextMenu(String),
162 /// User wants to view the diff of a stash entry.
163 ViewStashDiff(usize),
164 /// Stash diff loaded.
165 StashDiffLoaded(Result<Vec<DiffInfo>, String>),
166 /// Apply a stash (like pop but keeps the stash).
167 StashApply(usize),
168
169 // ── Remotes ───────────────────────────────────────────────────────────
170 /// Fetch from the first configured remote.
171 Fetch,
172 /// Async fetch completed.
173 FetchCompleted(Result<(), String>),
174
175 // ── UI ────────────────────────────────────────────────────────────────
176 /// User clicked a file in the commit-diff file list (by index into `commit_files`).
177 SelectDiffByIndex(usize),
178 /// User clicked a file in the staging area to view its diff (triggers on-demand load).
179 /// The `bool` is `true` for staged, `false` for unstaged.
180 LoadStagingFileDiff(String, bool),
181 /// On-demand staging file diff loaded.
182 StagingFileDiffLoaded(Result<DiffInfo, String>),
183 /// Dismiss the current error banner.
184 DismissError,
185 /// Zoom in (increase UI scale).
186 ZoomIn,
187 /// Zoom out (decrease UI scale).
188 ZoomOut,
189 /// Reset zoom to 100%.
190 ZoomReset,
191 /// Toggle the left sidebar.
192 ToggleSidebar,
193 /// Close the current repository and return to the welcome screen.
194 CloseRepo,
195
196 // ── Pane resize ───────────────────────────────────────────────────────
197 /// User pressed the mouse button on a vertical divider to start dragging.
198 PaneDragStart(crate::state::DragTarget, f32),
199 /// User pressed the mouse button on the horizontal staging divider.
200 PaneDragStartH(crate::state::DragTargetH, f32),
201 /// Mouse moved during a drag — `(x, y)` in window coordinates.
202 PaneDragMove(f32, f32),
203 /// Mouse button released — stop dragging.
204 PaneDragEnd,
205 // ── Async persistence results ─────────────────────────────────────
206 /// Background `record_repo_opened` + `load_settings` completed.
207 /// Carries the refreshed recent-repos list (or an error string).
208 RepoRecorded(Result<Vec<gitkraft_core::RepoHistoryEntry>, String>),
209 /// Background `load_settings` completed (e.g. after closing a repo).
210 SettingsLoaded(Result<Vec<gitkraft_core::RepoHistoryEntry>, String>),
211 /// Background `save_theme` completed (fire-and-forget, errors logged).
212 ThemeSaved(Result<(), String>),
213 /// Background layout save completed (fire-and-forget, errors logged).
214 LayoutSaved(Result<(), String>),
215 /// Layout loaded from persisted settings on startup.
216 LayoutLoaded(Result<Option<gitkraft_core::LayoutSettings>, String>),
217 /// Background session save completed (fire-and-forget).
218 SessionSaved(Result<(), String>),
219 /// Async restore of a specific tab (by index) completed on startup.
220 RepoRestoredAt(usize, Result<RepoPayload, String>),
221
222 // ── Context menus ─────────────────────────────────────────────────────────────
223 /// User right-clicked a local branch.
224 /// Payload: (branch_name, index_in_local_list, is_current_branch).
225 OpenBranchContextMenu(String, usize, bool),
226
227 /// User right-clicked a remote branch.
228 OpenRemoteBranchContextMenu(String),
229
230 /// Checkout a remote branch (creates local tracking branch).
231 CheckoutRemoteBranch(String),
232
233 /// Delete a remote branch.
234 DeleteRemoteBranch(String),
235 /// User right-clicked a commit row.
236 OpenCommitContextMenu(usize),
237
238 /// Dismiss the context menu without taking an action.
239 CloseContextMenu,
240
241 // ── Branch actions ────────────────────────────────────────────────────────────
242 /// Push the named branch to its default remote.
243 PushBranch(String),
244
245 /// Force-push the current branch to its remote (with --force-with-lease).
246 ForcePushBranch,
247
248 /// Pull the current branch from its remote, rebasing local commits on top.
249 PullBranch(String),
250
251 /// Rebase the current HEAD onto `target` (a branch name or OID string).
252 RebaseOnto(String),
253
254 /// Begin an inline rename: record the branch being renamed and pre-fill input.
255 BeginRenameBranch(String),
256
257 /// User is typing in the rename input.
258 RenameBranchInputChanged(String),
259
260 /// User confirmed the rename.
261 ConfirmRenameBranch,
262
263 /// User cancelled the rename.
264 CancelRename,
265
266 /// Merge a named branch into the current HEAD branch.
267 MergeBranch(String),
268
269 /// Begin an inline tag-creation form at the given commit OID.
270 /// The bool indicates whether this is an annotated tag (true) or lightweight (false).
271 BeginCreateTag(String, bool),
272
273 /// User is typing in the tag name input.
274 TagNameChanged(String),
275
276 /// User is typing in the annotated tag message input.
277 TagMessageChanged(String),
278
279 /// User confirmed tag creation.
280 ConfirmCreateTag,
281
282 /// User cancelled tag creation.
283 CancelCreateTag,
284
285 /// Cherry-pick a commit by OID onto the current branch.
286 CherryPickCommit(String),
287
288 /// Begin an inline "create branch at this commit" form.
289 BeginCreateBranchAtCommit(String),
290
291 /// User confirmed branch creation at a specific commit.
292 ConfirmCreateBranchAtCommit,
293
294 /// User cancelled branch creation at a specific commit.
295 CancelCreateBranchAtCommit,
296
297 /// Execute a commit action that requires no further user input.
298 /// Actions that need input (branch name, tag name) continue to use the
299 /// existing `BeginCreate*` messages.
300 ExecuteCommitAction(String, gitkraft_core::CommitAction),
301
302 // ── Commit actions ────────────────────────────────────────────────────────────
303 /// Checkout a specific commit in detached HEAD mode.
304 CheckoutCommitDetached(String),
305
306 /// Rebase the current branch on top of a specific commit.
307 RebaseOntoCommit(String),
308
309 /// Revert a specific commit (creates a revert commit).
310 RevertCommit(String),
311
312 /// git reset --soft `oid` — move HEAD, keep staged + working changes.
313 ResetSoft(String),
314 /// git reset --mixed `oid` — move HEAD and unstage; keep working directory.
315 ResetMixed(String),
316 /// git reset --hard `oid` — move HEAD and discard all uncommitted changes.
317 ResetHard(String),
318
319 // ── Shared ───────────────────────────────────────────────────────────────────
320 /// Copy a string to the system clipboard.
321 CopyText(String),
322
323 /// Generic result for any git operation that produces a full repo refresh.
324 /// The operation itself is responsible for a descriptive error string.
325 GitOperationResult(Result<RepoPayload, String>),
326
327 /// User selected a different theme from the picker (by index into
328 /// `gitkraft_core::THEME_NAMES`).
329 ThemeChanged(usize),
330
331 /// User selected a different editor from the picker.
332 EditorChanged(gitkraft_core::Editor),
333 /// Background `save_editor` completed (fire-and-forget, errors logged).
334 EditorSaved(Result<(), String>),
335 /// User clicked a recent repository entry on the welcome screen.
336 OpenRecentRepo(PathBuf),
337 // ── Search ────────────────────────────────────────────────────────────
338 /// Toggle the search overlay.
339 ToggleSearch,
340 /// User typed in the search input.
341 SearchQueryChanged(String),
342 /// Search results arrived from the background.
343 SearchResultsLoaded(Result<Vec<gitkraft_core::CommitInfo>, String>),
344 /// User selected a search result (by index).
345 SelectSearchResult(usize),
346 /// User confirmed the selected search result (Enter).
347 ConfirmSearchResult,
348 /// File list for commit-vs-workdir diff loaded.
349 SearchDiffFilesLoaded(Result<Vec<gitkraft_core::DiffFileEntry>, String>),
350 /// User toggled selection of a file in the search diff file list.
351 ToggleSearchDiffFile(usize),
352 /// User requested to view diff of selected search file against working tree.
353 ViewSearchDiffFile(usize),
354 /// The diff content for a search file loaded.
355 SearchFileDiffLoaded(Result<gitkraft_core::DiffInfo, String>),
356 /// User clicked "Select All" / "Deselect All" in search diff.
357 ToggleSearchDiffSelectAll,
358 /// Diff all selected files against working tree (combined view).
359 DiffSelectedFiles,
360 /// Combined diffs for selected files loaded.
361 SearchMultiDiffLoaded(Result<Vec<gitkraft_core::DiffInfo>, String>),
362 /// Go back from file diff view to file list in search.
363 SearchDiffBack,
364 /// User right-clicked a search result — open commit context menu.
365 OpenSearchResultContextMenu(usize),
366
367 /// File system change detected — auto-refresh staging area.
368 FileSystemChanged,
369
370 /// Open a file in the configured editor.
371 OpenInEditor(String),
372 /// Open multiple files in the configured editor.
373 OpenFilesInEditor(Vec<String>),
374 /// Open a file in the system's default program.
375 OpenInDefaultProgram(String),
376 /// Open multiple files in the system's default program.
377 OpenFilesInDefaultProgram(Vec<String>),
378 /// Preview a file's content in the diff pane (read-only).
379 PreviewFile(String),
380 /// Result of reading a file for preview.
381 FilePreviewLoaded(Result<(String, String), String>),
382 /// Show a file in the system file manager.
383 ShowInFolder(String),
384
385 /// Keyboard modifier state changed (e.g. Shift pressed/released).
386 ModifiersChanged(iced::keyboard::Modifiers),
387 /// Multiple commit file diffs loaded for a multi-file selection.
388 CommitMultiDiffLoaded(Result<Vec<gitkraft_core::DiffInfo>, String>),
389
390 // ── File history overlay ──────────────────────────────────────────────────
391 /// Open the file-history overlay for a specific repo-relative path.
392 ViewFileHistory(String),
393 /// Background load of file-history commits completed.
394 FileHistoryLoaded(Result<(String, Vec<gitkraft_core::CommitInfo>), String>),
395 /// Close the file-history overlay without taking an action.
396 CloseFileHistory,
397 /// User scrolled the file-history list.
398 FileHistoryScrolled(f32),
399 /// User selected a commit in file history — load its diff and close overlay.
400 SelectFileHistoryCommit(String),
401
402 // ── Blame overlay ─────────────────────────────────────────────────────────
403 /// Open the blame overlay for a specific repo-relative path.
404 ViewFileBlame(String),
405 /// Background load of blame lines completed.
406 FileBlameLoaded(Result<(String, Vec<gitkraft_core::BlameLine>), String>),
407 /// Close the blame overlay.
408 CloseFileBlame,
409 /// User scrolled the blame view.
410 BlameScrolled(f32),
411
412 // ── File deletion ─────────────────────────────────────────────────────────
413 /// Show delete-confirmation banner for a working-tree file.
414 DeleteFile(String),
415 /// User confirmed the deletion.
416 ConfirmDeleteFile,
417 /// User cancelled the deletion.
418 CancelDeleteFile,
419
420 /// Diff multiple files from a specific commit against the current working tree.
421 DiffMultiWithWorkingTree(String, Vec<String>), // (oid, file_paths)
422
423 /// Restore a single file from a specific commit to the working directory.
424 CheckoutFileAtCommit(String, String), // (oid, file_path)
425 /// Restore multiple files from a specific commit to the working directory.
426 CheckoutMultiFilesAtCommit(String, Vec<String>), // (oid, file_paths)
427
428 /// Cherry-pick a list of commits (by OID) onto the current branch.
429 CherryPickCommits(Vec<String>),
430 /// Revert a list of commits (by OID) in order.
431 RevertCommits(Vec<String>),
432
433 /// Combined range diff across multiple selected commits was loaded.
434 CommitRangeDiffLoaded(Result<Vec<gitkraft_core::DiffInfo>, String>),
435
436 /// The application window was resized.
437 WindowResized(f32, f32), // (width, height)
438 /// The application window was moved.
439 WindowMoved(f32, f32), // (x, y)
440
441 /// Open the GUI settings file (settings.json) in the configured editor.
442 /// Triggered by Ctrl/Cmd + , (like Zed).
443 OpenSettingsFile,
444
445 /// Shift+Down arrow — extends range selection in the file list (if files are
446 /// loaded) or in the commit log (fallback).
447 ShiftArrowDown,
448 /// Shift+Up arrow — same as above but upward.
449 ShiftArrowUp,
450
451 /// Jump to the first (newest) commit in the log.
452 JumpToFirstCommit,
453 /// Jump to the last (oldest) commit in the log.
454 JumpToLastCommit,
455
456 /// Toggle whether the currently selected commit is in the multi-selection set.
457 ToggleCommitSelection,
458
459 /// Fired ~10× per second to advance loading-spinner animation frames.
460 /// Only emitted while at least one tab is loading.
461 AnimationTick,
462
463 /// User interacted with the file preview editor (selection, edit, etc.).
464 PreviewEditorAction(iced::widget::text_editor::Action),
465 /// Close the file preview editor.
466 ClosePreview,
467
468 /// Mouse entered/left a commit row in the commit log.
469 HoverCommit(Option<usize>),
470
471 /// No-op (used for disabled buttons, etc.).
472 Noop,
473}