use super::*;
const SAME_TIME: Duration = Duration::from_secs(2);
pub(super) fn differences(left: &[Item], right: &[Item]) -> (HashSet<OsString>, HashSet<OsString>) {
fn entries(items: &[Item]) -> HashMap<OsString, &Item> {
items.iter().filter(|item| item.name_os != "..").map(|item| (item.name_os.clone(), item)).collect()
}
let (left, right) = (entries(left), entries(right));
let (mut on_left, mut on_right) = (HashSet::new(), HashSet::new());
for (name, &item) in &left {
let Some(&other) = right.get(name) else {
on_left.insert(name.clone());
continue;
};
let (newer, older) = match (item.is_dir, other.is_dir) {
(true, true) => continue,
(false, false) => match (item.modified_at, other.modified_at) {
(Some(mine), Some(theirs)) if mine > theirs + SAME_TIME => (true, false),
(Some(mine), Some(theirs)) if theirs > mine + SAME_TIME => (false, true),
_ => {
let differ = item.size_bytes != other.size_bytes;
(differ, differ)
}
},
_ => (true, true),
};
if newer {
on_left.insert(name.clone());
}
if older {
on_right.insert(name.clone());
}
}
on_right.extend(right.into_keys().filter(|name| !left.contains_key(name)));
(on_left, on_right)
}
impl AppState {
pub fn compare_files(&mut self, left: PathBuf, right: PathBuf) {
use crate::diff::Compared;
match crate::diff::compare(&left, &right, self.options.large_file_bytes()) {
Ok(Compared::Text(state)) => self.screen = Screen::Diff(*state),
Ok(Compared::Identical) => self.find_note = Some("The files are identical".to_string()),
Ok(Compared::LineEndings) => self.find_note = Some("The files differ only in line endings".to_string()),
Ok(Compared::Binary { at, sizes_differ }) => {
let sizes = if sizes_differ { ", and in size" } else { "" };
self.find_note = Some(format!("The files differ from byte {}{}", crate::utils::grouped(at), sizes));
}
Err(message) => self.display_error(message),
}
}
pub fn diff_mut(&mut self) -> Option<&mut crate::diff::DiffState> {
match &mut self.screen {
Screen::Diff(state) => Some(state),
_ => None,
}
}
pub fn close_diff(&mut self) {
self.screen = Screen::Panels;
}
pub fn diff_scroll(&mut self, rows: isize, columns: isize) {
let height = self.diff_viewport_height;
let width = self.diff_viewport_width;
if let Some(state) = self.diff_mut() {
let bottom = state.rows.len().saturating_sub(height);
state.scroll_offset = state.scroll_offset.saturating_add_signed(rows).min(bottom);
let right_edge = state.max_line_width.saturating_sub(width);
state.horizontal_offset = state.horizontal_offset.saturating_add_signed(columns).min(right_edge);
}
}
pub fn compare_dirs(&mut self) {
if self.panel_busy(true) || self.panel_busy(false) {
return;
}
if self.dir_left == self.dir_right {
self.find_note = Some("Both panels show the same directory".to_string());
return;
}
let (left, right) = differences(&self.children_left, &self.children_right);
self.find_note = Some(if left.is_empty() && right.is_empty() {
"The directories hold the same files".to_string()
} else {
format!("Differ: {} on the left, {} on the right", left.len(), right.len())
});
self.selected_left = left;
self.selected_right = right;
}
}