OJPGHVC3RFBQ7TTSCZH6URSSATII3TESD74EISDNOTNXXSX7PQMAC SWWE2R6MVBX5CNM6X3WLXZTSRTU53PBJL7WJSFVF77XBPXDX4COAC WT3GA27PQ2AOAIGK65O3Q4DMX4AZDVNULBLRL6GF4QW6QCASUEAAC UB2ITZJSDADVINSQEZ3HA6PVGA7OA6JYFG5GMSO7Y7LOXJC4FI7AC YBJRDOTCX3ZRDB5EVXJBR55FX3CADCSIGMYWNYVC2PD5W3GXR3DQC AMPZ2BXK4IGUZO3OPBRSJ6Z4GI5K4PRFMLUGTR6AP4FKKRWQG7LQC V55EAIWQXWER2HWKZHPJBV7DDJMSPSPWSO3FSSAYODJHVDBHUN6QC Y5ATDI2HRWTTYJAVUR7SVWQVB4ZKKDZF3UVE4JJQFZ7RX7H7VPJQC ZVI4AWERNOTDJ3765HJXRBZT57XPNKVONQ6TGOGNPOL2VN42KMJQC OQ6HSAWHIRTAIIWMDGCTIOK47JDY7QVVAHLRDA2R5TTJKNSBFCWQC ONRCENKTUB4JJMPXNAQQYEWDYD54TAGOLWH742GF4EH3KTHV7YLQC 4ELJZGRJNL6FXB33QTYDNPY57JA3WZPUXKLQRTGSLDM7W65PD3YQC FR52XEMWD22VH3GKSARXJUJXOGO7ZSQEHWPXFRWHLGRAJU3WRKCAC L6KSEFQIWICZJ6HJUFKLZQDEH6X2QMFM4Z7ZZUGMLDMFF7EHRXWAC BFN2VHZS7VCBUHQ4S3CQ3LFQV2V4M6VANNAF32XMRFQVWRGYSZ6AC 23SFYK4Q5NKBPJG53PQNPWQH6UOUU2YKJEL7RLXYBRLJOJYV7AWQC MYGIBRRHHXPKVRAMQQRJTZH74L2XOK3SF7J57JPCRKSVRLZ2D6NQC XSZZB47UXR6KGYFZZQFQR63X2LDKOH6TPNNBRRGHUCI5JJ4JIWVAC ACDXXAX26ZJJFKJDGRC2GOSJY5JHQWCSTP55SYI6D6LH5UIRYUBAC 7SSBM4UQMYVRL6L3ICYZQPSMYLZZQNMDWH6JKA3KOOSXZDJHESHQC FL2ULDJNRO3KPS24T2PEZWWNXAVFYC42SRVN3LDFVPIAFTDCOBGQC S2T7RUKWXAHMOW5HHITQKTKCBKTUKGMRXK7EQI6RNEBBHRJ5W2RQC I2AG42PAVOII4V4TWDJV5ZVNDIHKBRDT254BFQLFUIY723TW6CCQC SK3WVX7AIP6K6SP6A46HP6D7HQCDZT7ZEWZOXVM4OSRVHCHBXKKQC SASAN2XCWDQ2VEHZ7TAQEN2R3Y7AG7JUGEFVRL4DZAGHXDFEZFRQC YKHE3XMWOWPGOWYSISF73MIAKN7WB3AHCV2OA4ECAFPF47YHUXEAC KEPKF3WO7ZZ2VB2DRVVTWTGPL7TCA52BMYUPHUNUJH6WO3HAT6JQC KWTBNTO3QUUE2YADF6SYW6G6ZOKYEWRJQKIWDGZXR33S3YNDVIZQC 5MUEECMJHU44FL5RDUR3VFBIWK3H4X2L5MVJ73J37PYHZWLUKU2AC 3TLPJ57B2OD5OWJN5WMS7A4W7IGFUWJJHVIXRM34VT6KUN6R4YSAC BNHJU2DU4HHADLKTDQMRG5PC5VHCJ2G7UFQRRVUBTALXVBUAQSKQC KQABQCCZCM23QWW43LZD5QBNFOXWLGFNCPPIMNMNFMZSNPSTFVEAC AI3IMKC3HRPMTWQCU5HGWKUHGKTJ22QF7V4AAEI6IEBIZ4WYWCKQC WXQBBQ2ACNPKCTDF7OTBLP342324ZIOJK42PUO2KT2IYVJ2ETCMAC 65DXFP3YLRMC7IECI5VO5ZJOQGIBYWBRJX4BK2NYVRGE4ZZDQWMQC DST3HRZZXO3RNS2GW5ADK43LT7PX5SZ4GLLO7MC7U3VWCYNFTZTQC PTWZYQFRWWUOE2WMQT26CKZKFSHAIJVJS3QWHJFYUFDRRTVPHSUAC RDRBP7AL74NBFNZSQFTU7VQCMWTGJO5RZWGPCWVVS5WRTXJ77DFAC 4G6DZDO6BK7ICVDZNIWMWJUKLOVJCVMO6ZSQ2RDCC4PA6YVKUKWAC UR4J677RWA3OFG6HQTD46BUUE5YFPSBEFCJAEM5OMT4V5A7SBNNQC K63JN6CRRCP4S5NY2FPH46Q7QLWH4B6QXQHKFPVQBHHIHNDVXFDQC C5P3JIFC55TCXP53SWVYB5GHM4O7DBSBIEYSQEJ3NUX7762TUQAAC GYZWZ33TH3WUFLF3XNFQW2NBZXAAK5WQG65YVA7SJEYENUZKZRSAC A6Z4O6RC33HYWP7JIVQ6FDWE4EOCQWQTIGENK2WAHUGSHDDLSA7QC 7BLZN73OYUAJEYTJ6WWHRZ7S7ONDGRBKNJGFGW62NAIZBUK3CECQC JZXYSIYDPBWQZCAMGDZ5BFMN6SU73EVVDIYEGTDJN6DVOSBNHN4QC 5ZRDYL6KIQPUI3ZZETH5KJ64N6RUF7KYM3P6Q6HER5XVJZ7GZ4WQC BAUK5BONEFQ3KIQPFLM7MGNCS5GWBILBXZMTIGN5LWTYTNNNNSPQC NZD56PVBVHARAQD7JNXE2F3DRT4S2NNRDHVVJTKTQK474LDMVIXQC 6YZAVBWU6E5FYOI5JGEIPXGZLIKAW6LS2AOFIQWEE5DMOPPCD5PQC MJDGPSHGF62FTVWZBE7MFNJTUQD42OBVJEOSVPBT553UFJLTEMXQC WW36JYLR4AILV7RHQEDJWMX74P74B7G7DRBHH3O2V5TCHRTZJWZQC #[derive(Debug)]pub struct LogChange {pub ix: usize,pub hash: repo::ChangeHash,pub message: String,pub file: Option<LogChangeFileSelection>,}
#[derive(Debug)]pub enum EntireLog {Loading,LoadingButNotViewing,Got {log: repo::Log,nav: Box<NavScrollable>,},NotViewing {log: repo::Log,nav_stored_offset: Option<f32>,},}#[derive(Debug)]pub struct EntireLogSelection<'a> {pub ix: usize,pub hash: repo::ChangeHash,pub message: &'a str,pub file: Option<LogChangeFileSelection<'a>>,pub nav: Option<&'a NavScrollable>,}
let view_repo_info = el(row([el(text(dir_name)),el(text(", channel: ")),el(button(text(channel)).on_press_maybe(can_switch_channel.then_some(Msg::SwitchChannel))),]).align_y(alignment::Vertical::Center));
let view_repo_info = || {el(row([el(text(dir_name)),el(text(", channel: ")),el(button(text(channel)).on_press_maybe(can_switch_channel.then_some(Msg::SwitchChannel),)),]).align_y(alignment::Vertical::Center))};
el(row([el(button(text(short_hash).font(Font::MONOSPACE)).on_press(Msg::Selection(selection::Msg::Select(selection::Select::LogChange { ix, hash: *hash, message: message.clone() },))).class(selectable_button_class(is_selected))),el(text(message).shaping(text::Shaping::Advanced)),]).spacing(SPACING))
view_log_change(ix, entry, is_selected)
let is_selected = matches!(file, Some(LogChangeFileSelection{ path: selected_path, .. }) if selected_path == path);el(button(text(path)).on_press_with(move || {Msg::Selection(selection::Msg::Select(selection::Select::LogChangeFile { ix, path: path.clone() }))}).class(selectable_button_class(is_selected)))});
let is_selected = matches!(file, Some(LogChangeFileSelection{ path: selected_path, .. }) if selected_path == path);el(button(text(path)).on_press_with(move || {Msg::Selection(selection::Msg::Select(selection::Select::LogChangeFile { ix, path: path.clone() }))}).class(selectable_button_class(is_selected)))});
let status_nav_children = [el(text("Untracked files:"))].into_iter().chain(view_untracked_files()).chain([el(text("Changed files:"))]).chain(view_changed_files()).chain([el(text("Recent changes:"))]).chain(view_log());
let status_nav_children = || {[el(text("Untracked files:"))].into_iter().chain(view_untracked_files()).chain([el(text("Changed files:"))]).chain(view_changed_files()).chain([el(text("Recent changes:"))]).chain(view_status_log())};
let col_0 = el(column([view_repo_info,el(iced_nav_scrollable::view(status_nav,status_nav_children,Msg::StatusNav,).class(if status_selected {theme::Scrollable::Selected} else {theme::Scrollable::Normal})),]).width(Length::Fill).height(Length::Fill));
let col_1 = el(column([el(column([record_msg_editor, selection_details])
let status_col_0 = || {el(column([view_repo_info(),el(iced_nav_scrollable::view(status_nav,status_nav_children(),Msg::StatusNav,).class(if status_selected() {theme::Scrollable::Selected} else {theme::Scrollable::Normal})
if hidden_cols == 1 {el(button(row([el(text("← Status").shaping(text::Shaping::Advanced))])).on_press(Msg::Selection(selection::Msg::PressDir(selection::Dir::Left,))))} else {el(row([]))},]).width(Length::Fill).height(Length::Fill).spacing(SPACING));
]).width(Length::Fill).height(Length::Fill))};
let col_2 = match status_selection.as_ref() {
let status_col_1 = || {el(column([el(column([record_msg_editor(), selection_details()]).width(Length::Fill).height(Length::Fill)),if hidden_cols == 1 {el(button(row([el(text("← Status").shaping(text::Shaping::Advanced))])).on_press(Msg::Selection(selection::Msg::PressDir(selection::Dir::Left),)))} else {el(row([]))},]).width(Length::Fill).height(Length::Fill).spacing(SPACING))};let status_col_2 = || match status_selection.as_ref() {
};let entire_log_selected = || match entire_log_selection.as_ref() {Some(EntireLogSelection { file, .. }) => file.is_none(),None => false,};let entire_log_change_selected = || match entire_log_selection.as_ref() {Some(EntireLogSelection {file: Some(LogChangeFileSelection { diff, .. }),..}) => !diff.as_ref().map(|diff| diff.is_selected).unwrap_or_default(),_ => false,};let entire_log_col_0 = || {let Some(EntireLog::Got { log, nav }) = entire_log.as_ref() else {unreachable!()};let entries = log.iter().enumerate().map(|(ix, entry)| {let is_selected = matches!(entire_log_selection.as_ref(),Some(EntireLogSelection { ix: selected_ix, .. }) if &ix == selected_ix);view_log_change(ix, entry, is_selected)});let len = log.len();let selected_ix = entire_log_selection.as_ref().map(|EntireLogSelection { ix, .. }| len - *ix);el(column([view_repo_info(),if let Some(selected_ix) = selected_ix {el(text(format!("Entire log ({selected_ix}/{len})")))} else {el(text(format!("Entire log ({len})")))},el(iced_nav_scrollable::view(nav, entries, Msg::EntireLogNav).class(if entire_log_selected() {theme::Scrollable::Selected} else {theme::Scrollable::Normal}).width(Length::Fill).height(Length::Fill)),]).width(Length::Fill).height(Length::Fill))
let entire_log_col_1 = || {let files_view = match entire_log_selection.as_ref() {Some(EntireLogSelection {ix,hash,message,file,nav,}) => {let short_hash = display_short_hash(hash);let view = match (nav.as_ref(), entire_log.as_ref()) {(Some(nav), Some(EntireLog::Got { log, nav: _ })) => {let entry = log.get(*ix).unwrap();let files = entry.file_paths.iter().enumerate().map(|(ix, path)| {let is_selected = matches!(file, Some(LogChangeFileSelection{ path: selected_path, .. }) if selected_path == path);el(button(text(path)).on_press_with(move || {Msg::Selection(selection::Msg::Select(selection::Select::LogChangeFile { ix, path: path.clone() }))}).class(selectable_button_class(is_selected)))});let hash = *hash;el(iced_nav_scrollable::view(nav, files, move |msg| {Msg::LogChangeNav { change: hash, msg }}).class(if entire_log_change_selected() {theme::Scrollable::Selected} else {theme::Scrollable::Normal}).width(Length::Fill).height(Length::Fill))}_ => el(text("Loading...")),};
el(column([view_diff_header(format!("{short_hash} message:")),el(text(*message).shaping(text::Shaping::Advanced)),view_diff_header("Changed files:".to_string()),view,]).width(Length::Fill).height(Length::Fill).spacing(SPACING))}None => el(row([])),};el(column([files_view,if hidden_cols == 1 {el(button(row([el(text("← Log").shaping(text::Shaping::Advanced))])).on_press(Msg::Selection(selection::Msg::PressDir(selection::Dir::Left),)))} else {el(row([]))},]).width(Length::Fill).height(Length::Fill).spacing(SPACING))};let entire_log_col_2 = || match entire_log_selection.as_ref() {Some(EntireLogSelection {ix: _,hash,message: _,file: Some(LogChangeFileSelection { ix: _, path, diff }),nav: _,}) => Some(el(column([el(column([view_diff_header(format!("{path} changes in {}:",display_short_hash(hash))),match diff {Some(Diff {is_selected,file,state,nav,}) => {let id_hash = file::log_id_parts_hash(*hash, path);diff::view(state, nav, file, *is_selected).map(move |action| Msg::LogChangeFileDiff {id_hash,msg: action,},)}None => el(text("Loading diff..")),},]).spacing(SPACING)),// NOTE: This is currently never true - there are only up to 3// colsif hidden_cols == 2 {el(button(row([el(text("← Files").shaping(text::Shaping::Advanced))])).on_press(Msg::Selection(selection::Msg::PressDir(selection::Dir::Left),)))} else {el(row([]))},]).width(Length::Fill).height(Length::Fill).spacing(SPACING))),Some(EntireLogSelection { .. }) | None => None,};
} else if let Some(col_2) = col_2 {let cols = [col_0, col_1, col_2].into_iter().skip(hidden_cols);
} else if let Some(EntireLog::Loading) = entire_log.as_ref() {el(column([view_repo_info(),el(text("Entire log")),el(text("Loading...")),]).width(Length::Fill).height(Length::Fill))} else if let Some(EntireLog::Got { .. }) = entire_log.as_ref() {if let Some(entire_log_col_2) = entire_log_col_2() {let cols =[entire_log_col_0(), entire_log_col_1(), entire_log_col_2].into_iter().skip(hidden_cols);el(row(cols).spacing(SPACING).width(Length::Fill).height(Length::Fill))} else {let cols = [entire_log_col_0(), entire_log_col_1()];el(row(cols).spacing(SPACING).width(Length::Fill).height(Length::Fill))}} else if let Some(status_col_2) = status_col_2() {let cols = [status_col_0(), status_col_1(), status_col_2].into_iter().skip(hidden_cols);
row.push(cancel())
let row = row.push(cancel());row}ActionState::EntireLog { can_select_right } => {let row = row([down(), up()]);let row = add_if(can_select_right, right, row);row}ActionState::EntireLogChange { can_select_right } => {let row = row([left(), down(), up()]);let row = add_if(can_select_right, right, row);row}ActionState::EntireLogChangeDiff => {let row = row([left(), down(), up(), down_no_skip(), up_no_skip()]);row
if let Some(EntireLogSelection { file, nav, .. }) = entire_log_selection {return match file {Some(LogChangeFileSelection {ix: _,path: _,diff,}) => {if let Some(diff) = diff {if diff.is_selected {ActionState::EntireLogChangeDiff} else {ActionState::EntireLogChange {can_select_right: matches!(diff.nav.ready,Some(iced_nav_scrollable::NeedsScrolling::Yes)),}}} else {ActionState::EntireLogChange {can_select_right: false,}}}None => ActionState::EntireLog {can_select_right: nav.and_then(|nav| nav.ready).is_some(),},};}
}fn view_log_change(ix: usize,repo::LogEntry {hash,message,file_paths: _,}: &repo::LogEntry,is_selected: bool,) -> Element<'_, Msg, Theme> {let short_hash = display_short_hash(hash);el(row([el(button(text(short_hash).font(Font::MONOSPACE)).on_press(Msg::Selection(selection::Msg::Select(selection::Select::LogChange {ix,hash: *hash,message: message.clone(),},))).class(selectable_button_class(is_selected))),el(text(message).shaping(text::Shaping::Advanced)),]).spacing(SPACING))
Dir::Down => {select_down(state, files, files_diffs, logs, repo, delta)}Dir::Up => {select_up(state, files, files_diffs, logs, repo, delta)}
Dir::Down => select_down(state,files,files_diffs,status_logs,entire_logs,repo,delta,),Dir::Up => select_up(state,files,files_diffs,status_logs,entire_logs,repo,delta,),
Dir::Down => alt_select_down(state, files_diffs, logs, delta),Dir::Up => alt_select_up(state, files_diffs, logs, delta),
Dir::Down => {alt_select_down(state, files_diffs, status_logs, delta)}Dir::Up => {alt_select_up(state, files_diffs, status_logs, delta)}
}if let Some(app::EntireLog::Got { log, nav }) = entire_log.as_ref() {let (selection, task) = if let Some(LogChange {ix: log_ix,hash,message,file,}) = state.view.entire_log.take(){match file {Some(LogChangeFileSelection {ix: file_ix,path,diff_selected,}) => {if diff_selected {let id_hash = file::log_id_parts_hash(hash, &path);let selection = LogChange {ix: log_ix,hash,message,file: Some(LogChangeFileSelection {ix: file_ix,path,diff_selected,}),};let task = if let Some(nav) = entire_logs.diffs_nav.as_mut().and_then(|(nav_id_hash, nav)| {(*nav_id_hash == id_hash).then_some(nav)}) {iced_nav_scrollable::scroll_down(nav, delta)} else {Task::none()};(selection, task)} else {let log_entry = log.get(log_ix).unwrap();let file_ix =if log_entry.file_paths.len().saturating_sub(1)== file_ix{0} else {file_ix + 1};let (file, selection_task) = entire_log_file_selection(entire_logs,log_entry,hash,file_ix,);let scroll_task = if let Some((nav_changes_hash, nav)) =entire_logs.changes_nav.as_ref()&& *nav_changes_hash == hash{iced_nav_scrollable::scroll_down_to_section(file_ix, nav,)} else {Task::none()};let selection = LogChange {ix: log_ix,hash,message,file: Some(file),};(selection, Task::batch([selection_task, scroll_task]))}}None => {if log.len().saturating_sub(1) == log_ix {let ix = 0;entire_log_selection(nav,entire_logs,log,ix,VertDir::Up,)} else {let ix = log_ix + 1;entire_log_selection(nav,entire_logs,log,ix,VertDir::Down,)}}}} else {let ix = 0;entire_log_selection(nav, entire_logs, log, ix, VertDir::Down)};state.view.entire_log = Some(selection);return task;
if let Some(app::EntireLog::Got { log, nav }) = entire_log.as_ref() {let (selection, task) = if let Some(LogChange {ix: log_ix,hash,message,file,}) = state.view.entire_log.take(){match file {Some(LogChangeFileSelection {ix: file_ix,path,diff_selected,}) => {if diff_selected {let id_hash = file::log_id_parts_hash(hash, &path);let selection = LogChange {ix: log_ix,hash,message,file: Some(LogChangeFileSelection {ix: file_ix,path,diff_selected,}),};let task = if let Some(nav) = entire_logs.diffs_nav.as_mut().and_then(|(nav_id_hash, nav)| {(*nav_id_hash == id_hash).then_some(nav)}) {iced_nav_scrollable::scroll_up(nav, delta)} else {Task::none()};(selection, task)} else {let log_entry = log.get(log_ix).unwrap();let file_ix = if 0 == file_ix {log_entry.file_paths.len().saturating_sub(1)} else {file_ix - 1};let (file, selection_task) = entire_log_file_selection(entire_logs,log_entry,hash,file_ix,);let scroll_task = if let Some((nav_changes_hash, nav)) =entire_logs.changes_nav.as_ref()&& *nav_changes_hash == hash{iced_nav_scrollable::scroll_up_to_section(file_ix, nav,)} else {Task::none()};
let selection = LogChange {ix: log_ix,hash,message,file: Some(file),};(selection, Task::batch([selection_task, scroll_task]))}}None => {if 0 == log_ix {let ix = log.len().saturating_sub(1);entire_log_selection(nav,entire_logs,log,ix,VertDir::Down,)} else {let ix = log_ix - 1;entire_log_selection(nav,entire_logs,log,ix,VertDir::Up,)}}}} else {let ix = 0;entire_log_selection(nav, entire_logs, log, ix, VertDir::Down)};state.view.entire_log = Some(selection);return task;}
let (selection, task): (Option<Status>, Task<crate::Msg>) =match state.view.status.take() {Some(Status::LogChange {
if let Some(app::EntireLog::Got { log: _, nav }) = entire_log.as_ref() {let (selection, task) = match state.view.entire_log.take() {Some(LogChange {
let task =iced_nav_scrollable::scroll_to_stored_offset(nav);(Some(selection), task)}}selection @ (Some(LogChange { file: None, .. }) | None) => {(selection, Task::none())}};state.view.entire_log = selection;return task;}let (selection, task): (Option<Status>, Task<crate::Msg>) =match state.view.status.take() {Some(Status::LogChange(LogChange {ix,hash,message,file:Some(LogChangeFileSelection {ix: file_ix,path,diff_selected,}),})) => {if diff_selected {(Some(Status::LogChange(LogChange {ix,hash,message,file: Some(LogChangeFileSelection {ix: file_ix,path,diff_selected: false,}),})),Task::none(),)} else {let selection = Status::LogChange(LogChange {ix,hash,message,file: None,});
if let Some(app::EntireLog::Got { log, nav: _ }) = entire_log.as_ref()&& let Some(entire_log) = state.view.entire_log.take(){let (selection, task) = match entire_log {LogChange {ix,hash,message,file: None,} => {let log_entry = log.get(ix).unwrap();let (file, task) = if let Some(path) =log_entry.file_paths.first(){let id_hash = file::log_id_parts_hash(log_entry.hash, path);let task = if let Some(log) =entire_logs.diffs.get(&id_hash){// Init log diffs navlet contents_count = diff::contents_count(&log.file);let unchanged_sections =diff::unchanged_sections(&log.file);let (nav, tasks) = iced_nav_scrollable::init(contents_count,unchanged_sections,);entire_logs.diffs_nav = Some((id_hash, nav));tasks.map(move |msg| {crate::Msg::View(app::Msg::LogChangeFileDiff {id_hash,msg: diff::Msg::DiffNav(msg),})})} else {// If the log is not loaded yet, the nav will be// initialized once it's loaded// (`repo::MsgOut::GotChangeDifs`)Task::none()};(Some(LogChangeFileSelection {ix: 0,path: path.clone(),diff_selected: false,}),task,)} else {(None, Task::none())};(Some(LogChange {ix,hash,message,file,}),task,)}LogChange {ix,hash,message,file:Some(LogChangeFileSelection {ix: file_ix,path,diff_selected: false,}),} => {let id_hash = file::log_id_parts_hash(hash, &path);let is_diff_scrollable =diff::log_diff_needs_scrolling(entire_logs, id_hash);// If the selected file's diff is already loaded (it has an// attached state), scroll back to its last offsetlet task = if is_diff_scrollable&& let Some(nav) = entire_logs.diffs_nav.as_ref().and_then(|(nav_id_hash, nav)| {(*nav_id_hash == id_hash).then_some(nav)},) {iced_nav_scrollable::scroll_to_stored_offset(nav)} else {Task::none()};(Some(LogChange {ix,hash,message,file: Some(LogChangeFileSelection {ix: file_ix,path,diff_selected: is_diff_scrollable,}),}),task,)}selection @ LogChange { .. } => (Some(selection), Task::none()),};state.view.entire_log = selection;return task;}
logs.diffs_nav = Some((id_hash, nav));tasks.map(move |msg| crate::Msg::LogDiffNav {id_hash,msg: diff::Msg::DiffNav(msg),
status_logs.diffs_nav = Some((id_hash, nav));tasks.map(move |msg| {crate::Msg::View(app::Msg::LogChangeFileDiff {id_hash,msg: diff::Msg::DiffNav(msg),})
if let Some(app::EntireLog::Got { log, nav }) = entire_log.as_ref() {let (selection, task) = match select {Select::LogChange {ix,hash: _,message: _,} => {let (selection, task) = entire_log_selection(nav,entire_logs,log,ix,VertDir::Down,);(Some(selection), task)}Select::LogChangeFile { ix: file_ix, path } => {match state.view.entire_log.take() {Some(LogChange {ix: log_ix,hash,message,file: _,}) => {let log_entry = log.get(log_ix).unwrap();let id_hash = file::log_id_parts_hash(hash, &path);let diff_selected = diff::log_diff_needs_scrolling(entire_logs,id_hash,);// If the selected file's diff is already loaded (it has// an attached state), scroll// back to its last offsetlet scroll_task = if diff_selected&& let Some(nav) = entire_logs.diffs_nav.as_ref().and_then(|(nav_id_hash, nav)| {(*nav_id_hash == id_hash).then_some(nav)}) {iced_nav_scrollable::scroll_to_stored_offset(nav)} else {Task::none()};let (file, selection_task) = log_file_selection(entire_logs,log_entry,hash,file_ix,);let selection = Some(LogChange {ix: log_ix,hash,message,file: Some(file),});(selection, Task::batch([selection_task, scroll_task]))}selection => (selection, Task::none()),}}_ => {unreachable!()}};state.view.entire_log = selection;return task;}
let selection = Status::LogChange(LogChange {ix,hash,message: entry.message.clone(),file: None,});(selection, Task::batch([nav_task, scroll_task]))}fn entire_log_selection(nav: &NavScrollable,logs: &mut diff::LogFilesAndState,log: &repo::Log,ix: usize,dir: VertDir,) -> (LogChange, Task<crate::Msg>) {let entry = log.get(ix).unwrap();let hash = entry.hash;
let selection = Status::LogChange {
let nav_task = match logs.change_file_counts.get(&hash) {Some(contents_count) => {// Init log changes navlet (nav, tasks) =iced_nav_scrollable::init(*contents_count, HashSet::new());logs.changes_nav = Some((hash, nav));tasks.map(move |msg| {crate::Msg::View(app::Msg::EntireLogChangeNav {change: hash,msg,})})}None => {// If the log is not loaded yet, the nav will be initialized once// it's loaded (`repo::MsgOut::GotChangeDifs`)Task::done(crate::Msg::View(app::Msg::ToRepo(repo::MsgIn::GetChangeDiffs { hash },)))}};let scroll_task = match dir {VertDir::Up => iced_nav_scrollable::scroll_up_to_section(ix, nav),VertDir::Down => iced_nav_scrollable::scroll_down_to_section(ix, nav),};let selection = LogChange {
fn entire_log_file_selection(logs: &mut diff::LogFilesAndState,log_entry: &repo::LogEntry,hash: repo::ChangeHash,file_ix: usize,) -> (LogChangeFileSelection, Task<crate::Msg>) {let path = log_entry.file_paths.get(file_ix).unwrap().clone();let id_hash = file::log_id_parts_hash(hash, &path);let nav_task = match logs.diffs.get(&id_hash) {Some(diff) => {let needs_new_nav = logs.diffs_nav.is_none()|| logs.diffs_nav.as_ref().map(|(nav_id_hash, _nav)| *nav_id_hash != id_hash).unwrap_or_default();if needs_new_nav {let contents_count = diff::contents_count(&diff.file);// Init log change diff navlet (nav, tasks) =iced_nav_scrollable::init(contents_count, HashSet::new());logs.diffs_nav = Some((id_hash, nav));tasks.map(move |msg| {crate::Msg::View(app::Msg::LogChangeFileDiff {id_hash,msg: diff::Msg::DiffNav(msg),})})} else {Task::none()}}None => {// If the log is not loaded yet, the nav will be initialized once// it's loaded (`repo::MsgOut::GotChangeDifs`)Task::none()}};(LogChangeFileSelection {ix: file_ix,path,diff_selected: false,},nav_task,)}
/// The diffs are loaded async and not present while loading.logs: diff::LogFilesAndState,
/// Logs shown in the status view. The diffs are loaded async and not/// present while loading.status_logs: diff::LogFilesAndState,/// Logs for the entire logentire_logs: diff::LogFilesAndState,
Msg::LogDiffNav { id_hash, msg } => {if let Some(diff::FileAndState {file: _,state: diff_state,}) = state.logs.diffs.get_mut(&id_hash){let nav = state.logs.diffs_nav.as_mut().and_then(|(nav_id_hash, nav)| {(*nav_id_hash == id_hash).then_some(nav)},);let task = diff::update(diff_state, nav, msg);return task.map(move |msg| Msg::LogDiffNav { id_hash, msg });}Task::none()}
} else if matches!(repo.entire_log.as_ref(),Some(app::EntireLog::Got { .. }),) {// Repeated match to take ownership of the logif let Some(app::EntireLog::Got { log, nav }) =repo.entire_log.take(){// Cache for later viewingrepo.entire_log = Some(app::EntireLog::NotViewing {log,nav_stored_offset: nav.stored_offset,});} else {debug_assert!(false, "The outter match should have the same condition as the inner match)")}
app::Msg::EntireLog => {if let Some(repo) = state.repo.as_mut() {match repo.entire_log.as_ref() {None => {state.repo_tx_in.send(repo::MsgIn::LoadEntireLog).unwrap();repo.entire_log = Some(app::EntireLog::Loading);}Some(app::EntireLog::LoadingButNotViewing) => {repo.entire_log = Some(app::EntireLog::Loading);}Some(app::EntireLog::NotViewing { .. }) => {// Repeated match to take ownership of the logif let Some(app::EntireLog::NotViewing {log,nav_stored_offset,}) = repo.entire_log.take(){let (mut nav, nav_task) = iced_nav_scrollable::init(log.len(),HashSet::new(),);// Scroll to last selection if anynav.stored_offset = nav_stored_offset;let scroll_task =iced_nav_scrollable::scroll_to_stored_offset(&nav,);repo.entire_log = Some(app::EntireLog::Got {log,nav: Box::new(nav),});return Task::batch([nav_task.map(|msg| {Msg::View(app::Msg::EntireLogNav(msg))}),scroll_task,]);} else {debug_assert!(false, "The outter match should have the same condition as the inner match)")}}Some(app::EntireLog::Loading | app::EntireLog::Got { .. },) => {}}}Task::none()}app::Msg::EntireLogNav(msg) => {if let Some(app::Repo {entire_log: Some(app::EntireLog::Got { log: _, nav }),..}) = state.repo.as_mut(){return iced_nav_scrollable::update(nav, msg).map(|msg| Msg::View(app::Msg::EntireLogNav(msg)));}Task::none()}app::Msg::EntireLogChangeNav { change, msg } => {if let Some((selected_change, nav)) =state.entire_logs.changes_nav.as_mut()&& change == *selected_change{iced_nav_scrollable::update(nav, msg).map(move |msg| {Msg::View(app::Msg::EntireLogChangeNav { change, msg })})} else {Task::none()}}
let nav =state.logs.diffs_nav.as_mut().and_then(|(nav_id_hash, nav)| {(*nav_id_hash == id_hash).then_some(nav)});return diff::update(diff_state, nav, action).map(move |msg| Msg::LogDiffNav { id_hash, msg });
let nav = state.entire_logs.diffs_nav.as_mut().and_then(|(nav_id_hash, nav)| (*nav_id_hash == id_hash).then_some(nav),);return diff::update(diff_state, nav, action).map(move |msg| {Msg::View(app::Msg::LogChangeFileDiff { id_hash, msg })});}} else if let Some(selection::Status::LogChange(selection::LogChange {ix: _,hash: selected_hash,message: _,file:Some(selection::LogChangeFileSelection {ix: _,path: selected_path,diff_selected: _,}),})) = state.selection.view.status.as_mut(){let selected_id_hash =file::log_id_parts_hash(*selected_hash, selected_path);if selected_id_hash == id_hash&& let Some(diff::FileAndState {file: _,state: diff_state,}) = state.status_logs.diffs.get_mut(&id_hash){let nav = state.status_logs.diffs_nav.as_mut().and_then(|(nav_id_hash, nav)| (*nav_id_hash == id_hash).then_some(nav),);return diff::update(diff_state, nav, action).map(move |msg| {Msg::View(app::Msg::LogChangeFileDiff { id_hash, msg })});
let task = if let Some(selection::Status::LogChange {ix: _,hash: selected_hash,message: _,file,}) = state.selection.view.status.as_mut()&& *selected_hash == hash
if let Some(app::EntireLog::Got { .. }) = state.repo.as_ref().and_then(|repo| repo.entire_log.as_ref())
if let Some(file) = file.as_ref() {
let Some(selection::LogChange {ix: _,hash: selected_hash,message: _,file,}) = state.selection.view.entire_log.as_mut()else {return Task::none();};if *selected_hash != hash {return Task::none();}let task = if let Some(file) = file.as_ref() {
state.logs.changes_nav = Some((hash, changes_nav));
state.entire_logs.changes_nav = Some((hash, changes_nav));nav_task.map(move |msg| {Msg::View(app::Msg::EntireLogChangeNav { change: hash, msg })})};// Store the changesstate.entire_logs.change_file_counts.insert(hash, changed_files_count);diffs.into_iter().for_each(|(path, diffs)| {// NOTE: using unknown encoding as we don't yet have the file// for past changeslet file = diff::init_file(diff::FileContent::UnknownEncoding,Some(&diffs),);let id_hash = file::log_id_parts_hash(hash, &path);let log_file_diff = diff::FileAndState {file,// The nav is initialized only once a file is selected,// because its tasks need it to be visible to completestate: diff::State::default(),};state.entire_logs.diffs.insert(id_hash, log_file_diff);});task} else if let Some(selection::Status::LogChange(selection::LogChange {ix: _,hash: selected_hash,message: _,file,})) = state.selection.view.status.as_mut(){let task = if *selected_hash == hash {if let Some(file) = file.as_ref() {// If a file is selected, init the nav for its difflet diff = diffs.get(&file.path).unwrap();let diff_contents_count = diff.len();let (diffs_nav, nav_task) = iced_nav_scrollable::init(diff_contents_count,HashSet::new(),);let id_hash = file::log_id_parts_hash(hash, &file.path);
// Store the changesstate.logs.change_file_counts.insert(hash, changed_files_count);diffs.into_iter().for_each(|(path, diffs)| {// NOTE: using unknown encoding as we don't yet have the file// for past changeslet file =diff::init_file(diff::FileContent::UnknownEncoding, Some(&diffs));let id_hash = file::log_id_parts_hash(hash, &path);let log_file_diff = diff::FileAndState {file,// The nav is initialized only once a file is selected,// because its tasks need it to be visible to completestate: diff::State::default(),
nav_task.map(move |msg| {Msg::View(app::Msg::LogChangeFileDiff {id_hash,msg: diff::Msg::DiffNav(msg),})})} else {// Init scrollable nav for log changeslet (changes_nav, nav_task) = iced_nav_scrollable::init(changed_files_count,HashSet::new(),);state.status_logs.changes_nav = Some((hash, changes_nav));nav_task.map(move |msg| {Msg::View(app::Msg::LogChangeNav { change: hash, msg })})}} else {Task::none()
// Store the changesstate.status_logs.change_file_counts.insert(hash, changed_files_count);diffs.into_iter().for_each(|(path, diffs)| {// NOTE: using unknown encoding as we don't yet have the file// for past changeslet file = diff::init_file(diff::FileContent::UnknownEncoding,Some(&diffs),);let id_hash = file::log_id_parts_hash(hash, &path);let log_file_diff = diff::FileAndState {file,// The nav is initialized only once a file is selected,// because its tasks need it to be visible to completestate: diff::State::default(),};
task
fn got_entire_log(state: &mut State, log: repo::Log) -> Task<Msg> {if let Some(repo) = state.repo.as_mut() {match repo.entire_log.as_ref() {None| Some(app::EntireLog::Loading | app::EntireLog::Got { .. }) => {let (nav, task) =iced_nav_scrollable::init(log.len(), HashSet::new());repo.entire_log = Some(app::EntireLog::Got {log,nav: Box::new(nav),});return task.map(|msg| Msg::View(app::Msg::EntireLogNav(msg)));}Some(app::EntireLog::LoadingButNotViewing| app::EntireLog::NotViewing { .. },) => {repo.entire_log = Some(app::EntireLog::NotViewing {log,nav_stored_offset: None,});}}}Task::none()
"a" => Some(Msg::View(app::Msg::AddUntrackedFile)),"c" => Some(Msg::View(app::Msg::SwitchChannel)),"f" => Some(Msg::View(app::Msg::ForkChannel)),
// _________________________________________________________// Directions
// _________________________________________________________// Other keys (sort alphabetically)"a" => Some(Msg::View(app::Msg::AddUntrackedFile)),"c" => Some(Msg::View(app::Msg::SwitchChannel)),"e" => Some(Msg::View(app::Msg::EntireLog)),"f" => Some(Msg::View(app::Msg::ForkChannel)),
let status_nav_sub = if state.repo.is_some() {
let status_nav_sub = if let Some(selection::LogChange { .. }) =state.selection.view.entire_log{iced_nav_scrollable::subs().map(|msg| Msg::View(app::Msg::EntireLogNav(msg)))} else if state.repo.is_some() {
let log_nav_sub =if let Some((hash, _nav)) = state.logs.changes_nav.as_ref() {let hash = *hash;iced_nav_scrollable::subs().with(hash).map(|(hash, msg)| Msg::View(app::Msg::LogNav { hash, msg }))} else {Subscription::none()};
let log_nav_sub = if let Some((hash, _nav)) =state.entire_logs.changes_nav.as_ref(){let change = *hash;iced_nav_scrollable::subs().with(change).map(|(change, msg)| {Msg::View(app::Msg::EntireLogChangeNav { change, msg })})} else if let Some((hash, _nav)) = state.status_logs.changes_nav.as_ref() {let hash = *hash;iced_nav_scrollable::subs().with(hash).map(|(hash, msg)| {Msg::View(app::Msg::LogChangeNav { change: hash, msg })})} else {Subscription::none()};
let diff_nav_subs = match state.selection.view.status.as_ref() {Some(selection::Status::UntrackedFile {ix: _,path,diff_selected: true,}) => {let id_hash = file::id_parts_hash(path, file::Kind::Untracked);iced_nav_scrollable::subs().with(id_hash).map(|(id_hash, msg)| {Msg::View(app::Msg::FileDiff {id_hash,msg: diff::Msg::DiffNav(msg),})})
let diff_nav_subs = if let Some(selection) =state.selection.view.entire_log.as_ref(){match selection {selection::LogChange {ix: _,hash,message: _,file:Some(selection::LogChangeFileSelection {ix: _,path,diff_selected: true,}),} => {let id_hash = file::log_id_parts_hash(*hash, path);iced_nav_scrollable::subs().with(id_hash).map(|(id_hash, msg)| {Msg::View(app::Msg::LogChangeFileDiff {id_hash,msg: diff::Msg::DiffNav(msg),})},)}_ => Subscription::none(),
Some(selection::Status::ChangedFile {ix: _,path,diff_selected: true,}) => {let id_hash = file::id_parts_hash(path, file::Kind::Changed);iced_nav_scrollable::subs().with(id_hash).map(|(id_hash, msg)| {Msg::View(app::Msg::FileDiff {id_hash,msg: diff::Msg::DiffNav(msg),})})}Some(selection::Status::LogChange {ix: _,hash,message: _,file:Some(selection::LogChangeFileSelection {ix: _,path,diff_selected: true,}),}) => {let id_hash = file::log_id_parts_hash(*hash, path);iced_nav_scrollable::subs().with(id_hash).map(|(id_hash, msg)| Msg::LogDiffNav {id_hash,msg: diff::Msg::DiffNav(msg),})
} else {match state.selection.view.status.as_ref() {Some(selection::Status::UntrackedFile {ix: _,path,diff_selected: true,}) => {let id_hash = file::id_parts_hash(path, file::Kind::Untracked);iced_nav_scrollable::subs().with(id_hash).map(|(id_hash, msg)| {Msg::View(app::Msg::FileDiff {id_hash,msg: diff::Msg::DiffNav(msg),})},)}Some(selection::Status::ChangedFile {ix: _,path,diff_selected: true,}) => {let id_hash = file::id_parts_hash(path, file::Kind::Changed);iced_nav_scrollable::subs().with(id_hash).map(|(id_hash, msg)| {Msg::View(app::Msg::FileDiff {id_hash,msg: diff::Msg::DiffNav(msg),})},)}Some(selection::Status::LogChange(selection::LogChange {ix: _,hash,message: _,file:Some(selection::LogChangeFileSelection {ix: _,path,diff_selected: true,}),})) => {let id_hash = file::log_id_parts_hash(*hash, path);iced_nav_scrollable::subs().with(id_hash).map(|(id_hash, msg)| {Msg::View(app::Msg::LogChangeFileDiff {id_hash,msg: diff::Msg::DiffNav(msg),})},)}Some(selection::Status::UntrackedFile { .. })| Some(selection::Status::ChangedFile { .. })| Some(selection::Status::LogChange { .. })| None => Subscription::none(),
Some(selection::Status::LogChange {
Some(selection::Status::LogChange(selection::LogChange {ix: log_ix,hash,message,file,})) => {let file = file.as_ref().map(|selection::LogChangeFileSelection {ix: change_ix,path,diff_selected,}| {let id_hash = file::log_id_parts_hash(*hash, path);let nav = status_logs.diffs_nav.as_ref().and_then(|(nav_id_hash, nav)| {(*nav_id_hash == id_hash).then_some(nav)},);let diff = status_logs.diffs.get(&id_hash).zip(nav).map(|(diff::FileAndState { file, state }, nav)| app::Diff {is_selected: *diff_selected,file,state,nav,},);app::LogChangeFileSelection {ix: *change_ix,path,diff,}},);let nav = status_logs.changes_nav.as_ref().and_then(|(nav_hash, nav)| (nav_hash == hash).then_some(nav));Some(app::StatusSelection::Log {ix: *log_ix,hash: *hash,message,file,nav,})}None => None,};let entire_log_selection = match selection.view.entire_log.as_ref() {Some(selection::LogChange {
debug_assert_eq!(unmatched_sections, 0,"The `NavScrollable` was most likely initialized with a count different from the number of actual children given to the the view function. Actual number is {}, but got only {} children", nav.section_heights.len(), nav.section_heights.len() - unmatched_sections);
debug_assert_eq!(unmatched_sections, 0,"The `NavScrollable` was most likely initialized with a count different from the number of actual children given to the the view function. Initialized with {}, but viewing only {} children", nav.section_heights.len(), nav.section_heights.len() - unmatched_sections);