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<DiffInfo>,
23 pub staged: Vec<DiffInfo>,
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.
179 SelectDiff(DiffInfo),
180 /// Dismiss the current error banner.
181 DismissError,
182 /// Zoom in (increase UI scale).
183 ZoomIn,
184 /// Zoom out (decrease UI scale).
185 ZoomOut,
186 /// Reset zoom to 100%.
187 ZoomReset,
188 /// Toggle the left sidebar.
189 ToggleSidebar,
190 /// Close the current repository and return to the welcome screen.
191 CloseRepo,
192
193 // ── Pane resize ───────────────────────────────────────────────────────
194 /// User pressed the mouse button on a vertical divider to start dragging.
195 PaneDragStart(crate::state::DragTarget, f32),
196 /// User pressed the mouse button on the horizontal staging divider.
197 PaneDragStartH(crate::state::DragTargetH, f32),
198 /// Mouse moved during a drag — `(x, y)` in window coordinates.
199 PaneDragMove(f32, f32),
200 /// Mouse button released — stop dragging.
201 PaneDragEnd,
202 // ── Async persistence results ─────────────────────────────────────
203 /// Background `record_repo_opened` + `load_settings` completed.
204 /// Carries the refreshed recent-repos list (or an error string).
205 RepoRecorded(Result<Vec<gitkraft_core::RepoHistoryEntry>, String>),
206 /// Background `load_settings` completed (e.g. after closing a repo).
207 SettingsLoaded(Result<Vec<gitkraft_core::RepoHistoryEntry>, String>),
208 /// Background `save_theme` completed (fire-and-forget, errors logged).
209 ThemeSaved(Result<(), String>),
210 /// Background layout save completed (fire-and-forget, errors logged).
211 LayoutSaved(Result<(), String>),
212 /// Layout loaded from persisted settings on startup.
213 LayoutLoaded(Result<Option<gitkraft_core::LayoutSettings>, String>),
214 /// Background session save completed (fire-and-forget).
215 SessionSaved(Result<(), String>),
216 /// Async restore of a specific tab (by index) completed on startup.
217 RepoRestoredAt(usize, Result<RepoPayload, String>),
218
219 // ── Context menus ─────────────────────────────────────────────────────────────
220 /// User right-clicked a local branch.
221 /// Payload: (branch_name, index_in_local_list, is_current_branch).
222 OpenBranchContextMenu(String, usize, bool),
223
224 /// User right-clicked a remote branch.
225 OpenRemoteBranchContextMenu(String),
226
227 /// Checkout a remote branch (creates local tracking branch).
228 CheckoutRemoteBranch(String),
229
230 /// Delete a remote branch.
231 DeleteRemoteBranch(String),
232 /// User right-clicked a commit row.
233 OpenCommitContextMenu(usize),
234
235 /// Dismiss the context menu without taking an action.
236 CloseContextMenu,
237
238 // ── Branch actions ────────────────────────────────────────────────────────────
239 /// Push the named branch to its default remote.
240 PushBranch(String),
241
242 /// Force-push the current branch to its remote (with --force-with-lease).
243 ForcePushBranch,
244
245 /// Pull the current branch from its remote, rebasing local commits on top.
246 PullBranch(String),
247
248 /// Rebase the current HEAD onto `target` (a branch name or OID string).
249 RebaseOnto(String),
250
251 /// Begin an inline rename: record the branch being renamed and pre-fill input.
252 BeginRenameBranch(String),
253
254 /// User is typing in the rename input.
255 RenameBranchInputChanged(String),
256
257 /// User confirmed the rename.
258 ConfirmRenameBranch,
259
260 /// User cancelled the rename.
261 CancelRename,
262
263 /// Merge a named branch into the current HEAD branch.
264 MergeBranch(String),
265
266 /// Begin an inline tag-creation form at the given commit OID.
267 /// The bool indicates whether this is an annotated tag (true) or lightweight (false).
268 BeginCreateTag(String, bool),
269
270 /// User is typing in the tag name input.
271 TagNameChanged(String),
272
273 /// User is typing in the annotated tag message input.
274 TagMessageChanged(String),
275
276 /// User confirmed tag creation.
277 ConfirmCreateTag,
278
279 /// User cancelled tag creation.
280 CancelCreateTag,
281
282 /// Cherry-pick a commit by OID onto the current branch.
283 CherryPickCommit(String),
284
285 /// Begin an inline "create branch at this commit" form.
286 BeginCreateBranchAtCommit(String),
287
288 /// User confirmed branch creation at a specific commit.
289 ConfirmCreateBranchAtCommit,
290
291 /// User cancelled branch creation at a specific commit.
292 CancelCreateBranchAtCommit,
293
294 /// Execute a commit action that requires no further user input.
295 /// Actions that need input (branch name, tag name) continue to use the
296 /// existing `BeginCreate*` messages.
297 ExecuteCommitAction(String, gitkraft_core::CommitAction),
298
299 // ── Commit actions ────────────────────────────────────────────────────────────
300 /// Checkout a specific commit in detached HEAD mode.
301 CheckoutCommitDetached(String),
302
303 /// Rebase the current branch on top of a specific commit.
304 RebaseOntoCommit(String),
305
306 /// Revert a specific commit (creates a revert commit).
307 RevertCommit(String),
308
309 /// git reset --soft `oid` — move HEAD, keep staged + working changes.
310 ResetSoft(String),
311 /// git reset --mixed `oid` — move HEAD and unstage; keep working directory.
312 ResetMixed(String),
313 /// git reset --hard `oid` — move HEAD and discard all uncommitted changes.
314 ResetHard(String),
315
316 // ── Shared ───────────────────────────────────────────────────────────────────
317 /// Copy a string to the system clipboard.
318 CopyText(String),
319
320 /// Generic result for any git operation that produces a full repo refresh.
321 /// The operation itself is responsible for a descriptive error string.
322 GitOperationResult(Result<RepoPayload, String>),
323
324 /// User selected a different theme from the picker (by index into
325 /// `gitkraft_core::THEME_NAMES`).
326 ThemeChanged(usize),
327
328 /// User selected a different editor from the picker.
329 EditorChanged(gitkraft_core::Editor),
330 /// Background `save_editor` completed (fire-and-forget, errors logged).
331 EditorSaved(Result<(), String>),
332 /// User clicked a recent repository entry on the welcome screen.
333 OpenRecentRepo(PathBuf),
334 // ── Search ────────────────────────────────────────────────────────────
335 /// Toggle the search overlay.
336 ToggleSearch,
337 /// User typed in the search input.
338 SearchQueryChanged(String),
339 /// Search results arrived from the background.
340 SearchResultsLoaded(Result<Vec<gitkraft_core::CommitInfo>, String>),
341 /// User selected a search result (by index).
342 SelectSearchResult(usize),
343 /// User confirmed the selected search result (Enter).
344 ConfirmSearchResult,
345 /// File list for commit-vs-workdir diff loaded.
346 SearchDiffFilesLoaded(Result<Vec<gitkraft_core::DiffFileEntry>, String>),
347 /// User toggled selection of a file in the search diff file list.
348 ToggleSearchDiffFile(usize),
349 /// User requested to view diff of selected search file against working tree.
350 ViewSearchDiffFile(usize),
351 /// The diff content for a search file loaded.
352 SearchFileDiffLoaded(Result<gitkraft_core::DiffInfo, String>),
353 /// User clicked "Select All" / "Deselect All" in search diff.
354 ToggleSearchDiffSelectAll,
355 /// Diff all selected files against working tree (combined view).
356 DiffSelectedFiles,
357 /// Combined diffs for selected files loaded.
358 SearchMultiDiffLoaded(Result<Vec<gitkraft_core::DiffInfo>, String>),
359 /// Go back from file diff view to file list in search.
360 SearchDiffBack,
361 /// User right-clicked a search result — open commit context menu.
362 OpenSearchResultContextMenu(usize),
363
364 /// File system change detected — auto-refresh staging area.
365 FileSystemChanged,
366
367 /// Open a file in the configured editor.
368 OpenInEditor(String),
369 /// Open multiple files in the configured editor.
370 OpenFilesInEditor(Vec<String>),
371 /// Open a file in the system's default program.
372 OpenInDefaultProgram(String),
373 /// Open multiple files in the system's default program.
374 OpenFilesInDefaultProgram(Vec<String>),
375 /// Preview a file's content in the diff pane (read-only).
376 PreviewFile(String),
377 /// Result of reading a file for preview.
378 FilePreviewLoaded(Result<(String, String), String>),
379 /// Show a file in the system file manager.
380 ShowInFolder(String),
381
382 /// Keyboard modifier state changed (e.g. Shift pressed/released).
383 ModifiersChanged(iced::keyboard::Modifiers),
384 /// Multiple commit file diffs loaded for a multi-file selection.
385 CommitMultiDiffLoaded(Result<Vec<gitkraft_core::DiffInfo>, String>),
386
387 // ── File history overlay ──────────────────────────────────────────────────
388 /// Open the file-history overlay for a specific repo-relative path.
389 ViewFileHistory(String),
390 /// Background load of file-history commits completed.
391 FileHistoryLoaded(Result<(String, Vec<gitkraft_core::CommitInfo>), String>),
392 /// Close the file-history overlay without taking an action.
393 CloseFileHistory,
394 /// User scrolled the file-history list.
395 FileHistoryScrolled(f32),
396 /// User selected a commit in file history — load its diff and close overlay.
397 SelectFileHistoryCommit(String),
398
399 // ── Blame overlay ─────────────────────────────────────────────────────────
400 /// Open the blame overlay for a specific repo-relative path.
401 ViewFileBlame(String),
402 /// Background load of blame lines completed.
403 FileBlameLoaded(Result<(String, Vec<gitkraft_core::BlameLine>), String>),
404 /// Close the blame overlay.
405 CloseFileBlame,
406 /// User scrolled the blame view.
407 BlameScrolled(f32),
408
409 // ── File deletion ─────────────────────────────────────────────────────────
410 /// Show delete-confirmation banner for a working-tree file.
411 DeleteFile(String),
412 /// User confirmed the deletion.
413 ConfirmDeleteFile,
414 /// User cancelled the deletion.
415 CancelDeleteFile,
416
417 /// Diff multiple files from a specific commit against the current working tree.
418 DiffMultiWithWorkingTree(String, Vec<String>), // (oid, file_paths)
419
420 /// Restore a single file from a specific commit to the working directory.
421 CheckoutFileAtCommit(String, String), // (oid, file_path)
422 /// Restore multiple files from a specific commit to the working directory.
423 CheckoutMultiFilesAtCommit(String, Vec<String>), // (oid, file_paths)
424
425 /// Cherry-pick a list of commits (by OID) onto the current branch.
426 CherryPickCommits(Vec<String>),
427 /// Revert a list of commits (by OID) in order.
428 RevertCommits(Vec<String>),
429
430 /// Combined range diff across multiple selected commits was loaded.
431 CommitRangeDiffLoaded(Result<Vec<gitkraft_core::DiffInfo>, String>),
432
433 /// The application window was resized.
434 WindowResized(f32, f32), // (width, height)
435 /// The application window was moved.
436 WindowMoved(f32, f32), // (x, y)
437
438 /// Open the GUI settings file (settings.json) in the configured editor.
439 /// Triggered by Ctrl/Cmd + , (like Zed).
440 OpenSettingsFile,
441
442 /// Shift+Down arrow — extends range selection in the file list (if files are
443 /// loaded) or in the commit log (fallback).
444 ShiftArrowDown,
445 /// Shift+Up arrow — same as above but upward.
446 ShiftArrowUp,
447
448 /// Jump to the first (newest) commit in the log.
449 JumpToFirstCommit,
450 /// Jump to the last (oldest) commit in the log.
451 JumpToLastCommit,
452
453 /// Toggle whether the currently selected commit is in the multi-selection set.
454 ToggleCommitSelection,
455
456 /// Fired ~10× per second to advance loading-spinner animation frames.
457 /// Only emitted while at least one tab is loading.
458 AnimationTick,
459
460 /// No-op (used for disabled buttons, etc.).
461 Noop,
462}