use crate::app::App;
use crate::diff_tool::ExternalDiffTool;
use std::path::PathBuf;
use std::str::FromStr;
#[derive(Clone, Debug, PartialEq)]
pub enum KeyOutcome {
None,
LaunchDiff {
tool: ExternalDiffTool,
left: PathBuf,
right: PathBuf,
},
LaunchEditor {
path: PathBuf,
},
}
pub fn diff_launch_outcome(app: &App) -> KeyOutcome {
let Some(row) = app.filtered_rows.get(app.selected_idx) else {
return KeyOutcome::None;
};
let is_dir = row.left.as_ref().map(|f| f.is_dir).unwrap_or(false)
|| row.right.as_ref().map(|f| f.is_dir).unwrap_or(false);
if is_dir || row.left.is_none() || row.right.is_none() {
return KeyOutcome::None;
}
let Some(tool_str) = app.settings.external_diff_tool.as_ref() else {
return KeyOutcome::None;
};
let Ok(tool) = ExternalDiffTool::from_str(tool_str) else {
return KeyOutcome::None;
};
KeyOutcome::LaunchDiff {
tool,
left: app.left_path.join(&row.relative_path),
right: app.right_path.join(&row.relative_path),
}
}
pub fn editor_launch_outcome(app: &App) -> KeyOutcome {
let Some(row) = app.filtered_rows.get(app.selected_idx) else {
return KeyOutcome::None;
};
let file_exists = if app.active_side_left {
row.left.as_ref().map(|f| !f.is_dir).unwrap_or(false)
} else {
row.right.as_ref().map(|f| !f.is_dir).unwrap_or(false)
};
if !file_exists {
return KeyOutcome::None;
}
let path = if app.active_side_left {
app.left_path.join(&row.relative_path)
} else {
app.right_path.join(&row.relative_path)
};
KeyOutcome::LaunchEditor { path }
}
#[cfg(test)]
mod tests {
use super::*;
use crate::app::FlatRow;
use crate::diff::{DiffState, FileInfo};
use std::path::PathBuf;
use std::time::SystemTime;
fn file_row(name: &str, left: bool, right: bool, is_dir: bool) -> FlatRow {
let info = FileInfo {
is_dir,
size: 10,
modified: SystemTime::UNIX_EPOCH,
};
FlatRow {
depth: 0,
relative_path: PathBuf::from(name),
name: name.to_string(),
state: DiffState::DifferentNewerLeft,
left: left.then_some(info.clone()),
right: right.then_some(info),
}
}
#[test]
fn diff_launch_outcome_none_without_configured_tool() {
let mut app = App::new(PathBuf::from("/left"), PathBuf::from("/right"));
app.settings.external_diff_tool = None;
app.filtered_rows = vec![file_row("a.txt", true, true, false)];
app.selected_idx = 0;
assert_eq!(diff_launch_outcome(&app), KeyOutcome::None);
}
#[test]
fn diff_launch_outcome_none_for_directory() {
let mut app = App::new(PathBuf::from("/left"), PathBuf::from("/right"));
app.settings.external_diff_tool = Some("vim".to_string());
app.filtered_rows = vec![file_row("dir", true, true, true)];
app.selected_idx = 0;
assert_eq!(diff_launch_outcome(&app), KeyOutcome::None);
}
#[test]
fn diff_launch_outcome_none_for_single_sided_file() {
let mut app = App::new(PathBuf::from("/left"), PathBuf::from("/right"));
app.settings.external_diff_tool = Some("vim".to_string());
app.filtered_rows = vec![file_row("a.txt", true, false, false)];
app.selected_idx = 0;
assert_eq!(diff_launch_outcome(&app), KeyOutcome::None);
}
#[test]
fn diff_launch_outcome_builds_paths_for_both_sided_file() {
let mut app = App::new(PathBuf::from("/left"), PathBuf::from("/right"));
app.settings.external_diff_tool = Some("vim".to_string());
app.filtered_rows = vec![file_row("a.txt", true, true, false)];
app.selected_idx = 0;
assert_eq!(
diff_launch_outcome(&app),
KeyOutcome::LaunchDiff {
tool: ExternalDiffTool::Vim,
left: PathBuf::from("/left/a.txt"),
right: PathBuf::from("/right/a.txt"),
}
);
}
#[test]
fn editor_launch_outcome_none_for_directory() {
let mut app = App::new(PathBuf::from("/left"), PathBuf::from("/right"));
app.active_side_left = true;
app.filtered_rows = vec![file_row("dir", true, false, true)];
app.selected_idx = 0;
assert_eq!(editor_launch_outcome(&app), KeyOutcome::None);
}
#[test]
fn editor_launch_outcome_follows_active_side() {
let mut app = App::new(PathBuf::from("/left"), PathBuf::from("/right"));
app.filtered_rows = vec![file_row("a.txt", true, true, false)];
app.selected_idx = 0;
app.active_side_left = true;
assert_eq!(
editor_launch_outcome(&app),
KeyOutcome::LaunchEditor {
path: PathBuf::from("/left/a.txt"),
}
);
app.active_side_left = false;
assert_eq!(
editor_launch_outcome(&app),
KeyOutcome::LaunchEditor {
path: PathBuf::from("/right/a.txt"),
}
);
}
#[test]
fn editor_launch_outcome_none_when_missing_on_active_side() {
let mut app = App::new(PathBuf::from("/left"), PathBuf::from("/right"));
app.active_side_left = false;
app.filtered_rows = vec![file_row("a.txt", true, false, false)];
app.selected_idx = 0;
assert_eq!(editor_launch_outcome(&app), KeyOutcome::None);
}
#[test]
fn outcomes_are_none_when_selection_out_of_range() {
let app = App::new(PathBuf::from("/left"), PathBuf::from("/right"));
assert_eq!(diff_launch_outcome(&app), KeyOutcome::None);
assert_eq!(editor_launch_outcome(&app), KeyOutcome::None);
}
}