use nav-scrollable for repo status

[?]
Jul 16, 2025, 5:36 PM
PTWZYQFRWWUOE2WMQT26CKZKFSHAIJVJS3QWHJFYUFDRRTVPHSUAC

Dependencies

  • [2] SWWE2R6M display basic repo stuff
  • [3] WT3GA27P add cursor with selection
  • [4] W7IUT3ZV start recording impl
  • [5] YBJRDOTC make all repo actions async
  • [6] A5YBC77V record!
  • [7] D7A7MSIH allow to defer or abandon record, add buttons
  • [8] AMPZ2BXK show changed files diffs (only Edit atm)
  • [9] ONRCENKT rm unnecessary state from repo's internal state
  • [10] 23SFYK4Q big view refactor into a new crate
  • [11] OPXFZKEB view tests setup
  • [12] 3QVNMRNM test non-empty repo app view
  • [13] PKJCFSBM theme improvements
  • [14] XSZZB47U refactor stuff into lib
  • [15] 3BK22XE5 add a test for hover btn and more refactors
  • [16] WGID4LS4 absolutely slayed testing with iced task
  • [17] ACDXXAX2 refactor main's updates into smaller fns
  • [18] I56UGW7U make record test, fix log update
  • [19] YYKXNBFL test: add untracked file
  • [20] 5CYU7UT7 test: rm added file
  • [21] ESMM3FEL test selection reindexing
  • [22] UF5NJKAS test load repo
  • [23] 7SSBM4UQ view: refactor repo view
  • [24] OC6DLIZ3 test record when nothing to record
  • [25] I2AG42PA new cols layout
  • [26] DXAYDIMQ update to latest pijul
  • [27] WW36JYLR add iced_nav_scrollable widget crate
  • [28] SASAN2XC use nav-scrollable
  • [29] XZ6D3UUE avoid alloc
  • [30] KEPKF3WO unify diffs handling, simplify view
  • [31] GOLHUD6R nav-scrollable: set skip-able sections
  • [32] K5YUSV2W auto-scroll to last offset
  • [33] KWTBNTO3 diffs selection and scrolling
  • [34] 5MUEECMJ smooth scrolling nav
  • [35] WXQBBQ2A update nightly
  • [36] 65DXFP3Y fix status overflow
  • [37] KM5PSZ4A watch repo once loaded
  • [38] 4WO3ZJM2 show untracked files' contents
  • [39] ZVI4AWER woot contents_diff
  • [40] MYGIBRRH wip custom theme
  • [41] X6AK4QPX finish recording test
  • [42] WIFVLV37 nav-scrollabe: detect size to determine if needs scrolling, msg when ready
  • [43] IQDCHWCP load a pijul repo
  • [44] KMB6FND3 test view update fn rather than direct fn calls
  • [45] DST3HRZZ fix emoji rendering
  • [46] SWDPAGF6 test channel name
  • [47] EC3TVL4X add untracked files
  • [48] W4LFX7IH group diffs by file name
  • [49] BJXUYQ2Y show untracked file contents in read-only text editor
  • [50] YKHE3XMW refactor diffs handling
  • [51] ELG3UDT6 allow to rm added files
  • [52] S2NVIFXR allow to enter record msg
  • [*] VCNKFNUF app init test
  • [*] 6YZAVBWU Initial commit

Change contents

  • edit in libflorescence/src/repo.rs at line 205
    [2.595]
    [2.595]
    }
    pub fn load(path: &Path) -> Result<(InternalState, State), LoadError> {
    match std::fs::exists(path) {
    Ok(true) => {}
    Ok(false) => return Err(LoadError::DoesntExist),
    Err(e) => return Err(LoadError::Inaccessible(e)),
    }
    let repo = pijul::Repository::find_root(Some(path))
    .map_err(|_e| LoadError::NotPijulRepo)?;
    let internal_state = InternalState { repo };
    let state = get_state(&internal_state);
    Ok((internal_state, state))
    }
    pub fn hash_bytes(bytes: &[u8]) -> ChangeHash {
    let mut hasher = pijul::pristine::Hasher::default();
    hasher.update(bytes);
    hasher.finish()
    }
    /// Number of sections for nav-scrollable, containing untracked files, changed
    /// files, most recent log changes and a header for each of these 3 sections.
    pub fn nav_contents_count(repo: &State) -> usize {
    3 + repo.untracked_files.len() + repo.changed_files.len() + repo.log.len()
  • edit in libflorescence/src/repo.rs at line 296
    [9.1392][5.3056:3058](),[5.3056][5.3056:3058](),[5.3058][2.662:663](),[2.662][2.662:663](),[2.663][26.263:376](),[26.376][22.1657:1906](),[22.1657][22.1657:1906](),[22.1906][9.1445:1538](),[4.217][9.1445:1538](),[9.1538][22.1907:1939]()
    }
    pub(crate) fn load(path: &Path) -> Result<(InternalState, State), LoadError> {
    match std::fs::exists(path) {
    Ok(true) => {}
    Ok(false) => return Err(LoadError::DoesntExist),
    Err(e) => return Err(LoadError::Inaccessible(e)),
    }
    let repo = pijul::Repository::find_root(Some(path))
    .map_err(|_e| LoadError::NotPijulRepo)?;
    let internal_state = InternalState { repo };
    let state = get_state(&internal_state);
    Ok((internal_state, state))
  • edit in libflorescence/src/repo.rs at line 298
    [9.1569][33.317:471]()
    pub fn hash_bytes(bytes: &[u8]) -> ChangeHash {
    let mut hasher = pijul::pristine::Hasher::default();
    hasher.update(bytes);
    hasher.finish()
    }
  • edit in inflorescence_view/src/app.rs at line 7
    [28.1790]
    [10.12583]
    use iced_nav_scrollable::NavScrollable;
  • replacement in inflorescence_view/src/app.rs at line 28
    [10.12822][10.12822:12861]()
    pub repo: Option<&'a repo::State>,
    [10.12822]
    [10.12861]
    pub repo: Option<&'a Repo>,
  • edit in inflorescence_view/src/app.rs at line 36
    [10.12999]
    [10.12999]
    #[derive(Debug)]
    pub struct Repo {
    pub state: repo::State,
    /// Scrollable status view contains the overview of untracked files,
    /// changed files and most recent log changes.
    pub status_nav: NavScrollable,
    }
  • edit in inflorescence_view/src/app.rs at line 48
    [10.13088]
    [10.13088]
    StatusNav(iced_nav_scrollable::Msg),
  • replacement in inflorescence_view/src/app.rs at line 74
    [30.531][13.2689:2747](),[10.15637][13.2689:2747]()
    let inner = if let Some(repo) = state.repo.as_ref() {
    [30.531]
    [30.532]
    let inner = if let Some(repo) = state.repo {
  • replacement in inflorescence_view/src/app.rs at line 85
    [10.15950][30.565:583](),[30.583][23.279:329](),[23.279][23.279:329](),[23.329][30.584:615]()
    fn view_repo<'a>(
    state: &State<'a>,
    repo: &'a repo::State,
    ) -> Element<'a, Msg, Theme> {
    [10.15950]
    [23.436]
    fn view_repo<'a>(state: &State<'a>, repo: &'a Repo) -> Element<'a, Msg, Theme> {
    let Repo {
    state: repo,
    status_nav,
    } = repo;
  • replacement in inflorescence_view/src/app.rs at line 101
    [23.702][23.702:765]()
    el(column(repo.untracked_files.iter().enumerate().map(
    [23.702]
    [10.16052]
    repo.untracked_files.iter().enumerate().map(
  • replacement in inflorescence_view/src/app.rs at line 116
    [10.16619][10.16619:16631]()
    )))
    [10.16619]
    [23.766]
    )
  • replacement in inflorescence_view/src/app.rs at line 120
    [23.803][23.803:864]()
    el(column(repo.changed_files.iter().enumerate().map(
    [23.803]
    [10.16741]
    repo.changed_files.iter().enumerate().map(
  • replacement in inflorescence_view/src/app.rs at line 136
    [10.17345][10.17345:17357]()
    )))
    [10.17345]
    [23.865]
    )
  • replacement in inflorescence_view/src/app.rs at line 140
    [23.892][23.892:943]()
    el(column(repo.log.iter().enumerate().map(
    [23.892]
    [10.17447]
    repo.log.iter().enumerate().map(
  • replacement in inflorescence_view/src/app.rs at line 160
    [10.18307][10.18307:18319]()
    )))
    [10.18307]
    [23.944]
    )
  • replacement in inflorescence_view/src/app.rs at line 228
    [23.3613][23.3613:3689]()
    let entry = state.repo.as_ref().unwrap().log.get(*ix).unwrap();
    [23.3613]
    [10.18975]
    let entry = repo.log.get(*ix).unwrap();
  • edit in inflorescence_view/src/app.rs at line 279
    [25.1588]
    [25.1588]
    let status_nav_children = [el(text("Untracked files:"))]
    .into_iter()
    .chain(untracked_files())
    .chain([el(text("Changed files:"))])
    .chain(changed_files())
    .chain([el(text("Recent changes:"))])
    .chain(log());
    let status_nav_children_len = repo::nav_contents_count(repo);
  • replacement in inflorescence_view/src/app.rs at line 291
    [25.1635][36.28:281]()
    el(scrollable(column([
    el(column([el(text("Untracked files:")), untracked_files()])),
    el(column([el(text("Changed files:")), changed_files()])),
    el(column([el(text("Recent changes:")), log()])),
    ]))),
    [25.1635]
    [25.1831]
    el(iced_nav_scrollable::view(
    status_nav,
    status_nav_children,
    status_nav_children_len,
    Msg::StatusNav,
    )),
  • replacement in inflorescence_view/src/app/test.rs at line 1
    [11.4223][30.2549:2583]()
    use super::{cursor, view, State};
    [11.4223]
    [15.932]
    use super::{cursor, view, Repo, State};
  • replacement in inflorescence_view/src/app/test.rs at line 11
    [11.4393][14.5881:5925]()
    use std::collections::{BTreeMap, BTreeSet};
    [11.4393]
    [14.5925]
    use std::collections::{BTreeMap, BTreeSet, HashSet};
  • replacement in inflorescence_view/src/app/test.rs at line 49
    [11.5409][11.5409:5443]()
    let repo = Some(repo::State {
    [11.5409]
    [11.5443]
    let repo_state = repo::State {
  • edit in inflorescence_view/src/app/test.rs at line 55
    [11.5629]
    [12.96]
    };
    let contents_count = repo::nav_contents_count(&repo_state);
    let (status_nav, _task) =
    iced_nav_scrollable::init(contents_count, HashSet::new());
    let repo = Some(Repo {
    state: repo_state,
    status_nav,
  • replacement in inflorescence_view/src/app/test.rs at line 77
    [12.563][12.563:597]()
    let repo = Some(repo::State {
    [12.563]
    [12.597]
    let repo_state = repo::State {
  • edit in inflorescence_view/src/app/test.rs at line 96
    [12.1560]
    [11.5629]
    };
    let contents_count = repo::nav_contents_count(&repo_state);
    let (status_nav, _task) =
    iced_nav_scrollable::init(contents_count, HashSet::new());
    let repo = Some(Repo {
    state: repo_state,
    status_nav,
  • replacement in inflorescence/src/test.rs at line 78
    [24.97][24.97:170]()
    assert_eq!(state.repo.as_ref().unwrap().log.len(), INITIAL_LOG_LEN);
    [24.97]
    [18.2194]
    assert_eq!(
    state.repo.as_ref().unwrap().state.log.len(),
    INITIAL_LOG_LEN
    );
  • replacement in inflorescence/src/test.rs at line 158
    [18.4341][24.171:248]()
    assert_eq!(state.repo.as_ref().unwrap().log.len(), INITIAL_LOG_LEN + 1);
    [18.4341]
    [18.4488]
    assert_eq!(
    state.repo.as_ref().unwrap().state.log.len(),
    INITIAL_LOG_LEN + 1
    );
  • replacement in inflorescence/src/test.rs at line 301
    [19.426][19.426:564]()
    assert!(state.repo.as_ref().unwrap().untracked_files.is_empty());
    assert!(state.repo.as_ref().unwrap().changed_files.is_empty());
    [19.426]
    [19.564]
    {
    let repo_state = &state.repo.as_ref().unwrap().state;
    assert!(repo_state.untracked_files.is_empty());
    assert!(repo_state.changed_files.is_empty());
    }
  • replacement in inflorescence/src/test.rs at line 327
    [18.4747][19.1263:1531]()
    assert_eq!(state.repo.as_ref().unwrap().untracked_files.len(), 1);
    assert!(state
    .repo
    .as_ref()
    .unwrap()
    .untracked_files
    .contains(file_to_record));
    assert!(state.repo.as_ref().unwrap().changed_files.is_empty());
    [18.4747]
    [19.1531]
    {
    let repo_state = &state.repo.as_ref().unwrap().state;
    assert_eq!(repo_state.untracked_files.len(), 1);
    assert!(repo_state.untracked_files.contains(file_to_record));
    assert!(repo_state.changed_files.is_empty());
    }
  • replacement in inflorescence/src/test.rs at line 358
    [19.2197][19.2197:2708]()
    assert!(state.repo.as_ref().unwrap().untracked_files.is_empty());
    assert_eq!(state.repo.as_ref().unwrap().changed_files.len(), 1);
    assert!(state
    .repo
    .as_ref()
    .unwrap()
    .changed_files
    .contains_key(file_to_record));
    assert_eq!(
    state
    .repo
    .as_ref()
    .unwrap()
    .changed_files
    .get(file_to_record)
    .unwrap(),
    &BTreeSet::from_iter([repo::ChangedFileDiff::Add])
    );
    [19.2197]
    [20.79]
    {
    let repo_state = &state.repo.as_ref().unwrap().state;
    assert!(repo_state.untracked_files.is_empty());
    assert_eq!(repo_state.changed_files.len(), 1);
    assert!(repo_state.changed_files.contains_key(file_to_record));
    assert_eq!(
    repo_state.changed_files.get(file_to_record).unwrap(),
    &BTreeSet::from_iter([repo::ChangedFileDiff::Add])
    );
    }
  • replacement in inflorescence/src/test.rs at line 391
    [20.724][20.724:992]()
    assert_eq!(state.repo.as_ref().unwrap().untracked_files.len(), 1);
    assert!(state
    .repo
    .as_ref()
    .unwrap()
    .untracked_files
    .contains(file_to_record));
    assert!(state.repo.as_ref().unwrap().changed_files.is_empty());
    [20.724]
    [19.2708]
    {
    let repo_state = &state.repo.as_ref().unwrap().state;
    assert_eq!(repo_state.untracked_files.len(), 1);
    assert!(repo_state.untracked_files.contains(file_to_record));
    assert!(repo_state.changed_files.is_empty());
    }
  • replacement in inflorescence/src/test.rs at line 426
    [21.1187][21.1187:1226]()
    } = &state.repo.as_ref().unwrap();
    [21.1187]
    [21.1226]
    } = &state.repo.as_ref().unwrap().state;
  • replacement in inflorescence/src/test.rs at line 750
    [18.7033][18.7033:7082]()
    let log = &state.repo.as_ref().unwrap().log;
    [18.7001]
    [19.2712]
    let log = &state.repo.as_ref().unwrap().state.log;
  • replacement in inflorescence/src/main.rs at line 26
    [16.5338][33.8784:8816]()
    use std::collections::BTreeSet;
    [16.5338]
    [16.5369]
    use std::collections::{BTreeSet, HashSet};
  • replacement in inflorescence/src/main.rs at line 116
    [5.6144][5.6144:6175]()
    repo: Option<repo::State>,
    [5.6144]
    [3.625]
    repo: Option<app::Repo>,
  • replacement in inflorescence/src/main.rs at line 177
    [10.26187][28.2663:2762]()
    let loaded =
    file::update(&mut state.files, state.repo.as_ref(), msg);
    [10.26187]
    [31.37]
    let loaded = file::update(
    &mut state.files,
    state.repo.as_ref().map(|repo| &repo.state),
    msg,
    );
  • replacement in inflorescence/src/main.rs at line 271
    [34.800][32.233:270](),[32.233][32.233:270]()
    state.repo.as_ref(),
    [34.800]
    [32.291]
    state.repo.as_ref().map(|repo| &repo.state),
  • edit in inflorescence/src/main.rs at line 335
    [10.26733]
    [17.343]
    app::Msg::StatusNav(msg) => {
    if let Some(nav) =
    state.repo.as_mut().map(|repo| &mut repo.status_nav)
    {
    iced_nav_scrollable::update(nav, msg)
    .map(|msg| Msg::View(app::Msg::StatusNav(msg)))
    } else {
    Task::none()
    }
    }
  • replacement in inflorescence/src/main.rs at line 372
    [17.1265][35.101:145]()
    if let Some(repo) = state.repo.as_mut()
    [17.1265]
    [35.145]
    if let Some(app::Repo {
    state: repo_state,
    status_nav,
    }) = state.repo.as_mut()
  • replacement in inflorescence/src/main.rs at line 387
    [6.5330][35.341:501]()
    let removed = repo.untracked_files.remove(path);
    debug_assert!(removed, "{:?}, path: {path}", repo.untracked_files);
    repo.changed_files
    [6.5330]
    [35.501]
    let removed = repo_state.untracked_files.remove(path);
    debug_assert!(
    removed,
    "{:?}, path: {path}",
    repo_state.untracked_files
    );
    repo_state
    .changed_files
  • edit in inflorescence/src/main.rs at line 398
    [35.609]
    [35.609]
    // Re-initialize nav
    let contents_count = repo::nav_contents_count(repo_state);
    let (new_status_nav, status_nav_task) =
    iced_nav_scrollable::init(contents_count, HashSet::default());
    *status_nav = new_status_nav;
  • replacement in inflorescence/src/main.rs at line 406
    [35.659][35.659:729]()
    state.cursor.selection = if repo.untracked_files.is_empty() {
    [35.659]
    [35.729]
    state.cursor.selection = if repo_state.untracked_files.is_empty() {
  • replacement in inflorescence/src/main.rs at line 409
    [35.763][35.763:910]()
    let ix = cmp::min(*ix, repo.untracked_files.len() - 1);
    Some(cursor::untracked_file_selection(repo, ix, &mut state.files))
    [35.763]
    [35.910]
    let ix = cmp::min(*ix, repo_state.untracked_files.len() - 1);
    Some(cursor::untracked_file_selection(
    repo_state,
    ix,
    &mut state.files,
    ))
  • edit in inflorescence/src/main.rs at line 416
    [35.921]
    [17.2296]
    return status_nav_task.map(|msg| Msg::View(app::Msg::StatusNav(msg)));
  • replacement in inflorescence/src/main.rs at line 423
    [17.2373][35.922:966]()
    if let Some(repo) = state.repo.as_mut()
    [17.2373]
    [35.966]
    if let Some(app::Repo {
    state: repo_state,
    status_nav,
    }) = state.repo.as_mut()
  • replacement in inflorescence/src/main.rs at line 433
    [35.1026][35.1026:1085]()
    let diffs = repo.changed_files.get(path).unwrap();
    [35.1026]
    [35.1085]
    let diffs = repo_state.changed_files.get(path).unwrap();
  • replacement in inflorescence/src/main.rs at line 444
    [35.1377][35.1377:1436]()
    let removed = repo.changed_files.remove(path);
    [35.1377]
    [35.1436]
    let removed = repo_state.changed_files.remove(path);
  • replacement in inflorescence/src/main.rs at line 449
    [35.1562][35.1562:1597]()
    repo.changed_files
    [35.1562]
    [35.1597]
    repo_state.changed_files
  • replacement in inflorescence/src/main.rs at line 452
    [35.1650][35.1650:1705]()
    repo.untracked_files.insert(path.clone());
    [35.1650]
    [35.1705]
    repo_state.untracked_files.insert(path.clone());
    // Re-initialize nav
    let contents_count = repo::nav_contents_count(repo_state);
    let (new_status_nav, status_nav_task) =
    iced_nav_scrollable::init(contents_count, HashSet::default());
    *status_nav = new_status_nav;
  • replacement in inflorescence/src/main.rs at line 461
    [35.1757][35.1757:1829]()
    state.cursor.selection = if repo.changed_files.is_empty() {
    [35.1757]
    [35.1829]
    state.cursor.selection = if repo_state.changed_files.is_empty() {
  • replacement in inflorescence/src/main.rs at line 464
    [35.1871][35.1871:2022]()
    let ix = cmp::min(*ix, repo.changed_files.len() - 1);
    Some(cursor::changed_file_selection(repo, ix, &mut state.files))
    [35.1871]
    [35.2022]
    let ix = cmp::min(*ix, repo_state.changed_files.len() - 1);
    Some(cursor::changed_file_selection(
    repo_state,
    ix,
    &mut state.files,
    ))
  • edit in inflorescence/src/main.rs at line 471
    [35.2037]
    [7.1813]
    return status_nav_task
    .map(|msg| Msg::View(app::Msg::StatusNav(msg)));
  • replacement in inflorescence/src/main.rs at line 484
    [8.2062][17.3927:3970]()
    if repo.changed_files.is_empty() {
    [8.2062]
    [17.3970]
    if repo.state.changed_files.is_empty() {
  • replacement in inflorescence/src/main.rs at line 528
    [17.5532][17.5532:5589]()
    state.repo.as_mut().unwrap().changed_files =
    [17.5532]
    [17.5589]
    state.repo.as_mut().unwrap().state.changed_files =
  • replacement in inflorescence/src/main.rs at line 633
    [17.7930][17.7930:7959]()
    state.repo = Some(repo);
    [17.7930]
    [17.7959]
    let contents_count = repo::nav_contents_count(&repo);
    let (status_nav, status_nav_task) =
    iced_nav_scrollable::init(contents_count, HashSet::default());
    state.repo = Some(app::Repo {
    state: repo,
    status_nav,
    });
  • replacement in inflorescence/src/main.rs at line 677
    [17.9154][17.9154:9169]()
    watch_task
    [17.9154]
    [17.9169]
    Task::batch([
    status_nav_task.map(|msg| Msg::View(app::Msg::StatusNav(msg))),
    watch_task,
    ])
  • replacement in inflorescence/src/main.rs at line 695
    [17.9475][21.8635:8669]()
    let task = reindex_selection(
    [17.9475]
    [21.8669]
    let cursor_task = reindex_selection(
  • replacement in inflorescence/src/main.rs at line 703
    [21.8791][21.8791:8820]()
    state.repo = Some(repo);
    [21.8791]
    [21.8820]
    let contents_count = repo::nav_contents_count(&repo);
    let (status_nav, status_nav_task) =
    iced_nav_scrollable::init(contents_count, HashSet::default());
  • replacement in inflorescence/src/main.rs at line 707
    [21.8821][21.8821:8830]()
    task
    [21.8821]
    [21.8830]
    state.repo = Some(app::Repo {
    state: repo,
    status_nav,
    });
    Task::batch([
    cursor_task,
    status_nav_task.map(|msg| Msg::View(app::Msg::StatusNav(msg))),
    ])
  • replacement in inflorescence/src/main.rs at line 1030
    [25.5720][33.15746:15805]()
    let nav_subs = match state.cursor.selection.as_ref() {
    [25.5720]
    [33.15805]
    let status_nav_sub = if state.repo.is_some() {
    iced_nav_scrollable::subs()
    .map(|msg| Msg::View(app::Msg::StatusNav(msg)))
    } else {
    Subscription::none()
    };
    let diff_nav_subs = match state.cursor.selection.as_ref() {
  • replacement in inflorescence/src/main.rs at line 1093
    [34.4085][34.4085:4103]()
    nav_subs,
    [34.4085]
    [34.4103]
    status_nav_sub,
    diff_nav_subs,
  • replacement in iced_nav_scrollable/src/lib.rs at line 239
    [27.3943][29.414:692]()
    debug_assert_eq!(nav.section_heights.len(), children_len, "The `NavScrollable` was most likely initialized with a count different from the number of actual children given to the the view function. Count is {}, but got {} children", nav.section_heights.len(), children_len);
    [27.3943]
    [27.4211]
    debug_assert_eq!(nav.section_heights.len(), children_len, "The `NavScrollable` was most likely initialized with a count different from the number of actual children given to the the view function. Actual number is {}, but got {} children_len arg", nav.section_heights.len(), children_len);