move cursor related stuff into its module

[?]
May 10, 2025, 6:46 PM
L6KSEFQIWICZJ6HJUFKLZQDEH6X2QMFM4Z7ZZUGMLDMFF7EHRXWAC

Dependencies

  • [2] WT3GA27P add cursor with selection
  • [3] DVKSPF7R track selected file path together with an index
  • [4] UB2ITZJS refresh changed files on FS changes
  • [5] EC3TVL4X add untracked files
  • [6] KT5UYXGK fix selection after adding file, add changed file diffs
  • [7] W7IUT3ZV start recording impl
  • [8] YBJRDOTC make all repo actions async
  • [9] D7A7MSIH allow to defer or abandon record, add buttons
  • [10] UCBNZULE make changed files paths optional (no path for root)
  • [11] 4WO3ZJM2 show untracked files' contents
  • [12] W4LFX7IH group diffs by file name
  • [13] FDDPOH5R add arrow controls
  • [14] NOB64XMR fmt and clippy
  • [15] V55EAIWQ add src file LRU cache
  • [16] NRCUG4R2 load changed files src when selected
  • [17] Y5ATDI2H convert changed file diffs and load src only if any needs it
  • [18] ZVI4AWER woot contents_diff
  • [19] QMAUTRB6 refactor diff
  • [20] TTKR4Q76 use wrapping_add for cache counter
  • [21] OQ6HSAWH show record log
  • [22] NWJD6VM6 mv libflowers libflorescence
  • [23] AHWWRC73 navigate log entries
  • [24] UJPRF6DA fix log changes selection
  • [25] TEI5NQ3S add log files selection
  • [26] JE44NYHM display log files diffs
  • [27] ONRCENKT rm unnecessary state from repo's internal state
  • [28] 4ELJZGRJ load and store all change diffs at once
  • [29] HC7ROIBC move main diffs state out of cursor
  • [30] S2NVIFXR allow to enter record msg
  • [31] CALXOZXA flatten crates dir
  • [32] 6YZAVBWU Initial commit
  • [33] IQDCHWCP load a pijul repo
  • [34] 2VUX5BTD load identity
  • [35] ELG3UDT6 allow to rm added files
  • [36] A5YBC77V record!
  • [37] AMPZ2BXK show changed files diffs (only Edit atm)
  • [38] HOJZI52Y rename flowers_ui to inflorescence
  • [*] MJDGPSHG WIP contents diff

Change contents

  • replacement in inflorescence/src/main.rs at line 220
    [4.1634][2.668:698](),[8.6249][2.668:698](),[2.668][2.668:698](),[2.698][25.18:51](),[25.51][18.2884:2918](),[2.698][18.2884:2918]()
    CursorDown,
    CursorUp,
    CursorRight,
    CursorLeft,
    CursorSelect(cursor::Select),
    [8.6249]
    [6.4615]
    Cursor(cursor::Msg),
  • edit in inflorescence/src/main.rs at line 247
    [8.6312][18.3121:3413](),[15.2873][11.3002:3003](),[18.3413][11.3002:3003](),[8.6487][11.3002:3003](),[11.3003][18.3414:3619](),[15.2978][11.3212:3213](),[18.3619][11.3212:3213](),[11.3212][11.3212:3213](),[11.3213][29.140:198](),[29.198][18.3779:3790](),[18.3779][18.3779:3790](),[15.3040][8.6556:6557](),[18.3790][8.6556:6557](),[8.6556][8.6556:6557](),[8.6557][17.440:473](),[17.473][18.3791:3820](),[18.3820][17.529:549](),[17.529][17.529:549](),[17.549][18.3821:3920](),[18.3920][17.646:678](),[17.646][17.646:678](),[17.678][18.3921:3997](),[17.758][16.615:616](),[18.3997][16.615:616](),[16.615][16.615:616](),[16.616][17.759:805](),[17.805][18.3998:4221](),[18.4221][17.970:984](),[17.970][17.970:984](),[16.695][5.2511:2512](),[17.984][5.2511:2512](),[6.5128][5.2511:2512](),[5.2511][5.2511:2512](),[5.2512][17.985:1086](),[19.457][17.1086:1111](),[18.4278][17.1086:1111](),[17.1086][17.1086:1111](),[17.1111][23.683:684](),[23.684][28.447:573](),[28.573][26.1322:1369](),[23.763][26.1322:1369](),[23.817][16.755:756](),[17.1111][16.755:756](),[26.1369][16.755:756](),[16.755][16.755:756](),[16.756][28.574:686](),[28.686][23.873:903](),[23.873][23.873:903](),[23.903][28.687:956](),[28.956][23.937:945](),[23.937][23.937:945](),[23.945][28.957:1015](),[28.1015][29.199:245](),[29.245][28.1110:1222](),[28.1110][28.1110:1222](),[25.357][28.1223:1291](),[28.1291][25.533:534](),[26.2161][25.533:534](),[25.533][25.533:534]()
    let untracked_file_selection =
    |repo: &repo::State,
    ix: usize,
    diffs_cache: &mut DiffsCache,
    src_file_load_tx: &watch::Sender<(FileId, usize)>|
    -> cursor::Selection {
    let path = repo.untracked_files.iter().nth(ix).unwrap().clone();
    let id = FileId {
    path: path.clone(),
    file_kind: FileKind::Untracked,
    };
    load_src_file_if_not_cached(diffs_cache, src_file_load_tx, id);
    cursor::Selection::UntrackedFile { ix, path }
    };
    let changed_file_selection =
    |repo: &repo::State,
    ix: usize,
    diffs_cache: &mut DiffsCache,
    src_file_load_tx: &watch::Sender<(FileId, usize)>|
    -> cursor::Selection {
    let (path, diffs) = repo.changed_files.iter().nth(ix).unwrap();
    if any_diff_has_contents(diffs) {
    let id = FileId {
    path: path.clone(),
    file_kind: FileKind::Changed,
    };
    load_src_file_if_not_cached(diffs_cache, src_file_load_tx, id);
    }
    cursor::Selection::ChangedFile {
    ix,
    path: path.clone(),
    }
    };
    let log_selection = |repo: &repo::State,
    ix: usize|
    -> (cursor::Selection, Task<Message>) {
    let entry = repo.log.get(ix).unwrap();
    // Request to get the diffs
    let task = Task::done(Message::ToRepo(repo::MsgIn::GetChangeDiffs {
    hash: entry.hash,
    }));
    (
    cursor::Selection::LogChange {
    ix,
    hash: entry.hash,
    message: entry.message.clone(),
    diffs: None,
    file: None,
    },
    task,
    )
    };
    let log_file_selection = |log_entry: &repo::LogEntry,
    file_ix: usize|
    -> cursor::LogChangeFileSelection {
    let path = log_entry.file_paths.get(file_ix).unwrap().clone();
    cursor::LogChangeFileSelection { ix: file_ix, path }
    };
  • replacement in inflorescence/src/main.rs at line 265
    [4.1724][2.758:791](),[2.758][2.758:791](),[2.791][8.7042:7096](),[8.7096][26.2162:2240](),[26.2240][29.246:326](),[29.326][28.1292:1340](),[23.1210][28.1292:1340](),[28.1340][23.1254:1330](),[26.2281][23.1254:1330](),[23.1254][23.1254:1330](),[23.1330][26.2282:2320](),[26.2320][23.1369:1515](),[23.1369][23.1369:1515](),[23.1515][28.1341:1417](),[28.1417][23.1575:1794](),[23.1575][23.1575:1794](),[23.1794][28.1418:1519](),[28.1519][23.1832:2005](),[23.1832][23.1832:2005](),[23.2005][18.5730:5771](),[18.5730][18.5730:5771](),[18.5771][23.2006:2054](),[23.2054][28.1520:1598](),[28.1598][11.3316:3406](),[11.3316][11.3316:3406](),[11.3406][18.5825:5889](),[18.5889][15.3109:3174](),[15.3109][15.3109:3174](),[15.3174][28.1599:1700](),[28.1700][23.2055:2175](),[11.3591][23.2055:2175](),[23.2175][28.1701:1775](),[28.1775][23.2233:2436](),[23.2233][23.2233:2436](),[23.2436][28.1776:1869](),[28.1869][23.2470:2501](),[23.2470][23.2470:2501](),[23.2501][28.1870:1918](),[28.1918][23.2545:2567](),[26.2377][23.2545:2567](),[23.2545][23.2545:2567](),[23.2567][29.327:405](),[29.405][28.1919:1967](),[23.2748][28.1919:1967](),[28.1967][26.2418:2819](),[26.2418][26.2418:2819](),[26.2819][28.1968:2046](),[28.2046][26.2881:3100](),[26.2881][26.2881:3100](),[26.3100][28.2047:2148](),[28.2148][26.3138:3227](),[26.3138][26.3138:3227](),[26.3227][28.2149:2225](),[28.2225][26.3287:3506](),[26.3287][26.3287:3506](),[26.3506][28.2226:2327](),[28.2327][26.3544:3578](),[26.3544][26.3544:3578](),[26.3578][23.3552:3589](),[23.3552][23.3552:3589](),[23.3589][26.3579:3628](),[26.3628][28.2328:2400](),[28.2400][23.3689:3892](),[23.3689][23.3689:3892](),[23.3892][28.2401:2494](),[28.2494][26.3629:3660](),[23.3926][26.3629:3660](),[26.3660][28.2495:2543](),[28.2543][23.4374:4452](),[26.3716][23.4374:4452](),[23.4374][23.4374:4452](),[23.4452][25.535:601](),[25.601][26.3717:3750](),[26.3750][28.2544:2575](),[25.601][23.4513:4571](),[28.2575][23.4513:4571](),[26.3750][23.4513:4571](),[23.4513][23.4513:4571](),[23.4571][26.3751:3812](),[26.3812][25.602:754](),[23.4628][25.602:754](),[26.3854][25.754:790](),[25.754][25.754:790](),[25.790][26.3855:3934](),[26.3934][25.913:1119](),[25.913][25.913:1119](),[25.1119][26.3935:3982](),[26.3982][25.1167:1363](),[25.1167][25.1167:1363](),[25.1363][26.3983:3984](),[26.3984][29.406:525](),[29.525][25.1363:1364](),[26.4152][25.1363:1364](),[25.1363][25.1363:1364](),[25.1364][26.4153:4401](),[26.4401][28.2640:2687](),[28.2687][26.4401:4498](),[26.4401][26.4401:4498](),[26.4498][28.2688:2738](),[28.2738][25.1488:1552](),[26.4540][25.1488:1552](),[25.1488][25.1488:1552](),[25.1552][23.4676:4714](),[23.4676][23.4676:4714](),[23.4714][28.2739:3204](),[28.3204][26.4883:5204](),[26.4883][26.4883:5204](),[26.5204][28.3205:3873](),[28.3873][23.5078:5123](),[26.6009][23.5078:5123](),[18.7309][23.5078:5123](),[23.5123][28.3874:3926](),[28.3926][23.5175:5239](),[26.6071][23.5175:5239](),[23.5175][23.5175:5239](),[23.5239][28.3927:4208](),[16.1954][5.5061:5091](),[28.4208][5.5061:5091](),[6.5694][5.5061:5091](),[26.6169][5.5061:5091](),[8.8921][5.5061:5091](),[5.5061][5.5061:5091](),[5.5091][23.5301:5328](),[23.5328][26.6170:6218](),[26.6218][23.5372:5424](),[23.5372][23.5372:5424](),[23.5424][28.4209:5654](),[28.5654][23.6252:6293](),[26.6375][23.6252:6293](),[2.1573][23.6252:6293](),[23.6293][26.6376:6499](),[23.6293][8.9011:9025](),[26.6499][8.9011:9025](),[18.8391][8.9011:9025](),[8.9011][8.9011:9025](),[7.1339][2.1670:1711](),[5.5711][2.1670:1711](),[2.1670][2.1670:1711](),[2.1711][8.9026:9080](),[8.9080][26.6500:6578](),[26.6578][29.526:606](),[29.606][28.5655:5716](),[18.8575][28.5655:5716](),[28.5716][23.6294:6607](),[26.6632][23.6294:6607](),[8.9299][23.6294:6607](),[23.6607][28.5717:5789](),[28.5789][11.4574:4656](),[23.6663][11.4574:4656](),[11.4574][11.4574:4656](),[11.4656][18.8576:8636](),[18.8636][15.3609:3670](),[15.3609][15.3609:3670](),[15.3670][28.5790:5883](),[11.4829][8.9502:9539](),[28.5883][8.9502:9539](),[8.9502][8.9502:9539](),[8.9539][23.6664:6737](),[23.6737][28.5884:5958](),[28.5958][18.8637:8679](),[23.6795][18.8637:8679](),[16.2011][18.8637:8679](),[17.1451][16.2053:2093](),[18.8679][16.2053:2093](),[16.2053][16.2053:2093](),[16.2093][18.8680:8740](),[18.8740][16.2157:2218](),[16.2157][16.2157:2218](),[16.2218][28.5959:6052](),[16.2252][8.9675:9705](),[28.6052][8.9675:9705](),[8.9675][8.9675:9705](),[6.5958][2.1994:2027](),[5.6052][2.1994:2027](),[8.9705][2.1994:2027](),[2.1994][2.1994:2027](),[2.2027][8.9706:9751](),[8.9751][28.6053:6123](),[28.6123][11.4884:4958](),[11.4884][11.4884:4958](),[11.4958][18.8741:8797](),[18.8797][15.3731:3788](),[15.3731][15.3731:3788](),[15.3788][28.6124:6209](),[11.5119][8.9814:9841](),[28.6209][8.9814:9841](),[8.9814][8.9814:9841](),[8.9841][28.6210:6258](),[28.6258][8.9885:9907](),[26.6689][8.9885:9907](),[8.9885][8.9885:9907](),[8.9907][29.607:685](),[29.685][28.6259:6320](),[18.8979][28.6259:6320](),[28.6320][23.6796:6935](),[26.6743][23.6796:6935](),[8.10043][23.6796:6935](),[23.6935][28.6321:6395](),[28.6395][18.8980:9022](),[23.6993][18.8980:9022](),[16.2309][18.8980:9022](),[17.1519][16.2351:2391](),[18.9022][16.2351:2391](),[16.2351][16.2351:2391](),[16.2391][18.9023:9083](),[18.9083][16.2455:2516](),[16.2455][16.2455:2516](),[16.2516][28.6396:6489](),[28.6489][23.6994:7172](),[16.2550][23.6994:7172](),[16.2550][8.10244:10281](),[23.7172][8.10244:10281](),[8.10244][8.10244:10281](),[8.10281][23.7173:7244](),[23.7244][28.6490:6562](),[28.6562][11.5178:5260](),[23.7300][11.5178:5260](),[11.5178][11.5178:5260](),[11.5260][18.9084:9144](),[18.9144][15.3853:3914](),[15.3853][15.3853:3914](),[15.3914][28.6563:6656](),[11.5433][8.10421:10451](),[28.6656][8.10421:10451](),[8.10421][8.10421:10451](),[6.6145][2.2378:2411](),[5.6636][2.2378:2411](),[8.10451][2.2378:2411](),[2.2378][2.2378:2411](),[2.2411][8.10452:10497](),[8.10497][28.6657:6725](),[28.6725][18.9145:9183](),[16.2603][18.9145:9183](),[17.1583][16.2641:2677](),[18.9183][16.2641:2677](),[16.2641][16.2641:2677](),[16.2677][18.9184:9240](),[18.9240][16.2737:2794](),[16.2737][16.2737:2794](),[16.2794][28.6726:6811](),[16.2824][8.10558:10585](),[28.6811][8.10558:10585](),[8.10558][8.10558:10585](),[8.10585][28.6812:6860](),[26.6800][21.4730:4752](),[28.6860][21.4730:4752](),[8.10629][21.4730:4752](),[21.4752][25.1693:1815](),[25.1815][26.6801:6834](),[26.6834][28.6861:6892](),[26.6834][25.1815:1873](),[28.6892][25.1815:1873](),[25.1815][25.1815:1873](),[25.1873][26.6835:6896](),[26.6896][25.1874:2026](),[23.7437][25.1874:2026](),[26.6938][25.2026:2062](),[25.2026][25.2026:2062](),[25.2062][26.6939:7018](),[26.7018][25.2185:2186](),[25.2185][25.2185:2186](),[25.2186][26.7019:7083](),[26.7083][25.2251:2443](),[25.2251][25.2251:2443](),[25.2443][29.686:805](),[29.805][26.7252:7500](),[26.7252][26.7252:7500](),[26.7500][28.6957:7004](),[28.7004][26.7500:7597](),[26.7500][26.7500:7597](),[26.7597][28.7005:7055](),[28.7055][25.2567:2631](),[26.7639][25.2567:2631](),[25.2567][25.2567:2631](),[25.2631][23.7485:7523](),[23.7485][23.7485:7523](),[23.7523][28.7056:7129](),[25.2682][23.7569:7720](),[28.7129][23.7569:7720](),[26.7705][23.7569:7720](),[23.7569][23.7569:7720](),[23.7720][28.7130:7210](),[28.7210][23.7784:8019](),[23.7784][23.7784:8019](),[23.8019][28.7211:7320](),[28.7320][23.8061:8223](),[23.8061][23.8061:8223](),[23.8223][28.7321:7811](),[28.7811][23.8566:8823](),[23.8566][23.8566:8823](),[23.8823][25.2683:2740](),[25.2740][23.8876:8936](),[23.8876][23.8876:8936](),[23.8936][26.7706:7741](),[26.7741][28.7812:7862](),[26.7799][23.8970:9027](),[28.7862][23.8970:9027](),[23.8970][23.8970:9027](),[23.9027][26.7800:7848](),[21.4850][8.10629:10681](),[26.7848][8.10629:10681](),[23.9071][8.10629:10681](),[8.10629][8.10629:10681](),[8.10681][28.7863:7937](),[26.7915][24.241:298](),[28.7937][24.241:298](),[24.241][24.241:298](),[24.298][28.7938:8067](),[28.8067][24.356:423](),[24.356][24.356:423](),[24.423][8.11056:11123](),[23.9132][8.11056:11123](),[5.7217][8.11056:11123](),[8.11123][28.8068:8136](),[28.8136][18.9302:9340](),[16.2882][18.9302:9340](),[17.1647][16.2920:2956](),[18.9340][16.2920:2956](),[16.2920][16.2920:2956](),[16.2956][18.9341:9397](),[18.9397][16.3016:3073](),[16.3016][16.3016:3073](),[16.3073][28.8137:8228](),[28.8228][23.9133:9271](),[16.3104][23.9133:9271](),[23.9271][28.8229:8299](),[28.8299][23.9330:9517](),[23.9330][23.9330:9517](),[23.9517][28.8300:8391](),[28.8391][23.9548:9581](),[23.9548][23.9548:9581](),[23.9581][28.8392:8441](),[28.8441][26.7916:7943](),[23.9614][26.7916:7943](),[26.7943][28.8442:8484](),[6.6405][2.2497:2519](),[5.7511][2.2497:2519](),[26.7993][2.2497:2519](),[28.8484][2.2497:2519](),[2.2497][2.2497:2519](),[2.2519][8.11191:11210](),[8.11210][26.7994:8118](),[26.8118][25.2741:2755](),[8.11210][25.2741:2755](),[25.2780][25.2780:2900](),[25.2900][26.8119:8447](),[26.8447][28.8485:8516](),[28.8516][26.8447:8687](),[26.8447][26.8447:8687](),[26.8687][25.3516:3607](),[25.3516][25.3516:3607](),[26.9077][26.9077:9292](),[26.9344][26.9344:9384](),[26.9384][28.8517:8567](),[28.8567][26.9426:9460](),[26.9426][26.9426:9460](),[26.9460][25.3811:3848](),[25.3811][25.3811:3848](),[25.3848][26.9461:9514](),[26.9514][25.3885:3916](),[25.3885][25.3885:3916](),[25.3916][26.9515:9740](),[26.9740][28.8568:8611](),[28.8611][26.9740:9886](),[26.9740][26.9740:9886](),[26.9886][25.4129:4162](),[25.4129][25.4129:4162](),[25.4162][26.9887:10112](),[26.10112][28.8612:8655](),[28.8655][26.10112:10753](),[26.10112][26.10112:10753](),[25.4744][8.11210:11224](),[26.10753][8.11210:11224](),[8.11210][8.11210:11224](),[7.1380][2.2538:2548](),[5.7544][2.2538:2548](),[2.2538][2.2538:2548](),[2.2548][18.9398:9441](),[18.9441][26.10754:10805](),[26.10805][18.9484:9548](),[18.9484][18.9484:9548](),[18.9548][16.3105:3154](),[11.5852][16.3105:3154](),[16.3154][18.9549:9597](),[18.9597][16.3206:3255](),[16.3206][16.3206:3255](),[16.3255][18.9598:9766](),[18.9766][16.3285:3308](),[16.3285][16.3285:3308](),[16.3308][26.10806:10828](),[26.10828][29.806:883](),[29.883][26.11045:11105](),[26.11045][26.11045:11105](),[25.4829][16.3308:3326](),[18.9966][16.3308:3326](),[26.11105][16.3308:3326](),[16.3308][16.3308:3326](),[16.3326][18.9967:10739](),[18.10739][26.11106:11128](),[26.11128][29.884:959](),[29.959][26.11343:11403](),[26.11343][26.11343:11403](),[17.2028][11.6157:6175](),[15.4244][11.6157:6175](),[25.4912][11.6157:6175](),[26.11403][11.6157:6175](),[11.6157][11.6157:6175](),[11.6175][28.8656:9212](),[28.9212][29.960:990](),[29.990][28.9250:9290](),[28.9250][28.9250:9290](),[28.9290][26.11585:11716](),[26.11585][26.11585:11716](),[26.11716][25.5375:5435](),[25.5375][25.5375:5435](),[25.5435][26.11717:11760](),[26.11760][25.5467:5501](),[25.5467][25.5467:5501](),[25.5501][26.11761:11798](),[26.11798][28.9291:9326](),[28.9326][26.11798:11867](),[26.11798][26.11798:11867](),[26.12279][26.12279:12434](),[26.12478][26.12478:12745](),[26.12745][28.9327:9370](),[28.9370][26.12745:12835](),[26.12745][26.12745:12835](),[26.12835][28.9371:9417](),[28.9417][26.12873:12993](),[26.12873][26.12873:12993](),[25.5632][24.629:669](),[26.12993][24.629:669](),[24.629][24.629:669](),[24.669][18.10915:10930](),[21.4911][18.10915:10930](),[11.6175][18.10915:10930](),[18.10930][25.5633:5681](),[25.5681][26.12994:13011](),[7.1460][2.2647:2657](),[26.13011][2.2647:2657](),[2.2647][2.2647:2657]()
    Message::CursorDown => {
    if let Some(repo) = state.repo.as_ref() {
    let (selection, task) = match state.cursor.selection.take() {
    Some(cursor::Selection::UntrackedFile { ix, path: _ }) => {
    let (selection, task) =
    if repo.untracked_files.len().saturating_sub(1)
    == ix
    {
    if !repo.changed_files.is_empty() {
    let ix = 0;
    let selection = changed_file_selection(
    repo,
    ix,
    &mut state.diffs_cache,
    &state.src_file_load_tx,
    );
    (selection, Task::none())
    } else if !repo.log.is_empty() {
    let ix = 0;
    log_selection(repo, ix)
    } else {
    let ix = 0;
    let selection = untracked_file_selection(
    repo,
    ix,
    &mut state.diffs_cache,
    &state.src_file_load_tx,
    );
    (selection, Task::none())
    }
    } else {
    let ix = ix + 1;
    let selection = untracked_file_selection(
    repo,
    ix,
    &mut state.diffs_cache,
    &state.src_file_load_tx,
    );
    (selection, Task::none())
    };
    (Some(selection), task)
    }
    Some(cursor::Selection::ChangedFile { ix, path: _ }) => {
    let (selection, task) =
    if repo.changed_files.len().saturating_sub(1) == ix
    {
    if !repo.log.is_empty() {
    let ix = 0;
    log_selection(repo, ix)
    } else if !repo.untracked_files.is_empty() {
    let ix = 0;
    let selection = untracked_file_selection(
    repo,
    ix,
    &mut state.diffs_cache,
    &state.src_file_load_tx,
    );
    (selection, Task::none())
    } else {
    let ix = 0;
    let selection = changed_file_selection(
    repo,
    ix,
    &mut state.diffs_cache,
    &state.src_file_load_tx,
    );
    (selection, Task::none())
    }
    } else {
    let ix = ix + 1;
    let selection = changed_file_selection(
    repo,
    ix,
    &mut state.diffs_cache,
    &state.src_file_load_tx,
    );
    (selection, Task::none())
    };
    (Some(selection), task)
    }
    Some(cursor::Selection::LogChange {
    ix: log_ix,
    hash,
    message,
    diffs,
    file,
    }) => {
    let (selection, task) = match file {
    Some(cursor::LogChangeFileSelection {
    ix: file_ix,
    path: _,
    }) => {
    let log_entry = repo.log.get(log_ix).unwrap();
    let file_ix = if log_entry
    .file_paths
    .len()
    .saturating_sub(1)
    == file_ix
    {
    0
    } else {
    file_ix + 1
    };
    let file =
    log_file_selection(log_entry, file_ix);
    (
    cursor::Selection::LogChange {
    ix: log_ix,
    hash,
    message,
    diffs,
    file: Some(file),
    },
    Task::none(),
    )
    }
    None => {
    let (selection, task) = if repo
    .log
    .len()
    .saturating_sub(1)
    == log_ix
    {
    if !repo.untracked_files.is_empty() {
    let ix = 0;
    let selection =
    untracked_file_selection(
    repo,
    ix,
    &mut state.diffs_cache,
    &state.src_file_load_tx,
    );
    (selection, Task::none())
    } else if !repo.changed_files.is_empty() {
    let ix = 0;
    let selection = changed_file_selection(
    repo,
    ix,
    &mut state.diffs_cache,
    &state.src_file_load_tx,
    );
    (selection, Task::none())
    } else {
    let ix = 0;
    log_selection(repo, ix)
    }
    } else {
    let ix = log_ix + 1;
    log_selection(repo, ix)
    };
    (selection, task)
    }
    };
    (Some(selection), task)
    }
    None => {
    let (selection, task) =
    if !repo.untracked_files.is_empty() {
    let ix = 0;
    let selection = Some(untracked_file_selection(
    repo,
    ix,
    &mut state.diffs_cache,
    &state.src_file_load_tx,
    ));
    (selection, Task::none())
    } else if !repo.changed_files.is_empty() {
    let ix = 0;
    let selection = Some(changed_file_selection(
    repo,
    ix,
    &mut state.diffs_cache,
    &state.src_file_load_tx,
    ));
    (selection, Task::none())
    } else if !repo.log.is_empty() {
    let ix = repo.log.len() - 1;
    let (selection, task) = log_selection(repo, ix);
    (Some(selection), task)
    } else {
    (None, Task::none())
    };
    (selection, task)
    }
    };
    state.cursor.selection = selection;
    task
    } else {
    Task::none()
    }
    }
    Message::CursorUp => {
    if let Some(repo) = state.repo.as_ref() {
    let (selection, task) = match state.cursor.selection.take() {
    Some(cursor::Selection::UntrackedFile { ix, path: _ }) => {
    let (selection, task) = if 0 == ix {
    if !repo.log.is_empty() {
    let ix = repo.log.len() - 1;
    log_selection(repo, ix)
    } else if !repo.changed_files.is_empty() {
    let ix = repo.changed_files.len() - 1;
    let selection = changed_file_selection(
    repo,
    ix,
    &mut state.diffs_cache,
    &state.src_file_load_tx,
    );
    (selection, Task::none())
    } else {
    let ix = repo.untracked_files.len() - 1;
    let selection = untracked_file_selection(
    repo,
    ix,
    &mut state.diffs_cache,
    &state.src_file_load_tx,
    );
    (selection, Task::none())
    }
    } else {
    let ix = ix - 1;
    let selection = untracked_file_selection(
    repo,
    ix,
    &mut state.diffs_cache,
    &state.src_file_load_tx,
    );
    (selection, Task::none())
    };
    (Some(selection), task)
    }
    Some(cursor::Selection::ChangedFile { ix, path: _ }) => {
    let (selection, task) = if 0 == ix {
    if !repo.untracked_files.is_empty() {
    let ix = repo.untracked_files.len() - 1;
    let selection = untracked_file_selection(
    repo,
    ix,
    &mut state.diffs_cache,
    &state.src_file_load_tx,
    );
    (selection, Task::none())
    } else if !repo.log.is_empty() {
    let ix = repo.log.len() - 1;
    log_selection(repo, ix)
    } else {
    let ix = repo.changed_files.len() - 1;
    let selection = changed_file_selection(
    repo,
    ix,
    &mut state.diffs_cache,
    &state.src_file_load_tx,
    );
    (selection, Task::none())
    }
    } else {
    let ix = ix - 1;
    let selection = changed_file_selection(
    repo,
    ix,
    &mut state.diffs_cache,
    &state.src_file_load_tx,
    );
    (selection, Task::none())
    };
    (Some(selection), task)
    }
    Some(cursor::Selection::LogChange {
    ix: log_ix,
    hash,
    message,
    diffs,
    file,
    }) => {
    let (selection, task) = match file {
    Some(cursor::LogChangeFileSelection {
    ix: file_ix,
    path: _,
    }) => {
    let log_entry = repo.log.get(log_ix).unwrap();
    let file_ix = if 0 == file_ix {
    log_entry.file_paths.len() - 1
    } else {
    file_ix - 1
    };
    let file =
    log_file_selection(log_entry, file_ix);
    (
    cursor::Selection::LogChange {
    ix: log_ix,
    hash,
    message,
    diffs,
    file: Some(file),
    },
    Task::none(),
    )
    }
    None => {
    let (selection, task) = if 0 == log_ix {
    if !repo.changed_files.is_empty() {
    let ix = repo.changed_files.len() - 1;
    let selection = changed_file_selection(
    repo,
    ix,
    &mut state.diffs_cache,
    &state.src_file_load_tx,
    );
    (selection, Task::none())
    } else if !repo.untracked_files.is_empty() {
    let ix = repo.untracked_files.len() - 1;
    let selection =
    untracked_file_selection(
    repo,
    ix,
    &mut state.diffs_cache,
    &state.src_file_load_tx,
    );
    (selection, Task::none())
    } else {
    let ix = repo.log.len() - 1;
    log_selection(repo, ix)
    }
    } else {
    let ix = log_ix - 1;
    log_selection(repo, ix)
    };
    (selection, task)
    }
    };
    (Some(selection), task)
    }
    None => {
    let (selection, task) = if !repo.log.is_empty() {
    let ix = repo.log.len() - 1;
    let (selection, task) = log_selection(repo, ix);
    (Some(selection), task)
    } else if !repo.changed_files.is_empty() {
    let ix = repo.changed_files.len() - 1;
    let selection = changed_file_selection(
    repo,
    ix,
    &mut state.diffs_cache,
    &state.src_file_load_tx,
    );
    (Some(selection), Task::none())
    } else if !repo.untracked_files.is_empty() {
    let ix = repo.untracked_files.len() - 1;
    let selection = untracked_file_selection(
    repo,
    ix,
    &mut state.diffs_cache,
    &state.src_file_load_tx,
    );
    (Some(selection), Task::none())
    } else {
    (None, Task::none())
    };
    (selection, task)
    }
    };
    state.cursor.selection = selection;
    task
    } else {
    Task::none()
    }
    }
    Message::CursorLeft | Message::CursorRight => {
    if let Some(repo) = state.repo.as_ref() {
    let (selection, task): (
    Option<cursor::Selection>,
    Task<Message>,
    ) = match state.cursor.selection.take() {
    Some(cursor::Selection::LogChange {
    ix,
    hash,
    message,
    diffs,
    file,
    }) => {
    if file.is_none() {
    let log_entry = repo.log.get(ix).unwrap();
    let (file, task) = if let Some(path) =
    log_entry.file_paths.first()
    {
    (
    Some(cursor::LogChangeFileSelection {
    ix: 0,
    path: path.clone(),
    }),
    Task::none(),
    )
    } else {
    (None, Task::none())
    };
    (
    Some(cursor::Selection::LogChange {
    ix,
    hash,
    message,
    diffs,
    file,
    }),
    task,
    )
    } else {
    (
    Some(cursor::Selection::LogChange {
    ix,
    hash,
    message,
    diffs,
    file: None,
    }),
    Task::none(),
    )
    }
    }
    selection @ (Some(cursor::Selection::UntrackedFile {
    ..
    })
    | Some(cursor::Selection::ChangedFile {
    ..
    })
    | None) => (selection, Task::none()),
    };
    state.cursor.selection = selection;
    task
    } else {
    Task::none()
    }
    }
    Message::CursorSelect(select) => {
    let (selection, task) = match select {
    cursor::Select::UntrackedFile { ix, path } => {
    load_src_file_if_not_cached(
    &mut state.diffs_cache,
    &state.src_file_load_tx,
    FileId {
    path: path.clone(),
    file_kind: FileKind::Untracked,
    },
    );
    (
    Some(cursor::Selection::UntrackedFile { ix, path }),
    Task::none(),
    )
    }
    cursor::Select::ChangedFile { ix, path } => {
    if let Some(diffs) = state
    .repo
    .as_ref()
    .and_then(|repo| repo.changed_files.get(&path))
    {
    if any_diff_has_contents(diffs) {
    load_src_file_if_not_cached(
    &mut state.diffs_cache,
    &state.src_file_load_tx,
    FileId {
    path: path.clone(),
    file_kind: FileKind::Changed,
    },
    );
    }
    }
    (
    Some(cursor::Selection::ChangedFile { ix, path }),
    Task::none(),
    )
    }
    cursor::Select::LogChange { ix, hash, message } => {
    // Request to get the diffs
    let task = Task::done(Message::ToRepo(
    repo::MsgIn::GetChangeDiffs { hash },
    ));
    (
    Some(cursor::Selection::LogChange {
    ix,
    hash,
    message,
    diffs: None,
    file: None,
    }),
    task,
    )
    }
    cursor::Select::LogChangeFile { ix: file_ix, path } => {
    match state.cursor.selection.take() {
    Some(cursor::Selection::LogChange {
    ix: change_ix,
    hash,
    message,
    diffs,
    file: _,
    }) => {
    let file = cursor::LogChangeFileSelection {
    ix: file_ix,
    path,
    };
    (
    Some(cursor::Selection::LogChange {
    ix: change_ix,
    hash,
    message,
    diffs,
    file: Some(file),
    }),
    Task::none(),
    )
    }
    selection => (selection, Task::none()),
    }
    }
    };
    state.cursor.selection = selection;
    task
    }
    [4.1724]
    [6.6406]
    Message::Cursor(msg) => cursor::update(
    &mut state.cursor,
    &mut state.diffs_cache,
    state.repo.as_ref(),
    &state.src_file_load_tx,
    msg,
    )
    .map(Message::ToRepo),
  • replacement in inflorescence/src/main.rs at line 301
    [8.12334][11.6250:6305]()
    Some(untracked_file_selection(
    [8.12334]
    [11.6305]
    Some(cursor::untracked_file_selection(
  • replacement in inflorescence/src/main.rs at line 346
    [8.13827][16.3392:3453]()
    Some(changed_file_selection(
    [8.13827]
    [18.11376]
    Some(cursor::changed_file_selection(
  • replacement in inflorescence/src/main.rs at line 605
    [27.2456][18.14768:14830](),[26.13559][18.14768:14830](),[18.14768][18.14768:14830]()
    if any_diff_has_contents(diffs) {
    [27.2456]
    [18.14830]
    if diff::any_diff_has_contents(diffs) {
  • replacement in inflorescence/src/main.rs at line 723
    [9.2051][2.2903:3009](),[2.2903][2.2903:3009](),[2.3009][25.5682:5791]()
    "j" => Some(Message::CursorDown),
    "k" => Some(Message::CursorUp),
    "h" => Some(Message::CursorLeft),
    "l" => Some(Message::CursorRight),
    [9.2051]
    [9.2052]
    "j" => Some(Message::Cursor(cursor::Msg::CursorDown)),
    "k" => Some(Message::Cursor(cursor::Msg::CursorUp)),
    "h" => Some(Message::Cursor(cursor::Msg::CursorLeft)),
    "l" => Some(Message::Cursor(cursor::Msg::CursorRight)),
  • replacement in inflorescence/src/main.rs at line 731
    [2.3059][13.84:240](),[13.240][25.5792:5872]()
    Key::Named(key::Named::ArrowDown) => Some(Message::CursorDown),
    Key::Named(key::Named::ArrowUp) => Some(Message::CursorUp),
    Key::Named(key::Named::ArrowLeft) => Some(Message::CursorLeft),
    [2.3059]
    [25.5872]
    Key::Named(key::Named::ArrowDown) => {
    Some(Message::Cursor(cursor::Msg::CursorDown))
    }
    Key::Named(key::Named::ArrowUp) => {
    Some(Message::Cursor(cursor::Msg::CursorUp))
    }
    Key::Named(key::Named::ArrowLeft) => {
    Some(Message::Cursor(cursor::Msg::CursorLeft))
    }
  • replacement in inflorescence/src/main.rs at line 741
    [25.5928][25.5928:5975]()
    Some(Message::CursorRight)
    [25.5928]
    [25.5975]
    Some(Message::Cursor(cursor::Msg::CursorRight))
  • edit in inflorescence/src/main.rs at line 807
    [20.73][15.6465:6468](),[18.16330][15.6465:6468](),[15.6465][15.6465:6468](),[15.6468][17.5857:5922](),[17.5922][18.16331:16445](),[18.16445][19.1192:1244](),[19.1244][18.16505:16535](),[18.16505][18.16505:16535]()
    }
    /// Returns true if any of the changed file's diffs has contents
    fn any_diff_has_contents(changed_file: &repo::ChangedFile) -> bool {
    let (with_contents, _without_contents) =
    diff::from_repo_changed_file(changed_file);
    !with_contents.is_empty()
  • replacement in inflorescence/src/main.rs at line 832
    [5.8054][5.8054:8111]()
    .on_press(Message::CursorSelect(
    [5.8054]
    [18.16653]
    .on_press(Message::Cursor(cursor::Msg::CursorSelect(
  • replacement in inflorescence/src/main.rs at line 834
    [18.16736][5.8197:8224](),[5.8197][5.8197:8224]()
    ))
    [18.16736]
    [5.8224]
    )))
  • replacement in inflorescence/src/main.rs at line 850
    [14.331][2.3911:3968](),[10.1307][2.3911:3968](),[12.4612][2.3911:3968](),[6.7701][2.3911:3968](),[2.3911][2.3911:3968]()
    .on_press(Message::CursorSelect(
    [14.331]
    [18.16851]
    .on_press(Message::Cursor(cursor::Msg::CursorSelect(
  • replacement in inflorescence/src/main.rs at line 852
    [3.1196][2.4025:4052](),[12.4702][2.4025:4052](),[6.7791][2.4025:4052](),[5.8531][2.4025:4052](),[18.16937][2.4025:4052](),[2.4025][2.4025:4052]()
    ))
    [18.16937]
    [2.4052]
    )))
  • replacement in inflorescence/src/main.rs at line 872
    [21.5333][21.5333:5390]()
    .on_press(Message::CursorSelect(
    [21.5333]
    [26.17372]
    .on_press(Message::Cursor(cursor::Msg::CursorSelect(
  • replacement in inflorescence/src/main.rs at line 874
    [24.745][21.5458:5485](),[26.17473][21.5458:5485](),[21.5458][21.5458:5485]()
    ))
    [26.17473]
    [21.5485]
    )))
  • replacement in inflorescence/src/main.rs at line 968
    [25.6144][25.6144:6248]()
    Message::CursorSelect(cursor::Select::LogChangeFile { ix, path: path.clone() })
    [25.6144]
    [23.12270]
    Message::Cursor(cursor::Msg::CursorSelect(cursor::Select::LogChangeFile { ix, path: path.clone() }))
  • edit in inflorescence/src/diff.rs at line 262
    [19.8673]
    [19.8673]
    /// Returns true if any of the changed file's diffs has contents
    pub fn any_diff_has_contents(changed_file: &repo::ChangedFile) -> bool {
    let (with_contents, _without_contents) =
    from_repo_changed_file(changed_file);
    !with_contents.is_empty()
    }
  • replacement in inflorescence/src/cursor.rs at line 3
    [28.11959][22.581:613](),[2.26][22.581:613]()
    use libflorescence::prelude::*;
    [28.11959]
    [21.5870]
    use iced::Task;
    use libflorescence::{prelude::*, repo};
    use tokio::sync::watch;
  • replacement in inflorescence/src/cursor.rs at line 7
    [21.5871][19.8808:8825](),[2.26][19.8808:8825]()
    use crate::diff;
    [21.5871]
    [18.18884]
    use crate::{diff, load_src_file_if_not_cached, DiffsCache, FileId, FileKind};
  • edit in inflorescence/src/cursor.rs at line 14
    [2.113]
    [18.18886]
    #[derive(Debug, Clone)]
    pub enum Msg {
    CursorDown,
    CursorUp,
    CursorRight,
    CursorLeft,
    CursorSelect(Select),
    }
  • edit in inflorescence/src/cursor.rs at line 71
    [2.190]
    pub fn update(
    state: &mut State,
    diffs_cache: &mut DiffsCache,
    repo: Option<&repo::State>,
    src_file_load_tx: &watch::Sender<(FileId, usize)>,
    msg: Msg,
    ) -> Task<repo::MsgIn> {
    match msg {
    Msg::CursorDown => {
    if let Some(repo) = repo.as_ref() {
    let (selection, task) = match state.selection.take() {
    Some(Selection::UntrackedFile { ix, path: _ }) => {
    let (selection, task) =
    if repo.untracked_files.len().saturating_sub(1)
    == ix
    {
    if !repo.changed_files.is_empty() {
    let ix = 0;
    let selection = changed_file_selection(
    repo,
    ix,
    diffs_cache,
    src_file_load_tx,
    );
    (selection, Task::none())
    } else if !repo.log.is_empty() {
    let ix = 0;
    log_selection(repo, ix)
    } else {
    let ix = 0;
    let selection = untracked_file_selection(
    repo,
    ix,
    diffs_cache,
    src_file_load_tx,
    );
    (selection, Task::none())
    }
    } else {
    let ix = ix + 1;
    let selection = untracked_file_selection(
    repo,
    ix,
    diffs_cache,
    src_file_load_tx,
    );
    (selection, Task::none())
    };
    (Some(selection), task)
    }
    Some(Selection::ChangedFile { ix, path: _ }) => {
    let (selection, task) =
    if repo.changed_files.len().saturating_sub(1) == ix
    {
    if !repo.log.is_empty() {
    let ix = 0;
    log_selection(repo, ix)
    } else if !repo.untracked_files.is_empty() {
    let ix = 0;
    let selection = untracked_file_selection(
    repo,
    ix,
    diffs_cache,
    src_file_load_tx,
    );
    (selection, Task::none())
    } else {
    let ix = 0;
    let selection = changed_file_selection(
    repo,
    ix,
    diffs_cache,
    src_file_load_tx,
    );
    (selection, Task::none())
    }
    } else {
    let ix = ix + 1;
    let selection = changed_file_selection(
    repo,
    ix,
    diffs_cache,
    src_file_load_tx,
    );
    (selection, Task::none())
    };
    (Some(selection), task)
    }
    Some(Selection::LogChange {
    ix: log_ix,
    hash,
    message,
    diffs,
    file,
    }) => {
    let (selection, task) = match file {
    Some(LogChangeFileSelection {
    ix: file_ix,
    path: _,
    }) => {
    let log_entry = repo.log.get(log_ix).unwrap();
    let file_ix = if log_entry
    .file_paths
    .len()
    .saturating_sub(1)
    == file_ix
    {
    0
    } else {
    file_ix + 1
    };
    let file =
    log_file_selection(log_entry, file_ix);
    (
    Selection::LogChange {
    ix: log_ix,
    hash,
    message,
    diffs,
    file: Some(file),
    },
    Task::none(),
    )
    }
    None => {
    let (selection, task) = if repo
    .log
    .len()
    .saturating_sub(1)
    == log_ix
    {
    if !repo.untracked_files.is_empty() {
    let ix = 0;
    let selection =
    untracked_file_selection(
    repo,
    ix,
    diffs_cache,
    src_file_load_tx,
    );
    (selection, Task::none())
    } else if !repo.changed_files.is_empty() {
    let ix = 0;
    let selection = changed_file_selection(
    repo,
    ix,
    diffs_cache,
    src_file_load_tx,
    );
    (selection, Task::none())
    } else {
    let ix = 0;
    log_selection(repo, ix)
    }
    } else {
    let ix = log_ix + 1;
    log_selection(repo, ix)
    };
    (selection, task)
    }
    };
    (Some(selection), task)
    }
    None => {
    let (selection, task) =
    if !repo.untracked_files.is_empty() {
    let ix = 0;
    let selection = Some(untracked_file_selection(
    repo,
    ix,
    diffs_cache,
    src_file_load_tx,
    ));
    (selection, Task::none())
    } else if !repo.changed_files.is_empty() {
    let ix = 0;
    let selection = Some(changed_file_selection(
    repo,
    ix,
    diffs_cache,
    src_file_load_tx,
    ));
    (selection, Task::none())
    } else if !repo.log.is_empty() {
    let ix = repo.log.len() - 1;
    let (selection, task) = log_selection(repo, ix);
    (Some(selection), task)
    } else {
    (None, Task::none())
    };
    (selection, task)
    }
    };
    state.selection = selection;
    task
    } else {
    Task::none()
    }
    }
    Msg::CursorUp => {
    if let Some(repo) = repo.as_ref() {
    let (selection, task) = match state.selection.take() {
    Some(Selection::UntrackedFile { ix, path: _ }) => {
    let (selection, task) = if 0 == ix {
    if !repo.log.is_empty() {
    let ix = repo.log.len() - 1;
    log_selection(repo, ix)
    } else if !repo.changed_files.is_empty() {
    let ix = repo.changed_files.len() - 1;
    let selection = changed_file_selection(
    repo,
    ix,
    diffs_cache,
    src_file_load_tx,
    );
    (selection, Task::none())
    } else {
    let ix = repo.untracked_files.len() - 1;
    let selection = untracked_file_selection(
    repo,
    ix,
    diffs_cache,
    src_file_load_tx,
    );
    (selection, Task::none())
    }
    } else {
    let ix = ix - 1;
    let selection = untracked_file_selection(
    repo,
    ix,
    diffs_cache,
    src_file_load_tx,
    );
    (selection, Task::none())
    };
    (Some(selection), task)
    }
    Some(Selection::ChangedFile { ix, path: _ }) => {
    let (selection, task) = if 0 == ix {
    if !repo.untracked_files.is_empty() {
    let ix = repo.untracked_files.len() - 1;
    let selection = untracked_file_selection(
    repo,
    ix,
    diffs_cache,
    src_file_load_tx,
    );
    (selection, Task::none())
    } else if !repo.log.is_empty() {
    let ix = repo.log.len() - 1;
    log_selection(repo, ix)
    } else {
    let ix = repo.changed_files.len() - 1;
    let selection = changed_file_selection(
    repo,
    ix,
    diffs_cache,
    src_file_load_tx,
    );
    (selection, Task::none())
    }
    } else {
    let ix = ix - 1;
    let selection = changed_file_selection(
    repo,
    ix,
    diffs_cache,
    src_file_load_tx,
    );
    (selection, Task::none())
    };
    (Some(selection), task)
    }
    Some(Selection::LogChange {
    ix: log_ix,
    hash,
    message,
    diffs,
    file,
    }) => {
    let (selection, task) = match file {
    Some(LogChangeFileSelection {
    ix: file_ix,
    path: _,
    }) => {
    let log_entry = repo.log.get(log_ix).unwrap();
    let file_ix = if 0 == file_ix {
    log_entry.file_paths.len() - 1
    } else {
    file_ix - 1
    };
    let file =
    log_file_selection(log_entry, file_ix);
    (
    Selection::LogChange {
    ix: log_ix,
    hash,
    message,
    diffs,
    file: Some(file),
    },
    Task::none(),
    )
    }
    None => {
    let (selection, task) = if 0 == log_ix {
    if !repo.changed_files.is_empty() {
    let ix = repo.changed_files.len() - 1;
    let selection = changed_file_selection(
    repo,
    ix,
    diffs_cache,
    src_file_load_tx,
    );
    (selection, Task::none())
    } else if !repo.untracked_files.is_empty() {
    let ix = repo.untracked_files.len() - 1;
    let selection =
    untracked_file_selection(
    repo,
    ix,
    diffs_cache,
    src_file_load_tx,
    );
    (selection, Task::none())
    } else {
    let ix = repo.log.len() - 1;
    log_selection(repo, ix)
    }
    } else {
    let ix = log_ix - 1;
    log_selection(repo, ix)
    };
    (selection, task)
    }
    };
    (Some(selection), task)
    }
    None => {
    let (selection, task) = if !repo.log.is_empty() {
    let ix = repo.log.len() - 1;
    let (selection, task) = log_selection(repo, ix);
    (Some(selection), task)
    } else if !repo.changed_files.is_empty() {
    let ix = repo.changed_files.len() - 1;
    let selection = changed_file_selection(
    repo,
    ix,
    diffs_cache,
    src_file_load_tx,
    );
    (Some(selection), Task::none())
    } else if !repo.untracked_files.is_empty() {
    let ix = repo.untracked_files.len() - 1;
    let selection = untracked_file_selection(
    repo,
    ix,
    diffs_cache,
    src_file_load_tx,
    );
    (Some(selection), Task::none())
    } else {
    (None, Task::none())
    };
    (selection, task)
    }
    };
    state.selection = selection;
    task
    } else {
    Task::none()
    }
    }
    Msg::CursorLeft | Msg::CursorRight => {
    if let Some(repo) = repo.as_ref() {
    let (selection, task): (Option<Selection>, Task<repo::MsgIn>) =
    match state.selection.take() {
    Some(Selection::LogChange {
    ix,
    hash,
    message,
    diffs,
    file,
    }) => {
    if file.is_none() {
    let log_entry = repo.log.get(ix).unwrap();
    let (file, task) = if let Some(path) =
    log_entry.file_paths.first()
    {
    (
    Some(LogChangeFileSelection {
    ix: 0,
    path: path.clone(),
    }),
    Task::none(),
    )
    } else {
    (None, Task::none())
    };
    (
    Some(Selection::LogChange {
    ix,
    hash,
    message,
    diffs,
    file,
    }),
    task,
    )
    } else {
    (
    Some(Selection::LogChange {
    ix,
    hash,
    message,
    diffs,
    file: None,
    }),
    Task::none(),
    )
    }
    }
    selection @ (Some(Selection::UntrackedFile {
    ..
    })
    | Some(Selection::ChangedFile {
    ..
    })
    | None) => (selection, Task::none()),
    };
    state.selection = selection;
    task
    } else {
    Task::none()
    }
    }
    Msg::CursorSelect(select) => {
    let (selection, task) = match select {
    Select::UntrackedFile { ix, path } => {
    load_src_file_if_not_cached(
    diffs_cache,
    src_file_load_tx,
    FileId {
    path: path.clone(),
    file_kind: FileKind::Untracked,
    },
    );
    (Some(Selection::UntrackedFile { ix, path }), Task::none())
    }
    Select::ChangedFile { ix, path } => {
    if let Some(diffs) = repo
    .as_ref()
    .and_then(|repo| repo.changed_files.get(&path))
    {
    if diff::any_diff_has_contents(diffs) {
    load_src_file_if_not_cached(
    diffs_cache,
    src_file_load_tx,
    FileId {
    path: path.clone(),
    file_kind: FileKind::Changed,
    },
    );
    }
    }
    (Some(Selection::ChangedFile { ix, path }), Task::none())
    }
    Select::LogChange { ix, hash, message } => {
    // Request to get the diffs
    let task = Task::done(repo::MsgIn::GetChangeDiffs { hash });
    (
    Some(Selection::LogChange {
    ix,
    hash,
    message,
    diffs: None,
    file: None,
    }),
    task,
    )
    }
    Select::LogChangeFile { ix: file_ix, path } => {
    match state.selection.take() {
    Some(Selection::LogChange {
    ix: change_ix,
    hash,
    message,
    diffs,
    file: _,
    }) => {
    let file =
    LogChangeFileSelection { ix: file_ix, path };
    (
    Some(Selection::LogChange {
    ix: change_ix,
    hash,
    message,
    diffs,
    file: Some(file),
    }),
    Task::none(),
    )
    }
    selection => (selection, Task::none()),
    }
    }
    };
    state.selection = selection;
    task
    }
    }
    }
    pub fn untracked_file_selection(
    repo: &repo::State,
    ix: usize,
    diffs_cache: &mut DiffsCache,
    src_file_load_tx: &watch::Sender<(FileId, usize)>,
    ) -> Selection {
    let path = repo.untracked_files.iter().nth(ix).unwrap().clone();
    let id = FileId {
    path: path.clone(),
    file_kind: FileKind::Untracked,
    };
    load_src_file_if_not_cached(diffs_cache, src_file_load_tx, id);
    Selection::UntrackedFile { ix, path }
    }
    pub fn changed_file_selection(
    repo: &repo::State,
    ix: usize,
    diffs_cache: &mut DiffsCache,
    src_file_load_tx: &watch::Sender<(FileId, usize)>,
    ) -> Selection {
    let (path, diffs) = repo.changed_files.iter().nth(ix).unwrap();
    if diff::any_diff_has_contents(diffs) {
    let id = FileId {
    path: path.clone(),
    file_kind: FileKind::Changed,
    };
    load_src_file_if_not_cached(diffs_cache, src_file_load_tx, id);
    }
    Selection::ChangedFile {
    ix,
    path: path.clone(),
    }
    }
    pub fn log_selection(
    repo: &repo::State,
    ix: usize,
    ) -> (Selection, Task<repo::MsgIn>) {
    let entry = repo.log.get(ix).unwrap();
    // Request to get the diffs
    let task = Task::done(repo::MsgIn::GetChangeDiffs { hash: entry.hash });
    (
    Selection::LogChange {
    ix,
    hash: entry.hash,
    message: entry.message.clone(),
    diffs: None,
    file: None,
    },
    task,
    )
    }
    pub fn log_file_selection(
    log_entry: &repo::LogEntry,
    file_ix: usize,
    ) -> LogChangeFileSelection {
    let path = log_entry.file_paths.get(file_ix).unwrap().clone();
    LogChangeFileSelection { ix: file_ix, path }
    }