1use iced::Task;
8
9use crate::message::Message;
10use crate::state::GitKraft;
11
12impl GitKraft {
13 pub fn update(&mut self, message: Message) -> Task<Message> {
17 match &message {
18 Message::SwitchTab(index) => {
20 let index = *index;
21 if index < self.tabs.len() {
22 self.active_tab = index;
23 }
24 Task::none()
25 }
26
27 Message::NewTab => {
28 self.tabs.push(crate::state::RepoTab::new_empty());
29 self.active_tab = self.tabs.len() - 1;
30 crate::features::repo::commands::load_recent_repos_async()
32 }
33
34 Message::CloseTab(index) => {
35 let index = *index;
36 if self.tabs.len() > 1 && index < self.tabs.len() {
37 self.tabs.remove(index);
38 if self.active_tab >= self.tabs.len() {
40 self.active_tab = self.tabs.len() - 1;
41 } else if self.active_tab > index {
42 self.active_tab -= 1;
43 }
44 }
45 let open_tabs = self.open_tab_paths();
46 let active = self.active_tab;
47 crate::features::repo::commands::save_session_async(open_tabs, active)
48 }
49
50 Message::OpenRepo
52 | Message::InitRepo
53 | Message::RepoSelected(_)
54 | Message::RepoInitSelected(_)
55 | Message::RepoOpened(_)
56 | Message::RefreshRepo
57 | Message::RepoRefreshed(_)
58 | Message::OpenRecentRepo(_)
59 | Message::CloseRepo
60 | Message::RepoRecorded(_)
61 | Message::RepoRestoredAt(_, _)
62 | Message::MoreCommitsLoaded(_)
63 | Message::SettingsLoaded(_)
64 | Message::GitOperationResult(_) => {
65 crate::features::repo::update::update(self, message)
66 }
67
68 Message::CheckoutBranch(_)
70 | Message::BranchCheckedOut(_)
71 | Message::CreateBranch
72 | Message::NewBranchNameChanged(_)
73 | Message::BranchCreated(_)
74 | Message::DeleteBranch(_)
75 | Message::BranchDeleted(_)
76 | Message::ToggleBranchCreate
77 | Message::ToggleLocalBranches
78 | Message::ToggleRemoteBranches => {
79 crate::features::branches::update::update(self, message)
80 }
81
82 Message::SelectCommit(_)
84 | Message::CommitFileListLoaded(_)
85 | Message::SingleFileDiffLoaded(_) => {
86 crate::features::commits::update::update(self, message)
89 }
90
91 Message::CommitMessageChanged(_)
92 | Message::CreateCommit
93 | Message::CommitCreated(_) => crate::features::commits::update::update(self, message),
94
95 Message::CommitLogScrolled(abs_y, rel_y) => {
96 const COMMITS_PAGE_SIZE: usize = 200;
100 const LOAD_TRIGGER_RELATIVE: f32 = 0.85;
103
104 self.active_tab_mut().commit_scroll_offset = *abs_y;
105
106 let tab = self.active_tab();
107 if *rel_y >= LOAD_TRIGGER_RELATIVE
108 && tab.has_more_commits
109 && !tab.is_loading_more_commits
110 {
111 if let Some(path) = tab.repo_path.clone() {
112 let current = tab.commits.len();
113 self.active_tab_mut().is_loading_more_commits = true;
114 return crate::features::repo::commands::load_more_commits(
115 path,
116 current,
117 COMMITS_PAGE_SIZE,
118 );
119 }
120 }
121 Task::none()
122 }
123
124 Message::DiffViewScrolled(abs_y) => {
125 self.active_tab_mut().diff_scroll_offset = *abs_y;
126 Task::none()
127 }
128
129 Message::StageFile(_)
131 | Message::UnstageFile(_)
132 | Message::StageAll
133 | Message::UnstageAll
134 | Message::DiscardFile(_)
135 | Message::ConfirmDiscard(_)
136 | Message::CancelDiscard
137 | Message::StagingUpdated(_) => crate::features::staging::update::update(self, message),
138
139 Message::StashSave
141 | Message::StashPop(_)
142 | Message::StashDrop(_)
143 | Message::StashUpdated(_)
144 | Message::StashMessageChanged(_) => {
145 crate::features::stash::update::update(self, message)
146 }
147
148 Message::Fetch | Message::FetchCompleted(_) => {
150 crate::features::remotes::update::update(self, message)
151 }
152
153 Message::SelectDiff(_) | Message::SelectDiffByIndex(_) => {
155 crate::features::diff::update::update(self, message)
156 }
157
158 Message::DismissError => {
159 self.active_tab_mut().error_message = None;
160 Task::none()
161 }
162
163 Message::ZoomIn => {
164 self.ui_scale = (self.ui_scale + 0.1).min(2.0);
165 crate::features::repo::commands::save_layout_async(self.current_layout())
166 }
167
168 Message::ZoomOut => {
169 self.ui_scale = (self.ui_scale - 0.1).max(0.5);
170 crate::features::repo::commands::save_layout_async(self.current_layout())
171 }
172
173 Message::ZoomReset => {
174 self.ui_scale = 1.0;
175 crate::features::repo::commands::save_layout_async(self.current_layout())
176 }
177
178 Message::ToggleSidebar => {
179 self.sidebar_expanded = !self.sidebar_expanded;
180 crate::features::repo::commands::save_layout_async(self.current_layout())
181 }
182
183 Message::PaneDragStart(target, _x) => {
185 self.dragging = Some(*target);
186 self.drag_initialized = false;
190 Task::none()
191 }
192
193 Message::PaneDragStartH(target, _y) => {
194 self.dragging_h = Some(*target);
195 self.drag_initialized_h = false;
196 Task::none()
197 }
198
199 Message::PaneDragMove(x, y) => {
200 use crate::state::{DragTarget, DragTargetH};
201
202 self.cursor_pos = iced::Point::new(*x, *y);
204
205 if let Some(target) = self.dragging {
206 if !self.drag_initialized {
207 self.drag_start_x = *x;
209 self.drag_initialized = true;
210 } else {
211 let dx = *x - self.drag_start_x;
212 self.drag_start_x = *x;
213
214 match target {
215 DragTarget::SidebarRight => {
216 self.sidebar_width = (self.sidebar_width + dx).clamp(120.0, 500.0);
217 }
218 DragTarget::CommitLogRight => {
219 self.commit_log_width =
220 (self.commit_log_width + dx).clamp(200.0, 1200.0);
221 }
222 DragTarget::DiffFileListRight => {
223 self.diff_file_list_width =
224 (self.diff_file_list_width + dx).clamp(100.0, 400.0);
225 }
226 }
227 }
228 }
229
230 if let Some(target_h) = self.dragging_h {
231 if !self.drag_initialized_h {
232 self.drag_start_y = *y;
233 self.drag_initialized_h = true;
234 } else {
235 let dy = *y - self.drag_start_y;
236 self.drag_start_y = *y;
237
238 match target_h {
239 DragTargetH::StagingTop => {
240 self.staging_height =
242 (self.staging_height - dy).clamp(100.0, 600.0);
243 }
244 }
245 }
246 }
247
248 Task::none()
249 }
250
251 Message::PaneDragEnd => {
252 self.dragging = None;
253 self.dragging_h = None;
254 self.drag_initialized = false;
255 self.drag_initialized_h = false;
256 crate::features::repo::commands::save_layout_async(self.current_layout())
257 }
258
259 Message::OpenBranchContextMenu(name, local_index, is_current) => {
261 let pos = (self.cursor_pos.x, self.cursor_pos.y);
262 let tab = self.active_tab_mut();
263 tab.context_menu_pos = pos;
264 tab.context_menu = Some(crate::state::ContextMenu::Branch {
265 name: name.clone(),
266 is_current: *is_current,
267 local_index: *local_index,
268 });
269 Task::none()
270 }
271
272 Message::OpenRemoteBranchContextMenu(name) => {
273 let pos = (self.cursor_pos.x, self.cursor_pos.y);
274 let tab = self.active_tab_mut();
275 tab.context_menu_pos = pos;
276 tab.context_menu =
277 Some(crate::state::ContextMenu::RemoteBranch { name: name.clone() });
278 Task::none()
279 }
280
281 Message::OpenCommitContextMenu(idx) => {
282 let oid = self.active_tab().commits.get(*idx).map(|c| c.oid.clone());
283 let pos = (self.cursor_pos.x, self.cursor_pos.y);
284 if let Some(oid) = oid {
285 let tab = self.active_tab_mut();
286 tab.context_menu_pos = pos;
287 tab.context_menu = Some(crate::state::ContextMenu::Commit { index: *idx, oid });
288 }
289 Task::none()
290 }
291
292 Message::CloseContextMenu => {
293 self.active_tab_mut().context_menu = None;
294 Task::none()
295 }
296
297 Message::BeginRenameBranch(name) => {
299 let tab = self.active_tab_mut();
300 tab.context_menu = None;
301 tab.rename_branch_input = name.clone();
302 tab.rename_branch_target = Some(name.clone());
303 Task::none()
304 }
305
306 Message::RenameBranchInputChanged(s) => {
307 self.active_tab_mut().rename_branch_input = s.clone();
308 Task::none()
309 }
310
311 Message::CancelRename => {
312 let tab = self.active_tab_mut();
313 tab.rename_branch_target = None;
314 tab.rename_branch_input.clear();
315 Task::none()
316 }
317
318 Message::ConfirmRenameBranch => {
319 let (original, new_name, path) = {
320 let tab = self.active_tab();
321 (
322 tab.rename_branch_target.clone(),
323 tab.rename_branch_input.trim().to_string(),
324 tab.repo_path.clone(),
325 )
326 };
327 if let (Some(orig), false) = (&original, new_name.is_empty()) {
328 if *orig != new_name {
329 if let Some(path) = path {
330 let orig = orig.clone();
331 {
332 let tab = self.active_tab_mut();
333 tab.rename_branch_target = None;
334 tab.rename_branch_input.clear();
335 tab.is_loading = true;
336 tab.status_message =
337 Some(format!("Renaming '{orig}' → '{new_name}'…"));
338 }
339 return crate::features::repo::commands::rename_branch_async(
340 path, orig, new_name,
341 );
342 }
343 }
344 }
345 self.active_tab_mut().rename_branch_target = None;
346 Task::none()
347 }
348
349 Message::PushBranch(name) => {
351 let name = name.clone();
352 let remote = self
353 .active_tab()
354 .remotes
355 .first()
356 .map(|r| r.name.clone())
357 .unwrap_or_else(|| "origin".to_string());
358 self.active_tab_mut().context_menu = None;
359 with_repo!(
360 self,
361 loading,
362 format!("Pushing '{name}' to {remote}…"),
363 |path| crate::features::repo::commands::push_branch_async(path, name, remote)
364 )
365 }
366
367 Message::PullBranch(_name) => {
368 let remote = self
369 .active_tab()
370 .remotes
371 .first()
372 .map(|r| r.name.clone())
373 .unwrap_or_else(|| "origin".to_string());
374 self.active_tab_mut().context_menu = None;
375 with_repo!(
376 self,
377 loading,
378 format!("Pulling from {remote} (rebase)…"),
379 |path| crate::features::repo::commands::pull_rebase_async(path, remote)
380 )
381 }
382
383 Message::RebaseOnto(target) => {
384 let target = target.clone();
385 self.active_tab_mut().context_menu = None;
386 with_repo!(
387 self,
388 loading,
389 format!("Rebasing onto '{target}'…"),
390 |path| crate::features::repo::commands::rebase_onto_async(path, target)
391 )
392 }
393
394 Message::MergeBranch(name) => {
395 let name = name.clone();
396 self.active_tab_mut().context_menu = None;
397 with_repo!(
398 self,
399 loading,
400 format!("Merging '{name}' into current branch…"),
401 |path| crate::features::repo::commands::merge_branch_async(path, name)
402 )
403 }
404
405 Message::CheckoutRemoteBranch(name) => {
406 let name = name.clone();
407 self.active_tab_mut().context_menu = None;
408 with_repo!(self, loading, format!("Checking out '{name}'…"), |path| {
409 crate::features::repo::commands::checkout_remote_branch_async(path, name)
410 })
411 }
412
413 Message::DeleteRemoteBranch(name) => {
414 let name = name.clone();
415 self.active_tab_mut().context_menu = None;
416 with_repo!(
417 self,
418 loading,
419 format!("Deleting remote branch '{name}'…"),
420 |path| crate::features::repo::commands::delete_remote_branch_async(path, name)
421 )
422 }
423
424 Message::BeginCreateTag(oid, annotated) => {
425 let tab = self.active_tab_mut();
426 tab.context_menu = None;
427 tab.create_tag_target_oid = Some(oid.clone());
428 tab.create_tag_annotated = *annotated;
429 tab.create_tag_name.clear();
430 tab.create_tag_message.clear();
431 Task::none()
432 }
433
434 Message::TagNameChanged(s) => {
435 self.active_tab_mut().create_tag_name = s.clone();
436 Task::none()
437 }
438
439 Message::TagMessageChanged(s) => {
440 self.active_tab_mut().create_tag_message = s.clone();
441 Task::none()
442 }
443
444 Message::ConfirmCreateTag => {
445 let (oid, name, message, annotated, path) = {
446 let tab = self.active_tab();
447 (
448 tab.create_tag_target_oid.clone(),
449 tab.create_tag_name.trim().to_string(),
450 tab.create_tag_message.trim().to_string(),
451 tab.create_tag_annotated,
452 tab.repo_path.clone(),
453 )
454 };
455 if let (Some(oid), false) = (&oid, name.is_empty()) {
456 if let Some(path) = path {
457 let oid = oid.clone();
458 {
459 let tab = self.active_tab_mut();
460 tab.create_tag_target_oid = None;
461 tab.create_tag_name.clear();
462 tab.create_tag_message.clear();
463 tab.is_loading = true;
464 tab.status_message = Some(format!("Creating tag '{name}'…"));
465 }
466 return if annotated {
467 crate::features::repo::commands::create_annotated_tag_async(
468 path, name, message, oid,
469 )
470 } else {
471 crate::features::repo::commands::create_tag_async(path, name, oid)
472 };
473 }
474 }
475 Task::none()
476 }
477
478 Message::CancelCreateTag => {
479 let tab = self.active_tab_mut();
480 tab.create_tag_target_oid = None;
481 tab.create_tag_name.clear();
482 tab.create_tag_message.clear();
483 Task::none()
484 }
485
486 Message::CheckoutCommitDetached(oid) => {
488 let oid = oid.clone();
489 let short = gitkraft_core::utils::short_oid_str(&oid).to_string();
490 self.active_tab_mut().context_menu = None;
491 with_repo!(self, loading, format!("Checking out {short}…"), |path| {
492 crate::features::repo::commands::checkout_commit_async(path, oid)
493 })
494 }
495
496 Message::RebaseOntoCommit(oid) => {
497 let oid = oid.clone();
498 let short = gitkraft_core::utils::short_oid_str(&oid).to_string();
499 self.active_tab_mut().context_menu = None;
500 with_repo!(self, loading, format!("Rebasing onto {short}…"), |path| {
501 crate::features::repo::commands::rebase_onto_async(path, oid)
502 })
503 }
504
505 Message::RevertCommit(oid) => {
506 let oid = oid.clone();
507 let short = gitkraft_core::utils::short_oid_str(&oid).to_string();
508 self.active_tab_mut().context_menu = None;
509 with_repo!(self, loading, format!("Reverting {short}…"), |path| {
510 crate::features::repo::commands::revert_commit_async(path, oid)
511 })
512 }
513
514 Message::ResetSoft(ref oid)
515 | Message::ResetMixed(ref oid)
516 | Message::ResetHard(ref oid) => {
517 let mode = match &message {
518 Message::ResetSoft(_) => "soft",
519 Message::ResetMixed(_) => "mixed",
520 Message::ResetHard(_) => "hard",
521 _ => unreachable!(),
522 };
523 let oid = oid.clone();
524 let short = gitkraft_core::utils::short_oid_str(&oid).to_string();
525 self.active_tab_mut().context_menu = None;
526 with_repo!(
527 self,
528 loading,
529 format!("Resetting ({mode}) to {short}…"),
530 |path| crate::features::repo::commands::reset_to_commit_async(
531 path,
532 oid,
533 mode.to_string()
534 )
535 )
536 }
537
538 Message::CopyText(text) => iced::clipboard::write(text.clone()),
540
541 Message::ThemeChanged(index) => {
543 self.current_theme_index = *index;
544 let name = gitkraft_core::THEME_NAMES
546 .get(*index)
547 .copied()
548 .unwrap_or("Default");
549 crate::features::repo::commands::save_theme_async(name.to_string())
550 }
551
552 Message::ThemeSaved(_result) => {
553 Task::none()
555 }
556
557 Message::LayoutSaved(_result) => {
558 Task::none()
560 }
561
562 Message::SessionSaved(_) => {
563 Task::none()
565 }
566
567 Message::LayoutLoaded(result) => {
568 if let Ok(Some(layout)) = result {
569 if let Some(w) = layout.sidebar_width {
570 self.sidebar_width = w;
571 }
572 if let Some(w) = layout.commit_log_width {
573 self.commit_log_width = w;
574 }
575 if let Some(h) = layout.staging_height {
576 self.staging_height = h;
577 }
578 if let Some(w) = layout.diff_file_list_width {
579 self.diff_file_list_width = w;
580 }
581 if let Some(expanded) = layout.sidebar_expanded {
582 self.sidebar_expanded = expanded;
583 }
584 if let Some(scale) = layout.ui_scale {
585 self.ui_scale = scale.clamp(0.5, 2.0);
586 }
587 }
588 Task::none()
589 }
590
591 Message::Noop => Task::none(),
592 }
593 }
594}