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`; `THEME_COUNT` means "follow system").
329 ThemeChanged(usize),
330
331 /// Periodic poll of the OS light/dark setting (only while following it).
332 SystemThemeTick,
333
334 /// User selected a different editor from the picker.
335 EditorChanged(gitkraft_core::Editor),
336 /// Background `save_editor` completed (fire-and-forget, errors logged).
337 EditorSaved(Result<(), String>),
338 /// User clicked a recent repository entry on the welcome screen.
339 OpenRecentRepo(PathBuf),
340 // ── Search ────────────────────────────────────────────────────────────
341 /// Toggle the search overlay.
342 ToggleSearch,
343 /// User typed in the search input.
344 SearchQueryChanged(String),
345 /// Search results arrived from the background.
346 SearchResultsLoaded(Result<Vec<gitkraft_core::CommitInfo>, String>),
347 /// User selected a search result (by index).
348 SelectSearchResult(usize),
349 /// User confirmed the selected search result (Enter).
350 ConfirmSearchResult,
351 /// File list for commit-vs-workdir diff loaded.
352 SearchDiffFilesLoaded(Result<Vec<gitkraft_core::DiffFileEntry>, String>),
353 /// User toggled selection of a file in the search diff file list.
354 ToggleSearchDiffFile(usize),
355 /// User requested to view diff of selected search file against working tree.
356 ViewSearchDiffFile(usize),
357 /// The diff content for a search file loaded.
358 SearchFileDiffLoaded(Result<gitkraft_core::DiffInfo, String>),
359 /// User clicked "Select All" / "Deselect All" in search diff.
360 ToggleSearchDiffSelectAll,
361 /// Diff all selected files against working tree (combined view).
362 DiffSelectedFiles,
363 /// Combined diffs for selected files loaded.
364 SearchMultiDiffLoaded(Result<Vec<gitkraft_core::DiffInfo>, String>),
365 /// Go back from file diff view to file list in search.
366 SearchDiffBack,
367 /// User right-clicked a search result — open commit context menu.
368 OpenSearchResultContextMenu(usize),
369
370 /// File system change detected — auto-refresh staging area.
371 FileSystemChanged,
372
373 /// Open a file in the configured editor.
374 OpenInEditor(String),
375 /// Open multiple files in the configured editor.
376 OpenFilesInEditor(Vec<String>),
377 /// Open a file in the system's default program.
378 OpenInDefaultProgram(String),
379 /// Open multiple files in the system's default program.
380 OpenFilesInDefaultProgram(Vec<String>),
381 /// Preview a file's content in the diff pane (read-only).
382 PreviewFile(String),
383 /// Result of reading a file for preview.
384 FilePreviewLoaded(Result<(String, String), String>),
385 /// Show a file in the system file manager.
386 ShowInFolder(String),
387
388 /// Keyboard modifier state changed (e.g. Shift pressed/released).
389 ModifiersChanged(iced::keyboard::Modifiers),
390 /// Multiple commit file diffs loaded for a multi-file selection.
391 CommitMultiDiffLoaded(Result<Vec<gitkraft_core::DiffInfo>, String>),
392
393 // ── File history overlay ──────────────────────────────────────────────────
394 /// Open the file-history overlay for a specific repo-relative path.
395 ViewFileHistory(String),
396 /// Background load of file-history commits completed.
397 FileHistoryLoaded(Result<(String, Vec<gitkraft_core::CommitInfo>), String>),
398 /// Close the file-history overlay without taking an action.
399 CloseFileHistory,
400 /// User scrolled the file-history list.
401 FileHistoryScrolled(f32),
402 /// User selected a commit in file history — load its diff and close overlay.
403 SelectFileHistoryCommit(String),
404
405 // ── Blame overlay ─────────────────────────────────────────────────────────
406 /// Open the blame overlay for a specific repo-relative path.
407 ViewFileBlame(String),
408 /// Background load of blame lines completed.
409 FileBlameLoaded(Result<(String, Vec<gitkraft_core::BlameLine>), String>),
410 /// Close the blame overlay.
411 CloseFileBlame,
412 /// User scrolled the blame view.
413 BlameScrolled(f32),
414
415 // ── File deletion ─────────────────────────────────────────────────────────
416 /// Show delete-confirmation banner for a working-tree file.
417 DeleteFile(String),
418 /// User confirmed the deletion.
419 ConfirmDeleteFile,
420 /// User cancelled the deletion.
421 CancelDeleteFile,
422
423 /// Diff multiple files from a specific commit against the current working tree.
424 DiffMultiWithWorkingTree(String, Vec<String>), // (oid, file_paths)
425
426 /// Restore a single file from a specific commit to the working directory.
427 CheckoutFileAtCommit(String, String), // (oid, file_path)
428 /// Restore multiple files from a specific commit to the working directory.
429 CheckoutMultiFilesAtCommit(String, Vec<String>), // (oid, file_paths)
430
431 /// Cherry-pick a list of commits (by OID) onto the current branch.
432 CherryPickCommits(Vec<String>),
433 /// Revert a list of commits (by OID) in order.
434 RevertCommits(Vec<String>),
435
436 /// Combined range diff across multiple selected commits was loaded.
437 CommitRangeDiffLoaded(Result<Vec<gitkraft_core::DiffInfo>, String>),
438
439 /// The application window was resized.
440 WindowResized(f32, f32), // (width, height)
441 /// The application window was moved.
442 WindowMoved(f32, f32), // (x, y)
443
444 /// Open the GUI settings file (settings.json) in the configured editor.
445 /// Triggered by Ctrl/Cmd + , (like Zed).
446 OpenSettingsFile,
447
448 /// Show / hide the About dialog.
449 ToggleAbout,
450 /// Open the given URL in the system browser (About dialog links).
451 OpenUrl(String),
452
453 /// Shift+Down arrow — extends range selection in the file list (if files are
454 /// loaded) or in the commit log (fallback).
455 ShiftArrowDown,
456 /// Shift+Up arrow — same as above but upward.
457 ShiftArrowUp,
458
459 /// Jump to the first (newest) commit in the log.
460 JumpToFirstCommit,
461 /// Jump to the last (oldest) commit in the log.
462 JumpToLastCommit,
463
464 /// Toggle whether the currently selected commit is in the multi-selection set.
465 ToggleCommitSelection,
466
467 /// Fired ~10× per second to advance loading-spinner animation frames.
468 /// Only emitted while at least one tab is loading.
469 AnimationTick,
470
471 /// User interacted with the file preview editor (selection, edit, etc.).
472 PreviewEditorAction(iced::widget::text_editor::Action),
473 /// Close the file preview editor.
474 ClosePreview,
475
476 /// Mouse entered/left a commit row in the commit log.
477 HoverCommit(Option<usize>),
478
479 /// No-op (used for disabled buttons, etc.).
480 Noop,
481}