YK3MOJJLRYEKZ4FUCNJ3YKMTKOINWIYOJKR3ER7IRSGTC7O6FJZQC 6YZAVBWU6E5FYOI5JGEIPXGZLIKAW6LS2AOFIQWEE5DMOPPCD5PQC ELG3UDT6OJFEYSJR7HZEC65IUWBMGPPPCXEW3CDW5T74R6KC5LIAC W7IUT3ZVMFH77IGKLAL7WX7IVVTGTY3FKEJ3WHMP3KI37B6NENLQC AMPZ2BXK4IGUZO3OPBRSJ6Z4GI5K4PRFMLUGTR6AP4FKKRWQG7LQC L6KSEFQIWICZJ6HJUFKLZQDEH6X2QMFM4Z7ZZUGMLDMFF7EHRXWAC BFN2VHZS7VCBUHQ4S3CQ3LFQV2V4M6VANNAF32XMRFQVWRGYSZ6AC GWZGYNIBQP2AA7WYULNBS2BCV4B36IHK4OS7XHVOTUUG27E76XFQC 23SFYK4Q5NKBPJG53PQNPWQH6UOUU2YKJEL7RLXYBRLJOJYV7AWQC OPXFZKEBDHZZLXEJ2JRDYBOJH6YIN7UZNZYHVHMWMQVDTE2ZD53QC 3QVNMRNMI63L2VOFVTMPCVPXH3J4JXLXVTIIPNOMACQCPCAPWILQC MYGIBRRHHXPKVRAMQQRJTZH74L2XOK3SF7J57JPCRKSVRLZ2D6NQC 3BK22XE5LPOH2EK5AMRXFXHNQNCJ54HEPYRINHJT4DA7INT32I7AC ACDXXAX26ZJJFKJDGRC2GOSJY5JHQWCSTP55SYI6D6LH5UIRYUBAC FVA36HBVXZCYW7FMQLST63Q6IDGLJ23OIHORF67BUIO2GXYNBW2QC I56UGW7UUKLSR4753EYRGNROZB5PD522REEOGHVAQOZZTSVRUEEQC X6AK4QPXKTGTWIMJ5CIR46CVIXVUXV5WKTP73CNQOIRANQN4MD5QC KMB6FND35LWT4XTRUNEJZ7SQXFZEUYIJAADGWJVB4RY4IIAT4KSQC YYKXNBFL44LLOBABLXBKOF7IFUIGIEL2SYIPLGDH6UOEY5EZZZSQC 5CYU7UT74NXJWCC36GNQGVBXH676BHBXWZQVIINRMPDEJ27SBRGAC TSFQFCB2NXDOBLBRUSAT63VJIXLPPTJGSTIDNOTLGHVVWSHITRNQC 7SSBM4UQMYVRL6L3ICYZQPSMYLZZQNMDWH6JKA3KOOSXZDJHESHQC OC6DLIZ3BKN5ZCDK77653XBR4DSTCFRXUGWDSKVGGJQNM7QZHJBAC I2AG42PAVOII4V4TWDJV5ZVNDIHKBRDT254BFQLFUIY723TW6CCQC SASAN2XCWDQ2VEHZ7TAQEN2R3Y7AG7JUGEFVRL4DZAGHXDFEZFRQC GOLHUD6RCYCO2SPULCFGWIW3ALBDODNEMUHVAKCDWYQREKWGFTLQC KWTBNTO3QUUE2YADF6SYW6G6ZOKYEWRJQKIWDGZXR33S3YNDVIZQC 5MUEECMJHU44FL5RDUR3VFBIWK3H4X2L5MVJ73J37PYHZWLUKU2AC 3TLPJ57B2OD5OWJN5WMS7A4W7IGFUWJJHVIXRM34VT6KUN6R4YSAC BNHJU2DU4HHADLKTDQMRG5PC5VHCJ2G7UFQRRVUBTALXVBUAQSKQC AI3IMKC3HRPMTWQCU5HGWKUHGKTJ22QF7V4AAEI6IEBIZ4WYWCKQC PTWZYQFRWWUOE2WMQT26CKZKFSHAIJVJS3QWHJFYUFDRRTVPHSUAC UR4J677RWA3OFG6HQTD46BUUE5YFPSBEFCJAEM5OMT4V5A7SBNNQC A6Z4O6RC33HYWP7JIVQ6FDWE4EOCQWQTIGENK2WAHUGSHDDLSA7QC JZXYSIYDPBWQZCAMGDZ5BFMN6SU73EVVDIYEGTDJN6DVOSBNHN4QC 5ZRDYL6KIQPUI3ZZETH5KJ64N6RUF7KYM3P6Q6HER5XVJZ7GZ4WQC NZD56PVBVHARAQD7JNXE2F3DRT4S2NNRDHVVJTKTQK474LDMVIXQC OJPGHVC3RFBQ7TTSCZH6URSSATII3TESD74EISDNOTNXXSX7PQMAC 3XRG4BB6V5V4DICZCMOZMLQNTANWKPO7BBRATTXOZLRNSEUQIA5AC WAOGSCOJ5A372BZKHEYD2BCDBCENNVLFYW3INKUOOAZMDADDIFIQC WH57EHNML4OTGQQZBT2SG6SOFTBOD6OJPJYHJVGPH22CSSOE25AAC EJPSD5XO43DWUBBZGNQMY4TMCAXL5EWCGX3OEHUERQ5GRASGWQLQC VCNKFNUF7OWVSWC6I5D25KUZ3XZZICZ3LHWVPF2N5ZSP7LQ2JOUQC WT3GA27PQ2AOAIGK65O3Q4DMX4AZDVNULBLRL6GF4QW6QCASUEAAC MJDGPSHGF62FTVWZBE7MFNJTUQD42OBVJEOSVPBT553UFJLTEMXQC self as model, action, get_entire_log_diffs_nav, get_entire_log_files_nav,get_files_diffs_nav, get_status_log_diffs_nav, get_status_log_files_nav,selection, Log, ReadyState, RecordMsg, SubState,
self as model, action, selection, Log, ReadyState, RecordMsg, SubState,
let diff = get_diff(id_hash);let state = navigation.files_diffs.diffs.get(&id_hash);let nav = get_files_diffs_nav(navigation, id_hash);let diffs = match diff.zip(state).zip(nav) {Some(((file, state), nav)) => {
let diff = get_diff(file_id);let state = navigation.files_diffs.diffs.get(&file_id);let nav = &navigation.files_diffs.diffs_nav;let diffs = match diff.zip(state) {Some((file, state)) => {
let diff = get_diff(id_hash);let state = navigation.files_diffs.diffs.get(&id_hash);let nav = get_files_diffs_nav(navigation, id_hash);let diffs = match diff.zip(state).zip(nav) {Some(((file, state), nav)) => {
let diff = get_diff(file_id);let state = navigation.files_diffs.diffs.get(&file_id);let nav = &navigation.files_diffs.diffs_nav;let diffs = match diff.zip(state) {Some((file, state)) => {
let nav = get_status_log_files_nav(navigation, *hash);let view = match nav {Some(nav) => {let change_selected = match file.as_ref() {Some(selection::LogChangeFileSelection {ix: _,path: _,diff_selected,}) => *diff_selected,_ => false,};
let change_selected = match file.as_ref() {Some(selection::LogChangeFileSelection {ix: _,path: _,diff_selected,}) => !*diff_selected,_ => false,};
let files = entry.file_paths.iter().enumerate().map(|(ix, path)| {let is_selected = matches!(file, Some(selection::LogChangeFileSelection{ path: selected_path, .. }) if selected_path == path);el(button(text(path)).on_press_with(move || {Msg::Action(action::Msg::Selection(selection::Msg::Select(selection::Select::LogChangeFile { ix, path: path.clone() })))}).class(selectable_button_class(is_selected)))});el(nav_scrollable(nav, files).class(if change_selected {theme::Scrollable::Selected} else {theme::Scrollable::Normal}).width(Length::Fill).height(Length::Fill))}None => el(text("Loading...")),};
let files = entry.file_paths.iter().enumerate().map(|(ix, path)| {let is_selected = matches!(file, Some(selection::LogChangeFileSelection{ path: selected_path, .. }) if selected_path == path);el(button(text(path)).on_press_with(move || {Msg::Action(action::Msg::Selection(selection::Msg::Select(selection::Select::LogChangeFile { ix, path: path.clone() })))}).class(selectable_button_class(is_selected)))});let nav = &navigation.status_logs_navs.files_nav;let files = el(nav_scrollable(nav, files).class(if change_selected {theme::Scrollable::Selected} else {theme::Scrollable::Normal}).width(Length::Fill).height(Length::Fill));
} else if let selection::Unified::EntireLog(Some(selection::LogChange {file,..})) = selection{if file.is_none() {1} else {2}} else if let selection::Unified::Status(Some(selection)) = selection {
} else {
}) => 1,selection::Status::LogChange(selection::LogChange {file: Some(_),..}) => 2,
})) => {if file.is_none() {1} else {2}}selection::Unified::Status(Some(selection)) => match selection {selection::Status::UntrackedFile { .. }| selection::Status::ChangedFile { .. }| selection::Status::LogChange(selection::LogChange {file: None,..}) => 1,selection::Status::LogChange(selection::LogChange {file: Some(_),..}) => 2,},selection::Unified::Channel(Some(selection)) => match selection {selection::Channel { log: None, .. } => 1,selection::Channel {log: Some(selection::LogChange { file: None, .. }),..} => 2,selection::Channel {log: Some(selection::LogChange { file: Some(_), .. }),..} => 3,},selection::Unified::Status(None)| selection::Unified::Channel(None)| selection::Unified::EntireLog(None) => 0,
let id_hash = file::log_id_parts_hash(*hash, path);let state = navigation.log_diffs.diffs.get(&id_hash);let nav = get_status_log_diffs_nav(navigation, id_hash);Some(el(column([
let file_id = file::log_id_parts_hash(*hash, path);let state = navigation.log_diffs.diffs.get(&file_id);el(column([
.spacing(SPACING)))
.spacing(SPACING))}_ => el(column([])),};let other_channels_selected = || match selection {selection::Unified::Channel(channel) => match channel {Some(selection::Channel { log, .. }) => log.is_none(),None => true,},_ => false,};let other_channel_log_selected = || match selection {selection::Unified::Channel(Some(selection::Channel {ix: _,name: _,log: Some(selection::LogChange { file, .. }),})) => file.is_none(),_ => false,};let other_channel_log_change_selected = || match selection {selection::Unified::Channel(Some(selection::Channel {ix: _,name: _,log:Some(selection::LogChange {file:Some(selection::LogChangeFileSelection {diff_selected,..}),..}),})) => !*diff_selected,_ => false,};let view_channels = || {other_channels.iter().enumerate().map(|(ix, channel)| {let is_selected = matches!(selection,selection::Unified::Channel(Some(selection::Channel{ ix: selected_ix, .. })) if &ix == selected_ix);el(button(text(channel)).on_press(Msg::Action(action::Msg::Confirm)).class(selectable_button_class(is_selected)))})};// Other channel selectionlet channel_col_0 = || {el(column([el(text(format!("Current channel: {channel}"))),el(nav_scrollable(&navigation.other_channels_nav, view_channels()).class(if other_channels_selected() {theme::Scrollable::Selected} else {theme::Scrollable::Normal}).width(Length::Fill).height(Length::Fill),),]))};// Other channel change selection in selected channellet channel_col_1 = || match selection {selection::Unified::Channel(Some(selection::Channel {ix: _channel_ix,name,log: _,})) => {let Some(Log::Loaded { log }) = logs.other_channels_logs.get(name)else {return el(column([]));};let entries = log.iter().enumerate().map(|(ix, entry)| {let is_selected = matches!(selection,selection::Unified::Channel(Some(selection::Channel { log: Some(selection::LogChange { ix: selected_ix, .. }), .. })) if &ix == selected_ix);view_log_change(ix, entry, is_selected)});let len = log.len();let selected_ix = match selection {selection::Unified::EntireLog(Some(selection::LogChange {ix,..})) => Some(len - *ix),_ => None,};el(column([if let Some(selected_ix) = selected_ix {el(text(format!("Channel {name} log ({selected_ix}/{len})")))} else {el(text(format!("Channel {name} log ({len})")))},el(nav_scrollable(&navigation.entire_log_nav, entries).class(if other_channel_log_selected() {theme::Scrollable::Selected} else {theme::Scrollable::Normal}).width(Length::Fill).height(Length::Fill)),if hidden_cols == 1 {el(button(row([el(text("← Other channels").shaping(text::Shaping::Advanced))])).on_press(Msg::Action(action::Msg::Selection(selection::Msg::PressDir(selection::Dir::Left),))))} else {el(row([]))},]).width(Length::Fill).height(Length::Fill))
_ => None,
_ => el(column([])),};// Other channel log file selection in selected channel's changelet channel_col_2 = || match selection {selection::Unified::Channel(Some(selection::Channel {ix: _channel_ix,name,log:Some(selection::LogChange {ix,hash,message,file,}),})) => {let short_hash = display_short_hash(hash);let nav = &navigation.other_channel_logs_navs.files_nav;let view = match logs.other_channels_logs.get(name) {Some(Log::Loaded { log }) => {let entry = log.get(*ix).unwrap();let files = entry.file_paths.iter().enumerate().map(|(ix, path)| {let is_selected = matches!(file, Some(selection::LogChangeFileSelection{ path: selected_path, .. }) if selected_path == path);el(button(text(path)).on_press_with(move || {Msg::Action(action::Msg::Selection(selection::Msg::Select(selection::Select::LogChangeFile { ix, path: path.clone() })))}).class(selectable_button_class(is_selected)))});el(nav_scrollable(nav, files).class(if other_channel_log_change_selected() {theme::Scrollable::Selected} else {theme::Scrollable::Normal}).width(Length::Fill).height(Length::Fill))}_ => el(text("Loading...")),};let files_view = 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));el(column([files_view,if hidden_cols == 2 {el(button(row([el(text("← Log").shaping(text::Shaping::Advanced))])).on_press(Msg::Action(action::Msg::Selection(selection::Msg::PressDir(selection::Dir::Left,)),)))} else {el(row([]))},]).width(Length::Fill).height(Length::Fill).spacing(SPACING))}_ => el(row([])),
// Other channel log diff selection in selected channel's change's filelet channel_col_3 = || match selection {selection::Unified::Channel(Some(selection::Channel {name: _,log:Some(selection::LogChange {ix: _,hash,message: _,file:Some(selection::LogChangeFileSelection {ix: _,path,diff_selected,}),}),..})) => {let file_id = file::log_id_parts_hash(*hash, path);let state = navigation.log_diffs.diffs.get(&file_id);el(column([el(column([view_diff_header(format!("{path} changes in {}:",display_short_hash(hash))),match state {Some(diff::FileAndState { file, state }) => {let nav =&navigation.other_channel_logs_navs.diffs_nav;diff::view(state, nav, file, *diff_selected)}None => el(text("Loading diff..")),},]).spacing(SPACING)),if hidden_cols == 3 {el(button(row([el(text("← Files").shaping(text::Shaping::Advanced))])).on_press(Msg::Action(action::Msg::Selection(selection::Msg::PressDir(selection::Dir::Left,)),)))} else {el(row([]))},]).width(Length::Fill).height(Length::Fill).spacing(SPACING))}_ => el(column([])),};
_ => false,};let other_channels_selected = || match selection {selection::Unified::Channel(channel) => match channel {Some(selection::Channel { log, .. }) => log.is_none(),None => true,},
let entire_log_col_1 = || {let files_view = match selection {selection::Unified::EntireLog(Some(selection::LogChange {ix,hash,message,file,})) => {let short_hash = display_short_hash(hash);let nav = get_entire_log_files_nav(navigation, *hash);let view = match nav.as_ref().zip(logs.entire_log.as_ref()) {Some((nav, Log::Loaded { log })) => {let entry = log.get(*ix).unwrap();let files = entry.file_paths.iter().enumerate().map(|(ix, path)| {
// Entire log file selection in selected changelet entire_log_col_1 = || match selection {selection::Unified::EntireLog(Some(selection::LogChange {ix,hash,message,file,})) => {let short_hash = display_short_hash(hash);let view = match logs.entire_log.as_ref() {Some(Log::Loaded { log }) => {let nav = &navigation.entire_logs_navs.files_nav;let entry = log.get(*ix).unwrap();let files = entry.file_paths.iter().enumerate().map(|(ix, path)| {
el(nav_scrollable(nav, files).class(if entire_log_change_selected() {theme::Scrollable::Selected} else {theme::Scrollable::Normal}).width(Length::Fill).height(Length::Fill))}_ => el(text("Loading...")),};
el(nav_scrollable(nav, files).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))}_ => el(row([])),};
let files_view = 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));
el(column([files_view,if hidden_cols == 1 {el(button(row([el(text("← Log").shaping(text::Shaping::Advanced))])).on_press(Msg::Action(action::Msg::Selection(selection::Msg::PressDir(selection::Dir::Left,)),)))} else {el(row([]))},]).width(Length::Fill).height(Length::Fill).spacing(SPACING))
el(column([files_view,if hidden_cols == 1 {el(button(row([el(text("← Log").shaping(text::Shaping::Advanced))])).on_press(Msg::Action(action::Msg::Selection(selection::Msg::PressDir(selection::Dir::Left,)),)))} else {el(row([]))},]).width(Length::Fill).height(Length::Fill).spacing(SPACING))}_ => el(row([])),
let id_hash = file::log_id_parts_hash(*hash, path);let state = navigation.log_diffs.diffs.get(&id_hash);let nav = get_entire_log_diffs_nav(navigation, id_hash);Some(el(column([
let file_id = file::log_id_parts_hash(*hash, path);let state = navigation.log_diffs.diffs.get(&file_id);el(column([
_ => None,};let view_channels = || {other_channels.iter().enumerate().map(|(ix, channel)| {let is_selected = matches!(selection,selection::Unified::Channel(Some(selection::Channel{ ix: selected_ix, .. })) if &ix == selected_ix);el(button(text(channel)).on_press(Msg::Action(action::Msg::Confirm)).class(selectable_button_class(is_selected)))})
_ => el(column([])),
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);el(row(cols).spacing(SPACING).width(Length::Fill).height(Length::Fill))} else {let cols = [status_col_0(), status_col_1()];el(row(cols).spacing(SPACING).width(Length::Fill).height(Length::Fill))}
let cols = [status_col_0(), status_col_1(), status_col_2()].into_iter().skip(hidden_cols);el(row(cols).spacing(SPACING).width(Length::Fill).height(Length::Fill))
let nav = nav_scrollable(&navigation.other_channels_nav,view_channels(),).class(if other_channels_selected() {theme::Scrollable::Selected} else {theme::Scrollable::Normal}).width(Length::Fill).height(Length::Fill);el(column([el(text(format!("Current channel: {channel}"))),el(nav),]).width(Length::Fill).height(Length::Fill))
let cols = [channel_col_0(),channel_col_1(),channel_col_2(),channel_col_3(),].into_iter().skip(hidden_cols);el(row(cols).width(Length::Fill).height(Length::Fill))
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))}
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))
pub diffs_nav: Option<(file::LogIdHash, nav_scrollable::State)>,
pub diffs_nav: nav_scrollable::State,#[cfg(debug_assertions)]pub change_hash: Option<repo::ChangeHash>,#[cfg(debug_assertions)]pub file_id: Option<file::LogIdHash>,}pub fn init_files_nav(navs: &mut Navs, hash: repo::ChangeHash) {navs.files_nav = nav_scrollable::State::default();#[cfg(debug_assertions)]{navs.change_hash = Some(hash);navs.file_id = None;}#[cfg(not(debug_assertions))]let _ = hash;}pub fn init_diffs_nav(navs: &mut Navs,file_id: file::LogIdHash,) -> &mut nav_scrollable::State {navs.diffs_nav = nav_scrollable::State::default();#[cfg(debug_assertions)]{navs.file_id = Some(file_id);}#[cfg(not(debug_assertions))]let _ = file_id;&mut navs.diffs_nav
}/// Get untracked or changes files diff's nav if it matches given file IDpub fn get_files_diffs_nav(navigation: &Navigation,id_hash: file::IdHash,) -> Option<&nav_scrollable::State> {navigation.files_diffs.diffs_nav.as_ref().and_then(|(nav_id_hash, nav)| (*nav_id_hash == id_hash).then_some(nav))}/// Get untracked or changes files diff's nav if it matches given file IDpub fn get_files_diffs_nav_mut(navigation: &mut Navigation,id_hash: file::IdHash,) -> Option<&mut nav_scrollable::State> {navigation.files_diffs.diffs_nav.as_mut().and_then(|(nav_id_hash, nav)| (*nav_id_hash == id_hash).then_some(nav))
/// Get status log files nav if it matches given change hashpub fn get_status_log_files_nav(navigation: &Navigation,hash: repo::ChangeHash,) -> Option<&nav_scrollable::State> {navigation.status_logs_navs.files_nav.as_ref().and_then(|(nav_hash, nav)| (*nav_hash == hash).then_some(nav))}/// Get status log files nav if it matches given change hashpub fn get_status_log_files_nav_mut(
pub fn init_channel_nav(
hash: repo::ChangeHash,) -> Option<&mut nav_scrollable::State> {navigation.status_logs_navs.files_nav.as_mut().and_then(|(nav_hash, nav)| (*nav_hash == hash).then_some(nav))
#[cfg(debug_assertions)] name: String,) {navigation.other_channel_logs_navs = log::Navs::default();#[cfg(debug_assertions)]{navigation.other_channel_name = Some(name);}
/// Get status log diffs nav if it matches given log file IDpub fn get_status_log_diffs_nav(navigation: &Navigation,id_hash: file::LogIdHash,) -> Option<&nav_scrollable::State> {navigation.status_logs_navs.diffs_nav.as_ref().and_then(|(nav_id_hash, nav)| (*nav_id_hash == id_hash).then_some(nav))}/// Get status log diffs nav if it matches given log file IDpub fn get_status_log_diffs_nav_mut(navigation: &mut Navigation,id_hash: file::LogIdHash,) -> Option<&mut nav_scrollable::State> {navigation.status_logs_navs.diffs_nav.as_mut().and_then(|(nav_id_hash, nav)| (*nav_id_hash == id_hash).then_some(nav))}/// Get entire log files nav if it matches given change hashpub fn get_entire_log_files_nav(navigation: &Navigation,hash: repo::ChangeHash,) -> Option<&nav_scrollable::State> {navigation.entire_logs_navs.files_nav.as_ref().and_then(|(nav_hash, nav)| (*nav_hash == hash).then_some(nav))}/// Get entire log files nav if it matches given change hashpub fn get_entire_log_files_nav_mut(navigation: &mut Navigation,hash: repo::ChangeHash,) -> Option<&mut nav_scrollable::State> {navigation.entire_logs_navs.files_nav.as_mut().and_then(|(nav_hash, nav)| (*nav_hash == hash).then_some(nav))}/// Get entire log diffs nav if it matches given log file IDpub fn get_entire_log_diffs_nav(navigation: &Navigation,id_hash: file::LogIdHash,) -> Option<&nav_scrollable::State> {navigation.entire_logs_navs.diffs_nav.as_ref().and_then(|(nav_id_hash, nav)| (*nav_id_hash == id_hash).then_some(nav))}/// Get entire log diffs nav if it matches given log file IDpub fn get_entire_log_diffs_nav_mut(navigation: &mut Navigation,id_hash: file::LogIdHash,) -> Option<&mut nav_scrollable::State> {navigation.entire_logs_navs.diffs_nav.as_mut().and_then(|(nav_id_hash, nav)| (*nav_id_hash == id_hash).then_some(nav))}
pub diffs_nav: Option<(file::IdHash, nav_scrollable::State)>,
pub diffs_nav: nav_scrollable::State,#[cfg(debug_assertions)]pub file_id: Option<file::IdHash>,}pub fn init_diffs_nav(navs: &mut FilesState,#[cfg(debug_assertions)] file_id: file::IdHash,) -> &mut nav_scrollable::State {navs.diffs_nav = nav_scrollable::State::default();#[cfg(debug_assertions)]{navs.file_id = Some(file_id);}&mut navs.diffs_nav
/// Eq used for action filters. Properties that are not determined by which actions are allowed are ignored.
pub fn is_allowed(allowed_actions: &[Binding], msg: &Msg) -> bool {// Always allow actions that do not need to be explicitly enabledmatch msg {Msg::Selection(// Mouse selectionsselection::Msg::Select(_) |// Button releaseselection::Msg::ReleaseDir(_),) => {return true;}Msg::Confirm| Msg::Cancel| Msg::Selection(selection::Msg::PressDir(_) | selection::Msg::AltPressDir(_),)| Msg::PostponeRecord| Msg::SaveRecord| Msg::DiscardRecord| Msg::AddUntrackedFile| Msg::RmAddedFile| Msg::StartRecord| Msg::SelectChannel| Msg::ForkChannel| Msg::RefreshRepo| Msg::ShowEntireLog => {}}allowed_actions.iter().any(|Binding {msg: allowed_msg, ..}| {if let Some(allowed_msg) = allowed_msg {is_same_msg(msg, allowed_msg)} else {false}},)}/// Eq used for action filters. Properties that are not determined by which/// actions are allowed are ignored.
ma.push(show_entire_log());}SubState::OtherChannelLog { can_select_right } => {ma.push(left());ma.push(down());ma.push(up());push_if(can_select_right, right, ma);ma.push(exit_other_channels());ma.push(show_entire_log());}SubState::OtherChannelLogChange { can_select_right } => {ma.push(left());ma.push(down());ma.push(up());push_if(can_select_right, right, ma);ma.push(exit_other_channels());
SelectingChannel(SwitchingChannelState),
/// Selecting an other channelSelectingChannel(SelectingChannelState),/// Viewing other channel's logOtherChannelLog {can_select_right: bool,},/// Viewing other channel's log with some change selectedOtherChannelLogChange {can_select_right: bool,},/// Viewing other channel's log with some diff of a change selectedOtherChannelLogChangeDiff,
let id_hash =file::id_parts_hash(path, file::Kind::Untracked);let nav = get_files_diffs_nav(navigation, id_hash);if let Some(nav) = nav {if *diff_selected {SubState::StatusLogDiff { can_record }} else {SubState::Main {selection: MainSelection::Untracked,can_select_right:nav_scrollable::needs_scrolling(nav),can_record,}}
if *diff_selected {SubState::StatusLogDiff { can_record }
let id_hash =file::id_parts_hash(path, file::Kind::Changed);let nav = get_files_diffs_nav(navigation, id_hash);if let Some(nav) = nav {if *diff_selected {SubState::StatusLogDiff { can_record }} else {SubState::Main {selection: main_selection(),can_select_right:nav_scrollable::needs_scrolling(nav),can_record,}}
if *diff_selected {SubState::StatusLogDiff { can_record }
let log_id = file::log_id_parts_hash(*hash, path);let nav = get_status_log_diffs_nav(navigation, log_id);if let Some(nav) = nav {if *diff_selected {SubState::StatusLogDiff { can_record }} else {SubState::StatusLogChange {can_record,can_select_right:nav_scrollable::needs_scrolling(nav),}}
if *diff_selected {SubState::StatusLogDiff { can_record }
selection::Primary::Channel => {let sub_state = if other_channels.is_empty() {SwitchingChannelState::NoOtherChannels} else if channel_selection.is_some() {let can_switch = changed_files.is_empty();SwitchingChannelState::SomethingSelected { can_switch }} else {SwitchingChannelState::NothingSelected};SubState::SelectingChannel(sub_state)}
selection::Primary::Channel => match channel_selection {Some(selection::Channel {log: None, name, ..}) => {let sub_state = if other_channels.is_empty() {SelectingChannelState::NoOtherChannels} else {let log = logs.other_channels_logs.get(name);let can_switch = changed_files.is_empty();let can_select_right = log.is_some();SelectingChannelState::SomethingSelected {can_switch,can_select_right,}};SubState::SelectingChannel(sub_state)}Some(selection::Channel {log: Some(selection::LogChange { file: None, .. }),..}) => {let files_nav = &navigation.other_channel_logs_navs.files_nav;let can_select_right = nav_scrollable::has_sections(files_nav);SubState::OtherChannelLog { can_select_right }}Some(selection::Channel {log:Some(selection::LogChange {file:Some(selection::LogChangeFileSelection {diff_selected,..}),..}),..}) => {if *diff_selected {SubState::OtherChannelLogChangeDiff} else {let diffs_nav =&navigation.other_channel_logs_navs.diffs_nav;let can_select_right =nav_scrollable::needs_scrolling(diffs_nav);SubState::OtherChannelLogChange { can_select_right }}}None => SubState::SelectingChannel(SelectingChannelState::NothingSelected,),},
let log_id = file::log_id_parts_hash(*hash, path);let nav = get_entire_log_diffs_nav(navigation, log_id);if let Some(nav) = nav {if *diff_selected {SubState::EntireLogChangeDiff} else {SubState::EntireLogChange {can_select_right:nav_scrollable::needs_scrolling(nav),}}
if *diff_selected {SubState::EntireLogChangeDiff
let nav = get_entire_log_files_nav(navigation, *hash);SubState::EntireLog {can_select_right: nav.is_some(),}
let files_nav = &navigation.entire_logs_navs.files_nav;let can_select_right =nav_scrollable::has_sections(files_nav);SubState::EntireLog { can_select_right }
{let ready_state = get_ready_state_mut(&mut state);ready_state.selection.status = Some(selection::Status::UntrackedFile {ix: 0,path: file_to_record.to_string(),diff_selected: false,});}
update(&mut state,Msg::View(app::Msg::Action(action::Msg::Selection(selection::Msg::Select(selection::Select::UntrackedFile {ix: 0,path: file_to_record.to_string(),}),))),);// Selection triggers `LoadedSrcFile`let msg = task::await_next_msg(&mut tasks).await;let id = file::Id {path: file_to_record.to_string(),file_kind: file::Kind::Untracked,};assert_matches!(&msg,Msg::File(crate::file::Msg::LoadedSrcFile { id: loaded_id, .. })if *loaded_id == id);
let _msg = task::await_next_msg(&mut tasks).await;
let msg = task::await_next_msg(&mut tasks).await;let id = file::Id {path: file_to_record.to_string(),file_kind: file::Kind::Untracked,};assert_matches!(&msg,Msg::File(crate::file::Msg::LoadedSrcFile { id: loaded_id, .. })if *loaded_id == id);
);// Selection triggers `LoadedSrcFile`let msg = task::await_next_msg(&mut tasks).await;let id = file::Id {path: file_to_record.to_string(),file_kind: file::Kind::Changed,};assert_matches!(&msg,Msg::File(crate::file::Msg::LoadedSrcFile { id: loaded_id, .. })if *loaded_id == id
use inflorescence_model::{get_entire_log_diffs_nav_mut, get_entire_log_files_nav_mut,get_files_diffs_nav_mut, get_status_log_diffs_nav_mut,get_status_log_files_nav_mut, Log, Logs, Navigation,};
use inflorescence_model::{self as model, Log, Logs, Navigation};
let id_hash = file::id_parts_hash(&path, file::Kind::Untracked);if let Some(nav) =get_files_diffs_nav_mut(ctx.navigation, id_hash){nav_scrollable::scroll_down(nav, delta);}
nav_scrollable::scroll_down(&mut ctx.navigation.files_diffs.diffs_nav,delta,);
let id_hash = file::id_parts_hash(&path, file::Kind::Changed);if let Some(nav) =get_files_diffs_nav_mut(ctx.navigation, id_hash){nav_scrollable::scroll_down(nav, delta);}
nav_scrollable::scroll_down(&mut ctx.navigation.files_diffs.diffs_nav,delta,);
if let Some(nav) = get_status_log_diffs_nav_mut(ctx.navigation,id_hash,) {nav_scrollable::scroll_down(nav, delta)};
nav_scrollable::scroll_down(&mut ctx.navigation.status_logs_navs.diffs_nav,delta,);
if let Some(nav) =get_status_log_files_nav_mut(ctx.navigation, hash){nav_scrollable::scroll_down_to_section(nav, file_ix)};
fn select_down_channel(ctx: &mut Ctx<'_>) -> Task<crate::Msg> {let ix = match ctx.state.channel.take() {
fn select_down_channel(ctx: &mut Ctx<'_>,delta: Option<Duration>,) -> Task<crate::Msg> {let (selection, task) = match ctx.state.channel.take() {
None => 0,};let (selection, task) = channel_selection(ix, VDir::Down, ctx);ctx.state.channel = Some(selection);task}fn select_down_entire_log(ctx: &mut Ctx<'_>,delta: Option<Duration>,) -> Task<crate::Msg> {if let Some(Log::Loaded { log }) = ctx.logs.entire_log.as_ref() {let (selection, task) = if let Some(LogChange {ix: log_ix,hash,message,file,}) = ctx.state.entire_log.take(){match file {Some(LogChangeFileSelection {
None => {let ix = 0;channel_selection(ix, VDir::Down, ctx)}Some(Channel {ix: channel_ix,name,log:Some(LogChange {ix: change_ix,hash,message,file,}),}) => {if let Some(Log::Loaded { log }) =ctx.logs.other_channels_logs.get(&name){if let Some(LogChangeFileSelection {
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 selection = Channel {ix: channel_ix,name,log: Some(LogChange {ix: change_ix,hash,message,file: Some(LogChangeFileSelection {ix: file_ix,path,diff_selected,}),
};if let Some(nav) = ctx.navigation.entire_logs_navs.diffs_nav.as_mut().and_then(|(nav_id_hash, nav)| {(*nav_id_hash == id_hash).then_some(nav)}){nav_scrollable::scroll_down(nav, delta)
if let Some(nav) =get_entire_log_files_nav_mut(ctx.navigation, hash){nav_scrollable::scroll_down_to_section(nav, file_ix)};let selection = LogChange {ix: log_ix,hash,message,file: Some(file),
let selection = Channel {ix: channel_ix,name,log: Some(LogChange {ix: change_ix,hash,message,file: Some(file),}),
} else {let (selection, task) =if log.len().saturating_sub(1) == change_ix {let ix = 0;channel_log_selection(ix, VDir::Up, ctx, log)} else {let ix = change_ix + 1;channel_log_selection(ix, VDir::Down, ctx, log)};let selection = Channel {ix: channel_ix,name,log: Some(selection),};(selection, task)
None => {if log.len().saturating_sub(1) == log_ix {let ix = 0;entire_log_selection(ix, VDir::Up, ctx, log)} else {let ix = log_ix + 1;entire_log_selection(ix, VDir::Down, ctx, log)}
} else {let selection = Channel {ix: channel_ix,name,log: Some(LogChange {ix: change_ix,hash,message,file,}),};(selection, Task::none())}}};ctx.state.channel = Some(selection);task}fn select_down_entire_log(ctx: &mut Ctx<'_>,delta: Option<Duration>,) -> Task<crate::Msg> {if let Some(Log::Loaded { log }) = ctx.logs.entire_log.as_ref() {let (selection, task) = if let Some(LogChange {ix: change_ix,hash,message,file,}) = ctx.state.entire_log.take(){if let Some(LogChangeFileSelection {ix: file_ix,path,diff_selected,}) = file{if diff_selected {let selection = LogChange {ix: change_ix,hash,message,file: Some(LogChangeFileSelection {ix: file_ix,path,diff_selected,}),};nav_scrollable::scroll_down(&mut ctx.navigation.entire_logs_navs.diffs_nav,delta,);(selection, Task::none())} else {let log_entry = log.get(change_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(file_ix,hash,VDir::Down,ctx.navigation,log_entry,);let selection = LogChange {ix: change_ix,hash,message,file: Some(file),};(selection, selection_task)
let id_hash = file::id_parts_hash(&path, file::Kind::Untracked);if let Some(nav) =ctx.navigation.files_diffs.diffs_nav.as_mut().and_then(|(nav_id_hash, nav)| {(*nav_id_hash == id_hash).then_some(nav)},){nav_scrollable::scroll_up(nav, delta)}
nav_scrollable::scroll_up(&mut ctx.navigation.files_diffs.diffs_nav,delta,);
let id_hash = file::id_parts_hash(&path, file::Kind::Changed);if let Some(nav) =ctx.navigation.files_diffs.diffs_nav.as_mut().and_then(|(nav_id_hash, nav)| {(*nav_id_hash == id_hash).then_some(nav)},){nav_scrollable::scroll_up(nav, delta)}
nav_scrollable::scroll_up(&mut ctx.navigation.files_diffs.diffs_nav,delta,);
if let Some(nav) = ctx.navigation.status_logs_navs.diffs_nav.as_mut().and_then(|(nav_id_hash, nav)| {(*nav_id_hash == id_hash).then_some(nav)}){nav_scrollable::scroll_up(nav, delta)};
nav_scrollable::scroll_up(&mut ctx.navigation.status_logs_navs.diffs_nav,delta,);
if let Some(nav) =get_entire_log_files_nav_mut(ctx.navigation, hash){nav_scrollable::scroll_up_to_section(nav, file_ix)};
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,}),};if let Some(nav) = get_entire_log_diffs_nav_mut(ctx.navigation,id_hash,) {nav_scrollable::scroll_up(nav, delta)};(selection, Task::none())} 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};
if let Some(LogChangeFileSelection {ix: file_ix,path,diff_selected,}) = file{if diff_selected {let selection = LogChange {ix: log_ix,hash,message,file: Some(LogChangeFileSelection {ix: file_ix,path,diff_selected,}),};
let selection = LogChange {ix: log_ix,hash,message,file: Some(file),};(selection, selection_task)}}None => {if 0 == log_ix {let ix = log.len().saturating_sub(1);entire_log_selection(ix, VDir::Down, ctx, log)
let file_ix = if 0 == file_ix {log_entry.file_paths.len().saturating_sub(1)
let ix = log_ix - 1;entire_log_selection(ix, VDir::Up, ctx, log)}
file_ix - 1};let (file, selection_task) = entire_log_file_selection(file_ix,hash,VDir::Up,ctx.navigation,log_entry,);let selection = LogChange {ix: log_ix,hash,message,file: Some(file),};(selection, selection_task)
let id_hash = file::id_parts_hash(path, file::Kind::Untracked);if let Some(nav) =ctx.navigation.files_diffs.diffs_nav.as_mut().and_then(|(nav_id_hash, nav)| {(*nav_id_hash == id_hash).then_some(nav)},){nav_scrollable::alt_scroll_down(nav, delta)}
nav_scrollable::alt_scroll_down(&mut ctx.navigation.files_diffs.diffs_nav,delta,);
let id_hash = file::id_parts_hash(path, file::Kind::Changed);if let Some(nav) =ctx.navigation.files_diffs.diffs_nav.as_mut().and_then(|(nav_id_hash, nav)| {(*nav_id_hash == id_hash).then_some(nav)},){nav_scrollable::alt_scroll_down(nav, delta)}
nav_scrollable::alt_scroll_down(&mut ctx.navigation.files_diffs.diffs_nav,delta,);
let id_hash = file::log_id_parts_hash(*hash, path);if let Some(nav) =ctx.navigation.status_logs_navs.diffs_nav.as_mut().and_then(|(nav_id_hash, nav)| {(*nav_id_hash == id_hash).then_some(nav)},){nav_scrollable::alt_scroll_down(nav, delta)}
nav_scrollable::alt_scroll_down(&mut ctx.navigation.status_logs_navs.diffs_nav,delta,);
let id_hash = file::id_parts_hash(path, file::Kind::Untracked);if let Some(nav) =ctx.navigation.files_diffs.diffs_nav.as_mut().and_then(|(nav_id_hash, nav)| {(*nav_id_hash == id_hash).then_some(nav)},){nav_scrollable::alt_scroll_up(nav, delta)}
nav_scrollable::alt_scroll_down(&mut ctx.navigation.files_diffs.diffs_nav,delta,);
let id_hash = file::id_parts_hash(path, file::Kind::Changed);if let Some(nav) =ctx.navigation.files_diffs.diffs_nav.as_mut().and_then(|(nav_id_hash, nav)| {(*nav_id_hash == id_hash).then_some(nav)},){nav_scrollable::alt_scroll_up(nav, delta)}
nav_scrollable::alt_scroll_down(&mut ctx.navigation.files_diffs.diffs_nav,delta,);
let id_hash = file::log_id_parts_hash(*hash, path);if let Some(nav) =ctx.navigation.status_logs_navs.diffs_nav.as_mut().and_then(|(nav_id_hash, nav)| {(*nav_id_hash == id_hash).then_some(nav)},){nav_scrollable::alt_scroll_up(nav, delta)}
nav_scrollable::alt_scroll_down(&mut ctx.navigation.status_logs_navs.diffs_nav,delta,);
// todo!()
let (selection, task) = match ctx.state.channel.take() {Some(Channel {ix: channel_ix,name,log: Some(LogChange { file: None, .. }),}) => {let selection = Channel {ix: channel_ix,name,log: None,};(Some(selection), Task::none())}Some(Channel {ix: channel_ix,name,log:Some(LogChange {ix: change_ix,hash,message,file:Some(LogChangeFileSelection {ix: file_ix,path,diff_selected,}),}),}) => {if diff_selected {let selection = Channel {ix: channel_ix,name,log: Some(LogChange {ix: change_ix,hash,message,file: Some(LogChangeFileSelection {ix: file_ix,path,diff_selected: false,}),}),};(Some(selection), Task::none())} else {let selection = Channel {ix: channel_ix,name,log: Some(LogChange {ix: change_ix,hash,message,file: None,}),};(Some(selection), Task::none())}}selection @ (Some(Channel { .. }) | None) => {(selection, Task::none())}};ctx.state.channel = selection;task
Primary::Status => {let (selection, task): (Option<Status>, Task<crate::Msg>) =match ctx.state.status.take() {Some(Status::UntrackedFile {ix,path,diff_selected: false,}) => {let id_hash =file::id_parts_hash(&path, file::Kind::Untracked);let diff_selected = diff::file_diff_needs_scrolling(&ctx.navigation.files_diffs,id_hash,);(Some(Status::UntrackedFile {ix,path,diff_selected,}),Task::none(),)}Some(Status::ChangedFile {ix,
Primary::Status => select_right_status(ctx),Primary::Channel => select_right_channel(ctx),Primary::EntireLog => select_right_entire_log(ctx),}}fn select_right_status(ctx: &mut Ctx<'_>) -> Task<crate::Msg> {let (selection, task): (Option<Status>, Task<crate::Msg>) = match ctx.state.status.take(){Some(Status::UntrackedFile {ix,path,diff_selected: false,}) => {let diff_selected =diff::file_diff_needs_scrolling(&ctx.navigation.files_diffs);(Some(Status::UntrackedFile {ix,path,diff_selected,}),Task::none(),)}Some(Status::ChangedFile {ix,path,diff_selected: false,}) => {let diff_selected =diff::file_diff_needs_scrolling(&ctx.navigation.files_diffs);(Some(Status::ChangedFile {ix,path,diff_selected,}),Task::none(),)}Some(Status::LogChange(LogChange {ix,hash,message,file: None,})) => {let log_entry = ctx.repo.short_log.get(ix).unwrap();let (file, task) = if let Some(path) = log_entry.file_paths.first(){let file_id = file::log_id_parts_hash(log_entry.hash, path);// If the log is not loaded yet, the nav will be initialized// once it's loaded (`repo::MsgOut::GotChangeDiffs`)if let Some(log) = ctx.navigation.log_diffs.diffs.get(&file_id){// Init log diffs navlet unchanged_sections =diff::unchanged_sections(&log.file);log::init_diffs_nav(&mut ctx.navigation.status_logs_navs,file_id,).set_skip_sections(unchanged_sections);};let (selection, task) = status_log_file_selection(0,hash,VDir::Down,ctx.navigation,log_entry,);(Some(selection), task)} else {(None, Task::none())};(Some(Status::LogChange(LogChange {ix,hash,message,file,})),task,)}Some(Status::LogChange(LogChange {ix,hash,message,file:Some(LogChangeFileSelection {ix: file_ix,path,diff_selected: false,}),})) => {let is_diff_scrollable =log::diff_needs_scrolling(&ctx.navigation.status_logs_navs);(Some(Status::LogChange(LogChange {ix,hash,message,file: Some(LogChangeFileSelection {ix: file_ix,
diff_selected: false,}) => {let id_hash =file::id_parts_hash(&path, file::Kind::Changed);let diff_selected = diff::file_diff_needs_scrolling(&ctx.navigation.files_diffs,id_hash,);(Some(Status::ChangedFile {ix,path,diff_selected,}),Task::none(),)}Some(Status::LogChange(LogChange {ix,
diff_selected: is_diff_scrollable,}),})),Task::none(),)}selection => (selection, Task::none()),};ctx.state.status = selection;task}fn select_right_channel(ctx: &mut Ctx<'_>) -> Task<crate::Msg> {if let Some(channel) = ctx.state.channel.take() {let (selection, task) = match channel {Channel {ix: channel_ix,name,log: None,} => {if let Some(Log::Loaded { log }) =ctx.logs.other_channels_logs.get(&name){let change_ix = 0;let (log_selection, task) =channel_log_selection(change_ix, VDir::Down, ctx, log);let selection = Some(Channel {ix: channel_ix,name,log: Some(log_selection),});(selection, task)} else {(Some(Channel {ix: channel_ix,name,log: None,}),Task::none(),)}}Channel {ix: channel_ix,name,log:Some(LogChange {ix: change_ix,
})) => {let log_entry = ctx.repo.short_log.get(ix).unwrap();let (file, task) = if let Some(path) =log_entry.file_paths.first(){let id_hash =
}),} => {if let Some(Log::Loaded { log }) =ctx.logs.other_channels_logs.get(&name){let log_entry = log.get(change_ix).unwrap();let (file, task) =if let Some(path) = log_entry.file_paths.first() {let file_id =
let nav = nav_scrollable::State::default().with_skip_sections(unchanged_sections);ctx.navigation.status_logs_navs.diffs_nav =Some((id_hash, nav));
log::init_diffs_nav(&mut ctx.navigation.other_channel_logs_navs,file_id,).set_skip_sections(unchanged_sections);
})),task,)}Some(Status::LogChange(LogChange {
}),}),task,)} else {(Some(Channel {ix: channel_ix,name,log: Some(LogChange {ix: change_ix,hash,message,file: None,}),}),Task::none(),)}}Channel {ix: channel_ix,name,log:Some(LogChange {
})) => {let id_hash = file::log_id_parts_hash(hash, &path);let is_diff_scrollable = log::log_diff_needs_scrolling(&ctx.navigation.status_logs_navs,id_hash,);(Some(Status::LogChange(LogChange {ix,hash,message,file: Some(LogChangeFileSelection {ix: file_ix,path,diff_selected: is_diff_scrollable,}),})),Task::none(),
}),} => {let navs = &ctx.navigation.other_channel_logs_navs;let is_diff_scrollable = log::diff_needs_scrolling(navs);(Some(Channel {ix: channel_ix,name,log: Some(LogChange {ix,hash,message,file: Some(LogChangeFileSelection {ix: file_ix,path,diff_selected: is_diff_scrollable,}),}),}),Task::none(),)}selection @ Channel { .. } => (Some(selection), Task::none()),};ctx.state.channel = selection;return task;}Task::none()}fn select_right_entire_log(ctx: &mut Ctx<'_>) -> Task<crate::Msg> {if let Some(Log::Loaded { log }) = ctx.logs.entire_log.as_ref()&& let Some(entire_log) = ctx.state.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 file_id = file::log_id_parts_hash(log_entry.hash, path);// If the log is not loaded yet, the nav will be// initialized once it's// loaded (`repo::MsgOut::GotChangeDiffs`)if let Some(log) =ctx.navigation.log_diffs.diffs.get(&file_id){let unchanged_sections =diff::unchanged_sections(&log.file);log::init_diffs_nav(&mut ctx.navigation.entire_logs_navs,file_id,
ctx.state.status = selection;return task;}Primary::Channel => {// TODO}Primary::EntireLog => {if let Some(Log::Loaded { log }) = ctx.logs.entire_log.as_ref()&& let Some(entire_log) = ctx.state.entire_log.take(){let (selection, task) = match entire_log {LogChange {
(Some(LogChange {
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);// If the log is not loaded yet, the nav will be// initialized once it's// loaded (`repo::MsgOut::GotChangeDifs`)if let Some(log) =ctx.navigation.log_diffs.diffs.get(&id_hash){// Init log diffs navlet unchanged_sections =diff::unchanged_sections(&log.file);let nav = nav_scrollable::State::default().with_skip_sections(unchanged_sections);ctx.navigation.entire_logs_navs.diffs_nav =Some((id_hash, nav));};(Some(LogChangeFileSelection {ix: 0,path: path.clone(),diff_selected: false,}),Task::none(),)} else {(None, Task::none())};(Some(LogChange {ix,hash,message,file,}),task,)}LogChange {
file,}),task,)}LogChange {ix,hash,message,file:Some(LogChangeFileSelection {ix: file_ix,path,diff_selected: false,}),} => {let is_diff_scrollable =log::diff_needs_scrolling(&ctx.navigation.entire_logs_navs);(Some(LogChange {
file:Some(LogChangeFileSelection {ix: file_ix,path,diff_selected: false,}),} => {let id_hash = file::log_id_parts_hash(hash, &path);let is_diff_scrollable = log::log_diff_needs_scrolling(&ctx.navigation.entire_logs_navs,id_hash,);(Some(LogChange {ix,hash,message,file: Some(LogChangeFileSelection {ix: file_ix,path,diff_selected: is_diff_scrollable,}),}),Task::none(),)}selection @ LogChange { .. } => {(Some(selection), Task::none())}};ctx.state.entire_log = selection;return task;
file: Some(LogChangeFileSelection {ix: file_ix,path,diff_selected: is_diff_scrollable,}),}),Task::none(),)
let id_hash =file::id_parts_hash(path, file::Kind::Untracked);if let Some(nav) =navigation.files_diffs.diffs_nav.as_mut().and_then(|(nav_id_hash, nav)| {(*nav_id_hash == id_hash).then_some(nav)},){nav_scrollable::reset_skip_delay(nav);}
let nav = &mut navigation.files_diffs.diffs_nav;nav_scrollable::reset_skip_delay(nav);
let id_hash =file::id_parts_hash(path, file::Kind::Changed);if let Some(nav) =navigation.files_diffs.diffs_nav.as_mut().and_then(|(nav_id_hash, nav)| {(*nav_id_hash == id_hash).then_some(nav)},){nav_scrollable::reset_skip_delay(nav);}
let nav = &mut navigation.files_diffs.diffs_nav;nav_scrollable::reset_skip_delay(nav);
let id_hash = file::log_id_parts_hash(*hash, path);if let Some(nav) =navigation.status_logs_navs.diffs_nav.as_mut().and_then(|(nav_id_hash, nav)| {(*nav_id_hash == id_hash).then_some(nav)},){nav_scrollable::reset_skip_delay(nav);}
let nav = &mut navigation.status_logs_navs.diffs_nav;nav_scrollable::reset_skip_delay(nav);
let id_hash = file::id_parts_hash(path, file::Kind::Untracked);match file::try_get_src_file(files, id_hash) {
let file_id = file::id_parts_hash(path, file::Kind::Untracked);match file::try_get_src_file(files, file_id) {
let nav = nav_scrollable::State::default().with_skip_sections(unchanged_sections);navigation.files_diffs.diffs_nav = Some((id_hash, nav));
diff::init_diffs_nav(&mut navigation.files_diffs,#[cfg(debug_assertions)]file_id,).set_skip_sections(unchanged_sections);
let id_hash = file::id_parts_hash(path, file::Kind::Changed);match file::try_get_src_file(files, id_hash) {
let file_id = file::id_parts_hash(path, file::Kind::Changed);match file::try_get_src_file(files, file_id) {
let nav = nav_scrollable::State::default().with_skip_sections(unchanged_sections);navigation.files_diffs.diffs_nav = Some((id_hash, nav));
diff::init_diffs_nav(&mut navigation.files_diffs,#[cfg(debug_assertions)]file_id,).set_skip_sections(unchanged_sections);
}fn status_log_selection(ix: usize,dir: VDir,ctx: &mut Ctx<'_>,) -> (Status, Task<crate::Msg>) {let Ctx {state: _,files: _,navigation,repo,logs: _,} = ctx;let entry = repo.short_log.get(ix).unwrap();let hash = entry.hash;let task = if !navigation.log_diffs.changes_with_loaded_diffs.contains(&hash){Task::done(crate::Msg::ToRepo(repo::MsgIn::GetChangeDiffs { hash }))} else {Task::none()};log::init_files_nav(&mut navigation.status_logs_navs, hash);match dir {VDir::Up => nav_scrollable::scroll_up_to_section(&mut navigation.status_nav,status_section_ix(repo, ix, StatusSectionKind::Log),),VDir::Down => nav_scrollable::scroll_down_to_section(&mut navigation.status_nav,status_section_ix(repo, ix, StatusSectionKind::Log),),}let selection = Status::LogChange(LogChange {ix,hash,message: entry.message.clone(),file: None,});(selection, task)
fn status_log_file_selection(ix: usize,hash: repo::ChangeHash,dir: VDir,navigation: &mut Navigation,log_entry: &repo::LogEntry,) -> (LogChangeFileSelection, Task<crate::Msg>) {let path = log_entry.file_paths.get(ix).unwrap().clone();log::init_diffs_nav(&mut navigation.status_logs_navs,file::log_id_parts_hash(hash, &path),);
// Init log changes navlet nav = nav_scrollable::State::default();navigation.status_logs_navs.files_nav = Some((hash, nav));
Task::done(crate::Msg::ToRepo(repo::MsgIn::GetChangeDiffs { hash }))} else {
}fn channel_log_file_selection(ix: usize,hash: repo::ChangeHash,dir: VDir,navigation: &mut Navigation,log_entry: &repo::LogEntry,) -> (LogChangeFileSelection, Task<crate::Msg>) {let path = log_entry.file_paths.get(ix).unwrap().clone();log::init_diffs_nav(&mut navigation.other_channel_logs_navs,file::log_id_parts_hash(hash, &path),);let nav = &mut navigation.other_channel_logs_navs.files_nav;match dir {VDir::Up => nav_scrollable::scroll_up_to_section(nav, ix),VDir::Down => nav_scrollable::scroll_down_to_section(nav, ix),}(LogChangeFileSelection {ix,path,diff_selected: false,},Task::none(),)
// Init log changes navlet nav = nav_scrollable::State::default();navigation.entire_logs_navs.files_nav = Some((hash, nav));
Task::done(crate::Msg::ToRepo(repo::MsgIn::GetChangeDiffs { hash }))} else {
fn status_log_file_selection(ix: usize,hash: repo::ChangeHash,dir: VDir,navigation: &mut Navigation,log_entry: &repo::LogEntry,) -> (LogChangeFileSelection, Task<crate::Msg>) {let path = log_entry.file_paths.get(ix).unwrap().clone();let id_hash = file::log_id_parts_hash(hash, &path);// If the log is not loaded yet, the nav will be initialized once// it's loaded (`repo::MsgOut::GotChangeDifs`)if navigation.log_diffs.changes_with_loaded_diffs.contains(&hash){let needs_new_nav = navigation.status_logs_navs.diffs_nav.is_none()|| navigation.status_logs_navs.diffs_nav.as_ref().map(|(nav_id_hash, _nav)| *nav_id_hash != id_hash).unwrap_or_default();if needs_new_nav {// Init log change diff navlet nav = nav_scrollable::State::default();navigation.status_logs_navs.diffs_nav = Some((id_hash, nav));}};
if let Some((selected_hash, nav)) =&mut navigation.status_logs_navs.files_nav&& *selected_hash == hash{match dir {VDir::Up => nav_scrollable::scroll_up_to_section(nav, ix),VDir::Down => nav_scrollable::scroll_down_to_section(nav, ix),}}(LogChangeFileSelection {ix,path,diff_selected: false,},Task::none(),)}
let id_hash = file::log_id_parts_hash(hash, &path);// If the log is not loaded yet, the nav will be initialized once// it's loaded (`repo::MsgOut::GotChangeDifs`)if navigation.log_diffs.changes_with_loaded_diffs.contains(&hash){let needs_new_nav = navigation.entire_logs_navs.diffs_nav.is_none()|| navigation.entire_logs_navs.diffs_nav.as_ref().map(|(nav_id_hash, _nav)| *nav_id_hash != id_hash).unwrap_or_default();if needs_new_nav {// Init log change diff navlet nav = nav_scrollable::State::default();navigation.entire_logs_navs.diffs_nav = Some((id_hash, nav));}};
log::init_diffs_nav(&mut navigation.entire_logs_navs,file::log_id_parts_hash(hash, &path),);
if let Some((selected_hash, nav)) =&mut navigation.entire_logs_navs.files_nav&& *selected_hash == hash{match dir {VDir::Up => nav_scrollable::scroll_up_to_section(nav, ix),VDir::Down => nav_scrollable::scroll_down_to_section(nav, ix),}
let nav = &mut navigation.entire_logs_navs.files_nav;match dir {VDir::Up => nav_scrollable::scroll_up_to_section(nav, ix),VDir::Down => nav_scrollable::scroll_down_to_section(nav, ix),
}Msg::File(msg) => {if let Some(ReadyState {repo,navigation,selection,..}) = model::ready_mut(&mut state.sub){let loaded = file::update(&mut state.files, repo, msg);if let Some(file::Loaded {id_hash,unchanged_sections,}) = loaded{let is_selected = match selection.status.as_ref() {Some(selection::Status::UntrackedFile {ix: _,path,diff_selected: _,}) => {let selected_id_hash = file::id_parts_hash(path,file::Kind::Untracked,);selected_id_hash == id_hash}Some(selection::Status::ChangedFile {ix: _,path,diff_selected: _,}) => {let selected_id_hash =file::id_parts_hash(path, file::Kind::Changed);selected_id_hash == id_hash}_ => false,};if is_selected {let nav = nav_scrollable::State::default().with_skip_sections(unchanged_sections);navigation.files_diffs.diffs_nav = Some((id_hash, nav));}navigation.files_diffs.diffs.entry(id_hash).or_default();}}Task::none()
}fn update_file(state: &mut State, msg: file::Msg) -> Task<Msg> {if let Some(ReadyState {repo,navigation,selection,..}) = model::is_ready_mut(&mut state.sub){let loaded = file::update(&mut state.files, repo, msg);if let Some(file::Loaded {file_id,unchanged_sections,}) = loaded{let is_selected = match selection.status.as_ref() {Some(selection::Status::UntrackedFile {ix: _,path,diff_selected: _,}) => {let selected_file_id =file::id_parts_hash(path, file::Kind::Untracked);selected_file_id == file_id}Some(selection::Status::ChangedFile {ix: _,path,diff_selected: _,}) => {let selected_file_id =file::id_parts_hash(path, file::Kind::Changed);selected_file_id == file_id}_ => false,};if is_selected {diff::init_diffs_nav(&mut navigation.files_diffs,#[cfg(debug_assertions)]file_id,).set_skip_sections(unchanged_sections);}navigation.files_diffs.diffs.entry(file_id).or_default();}}Task::none()
if !state.sub.allowed_actions.iter().any(|action::Binding {msg: allowed_msg, ..}| {if let Some(allowed_msg) = allowed_msg {action::is_same_msg(msg, allowed_msg)} else {false}},) {return Task::none();}
// dbg!("Action not allowed", msg);// dbg!(&state.sub.allowed_actions);return Task::none();
}};#[cfg(debug_assertions)]{// Check that selection coreesponds to the navigation stateif let Some(ReadyState {selection,navigation,..}) = model::is_ready(&state.sub){let selection::State {primary: _,status,channel,entire_log,held_key: _,} = selection;if let Some(selection) = status {match selection {selection::Status::UntrackedFile { .. } => {}selection::Status::ChangedFile { .. } => {}selection::Status::LogChange(selection::LogChange {hash,file,..}) => {if let Some(selection::LogChangeFileSelection {path,..}) = file{assert_eq!(navigation.status_logs_navs.change_hash,Some(*hash));assert_eq!(navigation.status_logs_navs.file_id,Some(file::log_id_parts_hash(*hash, path)));} else {assert_eq!(navigation.status_logs_navs.change_hash,Some(*hash));}}}}if let Some(selection::Channel { name, log, .. }) = channel {assert_eq!(navigation.other_channel_name.as_ref(), Some(name));match log {None => {}Some(selection::LogChange {hash, file: None, ..}) => {assert_eq!(navigation.other_channel_logs_navs.change_hash,Some(*hash));}Some(selection::LogChange {hash,file:Some(selection::LogChangeFileSelection { path, .. }),..}) => {assert_eq!(navigation.other_channel_logs_navs.change_hash,Some(*hash));assert_eq!(navigation.other_channel_logs_navs.file_id,Some(file::log_id_parts_hash(*hash, path)));}}}if let Some(selection::LogChange { hash, file, .. }) = entire_log {assert_eq!(navigation.entire_logs_navs.change_hash,Some(*hash));if let Some(selection::LogChangeFileSelection {path, ..}) = file{assert_eq!(navigation.entire_logs_navs.change_hash,Some(*hash));assert_eq!(navigation.entire_logs_navs.file_id,Some(file::log_id_parts_hash(*hash, path)));} else {assert_eq!(navigation.entire_logs_navs.change_hash,Some(*hash));}}
))) => {if *selected_hash != hash {return Task::none();}if let Some(file) = file.as_ref() {// If a file is selected, init the nav for its difflet diffs_nav = nav_scrollable::State::default();let id_hash = file::log_id_parts_hash(hash, &file.path);navigation.status_logs_navs.diffs_nav =Some((id_hash, diffs_nav));} else {// Init scrollable nav for log fileslet changes_nav = nav_scrollable::State::default();navigation.status_logs_navs.files_nav =Some((hash, changes_nav));}}
))) if *selected_hash == hash => file.as_ref().map(|file| (file, &mut navigation.status_logs_navs)),
})) => {if *selected_hash != hash {return Task::none();}let Some(Log::Loaded { .. }) = logs.entire_log.as_ref() else {return Task::none();};if let Some(file) = file.as_ref() {// If a file is selected, init the nav for its difflet diffs_nav = nav_scrollable::State::default();let id_hash = file::log_id_parts_hash(hash, &file.path);navigation.entire_logs_navs.diffs_nav =Some((id_hash, diffs_nav));} else {// Init scrollable nav for log fileslet changes_nav = nav_scrollable::State::default();navigation.entire_logs_navs.files_nav =Some((hash, changes_nav));};}
})) if *selected_hash == hash => file.as_ref().map(|file| (file, &mut navigation.entire_logs_navs)),
})) => {if *selected_hash != hash {return Task::none();}
})) if *selected_hash == hash => file.as_ref().map(|file| (file, &mut navigation.other_channel_logs_navs)),selection::Unified::Status(_)| selection::Unified::Channel(_)| selection::Unified::EntireLog(_) => None,};
let Some(Log::Loaded { .. }) =logs.other_channels_logs.get(name)else {return Task::none();};if let Some((selected_name, navs)) =navigation.other_channel_log_navs.as_mut()&& name == selected_name{if let Some(file) = file.as_ref() {// If a file is selected, init the nav for its difflet diffs_nav = nav_scrollable::State::default();let id_hash = file::log_id_parts_hash(hash, &file.path);navs.diffs_nav = Some((id_hash, diffs_nav));} else {// Init scrollable nav for log fileslet changes_nav = nav_scrollable::State::default();navs.files_nav = Some((hash, changes_nav));};}
if let Some((file, navs)) = selected_file_and_its_navs {let file_id = file::log_id_parts_hash(hash, &file.path);if let Some(log) = navigation.log_diffs.diffs.get(&file_id) {let unchanged_sections = diff::unchanged_sections(&log.file);log::init_diffs_nav(navs, file_id).set_skip_sections(unchanged_sections);
if let Some(ReadyState {navigation,selection,logs,..}) = model::ready_mut(&mut state.sub){if let Some(selection::Channel {name: selection_name,..}) = selection.channel.as_ref()&& selection_name == &channel{navigation.other_channel_log_navs =Some((channel.clone(), log::Navs::default()));}
if let Some(ReadyState { logs, .. }) = model::is_ready_mut(&mut state.sub) {
pub fn log_diff_needs_scrolling(logs: &Navs, id_hash: file::LogIdHash) -> bool {logs.diffs_nav.as_ref().and_then(|(nav_id_hash, nav)| (*nav_id_hash == id_hash).then_some(nav)).map(nav_scrollable::needs_scrolling).unwrap_or_default()
pub fn diff_needs_scrolling(logs: &Navs) -> bool {nav_scrollable::needs_scrolling(&logs.diffs_nav)
let (src_file_load_tx, src_file_load_rx) = watch::channel((Id {path: "".to_string(),file_kind: Kind::Untracked,},0,));let src_file_load_rx = WatchStream::from_changes(src_file_load_rx);
let (src_file_load_tx, src_file_load_rx) = mpsc::unbounded_channel();let src_file_load_rx = UnboundedReceiverStream::from(src_file_load_rx);
pub fn file_diff_needs_scrolling(files_diffs: &FilesState,id_hash: file::IdHash,) -> bool {files_diffs.diffs_nav.as_ref().and_then(|(nav_id_hash, nav)| (*nav_id_hash == id_hash).then_some(nav)).map(nav_scrollable::needs_scrolling).unwrap_or_default()
pub fn file_diff_needs_scrolling(files_diffs: &FilesState) -> bool {nav_scrollable::needs_scrolling(&files_diffs.diffs_nav)