KWTBNTO3QUUE2YADF6SYW6G6ZOKYEWRJQKIWDGZXR33S3YNDVIZQC 6YZAVBWU6E5FYOI5JGEIPXGZLIKAW6LS2AOFIQWEE5DMOPPCD5PQC IQDCHWCP47LL46EXQLQGHQPGFYIHQLMQBHA57RWJCIOX5UEUIQAQC SWWE2R6MVBX5CNM6X3WLXZTSRTU53PBJL7WJSFVF77XBPXDX4COAC WT3GA27PQ2AOAIGK65O3Q4DMX4AZDVNULBLRL6GF4QW6QCASUEAAC UB2ITZJSDADVINSQEZ3HA6PVGA7OA6JYFG5GMSO7Y7LOXJC4FI7AC KT5UYXGKEEXUHURNOYFVIG7WQ3Y3SJZMM2TP4OSW6NXSXQ5XXRHAC ELG3UDT6OJFEYSJR7HZEC65IUWBMGPPPCXEW3CDW5T74R6KC5LIAC S2NVIFXRFER4SRA37WCT5XTXHDHAL5WIGGKY4A4XOTPLTKTZSRGQC W7IUT3ZVMFH77IGKLAL7WX7IVVTGTY3FKEJ3WHMP3KI37B6NENLQC YBJRDOTCX3ZRDB5EVXJBR55FX3CADCSIGMYWNYVC2PD5W3GXR3DQC A5YBC77VWH2LXCZJOPZORQJI5ZYABSCHJWVX5HVNWPM5RABXESLQC D7A7MSIHJS3IAOLEPK52M4CZLDPLO7JB3Y62XACT2AM6UUCPQ6BAC 4WO3ZJM2RNYZCBPS7FGYAEBELYD57OSS7LEUYCWGZBCAY272SNQQC BJXUYQ2YQMVULJITT5FEA6NERJVLWFKEAWSBYZVIB7KAT27KOWBAC W4LFX7IHQ7SDX67ATSGWDB5IN6472ZJDBKY2XZ54SBJEYD5GAT5QC AMPZ2BXK4IGUZO3OPBRSJ6Z4GI5K4PRFMLUGTR6AP4FKKRWQG7LQC FDDPOH5RRFK323PLIFJM2K2NWTSE2LW525YMHWJA6KIFPY2QVQNQC V55EAIWQXWER2HWKZHPJBV7DDJMSPSPWSO3FSSAYODJHVDBHUN6QC ZVI4AWERNOTDJ3765HJXRBZT57XPNKVONQ6TGOGNPOL2VN42KMJQC OQ6HSAWHIRTAIIWMDGCTIOK47JDY7QVVAHLRDA2R5TTJKNSBFCWQC WI2BVQ6JOJBM4OC5KSZBMTDPBWESIR7GD72B5TLO7H2SY7QBDHJAC DCSUCH6RRRQU4TQYO3K3HRC7SXAIBYP5R3ZOWAWS2LOXWNHEJM6AC JE44NYHM4QORCRKOF33QM42EDT7SBCPTULWGT6IVDL3D5LUHQXLAC ONRCENKTUB4JJMPXNAQQYEWDYD54TAGOLWH742GF4EH3KTHV7YLQC 4ELJZGRJNL6FXB33QTYDNPY57JA3WZPUXKLQRTGSLDM7W65PD3YQC FR52XEMWD22VH3GKSARXJUJXOGO7ZSQEHWPXFRWHLGRAJU3WRKCAC L6KSEFQIWICZJ6HJUFKLZQDEH6X2QMFM4Z7ZZUGMLDMFF7EHRXWAC BFN2VHZS7VCBUHQ4S3CQ3LFQV2V4M6VANNAF32XMRFQVWRGYSZ6AC VJNWIGSX5OIDOI27UYU7JAXM4X3KSPEQ37P5UXBLH5KHF3VBTZFQC GWZGYNIBQP2AA7WYULNBS2BCV4B36IHK4OS7XHVOTUUG27E76XFQC 23SFYK4Q5NKBPJG53PQNPWQH6UOUU2YKJEL7RLXYBRLJOJYV7AWQC MYGIBRRHHXPKVRAMQQRJTZH74L2XOK3SF7J57JPCRKSVRLZ2D6NQC PKJCFSBMXXA2H3US47IJEB7QMIYLEKTLGWQUYEZSKCDODDQTD6HQC XSZZB47UXR6KGYFZZQFQR63X2LDKOH6TPNNBRRGHUCI5JJ4JIWVAC XIASAP3GXH7YUHGWSFFLJQMLOM5C6GMI3AZHGTT2CFGPNV2QU5BAC 3BK22XE5LPOH2EK5AMRXFXHNQNCJ54HEPYRINHJT4DA7INT32I7AC WGID4LS4EISIOXB5Y5SOFGEF5PLBJSCPFCETH2CGRTFN3NC4WGJQC VCNKFNUF7OWVSWC6I5D25KUZ3XZZICZ3LHWVPF2N5ZSP7LQ2JOUQC ACDXXAX26ZJJFKJDGRC2GOSJY5JHQWCSTP55SYI6D6LH5UIRYUBAC I56UGW7UUKLSR4753EYRGNROZB5PD522REEOGHVAQOZZTSVRUEEQC X6AK4QPXKTGTWIMJ5CIR46CVIXVUXV5WKTP73CNQOIRANQN4MD5QC 5CYU7UT74NXJWCC36GNQGVBXH676BHBXWZQVIINRMPDEJ27SBRGAC ESMM3FELOBYIX7FUNOU37FYKRJHFU2IMX6LY6EGJTVPTBDU3SEEQC TSFQFCB2NXDOBLBRUSAT63VJIXLPPTJGSTIDNOTLGHVVWSHITRNQC UF5NJKASGMZSZMBUKSUI67B2GIMQFX5SNNQEHHGUBNDBQ2QZZWSAC 7SSBM4UQMYVRL6L3ICYZQPSMYLZZQNMDWH6JKA3KOOSXZDJHESHQC S2T7RUKWXAHMOW5HHITQKTKCBKTUKGMRXK7EQI6RNEBBHRJ5W2RQC I2AG42PAVOII4V4TWDJV5ZVNDIHKBRDT254BFQLFUIY723TW6CCQC 4PNWU55OLKQGTREOE7P27SGX4HPQO3NMHBABN27WQMCQADBGJJWQC WW36JYLR4AILV7RHQEDJWMX74P74B7G7DRBHH3O2V5TCHRTZJWZQC WIFVLV376GIMVTGVXBFWIPU7FR5O3SGIQ343KIJEBWB6UURTJZJQC SASAN2XCWDQ2VEHZ7TAQEN2R3Y7AG7JUGEFVRL4DZAGHXDFEZFRQC YKHE3XMWOWPGOWYSISF73MIAKN7WB3AHCV2OA4ECAFPF47YHUXEAC KEPKF3WO7ZZ2VB2DRVVTWTGPL7TCA52BMYUPHUNUJH6WO3HAT6JQC GOLHUD6RCYCO2SPULCFGWIW3ALBDODNEMUHVAKCDWYQREKWGFTLQC XHWLKCLDFUQFFHLFLFDC6WHK6RXRPQSXJG5AKGPER7R5AHVCRHUAC K5YUSV2WOLGMA75WKQWY2GRLQGPAFGVYTW3GMVTWEECXF4SXFEYAC OPXFZKEBDHZZLXEJ2JRDYBOJH6YIN7UZNZYHVHMWMQVDTE2ZD53QC MJDGPSHGF62FTVWZBE7MFNJTUQD42OBVJEOSVPBT553UFJLTEMXQC #[doc(inline)]pub use std::hash::DefaultHasher;pub type AHash = u64;pub fn hasher() -> DefaultHasher {DefaultHasher::new()}pub fn hash_one<T>(x: T) -> u64whereT: std::hash::Hash,{let mut hasher = hasher();x.hash(&mut hasher);std::hash::Hasher::finish(&hasher)}pub type NoHashMap<K, V> = std::collections::HashMap<K, V, BuildNoHashHasher>;pub type BuildNoHashHasher = std::hash::BuildHasherDefault<NoHashHasher>;#[derive(Debug, Default)]pub struct NoHashHasher(u64);impl std::hash::Hasher for NoHashHasher {fn write(&mut self, _: &[u8]) {unimplemented!("Invalid use of NoHashHasher")}fn write_u8(&mut self, n: u8) {self.0 = u64::from(n)}fn write_u16(&mut self, n: u16) {self.0 = u64::from(n)}fn write_u32(&mut self, n: u32) {self.0 = u64::from(n)}fn write_u64(&mut self, n: u64) {self.0 = n}fn write_usize(&mut self, n: usize) {self.0 = n as u64}fn write_i8(&mut self, n: i8) {self.0 = n as u64}fn write_i16(&mut self, n: i16) {self.0 = n as u64}fn write_i32(&mut self, n: i32) {self.0 = n as u64}fn write_i64(&mut self, n: i64) {self.0 = n as u64}fn write_isize(&mut self, n: isize) {self.0 = n as u64}fn finish(&self) -> u64 {self.0}}
}#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]pub struct IdHash(pub AHash);pub type IdMap<V> = NoHashMap<IdHash, V>;#[derive(Debug, Clone, Hash, PartialEq, Eq)]pub struct LogId {/// Hash of the log changepub hash: repo::ChangeHash,pub path: String,}#[derive(Debug, Clone, Hash, PartialEq, Eq)]pub struct LogIdRef<'a> {pub hash: repo::ChangeHash,pub path: &'a str,
}pub fn id_hash(id: &Id) -> IdHash {IdHash(hash_one(id))}pub fn id_ref_hash(id: &IdRef<'_>) -> IdHash {IdHash(hash_one(id))}pub fn id_parts_hash(path: &str, file_kind: Kind) -> IdHash {id_ref_hash(&IdRef { path, file_kind })}pub fn log_id_hash(id: &LogId) -> LogIdHash {LogIdHash(hash_one(id))}pub fn log_id_ref_hash(id: &LogIdRef<'_>) -> LogIdHash {LogIdHash(hash_one(id))}pub fn log_id_parts_hash(hash: repo::ChangeHash, path: &str) -> LogIdHash {log_id_ref_hash(&LogIdRef { path, hash })}#[cfg(test)]mod test {use super::*;#[test]fn id_hash_eq_id_ref_hash() {let path = "some random path";let file_kind = Kind::Untracked;assert_eq!(id_ref_hash(&IdRef { path, file_kind }),id_hash(&Id {path: path.to_string(),file_kind}));assert_eq!(id_ref_hash(&IdRef { path, file_kind }),id_parts_hash(path, file_kind),);assert_ne!(id_ref_hash(&IdRef { path, file_kind }),id_hash(&Id {path: path.to_string(),file_kind: Kind::Changed}));}#[test]fn log_id_hash_eq_log_id_ref_hash() {let path = "some change file path";let hash = repo::hash_bytes(&[0, 1, 2]);assert_eq!(log_id_ref_hash(&LogIdRef { path, hash }),log_id_hash(&LogId {path: path.to_string(),hash}));assert_eq!(log_id_ref_hash(&LogIdRef { path, hash }),log_id_parts_hash(hash, path),);assert_ne!(log_id_ref_hash(&LogIdRef { path, hash }),log_id_hash(&LogId {path: path.to_string(),hash: repo::hash_bytes(&[255])}));}
el(iced_nav_scrollable::view(nav,diffs,diffs_len,Msg::NavScrollable,))
el(iced_nav_scrollable::view(nav,diffs,diffs_len,Msg::NavScrollable,).class(if diff_selected {theme::Scrollable::Selected} else {theme::Scrollable::Normal}),)
Some(cursor::Selection::UntrackedFile { ix: _, path }) => {let id = file::Id {path: path.clone(),file_kind: file::Kind::Untracked,};
Some(cursor::Selection::UntrackedFile {ix: _,path,diff_selected,}) => {let id_hash = file::id_parts_hash(path, file::Kind::Untracked);
Some((file, state)) => {diff::view(state, file).map(move |msg| {Msg::FileDiffsContentsAction {id: id.clone(),action: msg,}})}
Some((file, state)) => diff::view(state, file, *diff_selected).map(move |msg| Msg::FileDiffsContentsAction {id_hash,action: msg,}),
Some(cursor::Selection::ChangedFile { path, ix: _ }) => {let id = file::Id {path: path.clone(),file_kind: file::Kind::Changed,};
Some(cursor::Selection::ChangedFile {path,ix: _,diff_selected,}) => {let id_hash = file::id_parts_hash(path, file::Kind::Changed);
Some((file, state)) => {diff::view(state, file).map(move |msg| {Msg::FileDiffsContentsAction {id: id.clone(),action: msg,}})}
Some((file, state)) => diff::view(state, file, *diff_selected).map(move |msg| Msg::FileDiffsContentsAction {id_hash,action: msg,}),
cursor::Selection::UntrackedFile { ix, path }if *ix == 1 && path == "untracked_1.rs"
cursor::Selection::UntrackedFile { ix, path, diff_selected }if *ix == 1 && path == "untracked_1.rs" && !diff_selected
cursor::Selection::ChangedFile { ix, path }if *ix == 0 && path == "changed_0.rs"
cursor::Selection::ChangedFile { ix, path, diff_selected }if *ix == 0 && path == "changed_0.rs" && !diff_selected
cursor::Selection::ChangedFile { ix, path }if *ix == 1 && path == "changed_1.rs"
cursor::Selection::ChangedFile { ix, path, diff_selected }if *ix == 1 && path == "changed_1.rs" && !diff_selected
#[derive(Debug)]struct LogFileDiff {pub diff: diff::File,pub state: diff::State,}
Msg::LogChangeDiff { hash, file, msg } => {if let Some(cursor::Selection::LogChange {ix: _,hash: selection_hash,message: _,file: Some(cursor::LogChangeFileSelection { ix: _, path }),}) = state.cursor.selection.as_mut()
Msg::LogDiffNav { id_hash: id, msg } => {if let Some(diff::FileAndState { file: _, state }) =state.logs.diffs.get_mut(&id)
if selection_hash == &hash {let id = repo::LogFileId {hash,path: path.clone(),};if let Some(LogFileDiff { diff: _, state }) =state.log_diffs.get_mut(&id){let task = diff::update(state, msg);return task.map(move |msg| Msg::LogChangeDiff {hash,file: file.clone(),msg,});}}
let task = diff::update(state, msg);return task.map(move |msg| Msg::LogDiffNav {id_hash: id.clone(),msg,});
// If the selected file's diff is already loaded, scroll back to its// last offsetlet scroll_task = match state.cursor.selection.as_ref() {Some(cursor::Selection::UntrackedFile { ix: _, path }) => {let id = file::Id {path: path.clone(),file_kind: file::Kind::Untracked,};if let Some(nav) = state.files_diffs.get(&id).and_then(|state| state.nav.as_ref()){task::scroll_to(nav.id.clone(),scrollable::AbsoluteOffset {x: 0.0,y: nav.offset,},)} else {Task::none()}}Some(cursor::Selection::ChangedFile { ix: _, path }) => {let id = file::Id {path: path.clone(),file_kind: file::Kind::Changed,};if let Some(nav) = state.files_diffs.get(&id).and_then(|state| state.nav.as_ref()){task::scroll_to(nav.id.clone(),scrollable::AbsoluteOffset {x: 0.0,y: nav.offset,},)} else {Task::none()}}Some(cursor::Selection::LogChange {
let init_log_nav_task =if let Some(cursor::Selection::LogChange {
file: Some(cursor::LogChangeFileSelection { ix: _, path }),}) => {let id = repo::LogFileId {hash: *hash,path: path.clone(),};if let Some(nav) = state.log_diffs.get(&id).and_then(|diff| diff.state.nav.as_ref()){task::scroll_to(nav.id.clone(),scrollable::AbsoluteOffset {x: 0.0,y: nav.offset,},)} else {Task::none()
file:Some(cursor::LogChangeFileSelection {ix: _,path,diff_selected: false,}),}) = state.cursor.selection.as_ref(){let id_hash = file::log_id_parts_hash(*hash, path);match state.logs.diffs.get_mut(&id_hash) {Some(log) if log.state.nav.is_none() => {let contents_count =diff::contents_count(&log.file);let unchanged_sections =diff::unchanged_sections(&log.file);let (nav, tasks) = iced_nav_scrollable::init(contents_count,unchanged_sections,);log.state.nav = Some(nav);tasks.map(move |msg| Msg::LogDiffNav {id_hash,msg: diff::Msg::NavScrollable(msg),})}_ => Task::none(),
} else {Task::none()};let get_diffs_task = if let Some(cursor::Selection::LogChange {ix: _,hash,message: _,file: None,}) = state.cursor.selection.as_ref(){if !state.logs.change_hashes.contains(hash) {Task::done(Msg::View(app::Msg::ToRepo(repo::MsgIn::GetChangeDiffs { hash: *hash },)))} else {Task::none()
match (selection, id.file_kind) {(cursor::Selection::UntrackedFile { ix: _, path },file::Kind::Untracked,) if path == &id.path => {let diffs = state.files_diffs.entry(id.clone()).or_default();return diff::update(diffs, action).map(move |msg| {Msg::DiffStateNav {id: id.clone(),msg,}});
match selection {cursor::Selection::UntrackedFile {ix: _,path,diff_selected: _,} => {let selection_hash =file::id_parts_hash(path, file::Kind::Untracked);if id_hash == selection_hash {let diffs = state.files_diffs.entry(id_hash).or_default();return diff::update(diffs, action).map(move |msg| Msg::DiffNav { id_hash, msg });}
(cursor::Selection::ChangedFile { ix: _, path },file::Kind::Changed,) if path == &id.path => {let diffs = state.files_diffs.entry(id.clone()).or_default();let task = diff::update(diffs, action);return task.map(move |msg| Msg::DiffStateNav {id: id.clone(),msg,});
cursor::Selection::ChangedFile {ix: _,path,diff_selected: _,} => {let selection_hash =file::id_parts_hash(path, file::Kind::Changed);if id_hash == selection_hash {let diffs = state.files_diffs.entry(id_hash).or_default();let task = diff::update(diffs, action);return task.map(move |msg| Msg::DiffNav { id_hash, msg });}
let id = repo::LogFileId {hash,path: file.clone(),};if let Some(LogFileDiff { diff: _, state }) =state.log_diffs.get_mut(&id)
let id_hash = file::log_id_parts_hash(hash, &file);if let Some(diff::FileAndState { file: _, state }) =state.logs.diffs.get_mut(&id_hash)
let file = file.and_then(|file| {entry.file_paths.iter().enumerate().find(|(_ix, path)| *path == &file.path).map(|(ix, path)| {cursor::LogChangeFileSelection {ix,path: path.clone(),}})});
let file = file.and_then(|cursor::LogChangeFileSelection {ix: _,path: selected_path,diff_selected,}| {entry.file_paths.iter().enumerate().find(|(_ix, path)| *path == &selected_path).map(|(ix, path)| {cursor::LogChangeFileSelection {ix,path: path.clone(),diff_selected,}})},);
let contents_count = diff::contents_count(&diff);let unchanged_sections = diff::unchanged_sections(&diff);let (nav, nav_tasks) = iced_nav_scrollable::init(contents_count,unchanged_sections,);let diff_state = diff::State {nav: Some(nav),state: libflorescence::diff::State::default(),
let id_hash = file::log_id_parts_hash(hash, &path);let log_file_diff = diff::FileAndState {file,// The nav is initialized only once a file is selected,// because its tasks need it to be visible to completestate: diff::State::default(),
state.log_diffs.insert(id, log_file_diff);tasks.push(nav_tasks.map(move |msg| Msg::LogChangeDiff {hash,file: path_clone.clone(),msg: diff::Msg::NavScrollable(msg),}))
state.logs.change_hashes.insert(hash);state.logs.diffs.insert(id_hash, log_file_diff);
Subscription::batch([key_subs, window_subs])
let nav_subs = match state.cursor.selection.as_ref() {Some(cursor::Selection::UntrackedFile {ix: _,path,diff_selected: true,}) => {let id_hash = file::id_parts_hash(path, file::Kind::Untracked);iced_nav_scrollable::subs().with(id_hash).map(|(id_hash, msg)| Msg::DiffNav {id_hash,msg: diff::Msg::NavScrollable(msg),})}Some(cursor::Selection::ChangedFile {ix: _,path,diff_selected: true,}) => {let id_hash = file::id_parts_hash(path, file::Kind::Changed);iced_nav_scrollable::subs().with(id_hash).map(|(id_hash, msg)| Msg::DiffNav {id_hash,msg: diff::Msg::NavScrollable(msg),})}Some(cursor::Selection::LogChange {ix: _,hash,message: _,file:Some(cursor::LogChangeFileSelection {ix: _,path,diff_selected: true,}),}) => {let id_hash = file::log_id_parts_hash(*hash, path);iced_nav_scrollable::subs().with(id_hash).map(|(id_hash, msg)| Msg::LogDiffNav {id_hash,msg: diff::Msg::NavScrollable(msg),})}Some(cursor::Selection::UntrackedFile { .. })| Some(cursor::Selection::ChangedFile { .. })| Some(cursor::Selection::LogChange { .. })| None => Subscription::none(),};Subscription::batch([key_subs, window_subs, nav_subs])
Some(cursor::Selection::UntrackedFile { ix: _, path }) => {let id = file::Id {path: path.clone(),file_kind: file::Kind::Untracked,};let file = files.diffs_cache.inner.peek(&id);
Some(cursor::Selection::UntrackedFile {ix: _,path,diff_selected: _,}) => {let id_hash = file::id_parts_hash(path, file::Kind::Untracked);let file = files.diffs_cache.inner.peek(&id_hash);
Some(cursor::Selection::ChangedFile { path, ix: _ }) => {let id = file::Id {path: path.clone(),file_kind: file::Kind::Changed,};let file = files.diffs_cache.inner.peek(&id);
Some(cursor::Selection::ChangedFile {path,ix: _,diff_selected: _,}) => {let id_hash = file::id_parts_hash(path, file::Kind::Changed);let file = files.diffs_cache.inner.peek(&id_hash);
if !state.diffs_cache.inner.contains(&id) {diffs_cache_put(&mut state.diffs_cache, id.clone(), Diff::Loading);
let id_hash = id_hash(&id);if !state.diffs_cache.inner.contains(&id_hash) {diffs_cache_put(&mut state.diffs_cache, id_hash, Diff::Loading);
impl WeightScale<Id, Diff> for DiffsCacheWeight {fn weight(&self, key: &Id, value: &Diff) -> usize {let key_weight = key.path.len();
impl WeightScale<IdHash, Diff> for DiffsCacheWeight {fn weight(&self, _key: &IdHash, value: &Diff) -> usize {const KEY_WEIGHT: usize = mem::size_of::<IdHash>();
pub type FilesState = file::IdMap<State>;#[derive(Debug, Default)]pub struct LogFilesAndState {/// All the hashes in this set have `diffs` loadedpub change_hashes: HashSet<repo::ChangeHash>,/// All the diffs in this map have the change hash present in/// `change_hashes`pub diffs: file::LogIdMap<FileAndState>,}
}pub fn file_diff_needs_scrolling(files_diffs: &FilesState,id: file::IdHash,) -> bool {matches!(files_diffs.get(&id).and_then(|diff| diff.nav.as_ref()).and_then(|nav| nav.ready),Some(iced_nav_scrollable::NeedsScrolling::Yes))}pub fn log_diff_needs_scrolling(logs: &LogFilesAndState,id_hash: file::LogIdHash,) -> bool {matches!(logs.diffs.get(&id_hash).and_then(|diff| diff.state.nav.as_ref()).and_then(|nav| nav.ready),Some(iced_nav_scrollable::NeedsScrolling::Yes))
Msg::Down => {if let Some(repo) = repo.as_ref() {let (selection, task) = match state.selection.take() {Some(Selection::UntrackedFile { ix, path: _ }) => {let (selection, task) =if repo.untracked_files.len().saturating_sub(1)== ix{if !repo.changed_files.is_empty() {let ix = 0;let selection =changed_file_selection(repo, ix, files);(selection, Task::none())} else if !repo.log.is_empty() {let ix = 0;log_selection(repo, ix)} else {let ix = 0;let selection = untracked_file_selection(repo, ix, files,);(selection, Task::none())}} else {let ix = ix + 1;let selection =untracked_file_selection(repo, ix, files);(selection, Task::none())};(Some(selection), task)
Msg::Down => select_down(state, files, repo, files_diffs, log_diffs),Msg::Up => select_up(state, files, repo, files_diffs, log_diffs),Msg::Left => select_left(state, repo),Msg::Right => select_right(state, repo, files_diffs, log_diffs),Msg::Select(select) => {select_exact(select, state, files, repo, files_diffs, log_diffs)}}}pub fn untracked_file_selection(repo: &repo::State,ix: usize,files: &mut file::State,) -> Selection {let path = repo.untracked_files.iter().nth(ix).unwrap().clone();let id = file::Id {path: path.clone(),file_kind: file::Kind::Untracked,};file::load_src_file_if_not_cached(files, id);Selection::UntrackedFile {ix,path,diff_selected: false,}}pub fn changed_file_selection(repo: &repo::State,ix: usize,files: &mut file::State,) -> Selection {let (path, diffs) = repo.changed_files.iter().nth(ix).unwrap();if diff::any_diff_has_contents(diffs) {let id = file::Id {path: path.clone(),file_kind: file::Kind::Changed,};file::load_src_file_if_not_cached(files, id);}Selection::ChangedFile {ix,path: path.clone(),diff_selected: false,}}pub fn log_selection(repo: &repo::State, ix: usize) -> Selection {let entry = repo.log.get(ix).unwrap();Selection::LogChange {ix,hash: entry.hash,message: entry.message.clone(),file: None,}}pub fn log_file_selection(log_entry: &repo::LogEntry,file_ix: usize,) -> LogChangeFileSelection {let path = log_entry.file_paths.get(file_ix).unwrap().clone();LogChangeFileSelection {ix: file_ix,path,diff_selected: false,}}fn select_down<M>(state: &mut State,files: &mut file::State,repo: Option<&repo::State>,files_diffs: &diff::FilesState,logs: &diff::LogFilesAndState,) -> Task<M> {// TODO stairif let Some(repo) = repo.as_ref() {let (selection, task) = match state.selection.take() {Some(Selection::UntrackedFile {ix,path,diff_selected,}) => {let (selection, task) = if diff_selected {let id_hash =file::id_parts_hash(&path, file::Kind::Untracked);(Selection::UntrackedFile {ix,path,diff_selected,},if let Some(nav) = files_diffs.get(&id_hash).and_then(|state| state.nav.as_ref()){iced_nav_scrollable::scroll_down(nav)} else {Task::none()},)} else if repo.untracked_files.len().saturating_sub(1) == ix {if !repo.changed_files.is_empty() {let ix = 0;let selection = changed_file_selection(repo, ix, files);(selection, Task::none())} else if !repo.log.is_empty() {let ix = 0;(log_selection(repo, ix), Task::none())} else {let ix = 0;let selection =untracked_file_selection(repo, ix, files);(selection, Task::none())
Some(Selection::ChangedFile { ix, path: _ }) => {let (selection, task) =if repo.changed_files.len().saturating_sub(1) == ix{if !repo.log.is_empty() {let ix = 0;log_selection(repo, ix)} else if !repo.untracked_files.is_empty() {let ix = 0;let selection = untracked_file_selection(repo, ix, files,);(selection, Task::none())} else {let ix = 0;let selection =changed_file_selection(repo, ix, files);(selection, Task::none())}} else {let ix = ix + 1;let selection =changed_file_selection(repo, ix, files);(selection, Task::none())};(Some(selection), task)
} else {let ix = ix + 1;let selection = untracked_file_selection(repo, ix, files);(selection, Task::none())};(Some(selection), task)}Some(Selection::ChangedFile {ix,path,diff_selected,}) => {let (selection, task) = if diff_selected {let id_hash =file::id_parts_hash(&path, file::Kind::Changed);(Selection::ChangedFile {ix,path,diff_selected,},if let Some(nav) = files_diffs.get(&id_hash).and_then(|state| state.nav.as_ref()){iced_nav_scrollable::scroll_down(nav)} else {Task::none()},)} else if repo.changed_files.len().saturating_sub(1) == ix {if !repo.log.is_empty() {let ix = 0;(log_selection(repo, ix), Task::none())} else if !repo.untracked_files.is_empty() {let ix = 0;let selection =untracked_file_selection(repo, ix, files);(selection, Task::none())} else {let ix = 0;let selection = changed_file_selection(repo, ix, files);(selection, Task::none())
Some(Selection::LogChange {ix: log_ix,hash,message,file,
} else {let ix = ix + 1;let selection = changed_file_selection(repo, ix, files);(selection, Task::none())};(Some(selection), task)}Some(Selection::LogChange {ix: log_ix,hash,message,file,}) => {let (selection, task) = match file {Some(LogChangeFileSelection {ix: file_ix,path,diff_selected,
let (selection, task) = match file {Some(LogChangeFileSelection {ix: file_ix,path: _,}) => {let log_entry = repo.log.get(log_ix).unwrap();
if diff_selected {let id_hash = file::log_id_parts_hash(hash, &path);(Selection::LogChange {ix: log_ix,hash,message,file: Some(LogChangeFileSelection {ix: file_ix,path,diff_selected,}),},if let Some(nav) = logs.diffs.get(&id_hash).and_then(|diff| diff.state.nav.as_ref()){iced_nav_scrollable::scroll_down(nav)} else {Task::none()},)} else {let log_entry = repo.log.get(log_ix).unwrap();
(Selection::LogChange {ix: log_ix,hash,message,file: Some(file),},Task::none(),)}None => {let (selection, task) = if repo.log.len().saturating_sub(1)== log_ix{if !repo.untracked_files.is_empty() {let ix = 0;let selection =untracked_file_selection(repo, ix, files,);(selection, Task::none())} else if !repo.changed_files.is_empty() {let ix = 0;let selection = changed_file_selection(repo, ix, files,);(selection, Task::none())} else {let ix = 0;log_selection(repo, ix)}
(Selection::LogChange {ix: log_ix,hash,message,file: Some(file),},Task::none(),)}}None => {let selection =if repo.log.len().saturating_sub(1) == log_ix {if !repo.untracked_files.is_empty() {let ix = 0;untracked_file_selection(repo, ix, files)} else if !repo.changed_files.is_empty() {let ix = 0;changed_file_selection(repo, ix, files)
None => {let (selection, task) = if !repo.untracked_files.is_empty()
};(Some(selection), task)}None => {let (selection, task) = if !repo.untracked_files.is_empty() {let ix = 0;let selection =Some(untracked_file_selection(repo, ix, files));(selection, Task::none())} else if !repo.changed_files.is_empty() {let ix = 0;let selection =Some(changed_file_selection(repo, ix, files));(selection, Task::none())} else if !repo.log.is_empty() {let ix = repo.log.len() - 1;let selection = log_selection(repo, ix);(Some(selection), Task::none())} else {(None, Task::none())};(selection, task)}};state.selection = selection;task} else {Task::none()}}fn select_up<M>(state: &mut State,files: &mut file::State,repo: Option<&repo::State>,files_diffs: &diff::FilesState,logs: &diff::LogFilesAndState,) -> Task<M> {// TODO stairif let Some(repo) = repo.as_ref() {let (selection, task) = match state.selection.take() {Some(Selection::UntrackedFile {ix,path,diff_selected,}) => {let (selection, task) = if diff_selected {let id_hash =file::id_parts_hash(&path, file::Kind::Untracked);(Selection::UntrackedFile {ix,path,diff_selected,},if let Some(nav) = files_diffs.get(&id_hash).and_then(|state| state.nav.as_ref())
let ix = 0;let selection =Some(untracked_file_selection(repo, ix, files));(selection, Task::none())} else if !repo.changed_files.is_empty() {let ix = 0;let selection =Some(changed_file_selection(repo, ix, files));(selection, Task::none())} else if !repo.log.is_empty() {let ix = repo.log.len() - 1;let (selection, task) = log_selection(repo, ix);(Some(selection), task)
iced_nav_scrollable::scroll_up(nav)
(None, Task::none())};(selection, task)
Task::none()},)} else if 0 == ix {if !repo.log.is_empty() {let ix = repo.log.len() - 1;(log_selection(repo, ix), Task::none())} else if !repo.changed_files.is_empty() {let ix = repo.changed_files.len() - 1;let selection = changed_file_selection(repo, ix, files);(selection, Task::none())} else {let ix = repo.untracked_files.len() - 1;let selection =untracked_file_selection(repo, ix, files);(selection, Task::none())
state.selection = selection;task} else {Task::none()
(Some(selection), task)}Some(Selection::ChangedFile {ix,path,diff_selected,}) => {let (selection, task) = if diff_selected {let id_hash =file::id_parts_hash(&path, file::Kind::Changed);(Selection::ChangedFile {ix,path,diff_selected,},if let Some(nav) = files_diffs.get(&id_hash).and_then(|state| state.nav.as_ref()){iced_nav_scrollable::scroll_up(nav)} else {Task::none()},)} else if 0 == ix {let selection = if !repo.untracked_files.is_empty() {let ix = repo.untracked_files.len() - 1;untracked_file_selection(repo, ix, files)} else if !repo.log.is_empty() {let ix = repo.log.len() - 1;log_selection(repo, ix)} else {let ix = repo.changed_files.len() - 1;changed_file_selection(repo, ix, files)};(selection, Task::none())} else {let ix = ix - 1;let selection = changed_file_selection(repo, ix, files);(selection, Task::none())};(Some(selection), task)
}Msg::Up => {if let Some(repo) = repo.as_ref() {let (selection, task) = match state.selection.take() {Some(Selection::UntrackedFile { ix, path: _ }) => {let (selection, task) = if 0 == ix {if !repo.log.is_empty() {let ix = repo.log.len() - 1;log_selection(repo, ix)} else if !repo.changed_files.is_empty() {let ix = repo.changed_files.len() - 1;let selection =changed_file_selection(repo, ix, files);(selection, Task::none())} else {let ix = repo.untracked_files.len() - 1;let selection =untracked_file_selection(repo, ix, files);(selection, Task::none())}
Some(Selection::LogChange {ix: log_ix,hash,message,file,}) => {let (selection, task) = match file {Some(LogChangeFileSelection {ix: file_ix,path,diff_selected,}) => {if diff_selected {let id_hash = file::log_id_parts_hash(hash, &path);(Selection::LogChange {ix: log_ix,hash,message,file: Some(LogChangeFileSelection {ix: file_ix,path,diff_selected,}),},if let Some(nav) = logs.diffs.get(&id_hash).and_then(|diff| diff.state.nav.as_ref()){iced_nav_scrollable::scroll_up(nav)} else {Task::none()},)
let ix = ix - 1;let selection =untracked_file_selection(repo, ix, files);(selection, Task::none())};(Some(selection), task)
let log_entry = repo.log.get(log_ix).unwrap();let file_ix = if 0 == file_ix {log_entry.file_paths.len() - 1} else {file_ix - 1};let file = log_file_selection(log_entry, file_ix);(Selection::LogChange {ix: log_ix,hash,message,file: Some(file),},Task::none(),)}
Some(Selection::ChangedFile { ix, path: _ }) => {let (selection, task) = if 0 == ix {if !repo.untracked_files.is_empty() {
None => {let selection = if 0 == log_ix {if !repo.changed_files.is_empty() {let ix = repo.changed_files.len() - 1;changed_file_selection(repo, ix, files)} else if !repo.untracked_files.is_empty() {
};(Some(selection), task)}None => {let (selection, task) = if !repo.log.is_empty() {let ix = repo.log.len() - 1;let selection = log_selection(repo, ix);(Some(selection), Task::none())} else if !repo.changed_files.is_empty() {let ix = repo.changed_files.len() - 1;let selection = changed_file_selection(repo, ix, files);(Some(selection), Task::none())} else if !repo.untracked_files.is_empty() {let ix = repo.untracked_files.len() - 1;let selection = untracked_file_selection(repo, ix, files);(Some(selection), Task::none())} else {(None, Task::none())};(selection, task)}};state.selection = selection;task} else {Task::none()}}fn select_left<M>(state: &mut State, repo: Option<&repo::State>) -> Task<M> {if let Some(_repo) = repo.as_ref() {let selection: Option<Selection> = match state.selection.take() {Some(Selection::LogChange {ix,hash,message,file:Some(LogChangeFileSelection {ix: file_ix,path,diff_selected,}),}) => {if diff_selected {Some(Selection::LogChange {ix,hash,message,file: Some(LogChangeFileSelection {ix: file_ix,path,diff_selected: false,}),})} else {
file,}) => {let (selection, task) = match file {Some(LogChangeFileSelection {ix: file_ix,path: _,}) => {let log_entry = repo.log.get(log_ix).unwrap();
file: None,})}}Some(Selection::UntrackedFile {ix,path,diff_selected: true,}) => Some(Selection::UntrackedFile {ix,path,diff_selected: false,}),Some(Selection::ChangedFile {ix,path,diff_selected: true,}) => Some(Selection::ChangedFile {ix,path,diff_selected: false,}),selection @ (Some(Selection::UntrackedFile { .. })| Some(Selection::ChangedFile { .. })| Some(Selection::LogChange { file: None, .. })| None) => selection,};state.selection = selection;}Task::none()}
let file_ix = if 0 == file_ix {log_entry.file_paths.len() - 1} else {file_ix - 1};let file =log_file_selection(log_entry, file_ix);(Selection::LogChange {ix: log_ix,hash,message,file: Some(file),},Task::none(),)}None => {let (selection, task) = if 0 == log_ix {if !repo.changed_files.is_empty() {let ix = repo.changed_files.len() - 1;let selection = changed_file_selection(repo, ix, files,);(selection, Task::none())} else if !repo.untracked_files.is_empty() {let ix = repo.untracked_files.len() - 1;let selection =untracked_file_selection(repo, ix, files,);(selection, Task::none())} else {let ix = repo.log.len() - 1;log_selection(repo, ix)}} else {let ix = log_ix - 1;log_selection(repo, ix)};(selection, task)}};(Some(selection), task)
fn select_right<M>(state: &mut State,repo: Option<&repo::State>,files_diffs: &diff::FilesState,logs: &diff::LogFilesAndState,) -> Task<M> {if let Some(repo) = repo.as_ref() {let (selection, task): (Option<Selection>, Task<M>) = match state.selection.take(){Some(Selection::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);// If the selected file's diff is already loaded (it has an// attached state), scroll back to its last offset// TODO: simplify in rust 1.88let task = if diff_selected {if let Some(nav) = files_diffs.get(&id_hash).and_then(|state| state.nav.as_ref()){iced_nav_scrollable::scroll_to_stored_offset(nav)} else {Task::none()}} else {Task::none()};(Some(Selection::UntrackedFile {ix,path,diff_selected,}),task,)}Some(Selection::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);// If the selected file's diff is already loaded (it has an// attached state), scroll back to its last offset// TODO: simplify in rust 1.88let task = if diff_selected {if let Some(nav) = files_diffs.get(&id_hash).and_then(|state| state.nav.as_ref()){iced_nav_scrollable::scroll_to_stored_offset(nav)} else {Task::none()
None => {let (selection, task) = if !repo.log.is_empty() {let ix = repo.log.len() - 1;let (selection, task) = log_selection(repo, ix);(Some(selection), task)} else if !repo.changed_files.is_empty() {let ix = repo.changed_files.len() - 1;let selection =changed_file_selection(repo, ix, files);(Some(selection), Task::none())} else if !repo.untracked_files.is_empty() {let ix = repo.untracked_files.len() - 1;let selection =untracked_file_selection(repo, ix, files);(Some(selection), Task::none())} else {(None, Task::none())};(selection, task)
} else {Task::none()};(Some(Selection::ChangedFile {ix,path,diff_selected,}),task,)}Some(Selection::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 diff_selected =diff::log_diff_needs_scrolling(logs, id_hash);// If the selected file's diff is already loaded (it has an// attached state), scroll back to its last offset// TODO: simplify in rust 1.88let task = if diff_selected {if let Some(nav) = logs.diffs.get(&id_hash).and_then(|diff| diff.state.nav.as_ref()){iced_nav_scrollable::scroll_to_stored_offset(nav)} else {Task::none()
}Msg::Left => {if let Some(_repo) = repo.as_ref() {let (selection, task): (Option<Selection>, Task<repo::MsgIn>) =match state.selection.take() {Some(Selection::LogChange {ix,hash,message,file: Some(_),}) => (Some(Selection::LogChange {ix,hash,message,file: None,
Some(Selection::LogChange {ix,hash,message,file: None,}) => {let log_entry = repo.log.get(ix).unwrap();let (file, task) =if let Some(path) = log_entry.file_paths.first() {(Some(LogChangeFileSelection {ix: 0,path: path.clone(),diff_selected: false,
state.selection = selection;task
(Some(Selection::LogChange {ix,hash,message,file,}),task,)}selection => (selection, Task::none()),};state.selection = selection;task} else {Task::none()}}fn select_exact<M>(select: Select,state: &mut State,files: &mut file::State,repo: Option<&repo::State>,files_diffs: &diff::FilesState,logs: &diff::LogFilesAndState,) -> Task<M> {let (selection, task) = match select {Select::UntrackedFile { ix, path } => {let id = file::Id {path: path.clone(),file_kind: file::Kind::Changed,};let id_hash = file::id_hash(&id);file::load_src_file_if_not_cached(files, id);// If the selected file's diff is already loaded (it has an attached// state), scroll back to its last offset// TODO: simplify in rust 1.88let is_diff_scrollable =diff::file_diff_needs_scrolling(files_diffs, id_hash);let task = if is_diff_scrollable {if let Some(nav) = files_diffs.get(&id_hash).and_then(|state| state.nav.as_ref()){iced_nav_scrollable::scroll_to_stored_offset(nav)} else {Task::none()}
};(Some(Selection::UntrackedFile {ix,path,diff_selected: false,}),task,)}Select::ChangedFile { ix, path } => {let id = file::Id {path: path.clone(),file_kind: file::Kind::Changed,};let id_hash = file::id_hash(&id);if let Some(diffs) =repo.as_ref().and_then(|repo| repo.changed_files.get(&path)){if diff::any_diff_has_contents(diffs) {file::load_src_file_if_not_cached(files, id);}
}Msg::Right => {if let Some(repo) = repo.as_ref() {let (selection, task): (Option<Selection>, Task<repo::MsgIn>) =match state.selection.take() {Some(Selection::LogChange {ix,hash,message,file: None,}) => {let log_entry = repo.log.get(ix).unwrap();let (file, task) = if let Some(path) =log_entry.file_paths.first(){(Some(LogChangeFileSelection {ix: 0,path: path.clone(),}),Task::none(),)} else {(None, Task::none())};(Some(Selection::LogChange {ix,hash,message,file,}),task,)}selection @ (Some(Selection::UntrackedFile {..})| Some(Selection::ChangedFile {..})| Some(Selection::LogChange {file: Some(_),..})| None) => (selection, Task::none()),};state.selection = selection;task
// If the selected file's diff is already loaded (it has an attached// state), scroll back to its last offset// TODO: simplify in rust 1.88let is_diff_scrollable =diff::file_diff_needs_scrolling(files_diffs, id_hash);let task = if is_diff_scrollable {if let Some(nav) = files_diffs.get(&id_hash).and_then(|state| state.nav.as_ref()){iced_nav_scrollable::scroll_to_stored_offset(nav)} else {Task::none()}
Msg::Select(select) => {let (selection, task) = match select {Select::UntrackedFile { ix, path } => {file::load_src_file_if_not_cached(files,file::Id {path: path.clone(),file_kind: file::Kind::Untracked,},);(Some(Selection::UntrackedFile { ix, path }), Task::none())}Select::ChangedFile { ix, path } => {if let Some(diffs) = repo.as_ref().and_then(|repo| repo.changed_files.get(&path)){if diff::any_diff_has_contents(diffs) {file::load_src_file_if_not_cached(files,file::Id {path: path.clone(),file_kind: file::Kind::Changed,},);
Select::LogChange { ix, hash, message } => (Some(Selection::LogChange {ix,hash,message,file: None,}),Task::none(),),Select::LogChangeFile { ix: file_ix, path } => {match state.selection.take() {Some(Selection::LogChange {ix: change_ix,hash,message,file: _,}) => {let id_hash = file::log_id_parts_hash(hash, &path);let diff_selected =diff::log_diff_needs_scrolling(logs, id_hash);// If the selected file's diff is already loaded (it has an// attached state), scroll back to its last offset// TODO: simplify in rust 1.88let task = if diff_selected {if let Some(nav) = logs.diffs.get(&id_hash).and_then(|diff| diff.state.nav.as_ref()){iced_nav_scrollable::scroll_to_stored_offset(nav)} else {Task::none()
}(Some(Selection::ChangedFile { ix, path }), Task::none())}Select::LogChange { ix, hash, message } => {// Request to get the diffslet task = Task::done(repo::MsgIn::GetChangeDiffs { hash });
} else {Task::none()};let file = LogChangeFileSelection {ix: file_ix,path,diff_selected: false,};
}Select::LogChangeFile { ix: file_ix, path } => {match state.selection.take() {Some(Selection::LogChange {ix: change_ix,hash,message,file: _,}) => {let file =LogChangeFileSelection { ix: file_ix, path };(Some(Selection::LogChange {ix: change_ix,hash,message,file: Some(file),}),Task::none(),)}selection => (selection, Task::none()),}
}}pub fn untracked_file_selection(repo: &repo::State,ix: usize,files: &mut file::State,) -> Selection {let path = repo.untracked_files.iter().nth(ix).unwrap().clone();let id = file::Id {path: path.clone(),file_kind: file::Kind::Untracked,
file::load_src_file_if_not_cached(files, id);Selection::UntrackedFile { ix, path }}pub fn changed_file_selection(repo: &repo::State,ix: usize,files: &mut file::State,) -> Selection {let (path, diffs) = repo.changed_files.iter().nth(ix).unwrap();if diff::any_diff_has_contents(diffs) {let id = file::Id {path: path.clone(),file_kind: file::Kind::Changed,};file::load_src_file_if_not_cached(files, id);}Selection::ChangedFile {ix,path: path.clone(),}
state.selection = selection;task
pub fn log_selection(repo: &repo::State,ix: usize,) -> (Selection, Task<repo::MsgIn>) {let entry = repo.log.get(ix).unwrap();// Request to get the diffslet task = Task::done(repo::MsgIn::GetChangeDiffs { hash: entry.hash });(Selection::LogChange {ix,hash: entry.hash,message: entry.message.clone(),file: None,},task,)}pub fn log_file_selection(log_entry: &repo::LogEntry,file_ix: usize,) -> LogChangeFileSelection {let path = log_entry.file_paths.get(file_ix).unwrap().clone();LogChangeFileSelection { ix: file_ix, path }}
Task::none()}Msg::ScrollToNext => {if nav.pending_tasks.is_none() {if let Some(y) = nav.section_offsets.iter().find(|offset| *offset > &nav.offset){return task::scroll_to(nav.id.clone(),scrollable::AbsoluteOffset { x: 0.0, y: *y },);}}
Msg::ScrollToPrev => {if nav.pending_tasks.is_none() {if let Some(y) = nav.section_offsets.iter().rev().find(|offset| *offset < &nav.offset){return task::scroll_to(nav.id.clone(),scrollable::AbsoluteOffset { x: 0.0, y: *y },);}}Task::none()}
let nav = Element::from(widget::scrollable(widget::column(children)).id(nav.id.clone()).on_scroll(move |viewport| map_msg(Msg::Scrolled(viewport))),);Element::from(nav)
widget::scrollable(widget::column(children)).id(nav.id.clone()).on_scroll(move |viewport| map_msg(Msg::Scrolled(viewport)))}/// A task to scroll to the last stored offsetpub fn scroll_to_stored_offset<M>(nav: &NavScrollable) -> Task<M> {task::scroll_to(nav.id.clone(),scrollable::AbsoluteOffset {x: 0.0,y: nav.offset,},)}// TODO: time deltaconst DELTA: f32 = 10.0;/// Scroll down, skipping any sections that are marked to skippub fn scroll_down<M>(nav: &NavScrollable) -> Task<M> {if let Some(y) = nav.section_offsets.iter().zip(nav.section_heights.values()).enumerate().find_map(|(ix, (offset, height))| {if !nav.skip_sections.contains(&ix) {// dbg!(ix, offset, height, nav.offset);let bottom_frame = saturating_sub(nav.offset + nav.height,VISIBLE_CONTEXT_HEIGHT,);if *offset < bottom_frame && offset + height > bottom_frame {// The top edge of the section is visible, but the bottom// edge of is below the bottom edge of nav, scroll downSome(nav.offset + DELTA)} else if *offset > nav.offset + nav.height {// Scroll to the next sectionSome(saturating_sub(*offset, VISIBLE_CONTEXT_HEIGHT))} else {None}} else {None}}){return task::scroll_to(nav.id.clone(),scrollable::AbsoluteOffset { x: 0.0, y },);}Task::none()}/// Scroll up, skipping any sections that are marked to skippub fn scroll_up<M>(nav: &NavScrollable) -> Task<M> {if let Some(y) = nav.section_offsets.iter().zip(nav.section_heights.values()).enumerate().rev().find_map(|(ix, (offset, height))| {if !nav.skip_sections.contains(&ix) {// dbg!(ix, offset, height, nav.offset);let top_frame = nav.offset + VISIBLE_CONTEXT_HEIGHT;if offset + height > top_frame && *offset < top_frame {// The bottom edge of the section is visible, but the top// edge of is above the top edge of nav, scroll upSome(saturating_sub(nav.offset, DELTA))} else if// offset + height < nav.offset &&offset + height< saturating_sub(nav.offset, VISIBLE_CONTEXT_HEIGHT){// Scroll to the bottom of the prev sectionSome(saturating_sub(offset + height + VISIBLE_CONTEXT_HEIGHT,nav.height,))} else {None}} else {None}}){return task::scroll_to(nav.id.clone(),scrollable::AbsoluteOffset { x: 0.0, y },);}Task::none()
fn saturating_sub(left: f32, right: f32) -> f32 {if left > right {left - right} else {0.0}}