EJPSD5XO43DWUBBZGNQMY4TMCAXL5EWCGX3OEHUERQ5GRASGWQLQC WT3GA27PQ2AOAIGK65O3Q4DMX4AZDVNULBLRL6GF4QW6QCASUEAAC UB2ITZJSDADVINSQEZ3HA6PVGA7OA6JYFG5GMSO7Y7LOXJC4FI7AC EC3TVL4X6VZZVLOKUN63LC73ADPHBHMZO7QMDXGX2ZPURVI4B4XQC S2NVIFXRFER4SRA37WCT5XTXHDHAL5WIGGKY4A4XOTPLTKTZSRGQC YBJRDOTCX3ZRDB5EVXJBR55FX3CADCSIGMYWNYVC2PD5W3GXR3DQC D7A7MSIHJS3IAOLEPK52M4CZLDPLO7JB3Y62XACT2AM6UUCPQ6BAC NRCUG4R2NIM2ANIETSUZ7WZDXFOOCMJ73ROP5MDYJA4RUT4PYA4QC ZVI4AWERNOTDJ3765HJXRBZT57XPNKVONQ6TGOGNPOL2VN42KMJQC AHWWRC73FXLSUDAJBU5UU76MZETHD3DSGJ7OLZPFEHXBDJ733QNAC TEI5NQ3SCTU6JQIPU62B2AUXWRFSEU6DZYJG5T526G666VV5XXPAC BFN2VHZS7VCBUHQ4S3CQ3LFQV2V4M6VANNAF32XMRFQVWRGYSZ6AC 23SFYK4Q5NKBPJG53PQNPWQH6UOUU2YKJEL7RLXYBRLJOJYV7AWQC MYGIBRRHHXPKVRAMQQRJTZH74L2XOK3SF7J57JPCRKSVRLZ2D6NQC PKJCFSBMXXA2H3US47IJEB7QMIYLEKTLGWQUYEZSKCDODDQTD6HQC XSZZB47UXR6KGYFZZQFQR63X2LDKOH6TPNNBRRGHUCI5JJ4JIWVAC ACDXXAX26ZJJFKJDGRC2GOSJY5JHQWCSTP55SYI6D6LH5UIRYUBAC FVA36HBVXZCYW7FMQLST63Q6IDGLJ23OIHORF67BUIO2GXYNBW2QC ESMM3FELOBYIX7FUNOU37FYKRJHFU2IMX6LY6EGJTVPTBDU3SEEQC 7SSBM4UQMYVRL6L3ICYZQPSMYLZZQNMDWH6JKA3KOOSXZDJHESHQC I2AG42PAVOII4V4TWDJV5ZVNDIHKBRDT254BFQLFUIY723TW6CCQC SASAN2XCWDQ2VEHZ7TAQEN2R3Y7AG7JUGEFVRL4DZAGHXDFEZFRQC YKHE3XMWOWPGOWYSISF73MIAKN7WB3AHCV2OA4ECAFPF47YHUXEAC K5YUSV2WOLGMA75WKQWY2GRLQGPAFGVYTW3GMVTWEECXF4SXFEYAC 5MUEECMJHU44FL5RDUR3VFBIWK3H4X2L5MVJ73J37PYHZWLUKU2AC 3TLPJ57B2OD5OWJN5WMS7A4W7IGFUWJJHVIXRM34VT6KUN6R4YSAC PTWZYQFRWWUOE2WMQT26CKZKFSHAIJVJS3QWHJFYUFDRRTVPHSUAC UR4J677RWA3OFG6HQTD46BUUE5YFPSBEFCJAEM5OMT4V5A7SBNNQC A6Z4O6RC33HYWP7JIVQ6FDWE4EOCQWQTIGENK2WAHUGSHDDLSA7QC 7BLZN73OYUAJEYTJ6WWHRZ7S7ONDGRBKNJGFGW62NAIZBUK3CECQC JZXYSIYDPBWQZCAMGDZ5BFMN6SU73EVVDIYEGTDJN6DVOSBNHN4QC 5ZRDYL6KIQPUI3ZZETH5KJ64N6RUF7KYM3P6Q6HER5XVJZ7GZ4WQC BAUK5BONEFQ3KIQPFLM7MGNCS5GWBILBXZMTIGN5LWTYTNNNNSPQC OJPGHVC3RFBQ7TTSCZH6URSSATII3TESD74EISDNOTNXXSX7PQMAC 3XRG4BB6V5V4DICZCMOZMLQNTANWKPO7BBRATTXOZLRNSEUQIA5AC S4WH75Y32ZYLEWXBLHRG6Z3H7KWHSXAS6H37CFQIW3FXKKWFFQ2QC WAOGSCOJ5A372BZKHEYD2BCDBCENNVLFYW3INKUOOAZMDADDIFIQC WH57EHNML4OTGQQZBT2SG6SOFTBOD6OJPJYHJVGPH22CSSOE25AAC 6YZAVBWU6E5FYOI5JGEIPXGZLIKAW6LS2AOFIQWEE5DMOPPCD5PQC get_entire_log_diffs_nav, get_entire_log_files_nav, get_files_diffs_nav,get_status_log_diffs_nav, get_status_log_files_nav, selection, Log,ReadyState, RecordMsg, SubState,
self as model, action, get_entire_log_diffs_nav, get_entire_log_files_nav,get_files_diffs_nav, get_status_log_diffs_nav, get_status_log_files_nav,selection, Log, ReadyState, RecordMsg, SubState,
el(button(text("Save")).on_press(Msg::SaveRecord)),el(button(text("Postpone")).on_press(Msg::PostponeRecord)),el(button(text("Discard")).on_press(Msg::DiscardRecord)),
el(button(text("Save")).on_press(Msg::Action(action::Msg::SaveRecord))),el(button(text("Postpone")).on_press(Msg::Action(action::Msg::PostponeRecord))),el(button(text("Discard")).on_press(Msg::Action(action::Msg::DiscardRecord))),
#[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 view_actions<'a>(state: ActionState) -> Element<'a, Msg, Theme> {let left = || {action_button("←| h","",Msg::Selection(selection::Msg::PressDir(selection::Dir::Left)),)};let down = || {action_button("↓| j","",Msg::Selection(selection::Msg::PressDir(selection::Dir::Down)),)};let up = || {action_button("↑| k","",Msg::Selection(selection::Msg::PressDir(selection::Dir::Up)),)};let right = || {action_button("→| l","",Msg::Selection(selection::Msg::PressDir(selection::Dir::Right)),)};let down_no_skip = || {action_button("S-(↓| j)","no skip",Msg::Selection(selection::Msg::AltPressDir(selection::Dir::Down)),)};let up_no_skip = || {action_button("S-(↑| k)","no skip",Msg::Selection(selection::Msg::AltPressDir(selection::Dir::Up)),)};let add_untracked =|| action_button("a", "track file", Msg::AddUntrackedFile);let rm_added_file = || action_button("x", "untrack file", Msg::RmAddedFile);let start_record = || action_button("r", "record", Msg::StartRecord);let save_record = || action_button("C-s", "save record", Msg::SaveRecord);let postpone_record =|| action_button("C-d", "postpone record", Msg::PostponeRecord);let discard_record =|| action_button("C-d", "discard record", Msg::DiscardRecord);let select_channel =|| action_button("c", "select channel", Msg::SelectChannel);let fork_channel = || action_button("f", "fork channel", Msg::ForkChannel);let refresh_repo =|| action_button("C-r", "refresh repo", Msg::RefreshRepo);let confirm =|label: &'static str| action_button("Enter", label, Msg::Confirm);let cant_confirm =|label: &'static str| el(action_button_inner("Enter", label));let cancel = || action_button("Esc", "cancel", Msg::Cancel);let forking_channel = matches!(state, ActionState::ForkingChannel { .. });#[allow(clippy::let_and_return)]let row = match state {ActionState::Main {selection,can_select_right,can_record,has_other_channels,} => {let row = row([down(), up()]);let row = match selection {MainSelection::Untracked => {let row = row.push(add_untracked());let row = add_if(can_select_right, right, row);add_if(can_record, start_record, row)}MainSelection::AddedFromUntracked => {let row = row.push(rm_added_file());let row = add_if(can_select_right, right, row);add_if(can_record, start_record, row)}MainSelection::Other => {let row = add_if(can_select_right, right, row);add_if(can_record, start_record, row)}};let row = add_if(has_other_channels, select_channel, row);row}ActionState::LogChange {can_select_right,can_record,has_other_channels,} => {let row = row([left(), down(), up()]);let row = add_if(can_select_right, right, row);let row = add_if(can_record, start_record, row);let row = add_if(has_other_channels, select_channel, row);row}ActionState::Diff {can_record,has_other_channels,} => {let row = row([left(), down(), up(), down_no_skip(), up_no_skip()]);let row = add_if(can_record, start_record, row);let row = add_if(has_other_channels, select_channel, row);row}ActionState::Recording => {row([save_record(), postpone_record(), discard_record()])}ActionState::SelectingChannel(state) => match state {SwitchingChannelState::NoOtherChannels => row([cancel()]),SwitchingChannelState::NothingSelected => {row([down(), up(), cancel()])}SwitchingChannelState::SomethingSelected { can_switch } => {let row = row([down(), up(), cancel()]);let row = add_if(!can_switch,|| cant_confirm("cannot switch with unrecorded changes"),row,);let row = add_if(can_switch, || confirm("switch channel"), row);row}},ActionState::ForkingChannel { empty, unique } => {let row = row([]);let row = add_if(!empty && unique, || confirm("confirm fork"), row);let row =add_if(!unique, || cant_confirm("channel already exists"), row);let row = row.push(cancel());row}ActionState::EntireLog { can_select_right } => {let row = row([down(), up()]);let row = add_if(can_select_right, right, row);row}ActionState::EntireLogChange { can_select_right } => {let row = row([left(), down(), up()]);let row = add_if(can_select_right, right, row);row}ActionState::EntireLogChangeDiff => {let row = row([left(), down(), up(), down_no_skip(), up_no_skip()]);row}};let row = add_if(!forking_channel, fork_channel, row);let row = row.push(refresh_repo());el(row.spacing(2).wrap())}fn add_if<'a, F>(predicate: bool,button: F,row: row::Row<'a, Msg, Theme>,) -> row::Row<'a, Msg, Theme>whereF: Fn() -> Element<'a, Msg, Theme>,{if predicate {row.push(button())} else {row}}enum ActionState {/// Main statusMain {selection: MainSelection,can_select_right: bool,can_record: bool,has_other_channels: bool,},/// Selected a log change, but not diffLogChange {can_select_right: bool,can_record: bool,has_other_channels: bool,},/// Untracked, changed or status log change's diffDiff {can_record: bool,has_other_channels: bool,},/// Making a record from current changesRecording,SelectingChannel(SwitchingChannelState),ForkingChannel {empty: bool,unique: bool,},/// Viewing entire logEntireLog {can_select_right: bool,},/// Viewing entire log with some change selectedEntireLogChange {can_select_right: bool,},/// Viewing entire log with some diff of a change selectedEntireLogChangeDiff,
enum MainSelection {Untracked,AddedFromUntracked,Other,}enum SwitchingChannelState {NoOtherChannels,NothingSelected,SomethingSelected { can_switch: bool },}/// Determine state for actions menufn action_state(state: &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;
match record_msg {Some(RecordMsg::Typing(_)) => return ActionState::Recording,Some(RecordMsg::Canceled { .. }) | None => {}}if let Some(name) = forking_channel_name.as_ref() {let name = name.trim();let empty = name.is_empty();let unique =channel != name && !other_channels.iter().any(|n| n == name);return ActionState::ForkingChannel { empty, unique };}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,}}} else {ActionState::Main {selection: MainSelection::Untracked,can_select_right: false,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,}}}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,},}}None => ActionState::Main {selection: MainSelection::Other,can_select_right: false,can_record,has_other_channels,},}}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};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);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,}}}None => ActionState::EntireLog {can_select_right: nav.map(nav_scrollable::needs_scrolling).unwrap_or_default(),},}
fn view_actions<'a>(allowed_actions: &[action::Binding],) -> Element<'a, Msg, Theme> {let buttons: Vec<_> = allowed_actions.iter().map(|action::Binding { key, label, msg }| {if let Some(msg) = msg {action_button(key, label, Msg::Action(msg.clone()))
use crate::{self as model, get_entire_log_diffs_nav, get_entire_log_files_nav,get_files_diffs_nav, get_status_log_diffs_nav, get_status_log_files_nav,selection, ReadyState, RecordMsg,};use iced_expl_widget::nav_scrollable;use libflorescence::{file, repo};#[derive(Debug)]pub struct Binding {pub key: &'static str,pub label: &'static str,pub msg: Option<Msg>,}/// Msgs that are bound to some key(s) and that are only allowed depending on the current state.#[derive(Debug, Clone)]pub enum Msg {Confirm,Cancel,Selection(selection::Msg),PostponeRecord,SaveRecord,DiscardRecord,AddUntrackedFile,RmAddedFile,StartRecord,/// Show a list of channels to switch toSelectChannel,ForkChannel,RefreshRepo,ShowEntireLog,}/// Eq used for action filters. Properties that are not determined by which actions are allowed are ignored.pub fn is_same_msg(left: &Msg, right: &Msg) -> bool {use Msg::*;match (left, right) {(Confirm, Confirm) => true,(Cancel, Cancel) => true,(Selection(left), Selection(right)) => left == right,(PostponeRecord, PostponeRecord) => true,(SaveRecord, SaveRecord) => true,(DiscardRecord, DiscardRecord) => true,(AddUntrackedFile, AddUntrackedFile) => true,(RmAddedFile, RmAddedFile) => true,(StartRecord, StartRecord) => true,(SelectChannel, SelectChannel) => true,(ForkChannel, ForkChannel) => true,(RefreshRepo, RefreshRepo) => true,(ShowEntireLog, ShowEntireLog) => true,(Confirm, _) => false,(Cancel, _) => false,(Selection(_), _) => false,(PostponeRecord, _) => false,(SaveRecord, _) => false,(DiscardRecord, _) => false,(AddUntrackedFile, _) => false,(RmAddedFile, _) => false,(StartRecord, _) => false,(SelectChannel, _) => false,(ForkChannel, _) => false,(RefreshRepo, _) => false,(ShowEntireLog, _) => false,}}pub fn get_allowed(state: &model::SubState) -> Vec<Binding> {match state {model::SubState::Loading { .. } => vec![],model::SubState::SelectingId { .. } => todo!(),model::SubState::Ready(ready_state) => get_ready_allowed(ready_state),}}fn get_ready_allowed(state: &ReadyState) -> Vec<Binding> {let State {has_other_channels,sub,} = derive_state(state);let left = || Binding {key: "←| h",label: "",msg: Some(Msg::Selection(selection::Msg::PressDir(selection::Dir::Left,))),};let down = || Binding {key: "↓| j",label: "",msg: Some(Msg::Selection(selection::Msg::PressDir(selection::Dir::Down,))),};let up = || Binding {key: "↑| k",label: "",msg: Some(Msg::Selection(selection::Msg::PressDir(selection::Dir::Up))),};let right = || Binding {key: "→| l",label: "",msg: Some(Msg::Selection(selection::Msg::PressDir(selection::Dir::Right,))),};let down_no_skip = || Binding {key: "S-(↓| j)",label: "no skip",msg: Some(Msg::Selection(selection::Msg::AltPressDir(selection::Dir::Down,))),};let up_no_skip = || Binding {key: "S-(↑| k)",label: "no skip",msg: Some(Msg::Selection(selection::Msg::AltPressDir(selection::Dir::Up,))),};let add_untracked = || Binding {key: "a",label: "track file",msg: Some(Msg::AddUntrackedFile),};let rm_added_file = || Binding {key: "x",label: "untrack file",msg: Some(Msg::RmAddedFile),};let start_record = || Binding {key: "r",label: "record",msg: Some(Msg::StartRecord),};let save_record = || Binding {key: "C-s",label: "save record",msg: Some(Msg::SaveRecord),};let postpone_record = || Binding {key: "C-d",label: "postpone record",msg: Some(Msg::PostponeRecord),};let discard_record = || Binding {key: "C-d",label: "discard record",msg: Some(Msg::DiscardRecord),};let select_channel = || Binding {key: "c",label: "select channel",msg: Some(Msg::SelectChannel),};let fork_channel = || Binding {key: "f",label: "fork channel",msg: Some(Msg::ForkChannel),};let refresh_repo = || Binding {key: "C-r",label: "refresh repo",msg: Some(Msg::RefreshRepo),};let confirm = |label: &'static str| Binding {key: "Enter",label,msg: Some(Msg::Confirm),};let cant_confirm = |label: &'static str| Binding {key: "Enter",label,msg: None,};let cancel = || Binding {key: "Esc",label: "cancel",msg: Some(Msg::Cancel),};let exit_entire_log = || Binding {key: "Esc",label: "exit entire log",msg: Some(Msg::Cancel),};let show_entire_log = || Binding {key: "e",label: "entire log",msg: Some(Msg::ShowEntireLog),};let forking_channel = matches!(sub, SubState::ForkingChannel { .. });let mut actions = vec![];let ma = &mut actions;match sub {SubState::Main {selection,can_select_right,can_record,} => {ma.push(down());ma.push(up());match selection {MainSelection::Untracked => {ma.push(add_untracked());push_if(can_select_right, right, ma);push_if(can_record, start_record, ma)}MainSelection::AddedFromUntracked => {ma.push(rm_added_file());push_if(can_select_right, right, ma);push_if(can_record, start_record, ma)}MainSelection::Other => {push_if(can_select_right, right, ma);push_if(can_record, start_record, ma)}};ma.push(show_entire_log());push_if(has_other_channels, select_channel, ma);}SubState::StatusLogChange {can_select_right,can_record,} => {ma.push(left());ma.push(down());ma.push(up());push_if(can_select_right, right, ma);push_if(can_record, start_record, ma);ma.push(show_entire_log());push_if(has_other_channels, select_channel, ma);}SubState::StatusLogDiff { can_record } => {ma.push(left());ma.push(down());ma.push(up());ma.push(down_no_skip());ma.push(up_no_skip());push_if(can_record, start_record, ma);ma.push(show_entire_log());push_if(has_other_channels, select_channel, ma);}SubState::Recording => {ma.push(save_record());ma.push(postpone_record());ma.push(discard_record());}SubState::SelectingChannel(state) => {match state {SwitchingChannelState::NoOtherChannels => ma.push(cancel()),SwitchingChannelState::NothingSelected => {ma.push(down());ma.push(up());ma.push(cancel());}SwitchingChannelState::SomethingSelected { can_switch } => {ma.push(down());ma.push(up());ma.push(cancel());push_if(!can_switch,|| {cant_confirm("cannot switch with unrecorded changes",)},ma,);push_if(can_switch, || confirm("switch channel"), ma);}};ma.push(show_entire_log());}SubState::ForkingChannel { empty, unique } => {push_if(!empty && unique, || confirm("confirm fork"), ma);push_if(!unique, || cant_confirm("channel already exists"), ma);ma.push(cancel());}SubState::EntireLog { can_select_right } => {ma.push(down());ma.push(up());push_if(can_select_right, right, ma);ma.push(exit_entire_log());push_if(has_other_channels, select_channel, ma);}SubState::EntireLogChange { can_select_right } => {ma.push(left());ma.push(down());ma.push(up());push_if(can_select_right, right, ma);ma.push(exit_entire_log());push_if(has_other_channels, select_channel, ma);}SubState::EntireLogChangeDiff => {ma.push(left());ma.push(down());ma.push(up());ma.push(down_no_skip());ma.push(up_no_skip());ma.push(exit_entire_log());}};push_if(!forking_channel, fork_channel, ma);ma.push(refresh_repo());actions}fn push_if<'a, F>(predicate: bool, to_add: F, actions: &mut Vec<Binding>)whereF: Fn() -> Binding,{if predicate {actions.push(to_add())}}#[derive(Debug, Clone, Copy)]struct State {has_other_channels: bool,sub: SubState,}#[derive(Debug, Clone, Copy)]enum SubState {/// Main statusMain {selection: MainSelection,can_select_right: bool,can_record: bool,},/// Selected a log change, but not diffStatusLogChange {can_select_right: bool,can_record: bool,},/// Untracked, changed or status log change's diffStatusLogDiff {can_record: bool,},/// Making a record from current changesRecording,SelectingChannel(SwitchingChannelState),ForkingChannel {empty: bool,unique: bool,},/// Viewing entire logEntireLog {can_select_right: bool,},/// Viewing entire log with some change selectedEntireLogChange {can_select_right: bool,},/// Viewing entire log with some diff of a change selectedEntireLogChangeDiff,}#[derive(Debug, Clone, Copy)]enum MainSelection {Untracked,AddedFromUntracked,Other,}#[derive(Debug, Clone, Copy)]enum SwitchingChannelState {NoOtherChannels,NothingSelected,SomethingSelected { can_switch: bool },}/// Determine state for actions menufn derive_state(state: &ReadyState) -> State {let has_other_channels = !state.repo.other_channels.is_empty();let sub = derive_sub_state(state);State {has_other_channels,sub,}}/// Determine state for actions menufn derive_sub_state(state: &ReadyState) -> SubState {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;match record_msg {Some(RecordMsg::Typing(_)) => return SubState::Recording,Some(RecordMsg::Canceled { .. }) | None => {if let Some(name) = forking_channel_name.as_ref() {let name = name.trim();let empty = name.is_empty();let unique = channel != name&& !other_channels.iter().any(|n| n == name);return SubState::ForkingChannel { empty, unique };}}}match primary {selection::Primary::Status => {let can_record = !changed_files.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 {SubState::StatusLogDiff { can_record }} else {SubState::Main {selection: MainSelection::Untracked,can_select_right:nav_scrollable::needs_scrolling(nav),can_record,}}} else {SubState::Main {selection: MainSelection::Untracked,can_select_right: false,can_record,}}}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 {SubState::StatusLogDiff { can_record }} else {SubState::Main {selection: main_selection(),can_select_right:nav_scrollable::needs_scrolling(nav),can_record,}}} else {SubState::Main {selection: main_selection(),can_select_right: false,can_record,}}}Some(selection::Status::LogChange(selection::LogChange {ix: _,hash,message: _,file,})) => match file {Some(selection::LogChangeFileSelection {ix: _,path,diff_selected,}) => {let log_id = file::log_id_parts_hash(*hash, path);let nav = get_status_log_diffs_nav(navigation, log_id);if let Some(nav) = nav {if *diff_selected {SubState::StatusLogDiff { can_record }} else {SubState::StatusLogChange {can_record,can_select_right:nav_scrollable::needs_scrolling(nav),}}} else {SubState::StatusLogChange {can_record,can_select_right: false,}}}None => {let nav = get_status_log_files_nav(navigation, *hash);SubState::Main {selection: MainSelection::Other,can_select_right: nav.is_some(),can_record,}}},None => SubState::Main {selection: MainSelection::Other,can_select_right: false,can_record,},}}selection::Primary::Channel => {let sub_state = if other_channels.is_empty() {SwitchingChannelState::NoOtherChannels} else if channel_selection.is_some() {let can_switch = changed_files.is_empty();SwitchingChannelState::SomethingSelected { can_switch }} else {SwitchingChannelState::NothingSelected};SubState::SelectingChannel(sub_state)}selection::Primary::EntireLog => {if let Some(selection::LogChange { hash, file, .. }) =entire_log_selection{match file {Some(selection::LogChangeFileSelection {ix: _,path,diff_selected,}) => {let log_id = file::log_id_parts_hash(*hash, path);let nav = get_entire_log_diffs_nav(navigation, log_id);if let Some(nav) = nav {if *diff_selected {SubState::EntireLogChangeDiff} else {SubState::EntireLogChange {can_select_right:nav_scrollable::needs_scrolling(nav),}}} else {SubState::EntireLogChange {can_select_right: false,}}}None => {let nav = get_entire_log_files_nav(navigation, *hash);SubState::EntireLog {can_select_right: nav.is_some(),}}}} else {SubState::EntireLog {can_select_right: false,}}}}}
fn update_from_app_view(state: &mut State, msg: app::Msg) -> Task<Msg> {
fn update_from_app(state: &mut State, msg: app::Msg) -> Task<Msg> {// Return early if the action is not allowed in the current stateif let app::Msg::Action(msg) = &msg&& let Some(_ready_state) = model::ready(&mut state.sub){if !state.sub.allowed_actions.iter().any(|action::Binding {msg: allowed_msg, ..}| {if let Some(allowed_msg) = allowed_msg {action::is_same_msg(msg, allowed_msg)} else {false}},) {return Task::none();}}
app::Msg::Confirm => {
app::Msg::Action(msg) => update_from_action(state, msg),app::Msg::EditRecordMsg(action) => edit_record_msg(state, action),app::Msg::EditForkChannelName(name) => {if let Some(ReadyState {forking_channel_name,..}) = model::ready_mut(&mut state.sub){*forking_channel_name = Some(name);}Task::none()}}}fn update_from_action(state: &mut State, msg: action::Msg) -> Task<Msg> {match msg {action::Msg::Confirm => {
app::Msg::ToRepo(msg) => {state.repo_tx_in.send(msg).unwrap();Task::none()}app::Msg::EditRecordMsg(action) => edit_record_msg(state, action),app::Msg::SaveRecord => save_record(state),app::Msg::PostponeRecord => defer_record(state),app::Msg::DiscardRecord => abandon_record(state),app::Msg::AddUntrackedFile => add_untracked_file(state),app::Msg::RmAddedFile => rm_added_file(state),app::Msg::StartRecord => start_record(state),app::Msg::SelectChannel => {if let Some(ReadyState { selection, .. }) =model::ready_mut(&mut state.sub)
action::Msg::SaveRecord => save_record(state),action::Msg::PostponeRecord => defer_record(state),action::Msg::DiscardRecord => abandon_record(state),action::Msg::AddUntrackedFile => add_untracked_file(state),action::Msg::RmAddedFile => rm_added_file(state),action::Msg::StartRecord => start_record(state),action::Msg::SelectChannel => {if let Some(ReadyState {selection, repo, ..}) = model::ready_mut(&mut state.sub)&& !repo.other_channels.is_empty()
app::Msg::ForkChannelName(name) => {if let Some(ReadyState {forking_channel_name,..}) = model::ready_mut(&mut state.sub){*forking_channel_name = Some(name);}Task::none()}app::Msg::RefreshRepo => {
action::Msg::RefreshRepo => {
MSubState::Loading { user_ids: _, repo } => *repo = Some(repo_state),MSubState::SelectingId { repo, .. } => *repo = Some(repo_state),MSubState::Ready(ReadyState {
model::SubState::Loading { user_ids: _, repo } => {*repo = Some(repo_state)}model::SubState::SelectingId { repo, .. } => *repo = Some(repo_state),model::SubState::Ready(ReadyState {
"j" => Some(Msg::View(app::Msg::Selection(selection::Msg::PressDir(selection::Dir::Down),))),"k" => Some(Msg::View(app::Msg::Selection(selection::Msg::PressDir(selection::Dir::Up),))),"h" => Some(Msg::View(app::Msg::Selection(selection::Msg::PressDir(selection::Dir::Left),))),"l" => Some(Msg::View(app::Msg::Selection(selection::Msg::PressDir(selection::Dir::Right),))),
"j" => selection(selection::Msg::PressDir(selection::Dir::Down,)),"k" => {selection(selection::Msg::PressDir(selection::Dir::Up))}"h" => selection(selection::Msg::PressDir(selection::Dir::Left,)),"l" => selection(selection::Msg::PressDir(selection::Dir::Right,)),
"a" => Some(Msg::View(app::Msg::AddUntrackedFile)),"c" => Some(Msg::View(app::Msg::SelectChannel)),"e" => Some(Msg::View(app::Msg::ShowEntireLog)),"f" => Some(Msg::View(app::Msg::ForkChannel)),"r" => Some(Msg::View(app::Msg::StartRecord)),"x" => Some(Msg::View(app::Msg::RmAddedFile)),
"a" => action(action::Msg::AddUntrackedFile),"c" => action(action::Msg::SelectChannel),"e" => action(action::Msg::ShowEntireLog),"f" => action(action::Msg::ForkChannel),"r" => action(action::Msg::StartRecord),"x" => action(action::Msg::RmAddedFile),
Key::Named(key::Named::Enter) => {Some(Msg::View(app::Msg::Confirm))}Key::Named(key::Named::Escape) => {Some(Msg::View(app::Msg::Cancel))}
Key::Named(key::Named::Enter) => action(action::Msg::Confirm),Key::Named(key::Named::Escape) => action(action::Msg::Cancel),
Some(Msg::View(app::Msg::RefreshRepo))}"j" if mods == Modifiers::SHIFT => {Some(Msg::View(app::Msg::Selection(selection::Msg::AltPressDir(selection::Dir::Down),)))}"k" if mods == Modifiers::SHIFT => {Some(Msg::View(app::Msg::Selection(selection::Msg::AltPressDir(selection::Dir::Up),)))
action(action::Msg::RefreshRepo)
"h" if mods == Modifiers::SHIFT => {Some(Msg::View(app::Msg::Selection(selection::Msg::AltPressDir(selection::Dir::Left),)))}"l" if mods == Modifiers::SHIFT => {Some(Msg::View(app::Msg::Selection(selection::Msg::AltPressDir(selection::Dir::Right),)))}
"j" if mods == Modifiers::SHIFT => selection(selection::Msg::AltPressDir(selection::Dir::Down),),"k" if mods == Modifiers::SHIFT => selection(selection::Msg::AltPressDir(selection::Dir::Up),),"h" if mods == Modifiers::SHIFT => selection(selection::Msg::AltPressDir(selection::Dir::Left),),"l" if mods == Modifiers::SHIFT => selection(selection::Msg::AltPressDir(selection::Dir::Right),),