refactor diffs handling

[?]
Jul 4, 2025, 12:12 PM
YKHE3XMWOWPGOWYSISF73MIAKN7WB3AHCV2OA4ECAFPF47YHUXEAC

Dependencies

  • [2] WT3GA27P add cursor with selection
  • [3] UB2ITZJS refresh changed files on FS changes
  • [4] D7A7MSIH allow to defer or abandon record, add buttons
  • [5] AMPZ2BXK show changed files diffs (only Edit atm)
  • [6] V55EAIWQ add src file LRU cache
  • [7] ZVI4AWER woot contents_diff
  • [8] HC7ROIBC move main diffs state out of cursor
  • [9] FR52XEMW add action for log change file diff
  • [10] L6KSEFQI move cursor related stuff into its module
  • [11] BFN2VHZS refactor file stuff into sub-mod
  • [12] 23SFYK4Q big view refactor into a new crate
  • [13] OPXFZKEB view tests setup
  • [14] 3QVNMRNM test non-empty repo app view
  • [15] MYGIBRRH wip custom theme
  • [16] XSZZB47U refactor stuff into lib
  • [17] 3BK22XE5 add a test for hover btn and more refactors
  • [18] ACDXXAX2 refactor main's updates into smaller fns
  • [19] ESMM3FEL test selection reindexing
  • [20] TSFQFCB2 test got repo change
  • [21] 7SSBM4UQ view: refactor repo view
  • [22] S2T7RUKW add nav back placeholder
  • [23] I2AG42PA new cols layout
  • [24] 4PNWU55O replace the circular hor navigation
  • [25] SASAN2XC use nav-scrollable
  • [26] XZ6D3UUE avoid alloc
  • [27] ZD56BUSU add back +/- bg colors
  • [28] ONRCENKT rm unnecessary state from repo's internal state
  • [29] Y5ATDI2H convert changed file diffs and load src only if any needs it
  • [30] FVA36HBV restart repo manager task if it crashes
  • [31] SK3WVX7A add wee spacing for nav back
  • [32] BJXUYQ2Y show untracked file contents in read-only text editor
  • [33] 4WO3ZJM2 show untracked files' contents
  • [34] S2NVIFXR allow to enter record msg
  • [35] JE44NYHM display log files diffs
  • [36] VJNWIGSX clippy
  • [37] PKJCFSBM theme improvements
  • [38] I56UGW7U make record test, fix log update
  • [*] VCNKFNUF app init test
  • [*] 6YZAVBWU Initial commit

Change contents

  • edit in inflorescence_view/src/diff.rs at line 18
    [25.281][25.281:364]()
    /// Initialized once the file is loaded
    pub contents_count: Option<usize>,
  • replacement in inflorescence_view/src/diff.rs at line 96
    [26.74][25.691:834](),[25.691][25.691:834](),[25.834][26.75:105](),[26.105][25.834:898](),[25.834][25.834:898]()
    el(column([
    el(text("NAV")),
    el(iced_nav_scrollable::view(
    nav,
    sections_view,
    children_len,
    Msg::NavScrollable,
    )),
    ]))
    [26.74]
    [25.898]
    el(iced_nav_scrollable::view(
    nav,
    sections_view,
    children_len,
    Msg::NavScrollable,
    ))
  • replacement in inflorescence_view/src/diff.rs at line 138
    [25.1403][25.1403:1515](),[25.1515][26.233:283](),[26.283][25.1550:1614](),[25.1550][25.1550:1614]()
    el(column([
    el(text("NAV")),
    el(iced_nav_scrollable::view(
    nav,
    diffs,
    diffs_len,
    Msg::NavScrollable,
    )),
    ]))
    [25.1403]
    [25.1614]
    el(iced_nav_scrollable::view(
    nav,
    diffs,
    diffs_len,
    Msg::NavScrollable,
    ))
  • edit in inflorescence_view/src/cursor.rs at line 1
    [16.3124][16.3125:3142]()
    use crate::diff;
  • edit in inflorescence_view/src/cursor.rs at line 2
    [25.1743][16.3169:3201](),[16.3169][16.3169:3201]()
    use std::collections::HashMap;
  • edit in inflorescence_view/src/cursor.rs at line 31
    [16.3648][16.3648:3951]()
    /// All the diffs in this change keyed by file path. Loaded async
    /// and set to None only while loading. The
    /// `diff::State` is also in here so that is it
    /// preserved while navigating between files.
    diffs: Option<HashMap<String, (diff::File, diff::State)>>,
  • edit in inflorescence_view/src/app.rs at line 16
    [23.406][12.12674:12705](),[12.12674][12.12674:12705]()
    use std::collections::HashMap;
  • replacement in inflorescence_view/src/app.rs at line 30
    [12.12939][12.12939:12996]()
    pub diffs_state: &'a HashMap<file::Id, diff::State>,
    [12.12939]
    [12.12996]
    /// State of selected untracked of changed file, if any
    pub selected_diff: Option<&'a diff::State>,
    /// Diff and state of selected log's file, if any
    pub selected_log_diff: Option<(&'a diff::File, &'a diff::State)>,
  • replacement in inflorescence_view/src/app.rs at line 185
    [21.1905][21.1905:2046]()
    let selection_state = state.diffs_state.get(&id);
    diff::view(selection_state, file).map(move |msg| {
    [21.1905]
    [21.2046]
    diff::view(state.selected_diff, file).map(move |msg| {
  • replacement in inflorescence_view/src/app.rs at line 208
    [21.2846][21.2846:2987]()
    let selection_state = state.diffs_state.get(&id);
    diff::view(selection_state, file).map(move |msg| {
    [21.2846]
    [21.2987]
    diff::view(state.selected_diff, file).map(move |msg| {
  • edit in inflorescence_view/src/app.rs at line 227
    [21.3557][21.3557:3579]()
    diffs: _,
  • replacement in inflorescence_view/src/app.rs at line 307
    [23.2381][23.2381:2437](),[23.2437][21.4203:4399](),[21.4203][21.4203:4399](),[21.4399][23.2438:2469](),[23.2469][22.28:52](),[21.5333][22.28:52](),[22.52][23.2470:2887]()
    let col_2 = match state.cursor.selection.as_ref() {
    Some(cursor::Selection::LogChange {
    ix: _,
    hash,
    message: _,
    diffs,
    file: Some(cursor::LogChangeFileSelection { ix: _, path }),
    }) => Some(el(column([
    el(column([
    view_diff_header(format!(
    "{path} changes in {}:",
    display_short_hash(hash)
    )),
    match diffs {
    Some(diffs) => {
    let (file, state) = diffs.get(path).unwrap();
    diff::view(Some(state), file).map(|action| {
    Msg::LogChangeFileDiffAction {
    [23.2381]
    [23.2887]
    let col_2 =
    match state.cursor.selection.as_ref() {
    Some(cursor::Selection::LogChange {
    ix: _,
    hash,
    message: _,
    file: Some(cursor::LogChangeFileSelection { ix: _, path }),
    }) => Some(el(column([
    el(column([
    view_diff_header(format!(
    "{path} changes in {}:",
    display_short_hash(hash)
    )),
    match state.selected_log_diff {
    Some((file, state)) => diff::view(Some(state), file)
    .map(|action| Msg::LogChangeFileDiffAction {
  • replacement in inflorescence_view/src/app.rs at line 326
    [23.3024][23.3024:3159]()
    }
    })
    }
    None => el(text("Loading diff..")),
    [23.3024]
    [23.3159]
    }),
    None => el(text("Loading diff..")),
    },
    ])
    .width(Length::Fill)
    .height(Length::Fill)
    .spacing(SPACING)),
    // NOTE: This is currently never true - there are only up to 3
    // cols
    if hidden_cols == 2 {
    el(button(row([
    el(text("← ").font(Font::MONOSPACE)),
    el(text("Log")),
    ]))
    .on_press(Msg::Cursor(cursor::Msg::Left)))
    } else {
    el(row([]))
  • replacement in inflorescence_view/src/app.rs at line 347
    [23.3213][23.3213:3631](),[22.574][21.5564:5575](),[23.3631][21.5564:5575](),[21.5564][21.5564:5575](),[21.5575][22.575:634](),[22.634][23.3632:3661]()
    .spacing(SPACING)),
    // NOTE: This is currently never true - there are only up to 3 cols
    if hidden_cols == 2 {
    el(button(row([
    el(text("← ").font(Font::MONOSPACE)),
    el(text("Log")),
    ]))
    .on_press(Msg::Cursor(cursor::Msg::Left)))
    } else {
    el(row([]))
    },
    ])
    .width(Length::Fill)
    .height(Length::Fill)
    .spacing(SPACING))),
    [23.3213]
    [12.24109]
    .spacing(SPACING))),
  • replacement in inflorescence_view/src/app.rs at line 349
    [12.24110][23.3662:3853]()
    Some(cursor::Selection::UntrackedFile { .. })
    | Some(cursor::Selection::ChangedFile { .. })
    | Some(cursor::Selection::LogChange { .. })
    | None => None,
    };
    [12.24110]
    [12.24230]
    Some(cursor::Selection::UntrackedFile { .. })
    | Some(cursor::Selection::ChangedFile { .. })
    | Some(cursor::Selection::LogChange { .. })
    | None => None,
    };
  • replacement in inflorescence_view/src/app/test.rs at line 6
    [13.4320][16.5801:5854]()
    use libflorescence::prelude::pijul::{self, HashMap};
    [13.4320]
    [16.5854]
    use libflorescence::prelude::pijul;
  • edit in inflorescence_view/src/app/test.rs at line 34
    [13.4879][13.4879:4917]()
    let diffs_state = HashMap::new();
  • replacement in inflorescence_view/src/app/test.rs at line 42
    [13.5104][13.5104:5139]()
    diffs_state: &diffs_state,
    [13.5104]
    [13.5139]
    selected_diff: None,
    selected_log_diff: None,
  • replacement in inflorescence_view/src/app/test.rs at line 69
    [14.254][14.254:289]()
    diffs_state: &diffs_state,
    [14.254]
    [14.289]
    selected_diff: None,
    selected_log_diff: None,
  • replacement in inflorescence_view/src/app/test.rs at line 109
    [15.8425][15.8425:8460]()
    diffs_state: &diffs_state,
    [15.8425]
    [15.8460]
    selected_diff: None,
    selected_log_diff: None,
  • replacement in inflorescence_view/src/app/test.rs at line 129
    [17.1433][17.1433:1468]()
    diffs_state: &diffs_state,
    [17.1433]
    [17.1468]
    selected_diff: None,
    selected_log_diff: None,
  • replacement in inflorescence_view/src/app/test.rs at line 159
    [13.5787][13.5787:5822]()
    diffs_state: &diffs_state,
    [13.5787]
    [13.5822]
    selected_diff: None,
    selected_log_diff: None,
  • edit in inflorescence/src/test.rs at line 628
    [19.7338][19.7338:7359]()
    diffs: None,
  • replacement in inflorescence/src/test.rs at line 694
    [20.1018][20.1018:1052]()
    // Case: selection is changed
    [20.1018]
    [20.1052]
    // Case: selection is changed and doesn't match the loaded diff
  • edit in inflorescence/src/test.rs at line 700
    [20.1195][20.1195:1216]()
    diffs: None,
  • edit in inflorescence/src/test.rs at line 706
    [20.1433][20.1433:1568]()
    assert_matches!(
    state.cursor.selection.as_ref().unwrap(),
    cursor::Selection::LogChange { diffs: None, .. }
    );
  • replacement in inflorescence/src/test.rs at line 708
    [20.1650][20.1650:1691]()
    // Case: selection is still the same
    [20.1650]
    [20.1691]
    // Case: selection is still the same and matches the loaded diff
  • edit in inflorescence/src/test.rs at line 714
    [20.1834][20.1834:1855]()
    diffs: None,
  • replacement in inflorescence/src/test.rs at line 718
    [20.1996][20.1996:2025]()
    assert!(task.is_none());
    [20.1996]
    [20.2025]
    // Initializes nav-scrollables for diffs
    assert!(task.is_some());
  • edit in inflorescence/src/test.rs at line 721
    [20.2072][20.2072:2210]()
    assert_matches!(
    state.cursor.selection.as_ref().unwrap(),
    cursor::Selection::LogChange { diffs: Some(_), .. }
    );
  • replacement in inflorescence/src/main.rs at line 102
    [11.164][8.50:91](),[7.991][8.50:91]()
    diffs_state: HashMap::new(),
    [11.164]
    [3.1520]
    files_diffs: HashMap::new(),
    log_diffs: HashMap::new(),
  • edit in inflorescence/src/main.rs at line 119
    [4.58]
    [11.165]
    /// Cache for untracked and changed files loaded from disk
  • replacement in inflorescence/src/main.rs at line 121
    [11.189][25.2444:2489](),[25.2489][11.189:238](),[11.189][11.189:238]()
    // Diffs for untracked and changed files
    diffs_state: HashMap<file::Id, diff::State>,
    [11.189]
    [6.1025]
    /// Diffs for untracked and changed files
    files_diffs: HashMap<file::Id, diff::State>,
    /// The diffs are loaded async and not present while loading.
    log_diffs: HashMap<LogFileId, LogFileDiff>,
  • edit in inflorescence/src/main.rs at line 148
    [25.2662]
    [2.735]
    }
    #[derive(Debug, Clone, PartialEq, Eq, Hash)]
    struct LogFileId {
    pub hash: pijul::Hash,
    pub path: String,
    }
    #[derive(Debug)]
    struct LogFileDiff {
    pub diff: diff::File,
    pub state: diff::State,
  • replacement in inflorescence/src/main.rs at line 194
    [25.2867][25.2867:2937]()
    state.diffs_state.entry(id.clone()).or_default();
    [25.2867]
    [25.2937]
    state.files_diffs.entry(id.clone()).or_default();
  • replacement in inflorescence/src/main.rs at line 206
    [25.3376][25.3376:3447]()
    if let Some(diff_state) = state.diffs_state.get_mut(&id) {
    [25.3376]
    [25.3447]
    if let Some(diff_state) = state.files_diffs.get_mut(&id) {
  • replacement in inflorescence/src/main.rs at line 221
    [25.3963][25.3963:4024]()
    diffs: Some(diffs),
    file: _,
    [25.3963]
    [25.4024]
    file: Some(cursor::LogChangeFileSelection { ix: _, path }),
  • replacement in inflorescence/src/main.rs at line 225
    [25.4132][25.4132:4276]()
    if let Some((_file, diff_state)) = diffs.get_mut(&file) {
    let task = diff::update(diff_state, msg);
    [25.4132]
    [25.4276]
    let id = LogFileId {
    hash,
    path: path.clone(),
    };
    if let Some(LogFileDiff { diff: _, state }) =
    state.log_diffs.get_mut(&id)
    {
    let task = diff::update(state, msg);
  • replacement in inflorescence/src/main.rs at line 285
    [12.26390][25.4546:6527]()
    app::Msg::Cursor(msg) => {
    let log_file_selection_before =
    if let Some(cursor::Selection::LogChange {
    file: Some(file),
    ..
    }) = state.cursor.selection.as_ref()
    {
    Some(file.ix)
    } else {
    None
    };
    let cursor_task = cursor::update(
    &mut state.cursor,
    &mut state.files,
    state.repo.as_ref(),
    msg,
    )
    .map(|msg| Msg::View(app::Msg::ToRepo(msg)));
    // If the log file selection has changed, initialize a nav for it
    if let Some(cursor::Selection::LogChange {
    hash,
    diffs: Some(diffs),
    file: Some(cursor::LogChangeFileSelection { ix, path }),
    ..
    }) = state.cursor.selection.as_mut()
    {
    if Some(*ix) != log_file_selection_before {
    let (
    _file,
    diff::State {
    contents_count,
    nav,
    ..
    },
    ) = diffs.get_mut(path).unwrap();
    let (new_nav, nav_tasks) =
    iced_nav_scrollable::init(contents_count.unwrap());
    *nav = Some(new_nav);
    let hash = *hash;
    let path_clone = path.clone();
    return Task::batch([
    cursor_task,
    nav_tasks.map(move |msg| Msg::LogChangeDiff {
    hash,
    file: path_clone.clone(),
    msg: diff::Msg::NavScrollable(msg),
    }),
    ]);
    }
    }
    cursor_task
    }
    [12.26390]
    [12.26614]
    app::Msg::Cursor(msg) => cursor::update(
    &mut state.cursor,
    &mut state.files,
    state.repo.as_ref(),
    msg,
    )
    .map(|msg| Msg::View(app::Msg::ToRepo(msg))),
  • replacement in inflorescence/src/main.rs at line 488
    [18.6738][25.6528:6606]()
    let diffs = state.diffs_state.entry(id.clone()).or_default();
    [18.6738]
    [25.6606]
    let diffs = state.files_diffs.entry(id.clone()).or_default();
  • replacement in inflorescence/src/main.rs at line 500
    [18.7008][25.6826:6904]()
    let diffs = state.diffs_state.entry(id.clone()).or_default();
    [18.7008]
    [25.6904]
    let diffs = state.files_diffs.entry(id.clone()).or_default();
  • edit in inflorescence/src/main.rs at line 525
    [18.7485][18.7485:7513]()
    diffs: Some(diffs),
  • replacement in inflorescence/src/main.rs at line 533
    [18.7740][18.7740:7804](),[18.7804][25.7105:7331]()
    let (_file, state) = diffs.get_mut(&file).unwrap();
    return diff::update(state, action).map(move |msg| {
    Msg::LogChangeDiff {
    hash,
    file: file.clone(),
    msg,
    }
    });
    [18.7740]
    [9.874]
    let id = LogFileId {
    hash,
    path: file.clone(),
    };
    if let Some(LogFileDiff { diff: _, state }) =
    state.log_diffs.get_mut(&id)
    {
    return diff::update(state, action).map(move |msg| {
    Msg::LogChangeDiff {
    hash,
    file: file.clone(),
    msg,
    }
    });
    }
  • edit in inflorescence/src/main.rs at line 681
    [18.11281][18.11281:11307]()
    diffs: _,
  • edit in inflorescence/src/main.rs at line 705
    [18.12468][18.12468:12509]()
    diffs: None,
  • edit in inflorescence/src/main.rs at line 739
    [18.13001][18.13001:13040]()
    diffs: selection_diffs @ None,
  • replacement in inflorescence/src/main.rs at line 743
    [18.13140][25.7332:7406](),[25.7406][18.13170:13311](),[18.13170][18.13170:13311](),[18.13311][20.2371:2420](),[20.2420][18.13383:13552](),[18.13383][18.13383:13552](),[18.13552][25.7407:7694](),[25.7694][18.13611:13702](),[18.13611][18.13611:13702]()
    let diffs: HashMap<String, (diff::File, diff::State)> = 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),
    );
    let contents_count = diff::contents_count(&file);
    let state = diff::State {
    contents_count: Some(contents_count),
    ..default()
    };
    (path.clone(), (file, state))
    })
    .collect();
    *selection_diffs = Some(diffs);
    [18.13140]
    [5.2911]
    let mut tasks = Vec::with_capacity(diffs.len());
    diffs.into_iter().for_each(|(path, diffs)| {
    // NOTE: using unknown encoding as we don't yet have the file
    // for past changes
    let diff = diff::init_file(
    diff::FileContent::UnknownEncoding,
    Some(&diffs),
    );
    let contents_count = diff::contents_count(&diff);
    let (nav, nav_tasks) =
    iced_nav_scrollable::init(contents_count);
    let diff_state = diff::State {
    nav: Some(nav),
    state: libflorescence::diff::State::default(),
    };
    let path_clone = path.clone();
    let id = LogFileId { hash, path };
    let log_file_diff = LogFileDiff {
    diff,
    state: diff_state,
    };
    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),
    }))
    });
    return Task::batch(tasks);
  • replacement in inflorescence/src/main.rs at line 846
    [12.28696][12.28696:28717]()
    diffs_state,
    [12.28696]
    [12.28717]
    files_diffs: diffs_state,
    log_diffs,
  • edit in inflorescence/src/main.rs at line 849
    [12.28732]
    [12.28732]
    let selected_diff = match state.cursor.selection.as_ref() {
    Some(cursor::Selection::UntrackedFile { ix: _, path }) => {
    let id = file::Id {
    path: path.clone(),
    file_kind: file::Kind::Untracked,
    };
    diffs_state.get(&id)
    }
    Some(cursor::Selection::ChangedFile { path, ix: _ }) => {
    let id = file::Id {
    path: path.clone(),
    file_kind: file::Kind::Changed,
    };
    diffs_state.get(&id)
    }
    Some(cursor::Selection::LogChange { .. }) | None => None,
    };
    let selected_log_diff = match state.cursor.selection.as_ref() {
    Some(cursor::Selection::LogChange {
    ix: _,
    hash,
    message: _,
    file: Some(cursor::LogChangeFileSelection { ix: _, path }),
    }) => {
    let id = LogFileId {
    hash: *hash,
    path: path.clone(),
    };
    log_diffs
    .get(&id)
    .map(|LogFileDiff { diff, state }| (diff, state))
    }
    _ => None,
    };
  • replacement in inflorescence/src/main.rs at line 893
    [12.28889][12.28889:28914]()
    diffs_state,
    [12.28889]
    [12.28914]
    selected_diff,
    selected_log_diff,
  • edit in inflorescence/src/cursor.rs at line 79
    [10.6700][10.6700:6731]()
    diffs,
  • edit in inflorescence/src/cursor.rs at line 107
    [10.7919][10.7919:7966]()
    diffs,
  • edit in inflorescence/src/cursor.rs at line 230
    [10.15687][10.15687:15718]()
    diffs,
  • edit in inflorescence/src/cursor.rs at line 252
    [10.16712][10.16712:16759]()
    diffs,
  • edit in inflorescence/src/cursor.rs at line 321
    [24.401][24.401:436]()
    diffs,
  • edit in inflorescence/src/cursor.rs at line 327
    [24.682][24.682:721]()
    diffs,
  • edit in inflorescence/src/cursor.rs at line 357
    [10.20793][10.20793:20828]()
    diffs,
  • edit in inflorescence/src/cursor.rs at line 439
    [10.24891][10.24891:24932]()
    diffs: None,
  • edit in inflorescence/src/cursor.rs at line 450
    [10.25352][10.25352:25387]()
    diffs,
  • edit in inflorescence/src/cursor.rs at line 459
    [10.25801][10.25801:25844]()
    diffs,
  • edit in inflorescence/src/cursor.rs at line 525
    [10.27628][10.27628:27653]()
    diffs: None,