refactor main's updates into smaller fns

[?]
May 21, 2025, 12:37 PM
ACDXXAX26ZJJFKJDGRC2GOSJY5JHQWCSTP55SYI6D6LH5UIRYUBAC

Dependencies

  • [2] UB2ITZJS refresh changed files on FS changes
  • [3] EC3TVL4X add untracked files
  • [4] KT5UYXGK fix selection after adding file, add changed file diffs
  • [5] ELG3UDT6 allow to rm added files
  • [6] S2NVIFXR allow to enter record msg
  • [7] W7IUT3ZV start recording impl
  • [8] YBJRDOTC make all repo actions async
  • [9] KM5PSZ4A watch repo once loaded
  • [10] 2VUX5BTD load identity
  • [11] A5YBC77V record!
  • [12] Z2CJPWZE focus record message text_editor on spawn
  • [13] D7A7MSIH allow to defer or abandon record, add buttons
  • [14] UCBNZULE make changed files paths optional (no path for root)
  • [15] 4WO3ZJM2 show untracked files' contents
  • [16] BJXUYQ2Y show untracked file contents in read-only text editor
  • [17] W4LFX7IH group diffs by file name
  • [18] AMPZ2BXK show changed files diffs (only Edit atm)
  • [19] AXSXZQDG fix updating changed file contents, styling
  • [20] V55EAIWQ add src file LRU cache
  • [21] NRCUG4R2 load changed files src when selected
  • [22] Y5ATDI2H convert changed file diffs and load src only if any needs it
  • [23] ZVI4AWER woot contents_diff
  • [24] QMAUTRB6 refactor diff
  • [25] OQ6HSAWH show record log
  • [26] AHWWRC73 navigate log entries
  • [27] JE44NYHM display log files diffs
  • [28] ONRCENKT rm unnecessary state from repo's internal state
  • [29] 4ELJZGRJ load and store all change diffs at once
  • [30] HC7ROIBC move main diffs state out of cursor
  • [31] FR52XEMW add action for log change file diff
  • [32] L6KSEFQI move cursor related stuff into its module
  • [33] BFN2VHZS refactor file stuff into sub-mod
  • [34] 23SFYK4Q big view refactor into a new crate
  • [35] WGID4LS4 absolutely slayed testing with iced task
  • [36] SWWE2R6M display basic repo stuff
  • [37] WT3GA27P add cursor with selection
  • [38] FDDPOH5R add arrow controls
  • [39] PTFDJ567 add untracked files encoding
  • [40] 6YZAVBWU Initial commit

Change contents

  • replacement in libflorescence/src/repo.rs at line 144
    [8.587][28.7:34]()
    RefreshedState(State),
    [8.587]
    [29.56]
    Refreshed(State),
  • replacement in libflorescence/src/repo.rs at line 201
    [8.2072][28.543:611]()
    let _ = msg_out_tx.send(MsgOut::RefreshedState(state));
    [8.2072]
    [8.2386]
    let _ = msg_out_tx.send(MsgOut::Refreshed(state));
  • replacement in inflorescence/src/main.rs at line 139
    [5.649][6.178:190]()
    Record,
    [5.649]
    [33.239]
    StartRecord,
  • replacement in inflorescence/src/main.rs at line 152
    [34.26032][8.6755:6820](),[8.6755][8.6755:6820]()
    error!("Repo task exited");
    Task::none()
    [34.26032]
    [8.6820]
    panic!("Repo task exited")
  • edit in inflorescence/src/main.rs at line 157
    [7.1298][2.1714:1724](),[3.3765][2.1714:1724](),[2.1714][2.1714:1724](),[2.1724][34.26066:26101](),[34.26101][8.11225:11279](),[4.6445][8.11225:11279](),[8.11279][30.991:1120](),[30.1120][8.11408:11426](),[23.11127][8.11408:11426](),[8.11408][8.11408:11426](),[8.11426][11.4647:4771](),[11.4771][8.11532:11580](),[8.11532][8.11532:11580](),[8.11580][11.4772:4834](),[11.4834][8.11608:11860](),[8.11608][8.11608:11860](),[8.11860][17.3400:3583](),[19.233][8.12089:12253](),[17.3583][8.12089:12253](),[8.12089][8.12089:12253](),[8.12253][4.7252:7310](),[4.7252][4.7252:7310](),[4.7310][8.12254:12334](),[8.12334][32.287:350](),[32.350][15.6305:6371](),[15.6305][15.6305:6371](),[15.6371][33.315:361](),[33.361][15.6494:6521](),[20.4354][15.6494:6521](),[15.6494][15.6494:6521](),[15.6521][4.7490:7513](),[8.12399][4.7490:7513](),[4.7490][4.7490:7513](),[4.7513][8.12400:12418](),[8.12418][4.7513:7527](),[4.7513][4.7513:7527](),[4.7527][7.1517:1542](),[7.1542][4.7527:7537](),[4.7527][4.7527:7537](),[4.7537][34.26102:26132](),[34.26132][8.12419:12473](),[5.684][8.12419:12473](),[8.12473][30.1121:1248](),[30.1248][8.12600:12618](),[23.11375][8.12600:12618](),[8.12600][8.12600:12618](),[8.12618][17.3584:3818](),[17.3818][11.4835:4966](),[8.12763][11.4835:4966](),[11.4966][17.3819:3871](),[14.890][11.4967:5037](),[17.3871][11.4967:5037](),[8.12924][11.4967:5037](),[11.5037][8.12956:12957](),[8.12956][8.12956:12957](),[8.13006][8.13006:13059](),[8.13059][17.3872:3943](),[19.301][8.13131:13170](),[17.3943][8.13131:13170](),[8.13131][8.13131:13170](),[8.13170][17.3944:3991](),[17.3991][8.13207:13261](),[8.13207][8.13207:13261](),[8.13261][17.3992:4073](),[17.4073][8.13314:13391](),[8.13314][8.13314:13391](),[8.13391][17.4074:4141](),[14.1046][8.13455:13827](),[17.4141][8.13455:13827](),[8.13455][8.13455:13827](),[8.13827][32.351:420](),[32.420][23.11376:11418](),[21.3453][23.11376:11418](),[22.2096][21.3495:3535](),[23.11418][21.3495:3535](),[21.3495][21.3495:3535](),[21.3535][33.362:416](),[33.416][21.3660:3695](),[21.3660][21.3660:3695](),[21.3695][8.13898:13951](),[8.13898][8.13898:13951](),[8.13951][5.2009:2027](),[5.2009][5.2009:2027](),[5.2027][6.241:255](),[6.255][7.1602:1627](),[7.1627][6.255:265](),[6.255][6.255:265](),[6.265][34.26133:26158](),[34.26158][8.13952:14006](),[6.294][8.13952:14006](),[8.14006][10.817:951](),[10.951][13.322:565](),[13.565][8.14054:14127](),[8.14054][8.14054:14127](),[8.14127][13.566:607](),[13.607][8.14254:14279](),[8.14254][8.14254:14279](),[8.14279][13.608:1078](),[13.1078][12.114:217](),[8.14353][12.114:217](),[12.217][35.5690:5744](),[12.266][8.14353:14371](),[35.5744][8.14353:14371](),[8.14353][8.14353:14371](),[6.603][5.2027:2041](),[8.14371][5.2027:2041](),[5.2027][5.2027:2041](),[5.2041][7.1628:1653]()
    }
    Msg::AddUntrackedFile => {
    if let Some(repo) = state.repo.as_mut() {
    if let Some(cursor::Selection::UntrackedFile { ix, path }) =
    state.cursor.selection.as_ref()
    {
    state
    .repo_tx_in
    .send(repo::MsgIn::AddUntrackedFile {
    path: path.clone(),
    })
    .unwrap();
    let removed = repo.untracked_files.remove(path);
    debug_assert!(
    removed,
    "{:?}, path: {path}",
    repo.untracked_files
    );
    repo.changed_files
    .entry(path.clone())
    .or_default()
    .insert(repo::ChangedFileDiff::Add);
    // Select the next untracked file, if any
    state.cursor.selection = if repo.untracked_files.is_empty()
    {
    None
    } else {
    let ix = cmp::min(*ix, repo.untracked_files.len() - 1);
    Some(cursor::untracked_file_selection(
    repo,
    ix,
    &mut state.files,
    ))
    };
    }
    }
    Task::none()
    }
    Msg::RmAddedFile => {
    if let Some(repo) = state.repo.as_mut() {
    if let Some(cursor::Selection::ChangedFile { ix, path }) =
    state.cursor.selection.as_ref()
    {
    let diffs = repo.changed_files.get(path).unwrap();
    if diffs
    .iter()
    .any(|diff| matches!(diff, repo::ChangedFileDiff::Add))
    {
    state
    .repo_tx_in
    .send(repo::MsgIn::RmAddedFile {
    path: path.clone(),
    })
    .unwrap();
    // Remove from changed files
    let removed = repo.changed_files.remove(path);
    debug_assert!(
    removed.is_some(),
    "{:?} not found in {:?}",
    path,
    repo.changed_files
    );
    // Update untracked files
    repo.untracked_files.insert(path.clone());
    // Select the next changed file, if any
    state.cursor.selection =
    if repo.changed_files.is_empty() {
    None
    } else {
    let ix =
    cmp::min(*ix, repo.changed_files.len() - 1);
    Some(cursor::changed_file_selection(
    repo,
    ix,
    &mut state.files,
    ))
    };
    }
    }
    }
    Task::none()
    }
    Msg::Record => {
    if let Some(repo) = state.repo.as_ref() {
    if state.id.is_none() {
    info!("Requested to record, but ID is not yet loaded");
    }
    if repo.changed_files.is_empty() {
    info!("Trying to record with no changed files");
    } else if let Some(RecordMsg::Typing(_)) =
    state.record_msg.as_ref()
    {
    info!("Requested to record, but already recording");
    return Task::none();
    } else {
    let content = match state.record_msg.take() {
    Some(RecordMsg::Canceled { old_msg }) => {
    text_editor::Content::with_text(&old_msg)
    }
    None | Some(RecordMsg::Typing(_)) => {
    text_editor::Content::new()
    }
    };
    state.record_msg = Some(RecordMsg::Typing(content));
    // TODO: change to use ID once https://github.com/iced-rs/iced/pull/2653 is merged
    return task::widget_focus_next();
    }
    }
    Task::none()
  • edit in inflorescence/src/main.rs at line 158
    [5.2051]
    [34.26159]
    Msg::AddUntrackedFile => add_untracked_file(state),
    Msg::RmAddedFile => rm_added_file(state),
    Msg::StartRecord => start_record(state),
  • replacement in inflorescence/src/main.rs at line 181
    [34.26733][34.26733:26778](),[34.26778][13.1079:1192](),[6.648][13.1079:1192](),[13.1192][6.714:772](),[6.714][6.714:772](),[6.772][7.1654:1679]()
    app::Msg::EditRecordMsg(action) => {
    if let Some(RecordMsg::Typing(record_msg)) =
    state.record_msg.as_mut()
    {
    record_msg.perform(action);
    }
    Task::none()
    [34.26733]
    [6.772]
    app::Msg::EditRecordMsg(action) => edit_record_msg(state, action),
    app::Msg::SaveRecord => save_record(state),
    app::Msg::DeferRecord => defer_record(state),
    app::Msg::AbandonRecord => abandon_record(state),
    app::Msg::FileDiffsContentsAction { id, action } => {
    file_diffs_contents_action(state, id, action)
  • replacement in inflorescence/src/main.rs at line 188
    [6.782][34.26779:26813](),[34.26813][13.1226:1339](),[13.1226][13.1226:1339](),[13.1339][7.1710:1755](),[8.14465][7.1710:1755](),[6.954][7.1710:1755](),[7.1755][13.1340:1383](),[13.1383][8.14466:14532](),[7.1815][8.14466:14532](),[7.1849][6.1118:1143](),[8.14532][6.1118:1143](),[6.1118][6.1118:1143](),[6.1143][11.5038:5327]()
    app::Msg::SaveRecord => {
    if let Some(RecordMsg::Typing(record_msg)) =
    state.record_msg.as_ref()
    {
    let msg = record_msg.text();
    if msg.trim().is_empty() {
    info!("Cannot record with an empty message");
    } else {
    let id = state
    .id
    .as_ref()
    .expect("ID must be loaded at this point");
    // TODO: this call has CLI prompt - replace it
    let (sk, _) = id.decrypt().unwrap();
    [6.782]
    [11.5327]
    app::Msg::LogChangeFileDiffAction { hash, file, action } => {
    log_change_file_diff_action(state, hash, file, action)
    }
    }
    }
    fn update_from_repo(state: &mut State, msg: repo::MsgOut) -> Task<Msg> {
    match msg {
    repo::MsgOut::Init(repo) => repo_init(state, repo),
    repo::MsgOut::Refreshed(repo) => repo_refreshed(state, repo),
    repo::MsgOut::GotChangeDiffs { hash, diffs } => {
    repo_got_change_diffs(state, hash, diffs)
    }
    }
    }
  • replacement in inflorescence/src/main.rs at line 204
    [11.5328][6.1143:1188](),[6.1143][6.1143:1188](),[6.1188][8.14533:14598](),[8.14598][17.4142:4197]()
    state.record_msg = None;
    state.repo.as_mut().unwrap().changed_files =
    repo::ChangedFiles::default();
    [11.5328]
    [11.5329]
    fn add_untracked_file(state: &mut State) -> Task<Msg> {
    if let Some(repo) = state.repo.as_mut() {
    if let Some(cursor::Selection::UntrackedFile { ix, path }) =
    state.cursor.selection.as_ref()
    {
    state
    .repo_tx_in
    .send(repo::MsgIn::AddUntrackedFile { path: path.clone() })
    .unwrap();
  • replacement in inflorescence/src/main.rs at line 214
    [11.5330][11.5330:5533](),[7.2108][6.1247:1279](),[11.5533][6.1247:1279](),[8.14719][6.1247:1279](),[6.1247][6.1247:1279](),[6.1279][8.14720:14745]()
    let sk = Arc::new(sk);
    state
    .repo_tx_in
    .send(repo::MsgIn::Record { msg, sk })
    .unwrap();
    }
    }
    Task::none()
    [11.5330]
    [8.14745]
    let removed = repo.untracked_files.remove(path);
    debug_assert!(removed, "{:?}, path: {path}", repo.untracked_files);
    repo.changed_files
    .entry(path.clone())
    .or_default()
    .insert(repo::ChangedFileDiff::Add);
    // Select the next untracked file, if any
    state.cursor.selection = if repo.untracked_files.is_empty() {
    None
    } else {
    let ix = cmp::min(*ix, repo.untracked_files.len() - 1);
    Some(cursor::untracked_file_selection(
    repo,
    ix,
    &mut state.files,
    ))
    };
  • replacement in inflorescence/src/main.rs at line 232
    [8.14755][34.26814:26849](),[34.26849][13.1418:1517](),[13.1418][13.1418:1517]()
    app::Msg::DeferRecord => {
    if let Some(RecordMsg::Typing(record_msg)) =
    state.record_msg.as_ref()
    [8.14755]
    [13.1517]
    }
    Task::none()
    }
    fn rm_added_file(state: &mut State) -> Task<Msg> {
    if let Some(repo) = state.repo.as_mut() {
    if let Some(cursor::Selection::ChangedFile { ix, path }) =
    state.cursor.selection.as_ref()
    {
    let diffs = repo.changed_files.get(path).unwrap();
    if diffs
    .iter()
    .any(|diff| matches!(diff, repo::ChangedFileDiff::Add))
  • replacement in inflorescence/src/main.rs at line 246
    [13.1531][13.1531:1730]()
    let old_msg = record_msg.text();
    state.record_msg = if !old_msg.trim().is_empty() {
    Some(RecordMsg::Canceled { old_msg })
    } else {
    [13.1531]
    [13.1730]
    state
    .repo_tx_in
    .send(repo::MsgIn::RmAddedFile { path: path.clone() })
    .unwrap();
    // Remove from changed files
    let removed = repo.changed_files.remove(path);
    debug_assert!(
    removed.is_some(),
    "{:?} not found in {:?}",
    path,
    repo.changed_files
    );
    // Update untracked files
    repo.untracked_files.insert(path.clone());
    // Select the next changed file, if any
    state.cursor.selection = if repo.changed_files.is_empty() {
  • edit in inflorescence/src/main.rs at line 264
    [13.1755]
    [13.1755]
    } else {
    let ix = cmp::min(*ix, repo.changed_files.len() - 1);
    Some(cursor::changed_file_selection(
    repo,
    ix,
    &mut state.files,
    ))
  • edit in inflorescence/src/main.rs at line 273
    [13.1788][13.1788:1813]()
    Task::none()
  • replacement in inflorescence/src/main.rs at line 274
    [13.1823][34.26850:26887](),[34.26887][13.1859:1976](),[13.1859][13.1859:1976](),[18.2013][18.2013:2027](),[23.13076][18.2027:2052](),[18.2027][18.2027:2052]()
    app::Msg::AbandonRecord => {
    if let Some(RecordMsg::Typing(_)) = state.record_msg.as_ref() {
    state.record_msg = None;
    }
    Task::none()
    [13.1823]
    [18.2052]
    }
    Task::none()
    }
    fn start_record(state: &mut State) -> Task<Msg> {
    if let Some(repo) = state.repo.as_ref() {
    if state.id.is_none() {
    info!("Requested to record, but ID is not yet loaded");
  • replacement in inflorescence/src/main.rs at line 283
    [18.2062][34.26888:26950](),[34.26950][23.13138:13281](),[23.13138][23.13138:13281](),[23.13281][30.1249:1323](),[30.1323][33.417:464](),[33.464][23.13407:13454](),[23.13407][23.13407:13454](),[23.13454][30.1324:1402](),[30.1402][24.948:1001](),[23.13454][24.948:1001](),[24.1001][23.13516:13560](),[23.13516][23.13516:13560](),[23.13560][30.1403:1475](),[30.1475][33.465:510](),[33.510][23.13682:13729](),[23.13682][23.13682:13729](),[23.13729][30.1476:1554](),[30.1554][24.1002:1055](),[23.13729][24.1002:1055](),[24.1055][23.13791:13911](),[23.13791][23.13791:13911]()
    app::Msg::FileDiffsContentsAction { id, action } => {
    if let Some(selection) = state.cursor.selection.as_mut() {
    match (selection, id.file_kind) {
    (
    cursor::Selection::UntrackedFile { ix: _, path },
    file::Kind::Untracked,
    ) if path == &id.path => {
    let diffs = state.diffs_state.entry(id).or_default();
    diff::update(diffs, action);
    }
    (
    cursor::Selection::ChangedFile { ix: _, path },
    file::Kind::Changed,
    ) if path == &id.path => {
    let diffs = state.diffs_state.entry(id).or_default();
    diff::update(diffs, action);
    }
    _ => {
    // Selection has changed
    }
    [18.2062]
    [16.1071]
    if repo.changed_files.is_empty() {
    info!("Trying to record with no changed files");
    } else if let Some(RecordMsg::Typing(_)) = state.record_msg.as_ref() {
    info!("Requested to record, but already recording");
    return Task::none();
    } else {
    let content = match state.record_msg.take() {
    Some(RecordMsg::Canceled { old_msg }) => {
    text_editor::Content::with_text(&old_msg)
    }
    None | Some(RecordMsg::Typing(_)) => {
    text_editor::Content::new()
  • edit in inflorescence/src/main.rs at line 296
    [16.1089]
    [15.7355]
    };
    state.record_msg = Some(RecordMsg::Typing(content));
    // TODO: change to use ID once https://github.com/iced-rs/iced/pull/2653 is merged
    return task::widget_focus_next();
    }
    }
    Task::none()
    }
    fn edit_record_msg(
    state: &mut State,
    action: text_editor::Action,
    ) -> Task<Msg> {
    if let Some(RecordMsg::Typing(record_msg)) = state.record_msg.as_mut() {
    record_msg.perform(action);
    }
    Task::none()
    }
    fn save_record(state: &mut State) -> Task<Msg> {
    if let Some(RecordMsg::Typing(record_msg)) = state.record_msg.as_ref() {
    let msg = record_msg.text();
    if msg.trim().is_empty() {
    info!("Cannot record with an empty message");
    } else {
    let id =
    state.id.as_ref().expect("ID must be loaded at this point");
    // TODO: this call has CLI prompt - replace it
    let (sk, _) = id.decrypt().unwrap();
    state.record_msg = None;
    state.repo.as_mut().unwrap().changed_files =
    repo::ChangedFiles::default();
    let sk = Arc::new(sk);
    state
    .repo_tx_in
    .send(repo::MsgIn::Record { msg, sk })
    .unwrap();
    }
    }
    Task::none()
    }
    fn defer_record(state: &mut State) -> Task<Msg> {
    if let Some(RecordMsg::Typing(record_msg)) = state.record_msg.as_ref() {
    let old_msg = record_msg.text();
    state.record_msg = if !old_msg.trim().is_empty() {
    Some(RecordMsg::Canceled { old_msg })
    } else {
    None
    };
    }
    Task::none()
    }
    fn abandon_record(state: &mut State) -> Task<Msg> {
    if let Some(RecordMsg::Typing(_)) = state.record_msg.as_ref() {
    state.record_msg = None;
    }
    Task::none()
    }
    fn file_diffs_contents_action(
    state: &mut State,
    id: file::Id,
    action: diff::Msg,
    ) -> Task<Msg> {
    if let Some(selection) = state.cursor.selection.as_mut() {
    match (selection, id.file_kind) {
    (
    cursor::Selection::UntrackedFile { ix: _, path },
    file::Kind::Untracked,
    ) if path == &id.path => {
    let diffs = state.diffs_state.entry(id).or_default();
    diff::update(diffs, action);
  • replacement in inflorescence/src/main.rs at line 373
    [15.7369][15.7369:7394]()
    Task::none()
    [15.7369]
    [15.7394]
    (
    cursor::Selection::ChangedFile { ix: _, path },
    file::Kind::Changed,
    ) if path == &id.path => {
    let diffs = state.diffs_state.entry(id).or_default();
    diff::update(diffs, action);
    }
    _ => {
    // Selection has changed
    }
  • replacement in inflorescence/src/main.rs at line 384
    [15.7404][34.26951:27021](),[34.27021][31.204:259](),[31.204][31.204:259]()
    app::Msg::LogChangeFileDiffAction { hash, file, action } => {
    if let Some(cursor::Selection::LogChange {
    [15.7404]
    [31.259]
    }
    Task::none()
    }
    fn log_change_file_diff_action(
    state: &mut State,
    hash: pijul::Hash,
    file: String,
    action: diff::Msg,
    ) -> Task<Msg> {
    if let Some(cursor::Selection::LogChange {
    ix: _,
    hash: selected_hash,
    message: _,
    diffs: Some(diffs),
    file:
    Some(cursor::LogChangeFileSelection {
  • replacement in inflorescence/src/main.rs at line 402
    [31.282][31.282:874]()
    hash: selected_hash,
    message: _,
    diffs: Some(diffs),
    file:
    Some(cursor::LogChangeFileSelection {
    ix: _,
    path: selected_path,
    }),
    }) = state.cursor.selection.as_mut()
    {
    if *selected_hash == hash && *selected_path == file {
    let (_file, state) = diffs.get_mut(&file).unwrap();
    diff::update(state, action);
    }
    }
    Task::none()
    [31.282]
    [31.874]
    path: selected_path,
    }),
    }) = state.cursor.selection.as_mut()
    {
    if *selected_hash == hash && *selected_path == file {
    let (_file, state) = diffs.get_mut(&file).unwrap();
    diff::update(state, action);
  • edit in inflorescence/src/main.rs at line 411
    [8.14761]
    [8.14761]
    Task::none()
  • replacement in inflorescence/src/main.rs at line 414
    [8.14764][34.27022:27095](),[34.27095][8.14841:14895](),[8.14841][8.14841:14895](),[18.2810][8.14895:14932](),[8.14895][8.14895:14932](),[8.14932][33.649:716]()
    fn update_from_repo(state: &mut State, msg: repo::MsgOut) -> Task<Msg> {
    match msg {
    repo::MsgOut::Init(repo) => {
    state.repo = Some(repo);
    file::diffs_cache_clear(&mut state.files.diffs_cache);
    [8.14764]
    [9.359]
    fn repo_init(state: &mut State, repo: repo::State) -> Task<Msg> {
    state.repo = Some(repo);
    file::diffs_cache_clear(&mut state.files.diffs_cache);
    // Start watching the repo's dir for changes
    let (fs_watch_tx, fs_watch_rx) = watch::channel(());
    let mut fs_watch = new_debouncer(
    Duration::from_secs(1),
    None,
    move |result: DebounceEventResult| match result {
    Ok(events) => events.iter().for_each(|event| {
    // TODO: distinguish ".pijul" changes
    // dbg!(event);
    if event.kind.is_create()
    || event.kind.is_modify()
    || event.kind.is_remove()
    {
    let _ = fs_watch_tx.send(());
    }
    }),
    Err(errors) => {
    errors.iter().for_each(|error| eprintln!("{error:?}"))
    }
    },
    )
    .unwrap();
    fs_watch
    .watch(&state.repo_path, RecursiveMode::Recursive)
    .unwrap();
    let fs_watch_rx = WatchStream::from_changes(fs_watch_rx);
    let watch_task = Task::run(fs_watch_rx, |()| {
    Msg::View(app::Msg::ToRepo(
    repo::MsgIn::RefreshChangedAndUntrackedFiles,
    ))
    });
    state.repo_fs_watch = Some(fs_watch);
    watch_task
    }
  • replacement in inflorescence/src/main.rs at line 454
    [9.360][9.360:1555](),[9.1555][34.27096:27225](),[34.27225][9.1633:1649](),[9.1633][9.1633:1649]()
    // Start watching the repo's dir for changes
    let (fs_watch_tx, fs_watch_rx) = watch::channel(());
    let mut fs_watch = new_debouncer(
    Duration::from_secs(1),
    None,
    move |result: DebounceEventResult| match result {
    Ok(events) => events.iter().for_each(|event| {
    // TODO: distinguish ".pijul" changes
    // dbg!(event);
    if event.kind.is_create()
    || event.kind.is_modify()
    || event.kind.is_remove()
    {
    let _ = fs_watch_tx.send(());
    }
    }),
    Err(errors) => {
    errors.iter().for_each(|error| eprintln!("{error:?}"))
    }
    },
    )
    .unwrap();
    fs_watch
    .watch(&state.repo_path, RecursiveMode::Recursive)
    .unwrap();
    let fs_watch_rx = WatchStream::from_changes(fs_watch_rx);
    let watch_task = Task::run(fs_watch_rx, |()| {
    Msg::View(app::Msg::ToRepo(
    repo::MsgIn::RefreshChangedAndUntrackedFiles,
    ))
    });
    [9.360]
    [9.1649]
    fn repo_refreshed(state: &mut State, repo: repo::State) -> Task<Msg> {
    let repo::State {
    dir_name: _,
    channel: _,
    untracked_files,
    changed_files,
    log,
    } = &repo;
    file::diffs_cache_clear(&mut state.files.diffs_cache);
  • replacement in inflorescence/src/main.rs at line 464
    [9.1650][9.1650:1723](),[9.1723][8.14957:14967](),[8.14957][8.14957:14967](),[8.14967][28.1990:2245](),[28.2245][33.717:784]()
    state.repo_fs_watch = Some(fs_watch);
    watch_task
    }
    repo::MsgOut::RefreshedState(repo_state) => {
    let repo::State {
    dir_name: _,
    channel: _,
    untracked_files,
    changed_files,
    log,
    } = &repo_state;
    file::diffs_cache_clear(&mut state.files.diffs_cache);
    [9.1650]
    [8.15249]
    // Re-index cursor selection
    let task = if let Some(selection) = state.cursor.selection.take() {
    // Try to find the file with the same name. If not found, remove
    // selection
    let (selection, task) = match selection {
    cursor::Selection::UntrackedFile { ix: _, path } => {
    file::load_src_file_if_not_cached(
    &mut state.files,
    file::Id {
    path: path.clone(),
    file_kind: file::Kind::Untracked,
    },
    );
  • replacement in inflorescence/src/main.rs at line 478
    [8.15250][8.15250:15291](),[8.15291][28.2246:2326](),[28.2326][8.15362:15472](),[27.13081][8.15362:15472](),[8.15362][8.15362:15472](),[8.15472][27.13082:13140](),[27.13140][30.1555:1629]()
    // Re-index cursor selection
    let task = if let Some(selection) = state.cursor.selection.take() {
    // Try to find the file with the same name. If not found, remove
    // selection
    let (selection, task) = match selection {
    cursor::Selection::UntrackedFile { ix: _, path } => {
    [8.15250]
    [33.785]
    let selection = untracked_files
    .iter()
    .enumerate()
    .find(|(_ix, file_path)| *file_path == &path)
    .map(|(ix, _path)| cursor::Selection::UntrackedFile {
    ix,
    path,
    });
    (selection, Task::none())
    }
    cursor::Selection::ChangedFile { ix: _, path } => {
    if let Some(diffs) = changed_files.get(&path) {
    if diff::any_diff_has_contents(diffs) {
  • replacement in inflorescence/src/main.rs at line 495
    [23.14344][33.930:996]()
    file_kind: file::Kind::Untracked,
    [23.14344]
    [23.14408]
    file_kind: file::Kind::Changed,
  • edit in inflorescence/src/main.rs at line 498
    [20.5534][15.7648:7649](),[15.7648][15.7648:7649](),[15.7649][28.2327:2383](),[28.2383][15.7694:7771](),[27.13231][15.7694:7771](),[15.7694][15.7694:7771](),[15.7771][27.13232:13355](),[27.13355][30.1630:1708](),[30.1708][27.13399:13481](),[15.8089][27.13399:13481]()
    let selection = untracked_files
    .iter()
    .enumerate()
    .find(|(_ix, file_path)| *file_path == &path)
    .map(|(ix, _path)| {
    cursor::Selection::UntrackedFile { ix, path }
    });
    (selection, Task::none())
  • replacement in inflorescence/src/main.rs at line 499
    [15.8142][30.1709:1781](),[30.1781][28.2384:2456](),[23.14692][28.2384:2456](),[28.2456][32.421:489](),[32.489][33.997:1165]()
    cursor::Selection::ChangedFile { ix: _, path } => {
    if let Some(diffs) = changed_files.get(&path) {
    if diff::any_diff_has_contents(diffs) {
    file::load_src_file_if_not_cached(
    &mut state.files,
    file::Id {
    [15.8142]
    [23.15057]
    }
    let selection = changed_files
    .iter()
    .enumerate()
    .find(|(_ix, (file_path, _diffs))| *file_path == &path)
    .map(|(ix, (file_path, _diffs))| {
    cursor::Selection::ChangedFile {
    ix,
    path: file_path.clone(),
    }
    });
    (selection, Task::none())
    }
    cursor::Selection::LogChange {
    ix: _,
    hash,
    message,
    diffs: _,
    file,
    } => {
    // Request to get the diffs
    let task = Task::done(Msg::View(app::Msg::ToRepo(
    repo::MsgIn::GetChangeDiffs { hash },
    )));
    let selection = log
    .iter()
    .enumerate()
    .find(|(_ix, entry)| entry.hash == hash)
    .map(|(ix, entry)| {
    let file = file.and_then(|file| {
    entry
    .file_paths
    .iter()
    .enumerate()
    .find(|(_ix, path)| *path == &file.path)
    .map(|(ix, path)| {
    cursor::LogChangeFileSelection {
    ix,
  • replacement in inflorescence/src/main.rs at line 540
    [23.15117][33.1166:1238](),[33.1238][23.15187:15291](),[23.15187][23.15187:15291]()
    file_kind: file::Kind::Changed,
    },
    );
    }
    [23.15117]
    [22.2991]
    }
    })
    });
    cursor::Selection::LogChange {
    ix,
    hash: entry.hash,
    message,
    diffs: None,
    file,
  • edit in inflorescence/src/main.rs at line 550
    [22.3017]
    [21.3992]
    });
  • replacement in inflorescence/src/main.rs at line 552
    [21.3993][28.2457:2511](),[28.2511][21.4036:4178](),[27.13648][21.4036:4178](),[21.4036][21.4036:4178](),[21.4178][27.13649:13701](),[27.13701][21.4229:4489](),[21.4229][21.4229:4489](),[24.1191][21.4489:4523](),[23.15368][21.4489:4523](),[21.4489][21.4489:4523](),[21.4523][27.13702:13784](),[27.13784][21.4554:4576](),[21.4554][21.4554:4576](),[21.4576][27.13785:13930](),[27.13930][29.9418:9452](),[29.9452][27.13930:13987](),[27.13930][27.13930:13987](),[27.13987][29.9453:9505](),[29.9505][34.27226:27300](),[34.27300][29.9568:9634](),[29.9568][29.9568:9634](),[29.9634][34.27301:27330]()
    let selection = changed_files
    .iter()
    .enumerate()
    .find(|(_ix, (file_path, _diffs))| {
    *file_path == &path
    })
    .map(|(ix, (file_path, _diffs))| {
    cursor::Selection::ChangedFile {
    ix,
    path: file_path.clone(),
    }
    });
    (selection, Task::none())
    }
    cursor::Selection::LogChange {
    ix: _,
    hash,
    message,
    diffs: _,
    file,
    } => {
    // Request to get the diffs
    let task = Task::done(Msg::View(app::Msg::ToRepo(
    repo::MsgIn::GetChangeDiffs { hash },
    )));
    [21.3993]
    [29.9662]
    (selection, task)
    }
    };
  • replacement in inflorescence/src/main.rs at line 556
    [29.9663][29.9663:9707](),[28.2570][27.14079:14225](),[29.9707][27.14079:14225](),[27.14079][27.14079:14225](),[27.14225][29.9708:10808](),[29.10808][26.10697:10729](),[26.10697][26.10697:10729]()
    let selection = log
    .iter()
    .enumerate()
    .find(|(_ix, entry)| entry.hash == hash)
    .map(|(ix, entry)| {
    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(),
    }
    })
    });
    cursor::Selection::LogChange {
    ix,
    hash: entry.hash,
    message,
    diffs: None,
    file,
    }
    });
    [29.9663]
    [27.15737]
    state.cursor.selection = selection;
    task
    } else {
    Task::none()
    };
  • replacement in inflorescence/src/main.rs at line 562
    [27.15738][27.15738:15802](),[21.4576][8.16407:16426](),[25.4979][8.16407:16426](),[26.10970][8.16407:16426](),[27.15802][8.16407:16426](),[8.16407][8.16407:16426]()
    (selection, task)
    }
    };
    [27.15738]
    [27.15803]
    state.repo = Some(repo);
  • replacement in inflorescence/src/main.rs at line 564
    [27.15804][27.15804:15927](),[27.15927][28.2571:2586]()
    state.cursor.selection = selection;
    task
    } else {
    Task::none()
    };
    [27.15804]
    [28.2586]
    task
    }
  • replacement in inflorescence/src/main.rs at line 567
    [28.2587][28.2587:2648](),[28.2648][27.15928:15938](),[8.16440][27.15928:15938](),[27.15938][29.10809:10867](),[29.10867][27.15991:16134](),[27.15991][27.15991:16134](),[27.16134][29.10868:10940](),[29.10940][27.16344:16407](),[27.16344][27.16344:16407](),[27.16407][29.10941:11659](),[29.11659][27.16888:16920](),[27.16888][27.16888:16920](),[27.16920][8.16441:16466](),[8.16441][8.16441:16466]()
    state.repo = Some(repo_state);
    task
    }
    repo::MsgOut::GotChangeDiffs { hash, diffs } => {
    if let Some(cursor::Selection::LogChange {
    ix: _,
    hash: selected_hash,
    message: _,
    diffs: selection_diffs @ None,
    file: _,
    }) = state.cursor.selection.as_mut()
    {
    if *selected_hash == hash {
    let diffs = diffs
    .into_iter()
    .map(|(path, diffs)| {
    // NOTE: using unknown encoding as we don't yet have
    // the file for past
    // changes
    let file = diff::init_file(
    diff::FileContent::UnknownEncoding,
    Some(&diffs),
    );
    (path, (file, diff::State::default()))
    })
    .collect();
    *selection_diffs = Some(diffs);
    }
    }
    Task::none()
    [28.2587]
    [18.2911]
    fn repo_got_change_diffs(
    state: &mut State,
    hash: pijul::Hash,
    diffs: repo::ChangedFiles,
    ) -> Task<Msg> {
    if let Some(cursor::Selection::LogChange {
    ix: _,
    hash: selected_hash,
    message: _,
    diffs: selection_diffs @ None,
    file: _,
    }) = state.cursor.selection.as_mut()
    {
    if *selected_hash == hash {
    let diffs = diffs
    .into_iter()
    .map(|(path, diffs)| {
    // NOTE: using unknown encoding as we don't yet have
    // the file for past
    // changes
    let file = diff::init_file(
    diff::FileContent::UnknownEncoding,
    Some(&diffs),
    );
    (path, (file, diff::State::default()))
    })
    .collect();
    *selection_diffs = Some(diffs);
  • edit in inflorescence/src/main.rs at line 597
    [18.2927]
    [18.4296]
    Task::none()
  • replacement in inflorescence/src/main.rs at line 614
    [34.27805][34.27805:27851]()
    "r" => Some(Msg::Record),
    [34.27805]
    [34.27851]
    "r" => Some(Msg::StartRecord),