UR4J677RWA3OFG6HQTD46BUUE5YFPSBEFCJAEM5OMT4V5A7SBNNQC WT3GA27PQ2AOAIGK65O3Q4DMX4AZDVNULBLRL6GF4QW6QCASUEAAC S2NVIFXRFER4SRA37WCT5XTXHDHAL5WIGGKY4A4XOTPLTKTZSRGQC W7IUT3ZVMFH77IGKLAL7WX7IVVTGTY3FKEJ3WHMP3KI37B6NENLQC D7A7MSIHJS3IAOLEPK52M4CZLDPLO7JB3Y62XACT2AM6UUCPQ6BAC 4WO3ZJM2RNYZCBPS7FGYAEBELYD57OSS7LEUYCWGZBCAY272SNQQC BJXUYQ2YQMVULJITT5FEA6NERJVLWFKEAWSBYZVIB7KAT27KOWBAC AMPZ2BXK4IGUZO3OPBRSJ6Z4GI5K4PRFMLUGTR6AP4FKKRWQG7LQC DCSUCH6RRRQU4TQYO3K3HRC7SXAIBYP5R3ZOWAWS2LOXWNHEJM6AC JE44NYHM4QORCRKOF33QM42EDT7SBCPTULWGT6IVDL3D5LUHQXLAC L6KSEFQIWICZJ6HJUFKLZQDEH6X2QMFM4Z7ZZUGMLDMFF7EHRXWAC BFN2VHZS7VCBUHQ4S3CQ3LFQV2V4M6VANNAF32XMRFQVWRGYSZ6AC 23SFYK4Q5NKBPJG53PQNPWQH6UOUU2YKJEL7RLXYBRLJOJYV7AWQC OPXFZKEBDHZZLXEJ2JRDYBOJH6YIN7UZNZYHVHMWMQVDTE2ZD53QC 3QVNMRNMI63L2VOFVTMPCVPXH3J4JXLXVTIIPNOMACQCPCAPWILQC MYGIBRRHHXPKVRAMQQRJTZH74L2XOK3SF7J57JPCRKSVRLZ2D6NQC PKJCFSBMXXA2H3US47IJEB7QMIYLEKTLGWQUYEZSKCDODDQTD6HQC XSZZB47UXR6KGYFZZQFQR63X2LDKOH6TPNNBRRGHUCI5JJ4JIWVAC 3BK22XE5LPOH2EK5AMRXFXHNQNCJ54HEPYRINHJT4DA7INT32I7AC ACDXXAX26ZJJFKJDGRC2GOSJY5JHQWCSTP55SYI6D6LH5UIRYUBAC TSFQFCB2NXDOBLBRUSAT63VJIXLPPTJGSTIDNOTLGHVVWSHITRNQC 7SSBM4UQMYVRL6L3ICYZQPSMYLZZQNMDWH6JKA3KOOSXZDJHESHQC ZD56BUSUGTPPDSHHTOC2H5RJJG4FUUUPOSM2BHRY3UA5D6OA43XQC I2AG42PAVOII4V4TWDJV5ZVNDIHKBRDT254BFQLFUIY723TW6CCQC 4PNWU55OLKQGTREOE7P27SGX4HPQO3NMHBABN27WQMCQADBGJJWQC WW36JYLR4AILV7RHQEDJWMX74P74B7G7DRBHH3O2V5TCHRTZJWZQC WIFVLV376GIMVTGVXBFWIPU7FR5O3SGIQ343KIJEBWB6UURTJZJQC SASAN2XCWDQ2VEHZ7TAQEN2R3Y7AG7JUGEFVRL4DZAGHXDFEZFRQC XZ6D3UUEHORAFR6E6NTIJAYEMNUREWPFHCR2FU7FRTHGGLUHZNVQC YKHE3XMWOWPGOWYSISF73MIAKN7WB3AHCV2OA4ECAFPF47YHUXEAC KEPKF3WO7ZZ2VB2DRVVTWTGPL7TCA52BMYUPHUNUJH6WO3HAT6JQC GOLHUD6RCYCO2SPULCFGWIW3ALBDODNEMUHVAKCDWYQREKWGFTLQC XHWLKCLDFUQFFHLFLFDC6WHK6RXRPQSXJG5AKGPER7R5AHVCRHUAC K5YUSV2WOLGMA75WKQWY2GRLQGPAFGVYTW3GMVTWEECXF4SXFEYAC KWTBNTO3QUUE2YADF6SYW6G6ZOKYEWRJQKIWDGZXR33S3YNDVIZQC 5MUEECMJHU44FL5RDUR3VFBIWK3H4X2L5MVJ73J37PYHZWLUKU2AC 3TLPJ57B2OD5OWJN5WMS7A4W7IGFUWJJHVIXRM34VT6KUN6R4YSAC BNHJU2DU4HHADLKTDQMRG5PC5VHCJ2G7UFQRRVUBTALXVBUAQSKQC KQABQCCZCM23QWW43LZD5QBNFOXWLGFNCPPIMNMNFMZSNPSTFVEAC AI3IMKC3HRPMTWQCU5HGWKUHGKTJ22QF7V4AAEI6IEBIZ4WYWCKQC WXQBBQ2ACNPKCTDF7OTBLP342324ZIOJK42PUO2KT2IYVJ2ETCMAC DST3HRZZXO3RNS2GW5ADK43LT7PX5SZ4GLLO7MC7U3VWCYNFTZTQC PTWZYQFRWWUOE2WMQT26CKZKFSHAIJVJS3QWHJFYUFDRRTVPHSUAC RDRBP7AL74NBFNZSQFTU7VQCMWTGJO5RZWGPCWVVS5WRTXJ77DFAC 4G6DZDO6BK7ICVDZNIWMWJUKLOVJCVMO6ZSQ2RDCC4PA6YVKUKWAC VCNKFNUF7OWVSWC6I5D25KUZ3XZZICZ3LHWVPF2N5ZSP7LQ2JOUQC 6YZAVBWU6E5FYOI5JGEIPXGZLIKAW6LS2AOFIQWEE5DMOPPCD5PQC MJDGPSHGF62FTVWZBE7MFNJTUQD42OBVJEOSVPBT553UFJLTEMXQC WGID4LS4EISIOXB5Y5SOFGEF5PLBJSCPFCETH2CGRTFN3NC4WGJQC #[derive(Debug, Default)]pub struct State {/// Initialized once the file is loaded and opened in viewpub nav: Option<NavScrollable>,pub state: libflorescence::diff::State,}
let sections = if let Some(nav) = state.nav.as_ref() {let children_len = sections_view.len();el(iced_nav_scrollable::view(nav,sections_view,children_len,Msg::DiffNav,).class(if diff_selected {theme::Scrollable::Selected} else {theme::Scrollable::Normal}))
let children_len = sections_view.len();let sections = el(iced_nav_scrollable::view(nav,sections_view,children_len,Msg::DiffNav,).class(if diff_selected {theme::Scrollable::Selected
if let Some(nav) = state.nav.as_ref() {el(iced_nav_scrollable::view(nav, diffs, diffs_len, Msg::DiffNav).class(if diff_selected {theme::Scrollable::Selected} else {theme::Scrollable::Normal}),)} else {el(column(diffs).spacing(10))}
el(iced_nav_scrollable::view(nav, diffs, diffs_len, Msg::DiffNav).class(if diff_selected {theme::Scrollable::Selected} else {theme::Scrollable::Normal},),)
}#[derive(Debug)]pub enum Selection<'a> {UntrackedFile {ix: usize,path: &'a str,diff: Option<Diff<'a>>,},ChangedFile {ix: usize,path: &'a str,diff: Option<Diff<'a>>,},Log {ix: usize,hash: repo::ChangeHash,message: &'a str,file: Option<LogChangeFileSelection<'a>>,nav: Option<&'a NavScrollable>,},
let inner = if let Some(repo) = state.repo {view_repo(&state, repo)
let State {window_size,repo_path,repo,cursor,record_msg,selection,} = state;let inner = if let Some(repo) = repo {view_repo(window_size, repo_path, repo, cursor, record_msg, selection)
fn view_repo<'a>(state: &State<'a>, repo: &'a Repo) -> Element<'a, Msg, Theme> {
fn view_repo<'a>(window_size: iced::Size,// TODO show path_repo_path: &'a Path,repo: &'a Repo,cursor: &'a cursor::State,record_msg: Option<&'a RecordMsg>,selection: Option<Selection<'a>>,) -> Element<'a, Msg, Theme> {
let record_msg_editor = if let Some(RecordMsg::Typing(msg_content)) =state.record_msg.as_ref(){el(column([el(text_editor(msg_content).placeholder("Type something here...").on_action(Msg::EditRecordMsg)),el(row([el(button(text("Save")).on_press(Msg::SaveRecord)),el(button(text("Defer")).on_press(Msg::DeferRecord)),el(button(text("Abandon")).on_press(Msg::AbandonRecord)),])),]))} else {el(row([]))};
let record_msg_editor =if let Some(RecordMsg::Typing(msg_content)) = record_msg.as_ref() {el(column([el(text_editor(msg_content).placeholder("Type something here...").on_action(Msg::EditRecordMsg)),el(row([el(button(text("Save")).on_press(Msg::SaveRecord)),el(button(text("Defer")).on_press(Msg::DeferRecord)),el(button(text("Abandon")).on_press(Msg::AbandonRecord)),])),]))} else {el(row([]))};
let selection_details = match state.cursor.selection.as_ref() {Some(cursor::Selection::UntrackedFile {ix: _,path,diff_selected,}) => {
let selection_details = match selection.as_ref() {Some(Selection::UntrackedFile { ix: _, path, diff }) => {
let diffs = match state.selected_diff {Some((file, state)) => diff::view(state, file, *diff_selected).map(move |msg| Msg::FileDiffsContentsAction {id_hash,action: msg,}),
let diffs = match diff {Some(Diff {is_selected,file,state,nav,}) => diff::view(state, nav, file, *is_selected).map(move |msg| Msg::FileDiff { id_hash, msg }),
let diffs = match state.selected_diff {Some((file, state)) => diff::view(state, file, *diff_selected).map(move |msg| Msg::FileDiffsContentsAction {id_hash,action: msg,}),
let diffs = match diff {Some(Diff {is_selected,file,state,nav,}) => diff::view(state, nav, file, *is_selected).map(move |msg| Msg::FileDiff { id_hash, msg }),
let files = entry.file_paths.iter().enumerate().map(|(ix, path)| {let is_selected = matches!(file, Some(cursor::LogChangeFileSelection{ path: selected_path, .. }) if selected_path == path);
let view = match nav {Some(nav) => {let files_len = entry.file_paths.len();let files = entry.file_paths.iter().enumerate().map(|(ix, path)| {let is_selected = matches!(file, Some(LogChangeFileSelection{ path: selected_path, .. }) if selected_path == path);
let status_selected = match selection.as_ref() {Some(Selection::UntrackedFile { diff, .. }) => !diff.as_ref().map(|diff| diff.is_selected).unwrap_or_default(),Some(Selection::ChangedFile { diff, .. }) => !diff.as_ref().map(|diff| diff.is_selected).unwrap_or_default(),Some(Selection::Log { file, .. }) => file.is_none(),None => true,};
match state.selected_diff {Some((file, state)) => {diff::view(state, file, *diff_selected).map(|action| {Msg::LogChangeFileDiffAction {hash: *hash,file: path.clone(),action,}})
match diff {Some(Diff {is_selected,file,state,nav,}) => {let id_hash = file::log_id_parts_hash(*hash, path);diff::view(state, nav, file, *is_selected).map(move |action| Msg::LogChangeFileDiff {id_hash,msg: action,},)
diff_state.nav = Some(nav);return tasks.map(move |msg| Msg::DiffNav {id_hash: id,msg: diff::Msg::DiffNav(msg),});}Task::none()}Msg::DiffNav { id_hash: id, msg } => {if let Some(diff_state) = state.files_diffs.get_mut(&id) {let task = diff::update(diff_state, msg);let id_clone = id;return task.map(move |msg| Msg::DiffNav {id_hash: id_clone,msg,
state.files_diffs.diffs_nav = Some((id_hash, nav));return tasks.map(move |msg| {Msg::View(app::Msg::FileDiff {id_hash,msg: diff::Msg::DiffNav(msg),})
Msg::LogDiffNav { id_hash: id, msg } => {if let Some(diff::FileAndState { file: _, state }) =state.logs.diffs.get_mut(&id)
Msg::LogDiffNav { id_hash, msg } => {if let Some(diff::FileAndState {file: _,state: diff_state,}) = state.logs.diffs.get_mut(&id_hash)
let task = diff::update(state, msg);return task.map(move |msg| Msg::LogDiffNav { id_hash: id, msg });
let nav = state.logs.diffs_nav.as_mut().and_then(|(nav_id_hash, nav)| {(*nav_id_hash == id_hash).then_some(nav)},);let task = diff::update(diff_state, nav, msg);return task.map(move |msg| Msg::LogDiffNav { id_hash, msg });
app::Msg::Cursor(msg) => {let cursor_task = cursor::update(msg,&mut state.cursor,&mut state.files,&mut state.files_diffs,&mut state.logs,state.repo.as_mut(),).map(|msg| Msg::View(app::Msg::ToRepo(msg)));let init_log_nav_task =if let Some(cursor::Selection::LogChange {ix: _,hash,message: _,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::DiffNav(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()}} else {Task::none()};Task::batch([cursor_task, get_diffs_task, init_log_nav_task])}
app::Msg::Cursor(msg) => cursor::update(msg,&mut state.cursor,&mut state.files,&mut state.files_diffs,&mut state.logs,state.repo.as_mut(),),
app::Msg::FileDiffsContentsAction { id_hash, action } => {file_diffs_contents_action(state, id_hash, action)}app::Msg::LogChangeFileDiffAction { hash, file, action } => {log_change_file_diff_action(state, hash, file, action)}
app::Msg::FileDiff {id_hash,msg: action,} => file_diffs_contents_action(state, id_hash, action),app::Msg::LogChangeFileDiff {id_hash,msg: action,} => log_change_file_diff_action(state, id_hash, action),
Some(cursor::changed_file_selection(repo_state,ix,&mut state.files,))
let (selection, selection_task) =cursor::changed_file_selection(repo_state,status_nav,ix,&mut state.files,&mut state.files_diffs,cursor::VertDir::Down,);state.cursor.selection = Some(selection);return Task::batch([status_nav_task, selection_task]);
if let Some(selection) = state.cursor.selection.as_mut() {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,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 });}}_ => {// Selection has changed}}}Task::none()
let diffs = state.files_diffs.diffs.entry(id_hash).or_default();let nav =state.files_diffs.diffs_nav.as_mut().and_then(|(nav_id_hash, nav)| {(*nav_id_hash == id_hash).then_some(nav)});diff::update(diffs, nav, action).map(move |msg| Msg::View(app::Msg::FileDiff { id_hash, msg }))
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 selected_id_hash =file::log_id_parts_hash(*selected_hash, selected_path);if selected_id_hash == id_hash&& let Some(diff::FileAndState {file: _,state: diff_state,}) = state.logs.diffs.get_mut(&id_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 {file,// The nav is initialized only once a file is selected,// because its tasks need it to be visible to completestate: diff::State::default(),};
if let Some(file) = file.as_ref() {// If a file is selected, init the nav for its difflet diff = diffs.get(&file.path).unwrap();let diff_contents_count = diff.len();let (diffs_nav, nav_task) =iced_nav_scrollable::init(diff_contents_count, HashSet::new());let id_hash = file::log_id_parts_hash(hash, &file.path);state.logs.diffs_nav = Some((id_hash, diffs_nav));nav_task.map(move |msg| {Msg::View(app::Msg::LogChangeFileDiff {id_hash,msg: diff::Msg::DiffNav(msg),})})} else {// Init scrollable nav for log changeslet (changes_nav, nav_task) =iced_nav_scrollable::init(changed_files_count, HashSet::new());state.logs.changes_nav = Some((hash, changes_nav));nav_task.map(move |msg| Msg::View(app::Msg::LogNav { hash, msg }))}} else {Task::none()};// Store the changesstate.logs.change_file_counts.insert(hash, changed_files_count);diffs.into_iter().for_each(|(path, diffs)| {// NOTE: using unknown encoding as we don't yet have the file// for past changeslet file =diff::init_file(diff::FileContent::UnknownEncoding, Some(&diffs));let id_hash = file::log_id_parts_hash(hash, &path);let log_file_diff = diff::FileAndState {file,// The nav is initialized only once a file is selected,// because its tasks need it to be visible to completestate: diff::State::default(),};state.logs.diffs.insert(id_hash, log_file_diff);});
files_diffs.get(&id_hash).map(|state| (file, state))
let nav = files_diffs.diffs_nav.as_ref().and_then(|(nav_id_hash, nav)| {(*nav_id_hash == id_hash).then_some(nav)}).unwrap();files_diffs.diffs.get(&id_hash).map(|state| app::Diff {is_selected: *diff_selected,file,state,nav,})
files_diffs.get(&id_hash).map(|state| (file, state))
let nav = files_diffs.diffs_nav.as_ref().and_then(|(nav_id_hash, nav)| {(*nav_id_hash == id_hash).then_some(nav)}).unwrap();files_diffs.diffs.get(&id_hash).map(|state| app::Diff {is_selected: *diff_selected,file,state,nav,})
let id_hash = file::log_id_parts_hash(*hash, path);log_diffs.diffs.get(&id_hash).map(|diff::FileAndState { file, state }| (file, state))
let file = file.as_ref().map(|cursor::LogChangeFileSelection {ix: change_ix,path,diff_selected,}| {let id_hash = file::log_id_parts_hash(*hash, path);let diff = logs.diffs.get(&id_hash).map(|diff::FileAndState { file, state }| {let nav = logs.diffs_nav.as_ref().and_then(|(nav_id_hash, nav)| {(*nav_id_hash == id_hash).then_some(nav)}).unwrap();app::Diff {is_selected: *diff_selected,file,state,nav,}},);app::LogChangeFileSelection {ix: *change_ix,path,diff,}},);let nav = logs.changes_nav.as_ref().and_then(|(nav_hash, nav)| (nav_hash == hash).then_some(nav));Some(app::Selection::Log {ix: *log_ix,hash: *hash,message,file,nav,})
app::view(app::State {window_size: *window_size,repo_path,repo: repo.as_ref(),cursor,record_msg: record_msg.as_ref(),selected_diff,},window_id,).map(Msg::View)
let state = app::State {window_size: *window_size,repo_path,repo: repo.as_ref(),cursor,record_msg: record_msg.as_ref(),selection,};app::view(state, window_id).map(Msg::View)
pub type FilesState = file::IdMap<State>;
#[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, NavScrollable)>,}
/// All the hashes in this set have `diffs` loadedpub change_hashes: HashSet<repo::ChangeHash>,
/// All the hashes in this map have `diffs` loaded. The value is the number/// of the changed files in the change (contents count used for/// scrollable-nav).pub change_file_counts: HashMap<repo::ChangeHash, usize>,
/// Log changes 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 changes_nav: Option<(repo::ChangeHash, NavScrollable)>,/// Log 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::LogIdHash, NavScrollable)>,
Dir::Down => {alt_select_down(state, files_diffs, log_diffs, delta)}Dir::Up => alt_select_up(state, files_diffs, log_diffs, delta),
Dir::Down => alt_select_down(state, files_diffs, logs, delta),Dir::Up => alt_select_up(state, files_diffs, logs, delta),
}}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>(
fn select_down(
(Selection::LogChange {ix: log_ix,hash,message,file: Some(LogChangeFileSelection {ix: file_ix,path,diff_selected,}),},if let Some(nav) = logs.diffs.get_mut(&id_hash).and_then(|diff| diff.state.nav.as_mut()){iced_nav_scrollable::scroll_down(nav, delta)} else {Task::none()},)
let selection = Selection::LogChange {ix: log_ix,hash,message,file: Some(LogChangeFileSelection {ix: file_ix,path,diff_selected,}),};let task = if let Some(nav) = logs.diffs_nav.as_mut().and_then(|(nav_id_hash, nav)| {(*nav_id_hash == id_hash).then_some(nav)}) {iced_nav_scrollable::scroll_down(nav, delta)} else {Task::none()};(selection, task)
let task =iced_nav_scrollable::scroll_up_to_section(status_section_ix(repo_state,ix,StatusSectionKind::Untracked,),status_nav,);(untracked_file_selection(repo_state, ix, files),task,
untracked_file_selection(repo_state,status_nav,ix,files,files_diffs,VertDir::Up,
let task =iced_nav_scrollable::scroll_up_to_section(status_section_ix(repo_state,ix,StatusSectionKind::Changed,),status_nav,);(changed_file_selection(repo_state, ix, files),task,
changed_file_selection(repo_state,status_nav,ix,files,files_diffs,VertDir::Up,
let selection =Some(untracked_file_selection(repo_state, ix, files));let task = iced_nav_scrollable::scroll_down_to_section(status_section_ix(repo_state,ix,StatusSectionKind::Untracked,),
let (selection, task) = untracked_file_selection(repo_state,
(Selection::LogChange {ix: log_ix,hash,message,file: Some(LogChangeFileSelection {ix: file_ix,path,diff_selected,}),},if let Some(nav) = logs.diffs.get_mut(&id_hash).and_then(|diff| diff.state.nav.as_mut()){iced_nav_scrollable::scroll_up(nav, delta)} else {Task::none()},)
let selection = Selection::LogChange {ix: log_ix,hash,message,file: Some(LogChangeFileSelection {ix: file_ix,path,diff_selected,}),};let task = if let Some(nav) = logs.diffs_nav.as_mut().and_then(|(nav_id_hash, nav)| {(*nav_id_hash == id_hash).then_some(nav)}) {iced_nav_scrollable::scroll_up(nav, delta)} else {Task::none()};(selection, task)
let file = log_file_selection(log_entry, file_ix);(Selection::LogChange {ix: log_ix,hash,message,file: Some(file),},Task::none(),)
let (file, selection_task) =log_file_selection(logs, log_entry, hash, file_ix);let scroll_task = if let Some((nav_changes_hash, nav)) =logs.changes_nav.as_ref()&& *nav_changes_hash == hash{iced_nav_scrollable::scroll_up_to_section(file_ix, nav,)} else {Task::none()};let selection = Selection::LogChange {ix: log_ix,hash,message,file: Some(file),};(selection, Task::batch([selection_task, scroll_task]))
let selection =changed_file_selection(repo_state, ix, files);let task =iced_nav_scrollable::scroll_up_to_section(status_section_ix(repo_state,ix,StatusSectionKind::Changed,),status_nav,);(selection, task)
changed_file_selection(repo_state,status_nav,ix,files,files_diffs,VertDir::Up,)
let selection =untracked_file_selection(repo_state, ix, files);let task =iced_nav_scrollable::scroll_up_to_section(status_section_ix(repo_state,ix,StatusSectionKind::Untracked,),status_nav,);(selection, task)
untracked_file_selection(repo_state,status_nav,ix,files,files_diffs,VertDir::Up,)
let selection = log_selection(repo_state, ix);let task =iced_nav_scrollable::scroll_down_to_section(status_section_ix(repo_state,ix,StatusSectionKind::Log,),status_nav,);(selection, task)
log_selection(status_nav,logs,repo_state,ix,VertDir::Down,)
let selection = untracked_file_selection(repo_state, ix, files);let task = iced_nav_scrollable::scroll_up_to_section(status_section_ix(repo_state,ix,StatusSectionKind::Untracked,),
let (selection, task) = untracked_file_selection(repo_state,
fn select_left<M>(state: &mut State, repo: Option<&app::Repo>) -> Task<M> {let Some(_repo) = repo.as_ref() else {
fn select_left(state: &mut State,repo: Option<&app::Repo>,) -> Task<crate::Msg> {let Some(app::Repo {state: _repo_state,status_nav,}) = repo.as_ref()else {
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 {
let (selection, task): (Option<Selection>, Task<crate::Msg>) =match state.selection.take() {Some(Selection::LogChange {ix,hash,message,file:Some(LogChangeFileSelection {
})} else {Some(Selection::LogChange {
}) => {if diff_selected {(Some(Selection::LogChange {ix,hash,message,file: Some(LogChangeFileSelection {ix: file_ix,path,diff_selected: false,}),}),Task::none(),)} else {let selection = Selection::LogChange {ix,hash,message,file: None,};let task = iced_nav_scrollable::scroll_to_stored_offset(status_nav,);(Some(selection), task)}}Some(Selection::UntrackedFile {ix,path,diff_selected: true,}) => (Some(Selection::UntrackedFile {
hash,message,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,};
path,diff_selected: false,}),Task::none(),),Some(Selection::ChangedFile {ix,path,diff_selected: true,}) => (Some(Selection::ChangedFile {ix,path,diff_selected: false,}),Task::none(),),selection @ (Some(Selection::UntrackedFile { .. })| Some(Selection::ChangedFile { .. })| Some(Selection::LogChange { file: None, .. })| None) => (selection, Task::none()),};
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 offsetlet task = if diff_selected&& let Some(nav) = files_diffs.get(&id_hash).and_then(|state| state.nav.as_ref()){iced_nav_scrollable::scroll_to_stored_offset(nav)
let (selection, task): (Option<Selection>, Task<crate::Msg>) = 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 offsetlet task = if diff_selected&& let Some(nav) = files_diffs.diffs_nav.as_ref().and_then(|(nav_id_hash, nav)| {(*nav_id_hash == id_hash).then_some(nav)},) {iced_nav_scrollable::scroll_to_stored_offset(nav)} else {Task::none()};(Some(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 offsetlet task = if diff_selected&& let Some(nav) = files_diffs.diffs_nav.as_ref().and_then(|(nav_id_hash, nav)| {(*nav_id_hash == id_hash).then_some(nav)},) {iced_nav_scrollable::scroll_to_stored_offset(nav)} else {Task::none()};(Some(Selection::ChangedFile {ix,path,diff_selected,}),task,)}Some(Selection::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);let task = if let Some(log) = logs.diffs.get(&id_hash) {// Init log diffs navlet contents_count = diff::contents_count(&log.file);let unchanged_sections =diff::unchanged_sections(&log.file);let (nav, tasks) = iced_nav_scrollable::init(contents_count,unchanged_sections,);logs.diffs_nav = Some((id_hash, nav));tasks.map(move |msg| crate::Msg::LogDiffNav {id_hash,msg: diff::Msg::DiffNav(msg),})
}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 offsetlet task = if diff_selected&& 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::ChangedFile {ix,
} else {(None, Task::none())};(Some(Selection::LogChange {ix,hash,message,file,}),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 offsetlet task = if diff_selected&& let Some(nav) =logs.diffs_nav.as_ref().and_then(|(nav_id_hash, nav)| {(*nav_id_hash == id_hash).then_some(nav)}) {iced_nav_scrollable::scroll_to_stored_offset(nav)} else {Task::none()};(Some(Selection::LogChange {ix,hash,message,file: Some(LogChangeFileSelection {ix: file_ix,
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 offsetlet task = if diff_selected&& 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::LogChange {ix,hash,message,file: Some(LogChangeFileSelection {ix: file_ix,path,diff_selected,}),}),task,)}Some(Selection::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() {(Some(LogChangeFileSelection {ix: 0,path: path.clone(),diff_selected: false,}),Task::none(),)} else {(None, Task::none())};(Some(Selection::LogChange {ix,hash,message,file,}),task,)}selection => (selection, Task::none()),};
}),task,)}selection => (selection, Task::none()),};
&& let Some(nav) = files_diffs.get(&id_hash).and_then(|state| state.nav.as_ref()){
&& let Some(nav) = files_diffs.diffs_nav.as_ref().and_then(|(nav_id_hash, nav)| {(*nav_id_hash == id_hash).then_some(nav)},) {
&& let Some(nav) = files_diffs.get(&id_hash).and_then(|state| state.nav.as_ref()){
&& let Some(nav) = files_diffs.diffs_nav.as_ref().and_then(|(nav_id_hash, nav)| {(*nav_id_hash == id_hash).then_some(nav)},) {
&& let Some(nav) = logs.diffs.get(&id_hash).and_then(|diff| diff.state.nav.as_ref()){
&& let Some(nav) = logs.diffs_nav.as_ref().and_then(|(nav_id_hash, nav)| {(*nav_id_hash == id_hash).then_some(nav)},) {
}#[derive(Debug, Clone, Copy)]pub enum VertDir {Up,Down,}pub fn untracked_file_selection(repo: &repo::State,status_nav: &NavScrollable,ix: usize,files: &mut file::State,files_diffs: &mut diff::FilesState,dir: VertDir,) -> (Selection, Task<crate::Msg>) {let path = repo.untracked_files.iter().nth(ix).unwrap();let id_hash = file::id_parts_hash(path, file::Kind::Untracked);let nav_task = match file::try_get_src_file(files, id_hash) {Some(file_diff) => {// If the diff is already loaded init nav for itlet contents_count = diff::contents_count(file_diff);let unchanged_sections = diff::unchanged_sections(file_diff);let (nav, nav_task) =iced_nav_scrollable::init(contents_count, unchanged_sections);files_diffs.diffs_nav = Some((id_hash, nav));nav_task.map(move |msg| {crate::Msg::View(app::Msg::FileDiff {id_hash,msg: diff::Msg::DiffNav(msg),})})}None => {// Nav will be initialize once the diff is loaded// (`crate::Msg::File`)let id = file::Id {path: path.clone(),file_kind: file::Kind::Untracked,};file::load_src_file_if_not_cached(files, id);Task::none()}};let scroll_task = match dir {VertDir::Up => iced_nav_scrollable::scroll_up_to_section(status_section_ix(repo, ix, StatusSectionKind::Untracked),status_nav,),VertDir::Down => iced_nav_scrollable::scroll_down_to_section(status_section_ix(repo, ix, StatusSectionKind::Untracked),status_nav,),};let selection = Selection::UntrackedFile {ix,path: path.clone(),diff_selected: false,};(selection, Task::batch([nav_task, scroll_task]))}pub fn changed_file_selection(repo: &repo::State,status_nav: &NavScrollable,ix: usize,files: &mut file::State,files_diffs: &mut diff::FilesState,dir: VertDir,) -> (Selection, Task<crate::Msg>) {let (path, diffs) = repo.changed_files.iter().nth(ix).unwrap();let id_hash = file::id_parts_hash(path, file::Kind::Changed);let nav_task = match file::try_get_src_file(files, id_hash) {Some(file_diff) => {// If the diff is already loaded init nav for itlet contents_count = diff::contents_count(file_diff);let unchanged_sections = diff::unchanged_sections(file_diff);let (nav, nav_task) =iced_nav_scrollable::init(contents_count, unchanged_sections);files_diffs.diffs_nav = Some((id_hash, nav));nav_task.map(move |msg| {crate::Msg::View(app::Msg::FileDiff {id_hash,msg: diff::Msg::DiffNav(msg),})})}None => {// Nav will be initialize once the diff is loaded// (`crate::Msg::File`)let id = file::Id {path: path.clone(),file_kind: file::Kind::Changed,};if diff::any_diff_has_contents(diffs) {file::load_src_file_if_not_cached(files, id);}Task::none()}};let scroll_task = match dir {VertDir::Up => iced_nav_scrollable::scroll_up_to_section(status_section_ix(repo, ix, StatusSectionKind::Changed),status_nav,),VertDir::Down => iced_nav_scrollable::scroll_down_to_section(status_section_ix(repo, ix, StatusSectionKind::Changed),status_nav,),};let selection = Selection::ChangedFile {ix,path: path.clone(),diff_selected: false,};(selection, Task::batch([nav_task, scroll_task]))
fn log_selection(status_nav: &NavScrollable,logs: &mut diff::LogFilesAndState,repo: &repo::State,ix: usize,dir: VertDir,) -> (Selection, Task<crate::Msg>) {let entry = repo.log.get(ix).unwrap();let hash = entry.hash;let nav_task = match logs.change_file_counts.get(&hash) {Some(contents_count) => {// Init log changes navlet (nav, tasks) =iced_nav_scrollable::init(*contents_count, HashSet::new());logs.changes_nav = Some((hash, nav));tasks.map(move |msg| {crate::Msg::View(app::Msg::LogNav { hash, msg })})}None => {// If the log is not loaded yet, the nav will be initialized once// it's loaded (`repo::MsgOut::GotChangeDifs`)Task::done(crate::Msg::View(app::Msg::ToRepo(repo::MsgIn::GetChangeDiffs { hash },)))}};let scroll_task = match dir {VertDir::Up => iced_nav_scrollable::scroll_up_to_section(status_section_ix(repo, ix, StatusSectionKind::Log),status_nav,),VertDir::Down => iced_nav_scrollable::scroll_down_to_section(status_section_ix(repo, ix, StatusSectionKind::Log),status_nav,),};let selection = Selection::LogChange {ix,hash,message: entry.message.clone(),file: None,};(selection, Task::batch([nav_task, scroll_task]))}fn log_file_selection(logs: &mut diff::LogFilesAndState,log_entry: &repo::LogEntry,hash: repo::ChangeHash,file_ix: usize,) -> (LogChangeFileSelection, Task<crate::Msg>) {let path = log_entry.file_paths.get(file_ix).unwrap().clone();let id_hash = file::log_id_parts_hash(hash, &path);let nav_task = match (logs.diffs.get(&id_hash), logs.diffs_nav.as_ref()) {(Some(diff), Some((nav_id_hash, _nav))) if *nav_id_hash != id_hash => {let contents_count = diff::contents_count(&diff.file);// Init log change diff navlet (nav, tasks) =iced_nav_scrollable::init(contents_count, HashSet::new());logs.diffs_nav = Some((id_hash, nav));tasks.map(move |msg| {crate::Msg::View(app::Msg::LogChangeFileDiff {id_hash,msg: diff::Msg::DiffNav(msg),})})}_ => {// If the log is not loaded yet, the nav will be initialized once// it's loaded (`repo::MsgOut::GotChangeDifs`)Task::none()}};(LogChangeFileSelection {ix: file_ix,path,diff_selected: false,},nav_task,)}
/// Produces a [`Task`] that scrolls the [`scrollable::Scrollable`] with the/// given [`scrollable::Id`] by the provided [`scrollable::AbsoluteOffset`].pub fn scroll_by<T>(id: impl Into<scrollable::Id>,offset: scrollable::AbsoluteOffset,) -> Task<T> {#[cfg(not(any(test, feature = "testing")))]let task = scrollable::scroll_by(id, offset);
let bottom_frame =saturating_sub(nav.offset + nav.height, VISIBLE_CONTEXT_HEIGHT);let top_frame = nav.offset + VISIBLE_CONTEXT_HEIGHT;
let bottom_frame = saturating_sub(nav.stored_offset.unwrap_or_default() + nav.height,VISIBLE_CONTEXT_HEIGHT,);let top_frame =nav.stored_offset.unwrap_or_default() + VISIBLE_CONTEXT_HEIGHT;
let bottom_frame =saturating_sub(nav.offset + nav.height, VISIBLE_CONTEXT_HEIGHT);let top_frame = nav.offset + VISIBLE_CONTEXT_HEIGHT;
let bottom_frame = saturating_sub(nav.stored_offset.unwrap_or_default() + nav.height,VISIBLE_CONTEXT_HEIGHT,);let top_frame =nav.stored_offset.unwrap_or_default() + VISIBLE_CONTEXT_HEIGHT;
if let Some(y) = nav.skip_sections.contains(&0).then(|| nav.section_offsets.get(1)).flatten()
if let Some(y) =nav.section_offsets.iter().enumerate().find_map(|(ix, offset)| {(!nav.skip_sections.contains(&ix)).then_some(*offset)})