Skip to main content

gitkraft_gui/features/diff/
update.rs

1//! Update logic for diff-related messages.
2
3use iced::Task;
4
5use crate::message::Message;
6use crate::state::GitKraft;
7
8/// Handle diff-related messages, returning a [`Task`] for any follow-up
9/// async work.
10pub fn update(state: &mut GitKraft, message: Message) -> Task<Message> {
11    match message {
12        Message::SelectDiffByIndex(index) => {
13            let shift_held = state.keyboard_modifiers.shift();
14            let tab = state.active_tab();
15            let repo_path = tab.repo_path.clone();
16            let oid = tab.selected_commit_oid.clone();
17
18            if shift_held {
19                // Shift+Click: range selection from anchor to clicked index.
20                let anchor = state
21                    .active_tab()
22                    .anchor_file_index
23                    .or(state.active_tab().selected_file_index);
24
25                let range = crate::view_utils::shift_click_range(anchor, index);
26                let count = range.len();
27
28                let tab = state.active_tab_mut();
29                tab.selected_commit_file_indices = range;
30                tab.selected_file_index = Some(index);
31
32                if count == 1 {
33                    // Range collapsed to a single file — behave like a normal
34                    // single-file selection (no multi-diff panel).
35                    tab.multi_file_diffs.clear();
36                    let file_entry = tab.commit_files.get(index).cloned();
37                    if let (Some(entry), Some(path), Some(oid)) = (file_entry, repo_path, oid) {
38                        let file_path = entry.display_path().to_string();
39                        let tab = state.active_tab_mut();
40                        tab.is_loading_file_diff = true;
41                        tab.diff_scroll_offset = 0.0;
42                        return crate::features::commits::commands::load_single_file_diff(
43                            path, oid, file_path,
44                        );
45                    }
46                } else {
47                    // Multiple files in range — load and display them all.
48                    tab.selected_diff = None;
49                    tab.is_loading_file_diff = true;
50                    tab.diff_scroll_offset = 0.0;
51                    let file_paths: Vec<String> = tab
52                        .selected_commit_file_indices
53                        .iter()
54                        .filter_map(|&i| {
55                            tab.commit_files
56                                .get(i)
57                                .map(|f| f.display_path().to_string())
58                        })
59                        .collect();
60                    if let (Some(path), Some(oid)) = (repo_path, oid) {
61                        return crate::features::commits::commands::load_commit_multi_diffs(
62                            path, oid, file_paths,
63                        );
64                    }
65                }
66                Task::none()
67            } else {
68                // ── Regular click: single-file selection, set range anchor ─────
69                let tab = state.active_tab_mut();
70                tab.anchor_file_index = Some(index); // fix the anchor for future Shift+Clicks
71                tab.selected_commit_file_indices.clear();
72                // NOTE: multi_file_diffs, commit_range_diffs, and
73                // diff_scroll_offset are intentionally NOT cleared here.
74                // Clearing them immediately would change the widget-tree
75                // structure before the replacement diff is ready, causing the
76                // file list panel to visually flash/blink.  Instead we defer
77                // those resets to SingleFileDiffLoaded where the new diff
78                // content is set atomically.
79
80                let file_entry = tab.commit_files.get(index).cloned();
81                if let (Some(entry), Some(path), Some(oid)) = (file_entry, repo_path, oid) {
82                    let file_path = entry.display_path().to_string();
83                    let tab = state.active_tab_mut();
84                    tab.selected_file_index = Some(index);
85                    tab.is_loading_file_diff = true;
86                    crate::features::commits::commands::load_single_file_diff(path, oid, file_path)
87                } else {
88                    Task::none()
89                }
90            }
91        }
92
93        Message::SelectDiff(diff_info) => {
94            state.active_tab_mut().context_menu = None;
95            let tab = state.active_tab_mut();
96            tab.selected_diff = Some(diff_info);
97            tab.diff_scroll_offset = 0.0;
98            Task::none()
99        }
100
101        Message::CommitMultiDiffLoaded(result) => {
102            let tab = state.active_tab_mut();
103            tab.is_loading_file_diff = false;
104            match result {
105                Ok(diffs) => {
106                    tab.multi_file_diffs = diffs;
107                    tab.selected_diff = None;
108                    tab.diff_scroll_offset = 0.0;
109                }
110                Err(e) => {
111                    tab.multi_file_diffs.clear();
112                    tab.error_message = Some(format!("Failed to load multi-file diff: {e}"));
113                }
114            }
115            Task::none()
116        }
117
118        _ => Task::none(),
119    }
120}