WAOGSCOJ5A372BZKHEYD2BCDBCENNVLFYW3INKUOOAZMDADDIFIQC 6YZAVBWU6E5FYOI5JGEIPXGZLIKAW6LS2AOFIQWEE5DMOPPCD5PQC IQDCHWCP47LL46EXQLQGHQPGFYIHQLMQBHA57RWJCIOX5UEUIQAQC SWWE2R6MVBX5CNM6X3WLXZTSRTU53PBJL7WJSFVF77XBPXDX4COAC WT3GA27PQ2AOAIGK65O3Q4DMX4AZDVNULBLRL6GF4QW6QCASUEAAC UB2ITZJSDADVINSQEZ3HA6PVGA7OA6JYFG5GMSO7Y7LOXJC4FI7AC KT5UYXGKEEXUHURNOYFVIG7WQ3Y3SJZMM2TP4OSW6NXSXQ5XXRHAC S2NVIFXRFER4SRA37WCT5XTXHDHAL5WIGGKY4A4XOTPLTKTZSRGQC YBJRDOTCX3ZRDB5EVXJBR55FX3CADCSIGMYWNYVC2PD5W3GXR3DQC KM5PSZ4A2FJOPHJA6RC7LHZAUXLQDZDQC2DVSE5YUORLFIPZO74QC 2VUX5BTDKHX3TJ677NW34H5WLSWH35C3PU46C7MXCN5O7PAZVXNQC A5YBC77VWH2LXCZJOPZORQJI5ZYABSCHJWVX5HVNWPM5RABXESLQC D7A7MSIHJS3IAOLEPK52M4CZLDPLO7JB3Y62XACT2AM6UUCPQ6BAC W4LFX7IHQ7SDX67ATSGWDB5IN6472ZJDBKY2XZ54SBJEYD5GAT5QC AMPZ2BXK4IGUZO3OPBRSJ6Z4GI5K4PRFMLUGTR6AP4FKKRWQG7LQC V55EAIWQXWER2HWKZHPJBV7DDJMSPSPWSO3FSSAYODJHVDBHUN6QC HOJZI52YIXKAYF766WR3SAOIFZH6YRMDOUE23VWEYNBZRBGEU25AC B4RMW5AEGAJX5CFC4RFPI6Y3NBSDM7GZKNBPPTTICRZSDZSYNXHQC MJDGPSHGF62FTVWZBE7MFNJTUQD42OBVJEOSVPBT553UFJLTEMXQC ZVI4AWERNOTDJ3765HJXRBZT57XPNKVONQ6TGOGNPOL2VN42KMJQC QMAUTRB6R5R7ABWT2JIDEA7LMILZOS3PGPZIF3YUFKRVLW6HGKTQC OQ6HSAWHIRTAIIWMDGCTIOK47JDY7QVVAHLRDA2R5TTJKNSBFCWQC DCSUCH6RRRQU4TQYO3K3HRC7SXAIBYP5R3ZOWAWS2LOXWNHEJM6AC JE44NYHM4QORCRKOF33QM42EDT7SBCPTULWGT6IVDL3D5LUHQXLAC ONRCENKTUB4JJMPXNAQQYEWDYD54TAGOLWH742GF4EH3KTHV7YLQC 4ELJZGRJNL6FXB33QTYDNPY57JA3WZPUXKLQRTGSLDM7W65PD3YQC CALXOZXANFZ64NBZBTR2KYTZ6ZLLCJXNFAEALBB2EYAVDVJJ6X6AC L6KSEFQIWICZJ6HJUFKLZQDEH6X2QMFM4Z7ZZUGMLDMFF7EHRXWAC BFN2VHZS7VCBUHQ4S3CQ3LFQV2V4M6VANNAF32XMRFQVWRGYSZ6AC GWZGYNIBQP2AA7WYULNBS2BCV4B36IHK4OS7XHVOTUUG27E76XFQC 3SYSJKYLVCXR54LRUPL6GOQISSJS6XWK4M6PRQRCKZN7F23NNVEAC 23SFYK4Q5NKBPJG53PQNPWQH6UOUU2YKJEL7RLXYBRLJOJYV7AWQC OPXFZKEBDHZZLXEJ2JRDYBOJH6YIN7UZNZYHVHMWMQVDTE2ZD53QC MYGIBRRHHXPKVRAMQQRJTZH74L2XOK3SF7J57JPCRKSVRLZ2D6NQC XSZZB47UXR6KGYFZZQFQR63X2LDKOH6TPNNBRRGHUCI5JJ4JIWVAC 3BK22XE5LPOH2EK5AMRXFXHNQNCJ54HEPYRINHJT4DA7INT32I7AC WGID4LS4EISIOXB5Y5SOFGEF5PLBJSCPFCETH2CGRTFN3NC4WGJQC VCNKFNUF7OWVSWC6I5D25KUZ3XZZICZ3LHWVPF2N5ZSP7LQ2JOUQC ACDXXAX26ZJJFKJDGRC2GOSJY5JHQWCSTP55SYI6D6LH5UIRYUBAC FVA36HBVXZCYW7FMQLST63Q6IDGLJ23OIHORF67BUIO2GXYNBW2QC I56UGW7UUKLSR4753EYRGNROZB5PD522REEOGHVAQOZZTSVRUEEQC ESMM3FELOBYIX7FUNOU37FYKRJHFU2IMX6LY6EGJTVPTBDU3SEEQC 7SSBM4UQMYVRL6L3ICYZQPSMYLZZQNMDWH6JKA3KOOSXZDJHESHQC FL2ULDJNRO3KPS24T2PEZWWNXAVFYC42SRVN3LDFVPIAFTDCOBGQC F542TMBEUP2AVIORK747P2LM74ZZLO6XAQWDMXQDEIRAZH2PTWSQC ZD56BUSUGTPPDSHHTOC2H5RJJG4FUUUPOSM2BHRY3UA5D6OA43XQC S2T7RUKWXAHMOW5HHITQKTKCBKTUKGMRXK7EQI6RNEBBHRJ5W2RQC I2AG42PAVOII4V4TWDJV5ZVNDIHKBRDT254BFQLFUIY723TW6CCQC 4PNWU55OLKQGTREOE7P27SGX4HPQO3NMHBABN27WQMCQADBGJJWQC SASAN2XCWDQ2VEHZ7TAQEN2R3Y7AG7JUGEFVRL4DZAGHXDFEZFRQC YKHE3XMWOWPGOWYSISF73MIAKN7WB3AHCV2OA4ECAFPF47YHUXEAC KEPKF3WO7ZZ2VB2DRVVTWTGPL7TCA52BMYUPHUNUJH6WO3HAT6JQC GOLHUD6RCYCO2SPULCFGWIW3ALBDODNEMUHVAKCDWYQREKWGFTLQC KWTBNTO3QUUE2YADF6SYW6G6ZOKYEWRJQKIWDGZXR33S3YNDVIZQC 5MUEECMJHU44FL5RDUR3VFBIWK3H4X2L5MVJ73J37PYHZWLUKU2AC 3TLPJ57B2OD5OWJN5WMS7A4W7IGFUWJJHVIXRM34VT6KUN6R4YSAC BNHJU2DU4HHADLKTDQMRG5PC5VHCJ2G7UFQRRVUBTALXVBUAQSKQC KQABQCCZCM23QWW43LZD5QBNFOXWLGFNCPPIMNMNFMZSNPSTFVEAC AI3IMKC3HRPMTWQCU5HGWKUHGKTJ22QF7V4AAEI6IEBIZ4WYWCKQC WXQBBQ2ACNPKCTDF7OTBLP342324ZIOJK42PUO2KT2IYVJ2ETCMAC DST3HRZZXO3RNS2GW5ADK43LT7PX5SZ4GLLO7MC7U3VWCYNFTZTQC PTWZYQFRWWUOE2WMQT26CKZKFSHAIJVJS3QWHJFYUFDRRTVPHSUAC RDRBP7AL74NBFNZSQFTU7VQCMWTGJO5RZWGPCWVVS5WRTXJ77DFAC UR4J677RWA3OFG6HQTD46BUUE5YFPSBEFCJAEM5OMT4V5A7SBNNQC K63JN6CRRCP4S5NY2FPH46Q7QLWH4B6QXQHKFPVQBHHIHNDVXFDQC GYZWZ33TH3WUFLF3XNFQW2NBZXAAK5WQG65YVA7SJEYENUZKZRSAC A6Z4O6RC33HYWP7JIVQ6FDWE4EOCQWQTIGENK2WAHUGSHDDLSA7QC 7BLZN73OYUAJEYTJ6WWHRZ7S7ONDGRBKNJGFGW62NAIZBUK3CECQC JZXYSIYDPBWQZCAMGDZ5BFMN6SU73EVVDIYEGTDJN6DVOSBNHN4QC 5ZRDYL6KIQPUI3ZZETH5KJ64N6RUF7KYM3P6Q6HER5XVJZ7GZ4WQC BAUK5BONEFQ3KIQPFLM7MGNCS5GWBILBXZMTIGN5LWTYTNNNNSPQC NZD56PVBVHARAQD7JNXE2F3DRT4S2NNRDHVVJTKTQK474LDMVIXQC OJPGHVC3RFBQ7TTSCZH6URSSATII3TESD74EISDNOTNXXSX7PQMAC ZIUHKVJKIXUQ3H3BK5XRTSBXAQXI5CZWKXCG7RSC56YT53GBDJNAC 3XRG4BB6V5V4DICZCMOZMLQNTANWKPO7BBRATTXOZLRNSEUQIA5AC S4WH75Y32ZYLEWXBLHRG6Z3H7KWHSXAS6H37CFQIW3FXKKWFFQ2QC UF5NJKASGMZSZMBUKSUI67B2GIMQFX5SNNQEHHGUBNDBQ2QZZWSAC pub log: Log,
pub short_log: Log,}#[derive(Clone, Derivative, strum::Display)]#[derivative(Debug)]pub enum MsgIn {RefreshChangedAndUntrackedFiles,AddUntrackedFile {path: String,},RmAddedFile {path: String,},Record {msg: String,#[derivative(Debug = "ignore")]sk: Arc<SKey>,},GetChangeDiffs {hash: ChangeHash,},SwitchToChannel(String),ForkChannel(String),LoadEntireLog,LoadOtherChannelLog(String),}#[derive(Debug, Clone, strum::Display)]pub enum MsgOut {Init(State),Refreshed {state: State,invalidate_logs: bool,},AddedUntrackedFile {path: String,},RmedAddedFile {path: String,},GotChangeDiffs {hash: ChangeHash,diffs: ChangedFiles,},LoadedEntireLog(Log),LoadedOtherChannelLog {channel: String,log: Log,},
}#[derive(Clone, Derivative, strum::Display)]#[derivative(Debug)]pub enum MsgIn {RefreshChangedAndUntrackedFiles,AddUntrackedFile {path: String,},RmAddedFile {path: String,},Record {msg: String,#[derivative(Debug = "ignore")]sk: Arc<SKey>,},GetChangeDiffs {hash: ChangeHash,},SwitchToChannel(String),ForkChannel(String),LoadEntireLog,}#[derive(Debug, Clone, strum::Display)]pub enum MsgOut {Init(State),Refreshed(State),AddedUntrackedFile {path: String,},RmedAddedFile {path: String,},/// The State is updated after making a recordRecorded(State),GotChangeDiffs {hash: ChangeHash,diffs: ChangedFiles,},LoadedEntireLog(Log),
MsgIn::LoadOtherChannelLog(channel) => {let channel_returned: String;let log: Log;(internal_state, channel_returned, log) =spawn_blocking(move || {let log = get_log(&internal_state.repo,Some(&channel),None,None,);(internal_state, channel, log)}).await.unwrap();let _ = msg_out_tx.send(MsgOut::LoadedOtherChannelLog {channel: channel_returned,log,});}
#[derive(Debug, Default)]pub struct State {pub selected_sections: Vec<usize>,pub expanded_unchanged_sections: Vec<usize>,pub collapsed_changed_sections: Vec<usize>,
use std::borrow::Cow;#[derive(Debug)]pub enum FileContent<'a> {Decoded(Cow<'a, str>),UnknownEncoding,
DiffWithoutContents, File, Lines, Section, State, UndecodableContents,UndecodableFile,
DiffWithoutContents, File, FileAndState, Lines, Section, State,UndecodableContents, UndecodableFile,
#[derive(Debug)]pub struct State<'a> {pub window_size: iced::Size,pub repo_path: &'a Path,pub repo: Option<&'a Repo>,pub record_msg: Option<&'a RecordMsg>,/// Diff and state of selected log's file (untracked, changed or from a/// log), if anypub status_selection: Option<StatusSelection<'a>>,pub channel_selection: Option<&'a selection::Channel>,pub entire_log_selection: Option<EntireLogSelection<'a>>,}#[derive(Debug)]pub struct Repo {pub state: repo::State,/// Scrollable status view contains the overview of untracked files,/// changed files and most recent log changes// pub status_nav: NavScrollable,pub status_nav: nav_scrollable::State,/// `true` when we're selecting a channel to switch topub switching_channel: bool,/// `Some` when we're selecting a channel to switch topub forking_channel: Option<String>,/// `Some` when looking at the entire log of changespub entire_log: Option<EntireLog>,}#[derive(Debug)]pub enum StatusSelection<'a> {UntrackedFile {ix: usize,path: &'a str,diff: Option<Diff<'a>>,},ChangedFile {ix: usize,path: &'a str,diff: Option<Diff<'a>>,is_added_from_untracked: bool,},Log {ix: usize,hash: repo::ChangeHash,message: &'a str,file: Option<LogChangeFileSelection<'a>>,nav: Option<&'a nav_scrollable::State>,},}#[derive(Debug)]pub struct LogChangeFileSelection<'a> {pub ix: usize,pub path: &'a str,pub diff: Option<Diff<'a>>,}#[derive(Debug)]pub struct Diff<'a> {pub is_selected: bool,pub file: &'a diff::File,pub state: &'a diff::State,pub nav: &'a nav_scrollable::State,}#[derive(Debug)]pub enum EntireLog {Loading,LoadingButNotViewing,Got {log: repo::Log,nav: Box<nav_scrollable::State>,},NotViewing {log: repo::Log,nav: Box<nav_scrollable::State>,},}#[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 nav_scrollable::State>,}#[derive(Debug, Clone)]pub enum Msg {Confirm,Cancel,Selection(selection::Msg),ToRepo(repo::MsgIn),EditRecordMsg(text_editor::Action),PostponeRecord,SaveRecord,DiscardRecord,AddUntrackedFile,RmAddedFile,StartRecord,/// Show a list of channels to switch toSelectChannel,ForkChannel,ForkChannelName(String),RefreshRepo,ShowEntireLog,}
#[derive(Debug)]pub enum RecordMsg {Typing(text_editor::Content),Canceled { old_msg: String },}pub fn view<'a>(state: State<'a>,
pub fn view<'a, F>(state: &'a inflorescence_model::State,get_diff: F,
let inner = if let Some(repo) = repo {view_repo(window_size,repo_path,repo,record_msg,status_selection,channel_selection,entire_log_selection,)} else {el(text("Loading repo..."))
let inner = match sub {SubState::Loading { .. } => el(text("Loading...")),SubState::SelectingId {user_ids: _,user_selection_ix: _,user_selection_nav: _,repo: _,} => todo!(),SubState::Ready(state) => {view_ready(*window_size, repo_path, state, get_diff)}
repo: &'a Repo,record_msg: Option<&'a RecordMsg>,status_selection: Option<StatusSelection<'a>>,channel_selection: Option<&'a selection::Channel>,entire_log_selection: Option<EntireLogSelection<'a>>,) -> Element<'a, Msg, Theme> {let Repo {state:
state: &'a ReadyState,get_diff: F,) -> Element<'a, Msg, Theme>whereF: Fn(file::IdHash) -> Option<&'a diff::File>,{let ReadyState {user_id: _,repo:
status_nav,switching_channel,forking_channel,entire_log,} = repo;
selection,navigation,record_msg,forking_channel_name,logs,} = state;let selection = selection::unify(selection);
let is_selected = matches!(status_selection.as_ref() ,Some(StatusSelection::UntrackedFile{ ix: selected_ix, .. }) if &ix == selected_ix
let is_selected = matches!(selection,selection::Unified::Status(Some(selection::Status::UntrackedFile{ ix: selected_ix, .. })) if &ix == selected_ix
let is_selected = matches!(status_selection.as_ref(),Some(StatusSelection::ChangedFile{ ix: selected_ix, .. }) if &ix == selected_ix
let is_selected = matches!(selection,selection::Unified::Status(Some(selection::Status::ChangedFile{ ix: selected_ix, .. })) if &ix == selected_ix
let is_selected = matches!(status_selection.as_ref(),Some(StatusSelection::Log { ix: selected_ix, .. }) if &ix == selected_ix
let is_selected = matches!(selection,selection::Unified::Status(Some(selection::Status::LogChange(selection::LogChange{ ix: selected_ix, .. }))) if &ix == selected_ix
let selection_details = || match status_selection.as_ref() {Some(StatusSelection::UntrackedFile { ix: _, path, diff }) => {let diffs = match diff {Some(Diff {is_selected,file,state,nav,}) => diff::view(state, nav, file, *is_selected),None => el(text("Loading diff...")),};
let selection_details = || {if let selection::Unified::Status(selection) = selection {match selection {Some(selection::Status::UntrackedFile {ix: _,path,diff_selected,}) => {let id_hash =file::id_parts_hash(path, file::Kind::Untracked);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)) => {diff::view(state, nav, file, *diff_selected)}None => el(text("Loading diff...")),};
el(column([view_diff_header(format!("Untracked file {path} contents:")),diffs,]).spacing(SPACING))}Some(StatusSelection::ChangedFile {path,ix: _,diff,is_added_from_untracked: _,}) => {let diffs = match diff {Some(Diff {is_selected,
el(column([view_diff_header(format!("Untracked file {path} contents:")),diffs,]).spacing(SPACING))}Some(selection::Status::ChangedFile {path,ix: _,diff_selected,}) => {let id_hash =file::id_parts_hash(path, file::Kind::Changed);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)) => {diff::view(state, nav, file, *diff_selected)}None => el(text("Loading diff...")),};el(column([view_diff_header(format!("Changed file {path} diff:")),diffs,]).spacing(SPACING))}Some(selection::Status::LogChange(selection::LogChange {ix,hash,message,
state,nav,}) => diff::view(state, nav, file, *is_selected),None => el(text("Loading diff...")),};el(column([view_diff_header(format!("Changed file {path} diff:")),diffs,]).spacing(SPACING))}Some(StatusSelection::Log {ix,hash,message,nav,file,}) => {let entry = log.get(*ix).unwrap();
})) => {let entry = short_log.get(*ix).unwrap();
let view = match nav {Some(nav) => {let change_selected = match file.as_ref() {Some(LogChangeFileSelection { diff, .. }) => !diff.as_ref().map(|diff| diff.is_selected).unwrap_or_default(),_ => false,
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 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::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(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)))});el(nav_scrollable(nav, files).class(if change_selected {theme::Scrollable::Selected} else {theme::Scrollable::Normal}).width(Length::Fill).height(Length::Fill))
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(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([])),}} else {el(row([]))
StatusSelection::UntrackedFile { .. }| StatusSelection::ChangedFile { .. }| StatusSelection::Log { file: None, .. } => 1,StatusSelection::Log { file: Some(_), .. } => 2,
selection::Status::UntrackedFile { .. }| selection::Status::ChangedFile { .. }| selection::Status::LogChange(selection::LogChange {file: None,..}) => 1,selection::Status::LogChange(selection::LogChange {file: Some(_),..}) => 2,
let status_selected = || match status_selection.as_ref() {Some(StatusSelection::UntrackedFile { diff, .. }) => !diff.as_ref().map(|diff| diff.is_selected).unwrap_or_default(),Some(StatusSelection::ChangedFile { diff, .. }) => !diff.as_ref().map(|diff| diff.is_selected).unwrap_or_default(),Some(StatusSelection::Log { file, .. }) => file.is_none(),None => true,};
let status_selected =|| match selection {selection::Unified::Status(Some(selection::Status::UntrackedFile { diff_selected, .. },)) => !diff_selected,selection::Unified::Status(Some(selection::Status::ChangedFile { diff_selected, .. },)) => !diff_selected,selection::Unified::Status(Some(selection::Status::LogChange(selection::LogChange { file, .. },))) => file.is_none(),selection::Unified::Status(None) => true,selection::Unified::Channel(_)| selection::Unified::EntireLog(_) => false,};
el(nav_scrollable(status_nav, status_nav_children()).class(if status_selected() {theme::Scrollable::Selected} else {theme::Scrollable::Normal}).width(Length::Fill).height(Length::Fill)),
el(nav_scrollable(&navigation.status_nav, status_nav_children()).class(if status_selected() {theme::Scrollable::Selected} else {theme::Scrollable::Normal}).width(Length::Fill).height(Length::Fill),),
let status_col_2 = || match status_selection.as_ref() {Some(StatusSelection::Log {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,}) => diff::view(state, nav, file, *is_selected),None => el(text("Loading diff..")),
let status_col_2 = || match selection {selection::Unified::Status(Some(selection::Status::LogChange(selection::LogChange {ix: _,hash,message: _,file:Some(selection::LogChangeFileSelection {ix: _,path,diff_selected,}),},))) => {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([el(column([view_diff_header(format!("{path} changes in {}:",display_short_hash(hash))),match state.zip(nav) {Some((diff::FileAndState { file, state }, nav)) => {diff::view(state, nav, file, *diff_selected)}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([]))
.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(StatusSelection::UntrackedFile { .. })| Some(StatusSelection::ChangedFile { .. })| Some(StatusSelection::Log { .. })| None => None,
.width(Length::Fill).height(Length::Fill).spacing(SPACING)))}_ => None,
let entire_log_selected = || match entire_log_selection.as_ref() {Some(EntireLogSelection { file, .. }) => file.is_none(),None => false,
let entire_log_selected = || match selection {selection::Unified::EntireLog(log_change) => match log_change {Some(selection::LogChange { file, .. }) => file.is_none(),None => true,},_ => 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(),
let entire_log_change_selected = || match selection {selection::Unified::EntireLog(log_change) => match log_change {Some(selection::LogChange {file:Some(selection::LogChangeFileSelection {diff_selected, ..}),..}) => !*diff_selected,_ => false,},
let is_selected = matches!(entire_log_selection.as_ref(),Some(EntireLogSelection { ix: selected_ix, .. }) if &ix == selected_ix
let is_selected = matches!(selection,selection::Unified::EntireLog(Some(selection::LogChange{ ix: selected_ix, .. })) if &ix == selected_ix
let view = match (nav.as_ref(), entire_log.as_ref()) {(Some(nav), Some(EntireLog::Got { log, nav: _ })) => {
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 })) => {
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,}) => diff::view(state, nav, file, *is_selected),None => el(text("Loading diff..")),
file:Some(selection::LogChangeFileSelection {ix: _,path,diff_selected,}),})) => {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([el(column([view_diff_header(format!("{path} changes in {}:",display_short_hash(hash))),match state.zip(nav) {Some((diff::FileAndState { file, state }, nav)) => {diff::view(state, nav, file, *diff_selected)}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([]))
.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,
.width(Length::Fill).height(Length::Fill).spacing(SPACING)))}_ => None,
let is_selected = matches!(channel_selection,Some(selection::Channel{ ix: selected_ix, .. }) if &ix == selected_ix
let is_selected = matches!(selection,selection::Unified::Channel(Some(selection::Channel{ ix: selected_ix, .. })) if &ix == selected_ix
let main = if *switching_channel {if other_channels.is_empty() {el(column([el(text(format!("Current channel: {channel}")))]).width(Length::Fill).height(Length::Fill))} else {el(column([el(text(format!("Current channel: {channel}. Switch to:"))),el(column(view_channels())),]).width(Length::Fill).height(Length::Fill))}} 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]
let main = match selection {selection::Unified::Status(_) => {if let Some(status_col_2) = status_col_2() {let cols = [status_col_0(), status_col_1(), status_col_2]
el(row(cols).spacing(SPACING)
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))}}selection::Unified::Channel(_) => {if other_channels.is_empty() {el(column([el(text(format!("Current channel: {channel}")))]).width(Length::Fill).height(Length::Fill))} else {let nav = nav_scrollable(&navigation.other_channels_nav,view_channels(),).class(if other_channels_selected() {theme::Scrollable::Selected} else {theme::Scrollable::Normal})
} 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);el(row(cols).spacing(SPACING)
selection::Unified::EntireLog(_) => match logs.entire_log.as_ref() {None | Some(Log::Loading) => el(column([view_repo_info(),el(text("Entire log")),el(text("Loading...")),])
.height(Length::Fill))} else {let cols = [status_col_0(), status_col_1()];el(row(cols).spacing(SPACING).width(Length::Fill).height(Length::Fill))
.height(Length::Fill)),Some(Log::Loaded { .. }) => {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 actions_inner = actions_inner.push(view_actions(action_state(repo,record_msg,status_selection,channel_selection,entire_log_selection,)));
let actions_inner = actions_inner.push(view_actions(action_state(state)));
}#[derive(Debug, Clone)]pub enum Msg {Confirm,Cancel,Selection(selection::Msg),ToRepo(repo::MsgIn),EditRecordMsg(text_editor::Action),PostponeRecord,SaveRecord,DiscardRecord,AddUntrackedFile,RmAddedFile,StartRecord,/// Show a list of channels to switch toSelectChannel,ForkChannel,ForkChannelName(String),RefreshRepo,ShowEntireLog,
fn action_state<'a>(repo: &'a Repo,record_msg: Option<&'a RecordMsg>,status_selection: Option<StatusSelection<'a>>,channel_selection: Option<&'a selection::Channel>,entire_log_selection: Option<EntireLogSelection<'a>>,) -> ActionState {
fn action_state<'a>(state: &'a ReadyState) -> ActionState {let ReadyState {user_id: _,repo:repo::State {dir_name: _,channel,other_channels,untracked_files: _,changed_files,short_log: _,},selection,navigation,record_msg,forking_channel_name,logs: _,} = state;let selection::State {primary,status: status_selection,channel: channel_selection,entire_log: entire_log_selection,held_key: _,} = selection;
if repo.switching_channel {let sub_state = if repo.state.other_channels.is_empty() {SwitchingChannelState::NoOtherChannels} else if channel_selection.is_some() {let can_switch = repo.state.changed_files.is_empty();SwitchingChannelState::SomethingSelected { can_switch }} else {SwitchingChannelState::NothingSelected};return ActionState::SwitchingChannel(sub_state);}if let Some(name) = repo.forking_channel.as_ref() {
if let Some(name) = forking_channel_name.as_ref() {
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
match primary {selection::Primary::Status => {let can_record = !changed_files.is_empty();let has_other_channels = !other_channels.is_empty();match status_selection {Some(selection::Status::UntrackedFile {ix: _,path,diff_selected,}) => {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 {ActionState::Diff {can_record,has_other_channels,}} else {ActionState::Main {selection: MainSelection::Untracked,can_select_right:nav_scrollable::needs_scrolling(nav),can_record,has_other_channels,}}
ActionState::EntireLogChange {can_select_right: nav_scrollable::needs_scrolling(diff.nav,),
ActionState::Main {selection: MainSelection::Untracked,can_select_right: false,can_record,has_other_channels,
}None => ActionState::EntireLog {can_select_right: nav.map(nav_scrollable::needs_scrolling).unwrap_or_default(),},};}let can_record = !repo.state.changed_files.is_empty();let has_other_channels = !repo.state.other_channels.is_empty();match status_selection {Some(StatusSelection::UntrackedFile {ix: _,path: _,diff,}) => {if let Some(diff) = diff {if diff.is_selected {ActionState::Diff {can_record,has_other_channels,
Some(selection::Status::ChangedFile {ix: _,path,diff_selected,}) => {let is_added_from_untracked = changed_files.get(path).map(|diffs| {diffs.iter().any(|diff| {matches!(diff, repo::ChangedFileDiff::Add)})}).unwrap_or_default();let main_selection = || {if is_added_from_untracked {MainSelection::AddedFromUntracked} else {MainSelection::Other}};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 {ActionState::Diff {can_record,has_other_channels,}} else {ActionState::Main {selection: main_selection(),can_select_right:nav_scrollable::needs_scrolling(nav),can_record,has_other_channels,}}} else {ActionState::Main {selection: main_selection(),can_select_right: false,can_record,has_other_channels,}
} else {ActionState::Main {selection: MainSelection::Untracked,can_select_right: nav_scrollable::needs_scrolling(diff.nav,),can_record,has_other_channels,
}Some(selection::Status::LogChange(selection::LogChange {ix: _,hash,message: _,file,})) => {let nav = get_status_log_files_nav(navigation, *hash);match file {Some(selection::LogChangeFileSelection {ix: _,path: _,diff_selected,}) => {if let Some(nav) = nav {if *diff_selected {ActionState::Diff {can_record,has_other_channels,}} else {ActionState::LogChange {can_select_right:nav_scrollable::needs_scrolling(nav),can_record,has_other_channels,}}} else {ActionState::LogChange {can_select_right: false,can_record,has_other_channels,}}}None => ActionState::Main {selection: MainSelection::Other,can_select_right: nav.map(nav_scrollable::needs_scrolling).unwrap_or_default(),can_record,has_other_channels,},
Some(StatusSelection::ChangedFile {ix: _,path: _,diff,is_added_from_untracked,}) => {let main_selection = || {if is_added_from_untracked {MainSelection::AddedFromUntracked} else {MainSelection::Other}
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
if let Some(diff) = diff {if diff.is_selected {ActionState::Diff {can_record,has_other_channels,}} else {ActionState::Main {selection: main_selection(),can_select_right: nav_scrollable::needs_scrolling(diff.nav,),can_record,has_other_channels,
return ActionState::SelectingChannel(sub_state);}selection::Primary::EntireLog => {if let Some(selection::LogChange { hash, file, .. }) =entire_log_selection{let nav = get_entire_log_files_nav(navigation, *hash);return match file {Some(selection::LogChangeFileSelection {ix: _,path: _,diff_selected,}) => {if let Some(nav) = nav {if *diff_selected {ActionState::EntireLogChangeDiff} else {ActionState::EntireLogChange {can_select_right:nav_scrollable::needs_scrolling(nav),}}} else {ActionState::EntireLogChange {can_select_right: false,}}
Some(StatusSelection::Log {ix: _,hash: _,message: _,file,nav,}) => match file {Some(LogChangeFileSelection {ix: _,path: _,diff,}) => {if let Some(diff) = diff {if diff.is_selected {ActionState::Diff {can_record,has_other_channels,}} else {ActionState::LogChange {can_select_right: nav_scrollable::needs_scrolling(diff.nav,),can_record,has_other_channels,}}} else {ActionState::LogChange {can_select_right: false,can_record,has_other_channels,}}}None => ActionState::Main {selection: MainSelection::Other,can_select_right: nav.map(nav_scrollable::needs_scrolling).unwrap_or_default(),can_record,has_other_channels,},},None => ActionState::Main {selection: MainSelection::Other,can_select_right: false,can_record,has_other_channels,},
/// Primary selection state. This is distinct from `status`, `channel`,/// `entire_log` sub-states fields to which each case corresponds, because/// they are preserved in different primary selection states.pub primary: Primary,/// Sub-selection in [`Primary::Status`] state
/// Last directional key down that's not yet been releasedpub held_key: Option<HeldKey>,}#[derive(Debug)]pub enum Unified<'a> {Status(Option<&'a Status>),Channel(Option<&'a Channel>),EntireLog(Option<&'a LogChange>),}#[derive(Debug, Default, Clone, Copy)]pub enum Primary {#[default]Status,Channel,EntireLog,
}pub fn unify(state: &State) -> Unified<'_> {let State {primary,status,channel,entire_log,held_key: _,} = state;match primary {Primary::Status => Unified::Status(status.as_ref()),Primary::Channel => Unified::Channel(channel.as_ref()),Primary::EntireLog => Unified::EntireLog(entire_log.as_ref()),}
use crate::diff;use iced_expl_widget::nav_scrollable;use libflorescence::{file, repo};use std::collections::HashSet;#[derive(Debug, Default)]pub struct FilesAndState {/// All the hashes in this set have `diffs` loaded.pub changes_with_loaded_diffs: HashSet<repo::ChangeHash>,/// All the diffs in this map have the change hash present in/// `change_with_loaded_diffs`pub diffs: file::LogIdMap<diff::FileAndState>,}#[derive(Debug, Default)]pub struct Navs {/// Log change's files scrollable nav. Only the currently selected nav is/// kept.pub files_nav: Option<(repo::ChangeHash, nav_scrollable::State)>,/// Log file's diffs scrollable nav. Only the currently selected nav is/// kept.pub diffs_nav: Option<(file::LogIdHash, nav_scrollable::State)>,}
pub mod diff;pub mod log;pub mod selection;use iced_expl_widget::nav_scrollable;use libflorescence::identity::Id;use libflorescence::{file, repo};use iced::widget::text_editor;use std::collections::HashMap;use std::path::PathBuf;pub fn ready(state: &State) -> Option<&ReadyState> {if let SubState::Ready(state) = &state.sub {return Some(state);}None}pub fn ready_mut(state: &mut State) -> Option<&mut ReadyState> {if let SubState::Ready(state) = &mut state.sub {return Some(state);}None}#[derive(Debug)]pub struct State {pub window_size: iced::Size,pub repo_path: PathBuf,pub sub: SubState,}#[derive(Debug)]pub enum SubState {Loading {user_ids: Vec<Id>,repo: Option<repo::State>,},SelectingId {user_ids: Vec<Id>,user_selection_ix: usize,user_selection_nav: nav_scrollable::State,repo: Option<repo::State>,},Ready(ReadyState),}#[derive(Debug)]pub struct ReadyState {pub user_id: Id,pub repo: repo::State,pub selection: selection::State,pub navigation: Navigation,pub record_msg: Option<RecordMsg>,/// `Some` when we're writing a name of the new channel forked from currentpub forking_channel_name: Option<String>,pub logs: Logs,}#[derive(Debug)]pub enum RecordMsg {Typing(text_editor::Content),Canceled { old_msg: String },}#[derive(Debug, Default)]pub struct Navigation {/// Scrollable status view contains the overview of untracked files,/// changed files and most recent log changespub status_nav: nav_scrollable::State,/// Logs shown in the status view.pub status_logs_navs: log::Navs,/// Other channels selection's navpub other_channels_nav: nav_scrollable::State,/// Navs for a log of selected channel other than the currentpub other_channel_log_navs: Option<(String, log::Navs)>,/// Entire log's change selection navpub entire_log_nav: nav_scrollable::State,/// Logs for the entire logpub entire_logs_navs: log::Navs,/// Diffs for untracked and changed files shown in status viewpub files_diffs: diff::FilesState,/// Diffs of status log changes, entire log changes and other channels'/// logspub log_diffs: log::FilesAndState,}#[derive(Debug, Default)]pub struct Logs {/// Populated when requested to look at the entire log of changespub entire_log: Option<Log>,/// Keys are channel namespub other_channels_logs: HashMap<String, Log>,}#[derive(Debug)]pub enum Log {Loading,Loaded { log: repo::Log },}/// 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(navigation: &mut Navigation,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))}/// 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))}
#[doc(inline)]pub use libflorescence::diff::{contents_to_lines, Combined, DecodedFile, DiffWithContents,DiffWithoutContents, File, FileContent, Lines, Section,UndecodableContents, UndecodableFile,};use iced_expl_widget::nav_scrollable;use libflorescence::file;#[derive(Debug, Default)]pub struct State {pub selected_sections: Vec<usize>,pub expanded_unchanged_sections: Vec<usize>,pub collapsed_changed_sections: Vec<usize>,}#[derive(Debug, Default)]pub struct FilesState {pub diffs: file::IdMap<State>,/// Diffs scrollable nav.////// Only the current nav is kept because the sizes of contents have to be/// queried everytime the selection changes due to possible container size/// changes.pub diffs_nav: Option<(file::IdHash, nav_scrollable::State)>,}#[derive(Debug)]pub struct FileAndState {pub file: File,pub state: State,}
[package]name = "inflorescence-model"version.workspace = trueedition.workspace = truelicense.workspace = trueauthors.workspace = true[dependencies]# Internal dependencies[dependencies.libflorescence]workspace = true[dependencies.iced-expl-widget]workspace = true# External dependencies[dependencies.iced]workspace = true
pub use inflorescence_view::selection::{Channel, Dir, HeldKey, LogChange, LogChangeFileSelection, Msg, Select,State as ViewState, Status,
pub use inflorescence_model::selection::{unify, Channel, Dir, HeldKey, LogChange, LogChangeFileSelection, Msg,Primary, Select, State, Status, Unified,
use crate::{diff, file};
use crate::{diff, file, log};use iced_expl_widget::nav_scrollable;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_view::app;
#[derive(Debug, Default)]pub struct State {pub view: ViewState,/// Last directional key down that's not yet been releasedpub held_key: Option<HeldKey>,
/// Hot potatopub struct Ctx<'a> {pub state: &'a mut State,pub files: &'a mut file::State,pub navigation: &'a mut Navigation,pub repo: &'a repo::State,pub logs: &'a Logs,
files_diffs: &mut diff::FilesState,status_logs: &mut diff::LogFilesAndState,entire_logs: &mut diff::LogFilesAndState,repo: Option<&mut app::Repo>,
navigation: &mut Navigation,repo: &repo::State,logs: &Logs,
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::Left => select_left(state, repo.as_deref()),Dir::Right => select_right(state,files_diffs,status_logs,entire_logs,repo.as_deref(),),
Dir::Down => select_down(mctx, delta),Dir::Up => select_up(mctx, delta),Dir::Left => select_left(mctx),Dir::Right => select_right(mctx),
Msg::Select(select) => select_exact(select,state,files,repo,files_diffs,status_logs,entire_logs,),
Msg::Select(select) => select_exact(select, mctx),
fn select_down(state: &mut State,files: &mut file::State,files_diffs: &mut diff::FilesState,status_logs: &mut diff::LogFilesAndState,entire_logs: &mut diff::LogFilesAndState,repo: Option<&mut app::Repo>,delta: Option<Duration>,) -> Task<crate::Msg> {let Some(app::Repo {state: repo_state,status_nav,switching_channel,forking_channel: _,entire_log,}) = repoelse {return Task::none();};if *switching_channel {let channels = &repo_state.other_channels;let selection = match state.view.channel.take() {Some(Channel { ix, name: _ }) => {let ix = if ix == channels.len() - 1 { 0 } else { ix + 1 };channel_selection(ix, channels)}None => {let ix = 0;channel_selection(ix, channels)}};state.view.channel = Some(selection);return Task::none();}if let Some(app::EntireLog::Got { log, nav }) = entire_log.as_mut() {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,}),};if let Some(nav) = entire_logs.diffs_nav.as_mut().and_then(|(nav_id_hash, nav)| {(*nav_id_hash == id_hash).then_some(nav)}){nav_scrollable::scroll_down(nav, delta)};(selection, Task::none())} 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,);if let Some((nav_changes_hash, nav)) =entire_logs.changes_nav.as_mut()&& *nav_changes_hash == hash{nav_scrollable::scroll_down_to_section(nav, file_ix)};let selection = LogChange {ix: log_ix,hash,message,file: Some(file),};(selection, selection_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;
fn select_down(ctx: &mut Ctx<'_>, delta: Option<Duration>) -> Task<crate::Msg> {match ctx.state.primary {Primary::Status => select_down_status(ctx, delta),Primary::Channel => select_down_channel(ctx),Primary::EntireLog => select_down_entire_log(ctx, delta),
changed_file_selection(repo_state,status_nav,ix,files,files_diffs,VertDir::Down,)} else if !repo_state.log.is_empty() {
changed_file_selection(ix, VDir::Down, ctx)} else if !ctx.repo.short_log.is_empty() {
status_log_selection(status_nav,status_logs,repo_state,ix,VertDir::Down,)
status_log_selection(ix, VDir::Down, ctx)
status_log_selection(status_nav,status_logs,repo_state,ix,VertDir::Down,)} else if !repo_state.untracked_files.is_empty() {
status_log_selection(ix, VDir::Down, ctx)} else if !ctx.repo.untracked_files.is_empty() {
if let Some(nav) = status_logs.diffs_nav.as_mut().and_then(|(nav_id_hash, nav)| {(*nav_id_hash == id_hash).then_some(nav)}){
if let Some(nav) = get_status_log_diffs_nav_mut(ctx.navigation,id_hash,) {
if let Some((nav_changes_hash, nav)) =status_logs.changes_nav.as_mut()&& *nav_changes_hash == hash
if let Some(nav) =get_status_log_files_nav_mut(ctx.navigation, hash)
untracked_file_selection(repo_state,status_nav,ix,files,files_diffs,VertDir::Up,)} else if !repo_state.changed_files.is_empty() {
untracked_file_selection(ix, VDir::Up, ctx)} else if !ctx.repo.changed_files.is_empty() {
status_log_selection(status_nav,status_logs,repo_state,ix,VertDir::Up,)
status_log_selection(ix, VDir::Up, ctx)
status_log_selection(status_nav,status_logs,repo_state,ix,VertDir::Down,)
status_log_selection(ix, VDir::Down, ctx)
let (selection, task) = untracked_file_selection(repo_state,status_nav,ix,files,files_diffs,VertDir::Down,);
let (selection, task) =untracked_file_selection(ix, VDir::Down, ctx);
let (selection, task) = changed_file_selection(repo_state,status_nav,ix,files,files_diffs,VertDir::Down,);
let (selection, task) =changed_file_selection(ix, VDir::Down, ctx);
} else if !repo_state.log.is_empty() {let ix = repo_state.log.len() - 1;let (selection, task) = status_log_selection(status_nav,status_logs,repo_state,ix,VertDir::Down,);
} else if !ctx.repo.short_log.is_empty() {let ix = ctx.repo.short_log.len() - 1;let (selection, task) =status_log_selection(ix, VDir::Down, ctx);
fn select_up(state: &mut State,files: &mut file::State,files_diffs: &mut diff::FilesState,status_logs: &mut diff::LogFilesAndState,entire_logs: &mut diff::LogFilesAndState,repo: Option<&mut app::Repo>,
fn select_down_channel(ctx: &mut Ctx<'_>) -> Task<crate::Msg> {let ix = match ctx.state.channel.take() {Some(Channel {ix,name: _,log: _,}) => {if ix == ctx.repo.other_channels.len() - 1 {0} else {ix + 1}}None => 0,};let (selection, task) = channel_selection(ix, VDir::Down, ctx);ctx.state.channel = Some(selection);return task;}fn select_down_entire_log(ctx: &mut Ctx<'_>,
let Some(app::Repo {state: repo_state,status_nav,switching_channel,forking_channel: _,entire_log,}) = repoelse {return Task::none();};if *switching_channel {let channels = &repo_state.other_channels;let selection = match state.view.channel.take() {Some(Channel { ix, name: _ }) => {let ix = if ix == 0 { channels.len() - 1 } else { ix - 1 };channel_selection(ix, channels)}None => {let ix = channels.len() - 1;channel_selection(ix, channels)}};state.view.channel = Some(selection);return Task::none();}if let Some(app::EntireLog::Got { log, nav }) = entire_log.as_mut() {
if let Some(Log::Loaded { log }) = ctx.logs.entire_log.as_ref() {
let (selection, task) = match state.view.status.take() {
fn select_up(ctx: &mut Ctx<'_>, delta: Option<Duration>) -> Task<crate::Msg> {match ctx.state.primary {Primary::Status => select_up_status(ctx, delta),Primary::Channel => select_up_channel(ctx),Primary::EntireLog => select_up_entire_log(ctx, delta),}}fn select_up_status(ctx: &mut Ctx<'_>,delta: Option<Duration>,) -> Task<crate::Msg> {let (selection, task) = match ctx.state.status.take() {
if let Some(nav) = files_diffs.diffs_nav.as_mut().and_then(|(nav_id_hash, nav)| {(*nav_id_hash == id_hash).then_some(nav)},) {
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)},){
if !repo_state.log.is_empty() {let ix = repo_state.log.len() - 1;status_log_selection(status_nav,status_logs,repo_state,ix,VertDir::Down,)} else if !repo_state.changed_files.is_empty() {let ix = repo_state.changed_files.len() - 1;changed_file_selection(repo_state,status_nav,ix,files,files_diffs,VertDir::Down,)
if !ctx.repo.short_log.is_empty() {let ix = ctx.repo.short_log.len() - 1;status_log_selection(ix, VDir::Down, ctx)} else if !ctx.repo.changed_files.is_empty() {let ix = ctx.repo.changed_files.len() - 1;changed_file_selection(ix, VDir::Down, ctx)
let ix = repo_state.untracked_files.len() - 1;untracked_file_selection(repo_state,status_nav,ix,files,files_diffs,VertDir::Down,)
let ix = ctx.repo.untracked_files.len() - 1;untracked_file_selection(ix, VDir::Down, ctx)
if let Some(nav) = files_diffs.diffs_nav.as_mut().and_then(|(nav_id_hash, nav)| {(*nav_id_hash == id_hash).then_some(nav)},) {
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)},){
if !repo_state.untracked_files.is_empty() {let ix = repo_state.untracked_files.len() - 1;untracked_file_selection(repo_state,status_nav,ix,files,files_diffs,VertDir::Up,)} else if !repo_state.log.is_empty() {let ix = repo_state.log.len() - 1;status_log_selection(status_nav,status_logs,repo_state,ix,VertDir::Down,)
if !ctx.repo.untracked_files.is_empty() {let ix = ctx.repo.untracked_files.len() - 1;untracked_file_selection(ix, VDir::Up, ctx)} else if !ctx.repo.short_log.is_empty() {let ix = ctx.repo.short_log.len() - 1;status_log_selection(ix, VDir::Down, ctx)
let ix = repo_state.changed_files.len() - 1;changed_file_selection(repo_state,status_nav,ix,files,files_diffs,VertDir::Down,)
let ix = ctx.repo.changed_files.len() - 1;changed_file_selection(ix, VDir::Down, ctx)
if let Some((nav_changes_hash, nav)) =status_logs.changes_nav.as_mut()&& *nav_changes_hash == hash
if let Some(nav) =get_entire_log_files_nav_mut(ctx.navigation, hash)
if !repo_state.changed_files.is_empty() {let ix = repo_state.changed_files.len() - 1;changed_file_selection(repo_state,status_nav,ix,files,files_diffs,VertDir::Up,)} else if !repo_state.untracked_files.is_empty() {let ix = repo_state.untracked_files.len() - 1;untracked_file_selection(repo_state,status_nav,ix,files,files_diffs,VertDir::Up,)
if !ctx.repo.changed_files.is_empty() {let ix = ctx.repo.changed_files.len() - 1;changed_file_selection(ix, VDir::Up, ctx)} else if !ctx.repo.untracked_files.is_empty() {let ix = ctx.repo.untracked_files.len() - 1;untracked_file_selection(ix, VDir::Up, ctx)
let ix = repo_state.log.len() - 1;status_log_selection(status_nav,status_logs,repo_state,ix,VertDir::Down,)
let ix = ctx.repo.short_log.len() - 1;status_log_selection(ix, VDir::Down, ctx)
status_log_selection(status_nav,status_logs,repo_state,ix,VertDir::Up,)
status_log_selection(ix, VDir::Up, ctx)
if !repo_state.log.is_empty() {let ix = repo_state.log.len() - 1;let (selection, task) = status_log_selection(status_nav,status_logs,repo_state,ix,VertDir::Up,);
if !ctx.repo.short_log.is_empty() {let ix = ctx.repo.short_log.len() - 1;let (selection, task) = status_log_selection(ix, VDir::Up, ctx);
} else if !repo_state.changed_files.is_empty() {let ix = repo_state.changed_files.len() - 1;let (selection, task) = changed_file_selection(repo_state,status_nav,ix,files,files_diffs,VertDir::Up,);
} else if !ctx.repo.changed_files.is_empty() {let ix = ctx.repo.changed_files.len() - 1;let (selection, task) =changed_file_selection(ix, VDir::Up, ctx);
} else if !repo_state.untracked_files.is_empty() {let ix = repo_state.untracked_files.len() - 1;let (selection, task) = untracked_file_selection(repo_state,status_nav,ix,files,files_diffs,VertDir::Up,);
} else if !ctx.repo.untracked_files.is_empty() {let ix = ctx.repo.untracked_files.len() - 1;let (selection, task) =untracked_file_selection(ix, VDir::Up, ctx);
fn alt_select_down<M>(state: &mut State,files_diffs: &mut diff::FilesState,logs: &mut diff::LogFilesAndState,
fn select_up_channel(ctx: &mut Ctx<'_>) -> Task<crate::Msg> {let ix = match ctx.state.channel.take() {Some(Channel {ix,name: _,log: _,}) => {if ix == 0 {ctx.repo.other_channels.len() - 1} else {ix - 1}}None => ctx.repo.other_channels.len() - 1,};let (selection, task) = channel_selection(ix, VDir::Up, ctx);ctx.state.channel = Some(selection);return task;}fn select_up_entire_log(ctx: &mut Ctx<'_>,
) -> Task<M> {match state.view.status.as_mut() {
) -> 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 {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};let (file, selection_task) = entire_log_file_selection(file_ix,hash,VDir::Up,ctx.navigation,log_entry,);if let Some(nav) =get_entire_log_files_nav_mut(ctx.navigation, hash){nav_scrollable::scroll_up_to_section(nav, file_ix)}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)} else {let ix = log_ix - 1;entire_log_selection(ix, VDir::Up, ctx, log)}}}} else {let ix = 0;entire_log_selection(ix, VDir::Down, ctx, log)};ctx.state.entire_log = Some(selection);return task;}Task::none()}fn alt_select_down<M>(ctx: &mut Ctx<'_>, delta: Option<Duration>) -> Task<M> {match ctx.state.status.as_mut() {
logs.diffs_nav.as_mut().and_then(|(nav_id_hash, nav)| {(*nav_id_hash == id_hash).then_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)},)
fn alt_select_up<M>(state: &mut State,files_diffs: &mut diff::FilesState,logs: &mut diff::LogFilesAndState,delta: Option<Duration>,) -> Task<M> {match state.view.status.as_mut() {
fn alt_select_up<M>(ctx: &mut Ctx<'_>, delta: Option<Duration>) -> Task<M> {match ctx.state.status.as_mut() {
logs.diffs_nav.as_mut().and_then(|(nav_id_hash, nav)| {(*nav_id_hash == id_hash).then_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)},)
fn select_left(state: &mut State,repo: Option<&app::Repo>,) -> Task<crate::Msg> {let Some(app::Repo {state: _repo_state,status_nav: _,switching_channel: _,forking_channel: _,entire_log,}) = repo.as_ref()else {return Task::none();};if let Some(app::EntireLog::Got { log: _, nav: _ }) = entire_log.as_ref() {let (selection, task) = match state.view.entire_log.take() {Some(LogChange {ix,hash,message,file:Some(LogChangeFileSelection {ix: file_ix,path,diff_selected,}),}) => {if diff_selected {(Some(LogChange {ix,hash,message,file: Some(LogChangeFileSelection {
fn select_left(ctx: &mut Ctx<'_>) -> Task<crate::Msg> {match ctx.state.primary {Primary::Status => {let (selection, task): (Option<Status>, Task<crate::Msg>) =match ctx.state.status.take() {Some(Status::LogChange(LogChange {ix,hash,message,file:Some(LogChangeFileSelection {
})) => {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,});(Some(selection), Task::none())}}Some(Status::UntrackedFile {ix,path,diff_selected: true,}) => (Some(Status::UntrackedFile {ix,path,diff_selected: false,
hash,message,file: None,};(Some(selection), Task::none())}}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,
});(Some(selection), Task::none())}}Some(Status::UntrackedFile {ix,path,diff_selected: true,}) => (Some(Status::UntrackedFile {ix,path,diff_selected: false,}),Task::none(),),Some(Status::ChangedFile {ix,path,diff_selected: true,}) => (Some(Status::ChangedFile {
..}))| None) => (selection, Task::none()),};ctx.state.status = selection;return task;}Primary::Channel => {// todo!()}Primary::EntireLog => {let (selection, task) = match ctx.state.entire_log.take() {Some(LogChange {
path,diff_selected: false,}),Task::none(),),selection @ (Some(Status::UntrackedFile { .. })| Some(Status::ChangedFile { .. })| Some(Status::LogChange(LogChange {file: None,..}))| None) => (selection, Task::none()),};state.view.status = selection;task}fn select_right(state: &mut State,files_diffs: &diff::FilesState,status_logs: &mut diff::LogFilesAndState,entire_logs: &mut diff::LogFilesAndState,repo: Option<&app::Repo>,) -> Task<crate::Msg> {let Some(app::Repo {state: repo_state,status_nav: _,switching_channel: _,forking_channel: _,entire_log,}) = repo.as_ref()else {return Task::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);// If the log is not loaded yet, the nav will be initialized// once it's loaded (`repo::MsgOut::GotChangeDifs`)if let Some(log) = entire_logs.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);entire_logs.diffs_nav = Some((id_hash, nav));};(
hash,message,file:
ix: 0,path: path.clone(),diff_selected: false,}),Task::none(),)} 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);(Some(LogChange {ix,hash,message,file: Some(LogChangeFileSelection {
}),Task::none(),)}selection @ LogChange { .. } => (Some(selection), Task::none()),};state.view.entire_log = selection;return task;
}) => {if diff_selected {(Some(LogChange {ix,hash,message,file: Some(LogChangeFileSelection {ix: file_ix,path,diff_selected: false,}),}),Task::none(),)} else {let selection = LogChange {ix,hash,message,file: None,};(Some(selection), Task::none())}}selection @ (Some(LogChange { file: None, .. }) | None) => {(selection, Task::none())}};ctx.state.entire_log = selection;return task;}
let (selection, task): (Option<Status>, Task<crate::Msg>) =match state.view.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(files_diffs, id_hash);(
fn select_right(ctx: &mut Ctx<'_>) -> Task<crate::Msg> {match ctx.state.primary {Primary::Status => {let (selection, task): (Option<Status>, Task<crate::Msg>) =match ctx.state.status.take() {
diff_selected,}),Task::none(),)}Some(Status::ChangedFile {ix,path,diff_selected: false,}) => {let id_hash = file::id_parts_hash(&path, file::Kind::Changed);let diff_selected =diff::file_diff_needs_scrolling(files_diffs, id_hash);(
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(),)}
diff_selected,}),Task::none(),)}Some(Status::LogChange(LogChange {ix,hash,message,file: None,})) => {let log_entry = repo_state.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) = status_logs.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);status_logs.diffs_nav = Some((id_hash, nav));};
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,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 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.status_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(LogChangeFileSelection {ix: 0,path: path.clone(),diff_selected: false,}),Task::none(),)} 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 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(),)}selection => (selection, Task::none()),
(Some(Status::LogChange(LogChange {
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 {
file,})),task,)}Some(Status::LogChange(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(status_logs, id_hash);(Some(Status::LogChange(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: Some(LogChangeFileSelection {ix: file_ix,path,diff_selected: is_diff_scrollable,}),})),Task::none(),)
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;
selection => (selection, Task::none()),};state.view.status = selection;task
}}Task::none()
fn release<M>(dir: Dir,state: &mut State,files_diffs: &mut diff::FilesState,logs: &mut diff::LogFilesAndState,) -> Task<M> {
fn release<M>(dir: Dir, ctx: &mut Ctx<'_>) -> Task<M> {let Ctx {state,files: _,navigation,repo: _,logs: _,} = ctx;
if let Some(nav) = files_diffs.diffs_nav.as_mut().and_then(|(nav_id_hash, nav)| {(*nav_id_hash == id_hash).then_some(nav)},) {
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)},){
if let Some(nav) = files_diffs.diffs_nav.as_mut().and_then(|(nav_id_hash, nav)| {(*nav_id_hash == id_hash).then_some(nav)},) {
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)},){
if let Some(nav) = logs.diffs_nav.as_mut().and_then(|(nav_id_hash, nav)| {(*nav_id_hash == id_hash).then_some(nav)},) {
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)},){
fn select_exact(select: Select,state: &mut State,files: &mut file::State,repo: Option<&mut app::Repo>,files_diffs: &mut diff::FilesState,status_logs: &mut diff::LogFilesAndState,entire_logs: &mut diff::LogFilesAndState,) -> Task<crate::Msg> {let Some(app::Repo {state: repo_state,status_nav,switching_channel: _,forking_channel: _,entire_log,}) = repoelse {return Task::none();};
if let Some(app::EntireLog::Got { log, nav }) = entire_log.as_mut() {let (selection, task) = match select {Select::LogChange {ix,hash: _,message: _,} => {let (selection, task) = entire_log_selection(nav,entire_logs,log,
fn select_exact(select: Select, ctx: &mut Ctx<'_>) -> Task<crate::Msg> {match ctx.state.primary {Primary::Status => {let (selection, task) = match select {Select::UntrackedFile { ix, path } => {let id = file::Id {path: path.clone(),file_kind: file::Kind::Changed,};file::load_src_file_if_not_cached(ctx.files, id);let (selection, selection_task) =untracked_file_selection(ix, VDir::Down, ctx);(Some(selection), selection_task)}Select::ChangedFile { ix, path } => {let id = file::Id {path: path.clone(),file_kind: file::Kind::Changed,};if let Some(diffs) = ctx.repo.changed_files.get(&path)&& diff::any_diff_has_contents(diffs){file::load_src_file_if_not_cached(ctx.files, id);}let (selection, selection_task) =changed_file_selection(ix, VDir::Down, ctx);(Some(selection), selection_task)}Select::LogChange {
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 (file, selection_task) = log_file_selection(entire_logs,log_entry,hash,file_ix,);let selection = Some(LogChange {
hash: _,message: _,} => {let (selection, task) =status_log_selection(ix, VDir::Down, ctx);(Some(selection), task)}Select::LogChangeFile {ix: file_ix,path: _,} => match ctx.state.status.take() {Some(Status::LogChange(LogChange {
file: Some(file),});(selection, selection_task)}selection => (selection, Task::none()),},_ => {unreachable!()}};state.view.entire_log = selection;return task;}
file: _,})) => {let log_entry = ctx.repo.short_log.get(log_ix).unwrap();
let (selection, task) = match select {Select::UntrackedFile { ix, path } => {let id = file::Id {path: path.clone(),file_kind: file::Kind::Changed,
let (file, selection_task) = status_log_file_selection(file_ix,hash,VDir::Down,ctx.navigation,log_entry,);let selection = Some(Status::LogChange(LogChange {ix: log_ix,hash,message,file: Some(file),}));(selection, selection_task)}selection => (selection, Task::none()),},
file::load_src_file_if_not_cached(files, id);let (selection, selection_task) = untracked_file_selection(repo_state,status_nav,ix,files,files_diffs,VertDir::Down,);(Some(selection), selection_task)
ctx.state.status = selection;return task;
Select::ChangedFile { ix, path } => {let id = file::Id {path: path.clone(),file_kind: file::Kind::Changed,};if let Some(diffs) = repo_state.changed_files.get(&path)&& diff::any_diff_has_contents(diffs){file::load_src_file_if_not_cached(files, id);}let (selection, selection_task) = changed_file_selection(repo_state,status_nav,ix,files,files_diffs,VertDir::Down,);(Some(selection), selection_task)
Primary::Channel => {// TODO
Select::LogChange {ix,hash: _,message: _,} => {let (selection, task) = status_log_selection(status_nav,status_logs,repo_state,ix,VertDir::Down,);(Some(selection), task)}Select::LogChangeFile {ix: file_ix,path: _,} => match state.view.status.take() {Some(Status::LogChange(LogChange {ix: log_ix,hash,message,file: _,})) => {let log_entry = repo_state.log.get(log_ix).unwrap();
Primary::EntireLog => {if let Some(Log::Loaded { log }) = ctx.logs.entire_log.as_ref() {let (selection, task) = match select {Select::LogChange {ix,hash: _,message: _,} => {let (selection, task) =entire_log_selection(ix, VDir::Down, ctx, log);(Some(selection), task)}Select::LogChangeFile {ix: file_ix,path: _,} => match ctx.state.entire_log.take() {Some(LogChange {ix: log_ix,hash,message,file: _,}) => {let log_entry = log.get(log_ix).unwrap();
let selection = Some(Status::LogChange(LogChange {ix: log_ix,hash,message,file: Some(file),}));(selection, selection_task)
let selection = Some(LogChange {ix: log_ix,hash,message,file: Some(file),});(selection, selection_task)}selection => (selection, Task::none()),},_ => {unreachable!()}};ctx.state.entire_log = selection;return task;
selection => (selection, Task::none()),},};state.view.status = selection;task
}}Task::none()
}fn channel_selection(ix: usize,dir: VDir,ctx: &mut Ctx<'_>,) -> (Channel, Task<crate::Msg>) {let Ctx {state: _,files: _,navigation,repo,logs,} = ctx;let name = repo.other_channels.get(ix).unwrap().clone();let task = if logs.other_channels_logs.contains_key(&name) {Task::none()} else {Task::done(crate::Msg::View(app::Msg::ToRepo(repo::MsgIn::LoadOtherChannelLog(name.clone()),)))};match dir {VDir::Up => nav_scrollable::scroll_up_to_section(&mut navigation.other_channels_nav,ix,),VDir::Down => nav_scrollable::scroll_down_to_section(&mut navigation.other_channels_nav,ix,),}let selection = Channel {ix,name,log: None,};(selection, task)
VertDir::Up => nav_scrollable::scroll_up_to_section(nav, ix),VertDir::Down => nav_scrollable::scroll_down_to_section(nav, ix),
VDir::Up => nav_scrollable::scroll_up_to_section(&mut navigation.entire_log_nav,ix,),VDir::Down => nav_scrollable::scroll_down_to_section(&mut navigation.entire_log_nav,ix,),
if logs.diffs.contains_key(&id_hash) {let needs_new_nav = logs.diffs_nav.is_none()|| logs
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
if logs.diffs.contains_key(&id_hash) {let needs_new_nav = logs.diffs_nav.is_none()|| logs
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
files_diffs: diff::FilesState::default(),status_logs: diff::LogFilesAndState::default(),entire_logs: diff::LogFilesAndState::default(),
sub: MState {window_size,repo_path,sub: MSubState::Loading {user_ids: Vec::new(),repo: None,},},
/// Diffs for untracked and changed files shown in status viewfiles_diffs: diff::FilesState,/// 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,
sub: inflorescence_model::State,
state.id = Some(*id);
match &mut state.sub.sub {MSubState::Loading { user_ids, repo } => {// TODO switch to `SelectingId` if more than one id foundif let Some(repo) = repo.take() {state.sub.sub = MSubState::Ready(ReadyState {user_id: *id,repo,selection: default(),navigation: default(),record_msg: default(),forking_channel_name: default(),logs: default(),})} else {user_ids.push(*id);}}MSubState::SelectingId { .. } => unreachable!(),MSubState::Ready(..) => {unreachable!()}}
let loaded = file::update(&mut state.files,state.repo.as_ref().map(|repo| &repo.state),msg,);if let Some(file::Loaded {id_hash,unchanged_sections,}) = loaded
if let Some(ReadyState {repo,navigation,selection,..}) = model::ready_mut(&mut state.sub)
let nav = nav_scrollable::State::default().with_skip_sections(unchanged_sections);state.files_diffs.diffs_nav = Some((id_hash, nav));state.files_diffs.diffs.entry(id_hash).or_default();
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();}
if repo.switching_channel {if let Some(selection::Channel { ix: _, name }) =state.selection.view.channel.take()
if matches!(selection.primary, selection::Primary::Channel) {if let Some(selection::Channel {ix: _,name,log: _,}) = selection.channel.take()
state.record_msg = Some(RecordMsg::Canceled {old_msg: content.text(),});}if let Some(repo) = state.repo.as_mut() {if repo.switching_channel {repo.switching_channel = false;state.selection.view.channel = None;} else if repo.forking_channel.is_some() {repo.forking_channel = None;} else if let Some(app::EntireLog::Got { log, nav }) = repo.entire_log.take_if(|log| matches!(log, app::EntireLog::Got { .. })){// Cache for later viewingrepo.entire_log =Some(app::EntireLog::NotViewing { log, nav });
if let Some(RecordMsg::Typing(content)) = record_msg.as_ref() {*record_msg = Some(RecordMsg::Canceled {old_msg: content.text(),});}match selection.primary {selection::Primary::Status => {if forking_channel_name.is_some() {*forking_channel_name = None;}}selection::Primary::Channel| selection::Primary::EntireLog => {selection.primary = selection::Primary::default();}
app::Msg::Selection(msg) => selection::update(msg,&mut state.selection,&mut state.files,&mut state.files_diffs,&mut state.status_logs,&mut state.entire_logs,state.repo.as_mut(),),
app::Msg::Selection(msg) => {if let Some(ReadyState {repo,selection,navigation,logs,..}) = model::ready_mut(&mut state.sub){return selection::update(msg,selection,&mut state.files,navigation,repo,logs,);}Task::none()}
if let Some(repo) = state.repo.as_mut() {match repo.entire_log.as_ref() {
if let Some(ReadyState {selection, logs, ..}) = model::ready_mut(&mut state.sub){selection.primary = selection::Primary::EntireLog;match logs.entire_log.as_ref() {
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 loglet Some(app::EntireLog::NotViewing { log, nav }) =repo.entire_log.take()else {unreachable!()};repo.entire_log =Some(app::EntireLog::Got { log, nav });
logs.entire_log = Some(Log::Loading);
if let Some(app::Repo {state: repo_state,status_nav,switching_channel: _,forking_channel: _,entire_log: _,}) = state.repo.as_mut()
if let Some(ReadyState {repo,selection,navigation,logs,..}) = model::ready_mut(&mut state.sub)
let removed = repo_state.untracked_files.remove(path);debug_assert!(removed,"{:?}, path: {path}",repo_state.untracked_files);repo_state.changed_files
let removed = repo.untracked_files.remove(path);debug_assert!(removed, "{:?}, path: {path}", repo.untracked_files);repo.changed_files
if let Some(app::Repo {state: repo_state,status_nav,switching_channel: _,forking_channel: _,entire_log: _,}) = state.repo.as_mut()
if let Some(ReadyState {repo,selection,navigation,logs,..}) = model::ready_mut(&mut state.sub)
if let Some(repo) = state.repo.as_ref() {if state.id.is_none() {info!("Requested to record, but ID is not yet loaded");}if repo.state.changed_files.is_empty() {
if let Some(ReadyState {repo, record_msg, ..}) = model::ready_mut(&mut state.sub){if repo.changed_files.is_empty() {
if let Some(RecordMsg::Typing(record_msg)) = state.record_msg.as_ref() {let msg = record_msg.text();if msg.trim().is_empty() {info!("Cannot record with an empty message");} else {let id =state.id.as_ref().expect("ID must be loaded at this point");// TODO: this call has CLI prompt - replace itlet (sk, _) = id.decrypt().unwrap();let sk = Arc::new(sk);state.repo_tx_in.send(repo::MsgIn::Record { msg, sk }).unwrap();// Reset most thingsstate.selection = selection::State::default();state.record_msg = None;state.files_diffs.diffs_nav = None;state.status_logs.changes_nav = None;state.status_logs.diffs_nav = None;
if let Some(ReadyState {record_msg,repo,user_id,selection,navigation,..}) = model::ready_mut(&mut state.sub){if let Some(RecordMsg::Typing(msg)) = record_msg.as_ref() {let msg = msg.text();if msg.trim().is_empty() {info!("Cannot record with an empty message");} else {// TODO: this call has CLI prompt - replace itlet (sk, _) = user_id.decrypt().unwrap();let sk = Arc::new(sk);state.repo_tx_in.send(repo::MsgIn::Record { msg, sk }).unwrap();
let app::Repo {state: repo_state,status_nav,switching_channel: _,forking_channel: _,entire_log: _,} = state.repo.as_mut().unwrap();repo_state.changed_files = repo::ChangedFiles::default();state.files_diffs.diffs_nav = None;
// Reset most things*selection = selection::State::default();*record_msg = None;// Status view fieldsnavigation.files_diffs.diffs_nav = None;navigation.status_logs_navs.files_nav = None;navigation.status_logs_navs.diffs_nav = None;
// Re-initialize status navlet new_status_nav = nav_scrollable::State::default();*status_nav = new_status_nav;
repo.changed_files = repo::ChangedFiles::default();}
if let Some(RecordMsg::Typing(record_msg)) = state.record_msg.as_ref() {let old_msg = record_msg.text();state.record_msg = if !old_msg.trim().is_empty() {Some(RecordMsg::Canceled { old_msg })} else {None};
if let Some(ReadyState { record_msg, .. }) =model::ready_mut(&mut state.sub){if let Some(RecordMsg::Typing(msg)) = record_msg.as_ref() {let old_msg = msg.text();*record_msg = if !old_msg.trim().is_empty() {Some(RecordMsg::Canceled { old_msg })} else {None};}
if let Some(RecordMsg::Typing(_)) = state.record_msg.as_ref() {state.record_msg = None;
if let Some(ReadyState { record_msg, .. }) =model::ready_mut(&mut state.sub){if let Some(RecordMsg::Typing(_msg)) = record_msg.as_ref() {*record_msg = None;}
fn repo_init(state: &mut State, repo: repo::State) -> Task<Msg> {let status_nav = nav_scrollable::State::default();state.repo = Some(app::Repo {state: repo,status_nav,switching_channel: false,forking_channel: None,entire_log: None,});
fn repo_init(state: &mut State, repo_state: repo::State) -> Task<Msg> {match &mut state.sub.sub {MSubState::Loading { user_ids, repo: _ } => {state.sub.sub = if user_ids.len() == 1 {let user_id = user_ids.pop().unwrap();MSubState::Ready(ReadyState {user_id,repo: repo_state,selection: default(),navigation: default(),record_msg: default(),forking_channel_name: default(),logs: default(),})} else {MSubState::SelectingId {user_ids: mem::take(user_ids),user_selection_ix: default(),user_selection_nav: default(),repo: Some(repo_state),}}}MSubState::SelectingId { repo, .. } => *repo = Some(repo_state),MSubState::Ready(_) => {}};
fn repo_refreshed(state: &mut State, repo: repo::State) -> Task<Msg> {let repo::State {dir_name: _,channel: _,other_channels: _,untracked_files,changed_files,log,} = &repo;file::diffs_cache_clear(&mut state.files.diffs_cache);
fn repo_refreshed(state: &mut State,repo_state: repo::State,invalidate_logs: bool,) -> Task<Msg> {match &mut state.sub.sub {MSubState::Loading { user_ids: _, repo } => *repo = Some(repo_state),MSubState::SelectingId { repo, .. } => *repo = Some(repo_state),MSubState::Ready(ReadyState {repo,user_id: _,selection,navigation: _,record_msg: _,forking_channel_name: _,logs,}) => {*repo = repo_state;// Throw away the logsif invalidate_logs {*logs = default();};
// Re-index selectionlet selection_task = reindex_selection(&mut state.selection,&mut state.files,untracked_files,changed_files,log,);let switching_channel = if changed_files.is_empty() {state.repo.as_mut().map(|repo| repo.switching_channel).unwrap_or_default()} else {// If any changed files gets added, exit out of channel selectionfalse};let forking_channel = state.repo.as_mut().and_then(|repo| repo.forking_channel.take());let status_nav = state.repo.take().map(|repo| repo.status_nav).unwrap_or_default();// TODO: reload entire_log if it's being viewed or it's loadingstate.repo = Some(app::Repo {state: repo,status_nav,switching_channel,forking_channel,entire_log: None,});selection_task
// Re-index selectionlet selection_task =reindex_selection(repo, selection, &mut state.files, logs);return selection_task;}}Task::none()
fn repo_got_change_diffs(state: &mut State,hash: repo::ChangeHash,diffs: repo::ChangedFiles,) -> Task<Msg> {if let Some(app::EntireLog::Got { .. }) = state.repo.as_ref().and_then(|repo| repo.entire_log.as_ref())
let channel_task = if let Some(selection::Channel { ix: _, name, log }) =selection.channel.take()
let Some(selection::LogChange {ix: _,hash: selected_hash,message: _,file,}) = state.selection.view.entire_log.as_mut()else {return Task::none();};
let (new_selection, task) = {let selection = repo.other_channels.iter().enumerate().find(|(_ix, channel_name)| *channel_name == &name)// TODO: re-index log selection once loaded for channels.map(|(ix, _name)| selection::Channel { ix, name, log });
if *selected_hash != hash {return Task::none();}
let task = if let Some(selection::Channel { name, .. }) =selection.as_ref(){// Request to get the logTask::done(Msg::View(app::Msg::ToRepo(repo::MsgIn::LoadOtherChannelLog(name.clone()),)))} else {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);
(selection, task)};selection.channel = new_selection;task} else {Task::none()};
state.entire_logs.diffs_nav = Some((id_hash, diffs_nav));
let entire_log_task = if let Some(selection::LogChange {ix: _,hash,message,file,}) = selection.entire_log.take(){let new_selection =if let Some(Log::Loaded { log }) = logs.entire_log.as_ref() {log.iter().enumerate().find(|(_ix, entry)| entry.hash == hash).map(|(ix, entry)| {let file = file.and_then(|selection::LogChangeFileSelection {ix: _,path: selected_path,diff_selected,}| {entry.file_paths.iter().enumerate().find(|(_ix, path)| *path == &selected_path).map(|(ix, path)| {selection::LogChangeFileSelection {ix,path: path.clone(),diff_selected,}})},);selection::LogChange {ix,hash: entry.hash,message,file,}})} else {None};let task = if new_selection.is_some() {// Request to get the entire logTask::done(Msg::View(app::Msg::ToRepo(repo::MsgIn::LoadEntireLog)))
// Init scrollable nav for log changeslet changes_nav = nav_scrollable::State::default();state.entire_logs.changes_nav = Some((hash, changes_nav));
Task::none()
Task::none()} else if let Some(selection::Status::LogChange(selection::LogChange {ix: _,hash: selected_hash,message: _,file,})) = state.selection.view.status.as_mut(){if *selected_hash == hash {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);
match selection::unify(selection) {selection::Unified::Status(Some(selection::Status::LogChange(selection::LogChange {ix: _,hash: selected_hash,message: _,file,},))) => {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);
state.status_logs.diffs_nav = Some((id_hash, diffs_nav));} else {// Init scrollable nav for log changeslet changes_nav = nav_scrollable::State::default();
navigation.status_logs_navs.diffs_nav =Some((id_hash, diffs_nav));} else {// Init scrollable nav for log fileslet changes_nav = nav_scrollable::State::default();
};// Store the changesstate.status_logs.changes_with_loaded_diffs.insert(hash);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 {
selection::Unified::EntireLog(Some(selection::LogChange {ix: _,hash: selected_hash,message: _,
state.status_logs.diffs.insert(id_hash, log_file_diff);});Task::none()} else {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));};}selection::Unified::Channel(Some(selection::Channel {ix: _,name,log:Some(selection::LogChange {ix: _,hash: selected_hash,message: _,file,}),})) => {if *selected_hash != hash {return Task::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(repo) = state.repo.as_mut() {match repo.entire_log.as_ref() {None| Some(app::EntireLog::Loading | app::EntireLog::Got { .. }) => {let nav = nav_scrollable::State::default();repo.entire_log = Some(app::EntireLog::Got {log,nav: Box::new(nav),});}Some(app::EntireLog::LoadingButNotViewing) => {let nav = nav_scrollable::State::default();repo.entire_log = Some(app::EntireLog::NotViewing {log,nav: Box::new(nav),});}Some(app::EntireLog::NotViewing { .. }) => {}
if let Some(ReadyState { logs, .. }) = model::ready_mut(&mut state.sub) {logs.entire_log = Some(Log::Loaded { log });}Task::none()}fn loaded_other_channel_log(state: &mut State,channel: String,log: Vec<repo::LogEntry>,) -> Task<Msg> {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()));
let State {id: _,window_size,repo_fs_watch: _,repo_path,repo_tx_in: _,repo,selection,record_msg,files,files_diffs,status_logs,entire_logs,} = state;let status_selection = match selection.view.status.as_ref() {Some(selection::Status::UntrackedFile {ix,path,diff_selected,}) => {let id_hash = file::id_parts_hash(path, file::Kind::Untracked);let diff = files.diffs_cache.inner.peek(&id_hash);let nav = files_diffs.diffs_nav.as_ref().and_then(|(nav_id_hash, nav)| (*nav_id_hash == id_hash).then_some(nav),);let diff = match (diff, nav) {(Some(file::Diff::Loaded(file)), Some(nav)) => {files_diffs.diffs.get(&id_hash).map(|state| app::Diff {is_selected: *diff_selected,file,state,nav,})}(Some(file::Diff::Loaded(_) | file::Diff::Loading) | None,_,) => None,};Some(app::StatusSelection::UntrackedFile {ix: *ix,path,diff,})}Some(selection::Status::ChangedFile {path,ix,diff_selected,}) => {let id_hash = file::id_parts_hash(path, file::Kind::Changed);let diff = files.diffs_cache.inner.peek(&id_hash);let nav = files_diffs.diffs_nav.as_ref().and_then(|(nav_id_hash, nav)| (*nav_id_hash == id_hash).then_some(nav),);let diff = match (diff, nav) {(Some(file::Diff::Loaded(file)), Some(nav)) => {files_diffs.diffs.get(&id_hash).map(|state| app::Diff {is_selected: *diff_selected,file,state,nav,})}(Some(file::Diff::Loaded(_) | file::Diff::Loading) | None,_,) => None,};let is_added_from_untracked = repo.as_ref().and_then(|repo| repo.state.changed_files.get(path)).map(|diffs| {diffs.iter().any(|diff| matches!(diff, repo::ChangedFileDiff::Add))}).unwrap_or_default();Some(app::StatusSelection::ChangedFile {ix: *ix,path,diff,is_added_from_untracked,})}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 {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 = entire_logs.diffs_nav.as_ref().and_then(|(nav_id_hash, nav)| {(*nav_id_hash == id_hash).then_some(nav)},);let diff = entire_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 = entire_logs.changes_nav.as_ref().and_then(|(nav_hash, nav)| (nav_hash == hash).then_some(nav));Some(app::EntireLogSelection {ix: *log_ix,hash: *hash,message,file,nav,})}None => None,};let state = app::State {window_size: *window_size,repo_path,repo: repo.as_ref(),record_msg: record_msg.as_ref(),status_selection,channel_selection: selection.view.channel.as_ref(),entire_log_selection,};app::view(state, window_id).map(Msg::View)
app::view(&state.sub,|id_hash| file::try_get_src_file(&state.files, id_hash),window_id,).map(Msg::View)
//! Dealing with record logs.#[doc(inline)]pub use inflorescence_model::log::Navs;use crate::file;use iced_expl_widget::nav_scrollable;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()}
let file_diff: diff::File =diff::init_file(file_content, changed_file);let skip_sections =diff::unchanged_sections(&file_diff);
let file_diff: diff::File =diff::init_file(file_content, changed_file);let skip_sections =diff::unchanged_sections(&file_diff);
diffs_cache_put(&mut state.diffs_cache,id_hash,Diff::Loaded(file_diff),);
diffs_cache_put(&mut state.diffs_cache,id_hash,Diff::Loaded(file_diff),);
return Some(Loaded {id_hash,unchanged_sections: skip_sections,});}
return Some(Loaded {id_hash,unchanged_sections: skip_sections,});
DiffWithoutContents, File, Lines, Section, UndecodableContents,UndecodableFile,
DiffWithoutContents, File, FileAndState, FileContent, FilesState, Lines,Section, State, UndecodableContents, UndecodableFile,
#[derive(Debug, Default)]pub struct FilesState {pub diffs: file::IdMap<State>,/// Diffs scrollable nav.////// Only the current nav is kept because the sizes of contents have to be/// queried everytime the selection changes due to possible container size/// changes.pub diffs_nav: Option<(file::IdHash, nav_scrollable::State)>,}#[derive(Debug, Default)]pub struct LogFilesAndState {/// All the hashes in this set have `diffs` loaded.pub changes_with_loaded_diffs: HashSet<repo::ChangeHash>,/// All the diffs in this map have the change hash present in/// `change_file_counts`'s keyspub diffs: file::LogIdMap<FileAndState>,/// Log changes scrollable nav. Only the currently selected nav is kept.pub changes_nav: Option<(repo::ChangeHash, nav_scrollable::State)>,/// Log diffs scrollable nav. Only the currently selected nav is kept.pub diffs_nav: Option<(file::LogIdHash, nav_scrollable::State)>,}#[derive(Debug)]pub struct FileAndState {pub file: File,pub state: State,}#[derive(Debug)]pub enum FileContent<'a> {Decoded(Cow<'a, str>),UnknownEncoding,}
.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 log_diff_needs_scrolling(logs: &LogFilesAndState,id_hash: file::LogIdHash,) -> bool {logs.diffs_nav