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