make all repo actions async

[?]
Feb 23, 2025, 11:36 AM
YBJRDOTCX3ZRDB5EVXJBR55FX3CADCSIGMYWNYVC2PD5W3GXR3DQC

Dependencies

  • [2] 6YZAVBWU Initial commit
  • [3] KLR5FRIB add fs state read/write of repos
  • [4] IQDCHWCP load a pijul repo
  • [5] SWWE2R6M display basic repo stuff
  • [6] WT3GA27P add cursor with selection
  • [7] DVKSPF7R track selected file path together with an index
  • [8] UB2ITZJS refresh changed files on FS changes
  • [9] EC3TVL4X add untracked files
  • [10] KT5UYXGK fix selection after adding file, add changed file diffs
  • [11] ELG3UDT6 allow to rm added files
  • [12] S2NVIFXR allow to enter record msg
  • [13] W7IUT3ZV start recording impl

Change contents

  • replacement in crates/libflowers_client/src/repo.rs at line 3
    [5.40][9.30:51]()
    use std::ops::Deref;
    [5.40]
    [9.51]
    use std::ops::{Deref, DerefMut};
  • edit in crates/libflowers_client/src/repo.rs at line 13
    [13.96]
    [5.265]
    use tokio::sync::mpsc;
    use tokio::task::spawn_blocking;
    #[derive(Clone, Debug)]
    pub struct State {
    pub dir_name: String,
    pub channel: String,
    pub untracked_files: BTreeSet<String>,
    pub changed_files: BTreeSet<ChangedFile>,
    }
  • replacement in crates/libflowers_client/src/repo.rs at line 26
    [5.309][5.309:328]()
    pub struct State {
    [5.309]
    [5.328]
    pub struct InternalState {
  • edit in crates/libflowers_client/src/repo.rs at line 54
    [10.575]
    [5.466]
    }
    #[derive(Debug, Clone, strum::Display)]
    pub enum MsgIn {
    RefreshChangedAndUntrackedFiles,
    AddUntrackedFile { path: String },
    RmAddedFile { path: String },
    Record { msg: String },
  • edit in crates/libflowers_client/src/repo.rs at line 63
    [5.468]
    [5.468]
    #[derive(Debug, Clone, strum::Display)]
    pub enum MsgOut {
    Init(State),
    RefreshedChangedAndUntrackedFiles {
    untracked_files: BTreeSet<String>,
    changed_files: BTreeSet<ChangedFile>,
    },
    }
    pub type Diff = LocalChange<Hunk<Option<Hash>, Local>, Author>;
  • replacement in crates/libflowers_client/src/repo.rs at line 75
    [5.469][5.469:529]()
    impl Deref for State {
    type Target = pijul::Repository;
    [5.469]
    [5.529]
    /// Run an async repo task to manage all actions sent via the other side of the
    /// receiver.
    pub async fn manage(
    path: PathBuf,
    mut msg_in_rx: mpsc::UnboundedReceiver<MsgIn>,
    msg_out_tx: mpsc::UnboundedSender<MsgOut>,
    ) {
    let mut state = spawn_blocking(move || load(path)).await.unwrap();
    // dbg!(diff(&state.repo));
    let dir_name = dir_name(&state.repo).to_string();
    let channel = current_channel(&state.repo);
    let untracked_files = state.untracked_files.clone();
    let changed_files = state.changed_files.clone();
  • replacement in crates/libflowers_client/src/repo.rs at line 90
    [5.530][5.530:569](),[5.569][13.131:150]()
    fn deref(&self) -> &Self::Target {
    &self.repo
    [5.530]
    [5.589]
    let _ = msg_out_tx.send(MsgOut::Init(State {
    dir_name,
    channel,
    untracked_files,
    changed_files,
    }));
    loop {
    if let Some(msg) = msg_in_rx.recv().await {
    info!("Repo received msg {msg}");
    state = update(state, msg, &msg_out_tx).await;
    } else {
    break;
    }
  • replacement in crates/libflowers_client/src/repo.rs at line 107
    [5.598][5.598:662]()
    pub type Diff = LocalChange<Hunk<Option<Hash>, Local>, Author>;
    [5.598]
    [5.662]
    async fn update<'a>(
    mut state: InternalState,
    msg_in: MsgIn,
    msg_out_tx: &mpsc::UnboundedSender<MsgOut>,
    ) -> InternalState {
    match msg_in {
    MsgIn::RefreshChangedAndUntrackedFiles => {
    state = spawn_blocking(move || {
    refresh(&mut state);
    state
    })
    .await
    .unwrap();
    let untracked_files = state.untracked_files.clone();
    let changed_files = state.changed_files.clone();
    let _ =
    msg_out_tx.send(MsgOut::RefreshedChangedAndUntrackedFiles {
    untracked_files,
    changed_files,
    });
    }
    MsgIn::AddUntrackedFile { path } => {
    state = spawn_blocking(move || {
    add(&mut state, &path);
    state
    })
    .await
    .unwrap();
    }
    MsgIn::RmAddedFile { path } => {
    state = spawn_blocking(move || {
    rm(&mut state, &path);
    state
    })
    .await
    .unwrap();
    }
    MsgIn::Record { msg } => {
    state = spawn_blocking(move || {
    record(&mut state, msg);
    state
    })
    .await
    .unwrap();
    }
    }
    state
    }
  • replacement in crates/libflowers_client/src/repo.rs at line 156
    [5.663][5.663:701]()
    pub fn load(path: PathBuf) -> State {
    [5.663]
    [13.151]
    fn load(path: PathBuf) -> InternalState {
  • replacement in crates/libflowers_client/src/repo.rs at line 161
    [5.843][5.843:855]()
    State {
    [5.843]
    [13.296]
    InternalState {
  • replacement in crates/libflowers_client/src/repo.rs at line 168
    [8.10][8.10:46]()
    pub fn refresh(state: &mut State) {
    [8.10]
    [9.360]
    fn refresh(state: &mut InternalState) {
  • replacement in crates/libflowers_client/src/repo.rs at line 175
    [5.902][5.902:962]()
    pub fn dir_name(repo: &pijul::Repository) -> Cow<'_, str> {
    [5.902]
    [5.962]
    fn dir_name(repo: &pijul::Repository) -> Cow<'_, str> {
  • replacement in crates/libflowers_client/src/repo.rs at line 179
    [5.1020][5.1020:1081]()
    pub fn current_channel(repo: &pijul::Repository) -> String {
    [5.1020]
    [5.1103]
    fn current_channel(repo: &pijul::Repository) -> String {
  • replacement in crates/libflowers_client/src/repo.rs at line 188
    [5.1299][13.311:361]()
    pub async fn record(state: &State, msg: String) {
    [5.1299]
    [13.361]
    fn record(state: &InternalState, msg: String) {
  • replacement in crates/libflowers_client/src/repo.rs at line 217
    [13.1173][5.1299:1347](),[5.1299][5.1299:1347]()
    pub fn diff(repo: &pijul::Repository) -> Diff {
    [13.1173]
    [5.1393]
    fn diff(repo: &pijul::Repository) -> Diff {
  • replacement in crates/libflowers_client/src/repo.rs at line 278
    [5.2897][10.576:635]()
    pub fn add(repo: &mut pijul::Repository, path_str: &str) {
    [5.2897]
    [10.635]
    /// The `file_path_str` must not contain the repo's path prefix.
    fn add(repo: &mut pijul::Repository, file_path_str: &str) {
  • replacement in crates/libflowers_client/src/repo.rs at line 284
    [10.823][10.823:849]()
    p.push(path_str);
    [10.823]
    [10.849]
    p.push(file_path_str);
  • replacement in crates/libflowers_client/src/repo.rs at line 294
    [10.1114][10.1114:1185]()
    println!("Won't add ignored file {path_str}");
    return;
    [10.1114]
    [10.1185]
    panic!("Cannot add ignored file \"{file_path_str}\"");
  • replacement in crates/libflowers_client/src/repo.rs at line 298
    [10.1232][11.72:148]()
    let path_str = path_slash::PathExt::to_slash_lossy(path.as_path());
    [10.1232]
    [10.1474]
    // NOTE: This has to be the file path without the repo's path prefix
    let file_path = PathBuf::from(file_path_str);
    let path_str = path_slash::PathExt::to_slash_lossy(file_path.as_path());
  • replacement in crates/libflowers_client/src/repo.rs at line 305
    [10.1590][10.1590:1640]()
    eprintln!("Failed to add {}", e);
    [10.1590]
    [10.1640]
    panic!("Failed to track file with: {}", e);
  • edit in crates/libflowers_client/src/repo.rs at line 307
    [10.1654]
    [10.1654]
    } else {
    panic!("Won't add file \"{path_str}\" as it's already tracked");
  • replacement in crates/libflowers_client/src/repo.rs at line 314
    [11.185][11.185:243]()
    pub fn rm(repo: &mut pijul::Repository, path_str: &str) {
    [11.185]
    [11.243]
    /// The `file_path_str` must not contain the repo's path prefix.
    fn rm(repo: &mut pijul::Repository, file_path_str: &str) {
    info!("Removing tracked {file_path_str}");
  • replacement in crates/libflowers_client/src/repo.rs at line 318
    [11.301][11.301:527]()
    let full_path = {
    let mut p = repo.path.clone();
    p.push(path_str);
    p
    };
    let path = full_path.canonicalize().unwrap();
    let path_str = path_slash::PathExt::to_slash_lossy(path.as_path());
    [11.301]
    [11.527]
    let file_path = PathBuf::from(file_path_str);
    let path_str = path_slash::PathExt::to_slash_lossy(file_path.as_path());
  • edit in crates/libflowers_client/src/repo.rs at line 464
    [5.4751]
    impl Deref for InternalState {
    type Target = pijul::Repository;
    fn deref(&self) -> &Self::Target {
    &self.repo
    }
    }
    impl DerefMut for InternalState {
    fn deref_mut(&mut self) -> &mut Self::Target {
    &mut self.repo
    }
    }
  • edit in crates/libflowers_client/Cargo.toml at line 25
    [10.4587]
    [4.580]
    workspace = true
    [dependencies.tokio]
  • edit in crates/libflowers/src/prelude.rs at line 7
    [3.2514]
    [4.601]
    #[doc(inline)]
    pub use tracing::{debug, error, info, warn};
  • edit in crates/libflowers/Cargo.toml at line 35
    [4.916]
    [dependencies.tracing]
    workspace = true
  • edit in crates/flowers_ui/src/main.rs at line 11
    [10.4614]
    [8.351]
    use std::collections::BTreeSet;
  • replacement in crates/flowers_ui/src/main.rs at line 13
    [8.375][8.375:399]()
    use tokio::sync::watch;
    [8.375]
    [8.399]
    use tokio::sync::{mpsc, watch};
  • replacement in crates/flowers_ui/src/main.rs at line 15
    [8.426][8.426:467]()
    use tokio_stream::wrappers::WatchStream;
    [8.426]
    [2.2834]
    use tokio_stream::wrappers::{UnboundedReceiverStream, WatchStream};
  • edit in crates/flowers_ui/src/main.rs at line 18
    [2.2867]
    [3.5850]
    use std::str::FromStr;
    use tracing_subscriber::EnvFilter;
    const LOG_ENV: &str = "RUST_LOG";
    let filter = std::env::var(LOG_ENV)
    .map(|env| {
    dbg!(&env);
    EnvFilter::from_str(&env).unwrap_or_else(|err| {
    panic!("invalid `{}` environment variable {}", LOG_ENV, err)
    })
    })
    .unwrap_or_else(|_| EnvFilter::new("flowers=info,libflowers=info"));
    tracing_subscriber::fmt().with_env_filter(filter).init();
  • edit in crates/flowers_ui/src/main.rs at line 41
    [8.516]
    [8.516]
    // TODO: start watch once loaded?
  • replacement in crates/flowers_ui/src/main.rs at line 67
    [8.1370][8.1370:1437]()
    let task = Task::run(fs_watch_rx, |()| Message::FilesChanged);
    [8.1370]
    [8.1437]
    let watch_task = Task::run(fs_watch_rx, |()| {
    Message::ToRepo(repo::MsgIn::RefreshChangedAndUntrackedFiles)
    });
  • edit in crates/flowers_ui/src/main.rs at line 71
    [8.1438][9.1883:1953]()
    let repo = repo::load(repo_path);
    // dbg!(repo::diff(&repo));
  • edit in crates/flowers_ui/src/main.rs at line 72
    [9.1996]
    [9.1996]
    let (repo_tx_in, repo_rx_in) = mpsc::unbounded_channel::<repo::MsgIn>();
    let (repo_tx_out, repo_rx_out) = mpsc::unbounded_channel::<repo::MsgOut>();
    let repo_task = Task::future(async move {
    repo::manage(repo_path, repo_rx_in, repo_tx_out).await;
    Message::RepoTaskExited
    });
    let repo_rx_out = UnboundedReceiverStream::new(repo_rx_out);
    let repo_msg_out_task = Task::run(repo_rx_out, Message::FromRepo);
  • edit in crates/flowers_ui/src/main.rs at line 82
    [9.1997]
    [8.1438]
    let tasks = Task::batch([watch_task, repo_task, repo_msg_out_task]);
  • replacement in crates/flowers_ui/src/main.rs at line 86
    [9.2031][8.1482:1500](),[8.1482][8.1482:1500]()
    repo,
    [9.2031]
    [8.1500]
    repo_tx_in,
    repo: None, // This is loaded by `repo_task`
  • replacement in crates/flowers_ui/src/main.rs at line 91
    [8.1531][8.1531:1545]()
    task,
    [8.1531]
    [8.1545]
    tasks,
  • replacement in crates/flowers_ui/src/main.rs at line 98
    [8.1615][5.5077:5100](),[9.2096][5.5077:5100](),[4.1363][5.5077:5100]()
    repo: repo::State,
    [9.2096]
    [6.625]
    repo_tx_in: mpsc::UnboundedSender<repo::MsgIn>,
    repo: Option<repo::State>,
  • replacement in crates/flowers_ui/src/main.rs at line 110
    [6.668][8.1616:1634]()
    FilesChanged,
    [6.668]
    [6.668]
    RepoTaskExited,
    FromRepo(repo::MsgOut),
    ToRepo(repo::MsgIn),
  • edit in crates/flowers_ui/src/main.rs at line 121
    [12.240][13.1189:1205]()
    MadeRecord,
  • replacement in crates/flowers_ui/src/main.rs at line 123
    [2.3099][13.1206:1272](),[13.1272][10.4638:4764](),[4.1455][10.4638:4764](),[10.4764][9.2167:2300](),[9.2167][9.2167:2300](),[9.2504][9.2504:2511]()
    fn update(state: &mut State, message: Message) -> Task<Message> {
    let untracked_file_selection = |state: &State,
    ix: usize|
    -> cursor::Selection {
    let path = state.repo.untracked_files.iter().nth(ix).unwrap().clone();
    cursor::Selection::UntrackedFile { ix, path }
    };
    [2.3099]
    [10.4765]
    fn update(state: &mut State, msg: Message) -> Task<Message> {
    let untracked_file_selection =
    |repo: &repo::State, ix: usize| -> cursor::Selection {
    let path = repo.untracked_files.iter().nth(ix).unwrap().clone();
    cursor::Selection::UntrackedFile { ix, path }
    };
  • replacement in crates/flowers_ui/src/main.rs at line 131
    [10.4798][10.4798:5061]()
    |state: &State, ix: usize| -> cursor::Selection {
    let path = state
    .repo
    .changed_files
    .iter()
    .nth(ix)
    .unwrap()
    .path
    .clone();
    [10.4798]
    [10.5061]
    |repo: &repo::State, ix: usize| -> cursor::Selection {
    let path = repo.changed_files.iter().nth(ix).unwrap().path.clone();
  • replacement in crates/flowers_ui/src/main.rs at line 136
    [7.160][6.738:758](),[9.2512][6.738:758](),[4.1455][6.738:758](),[6.758][8.1635:1670](),[8.1670][10.5129:5163](),[10.5163][8.1670:1714](),[8.1670][8.1670:1714](),[8.1714][9.2513:3480](),[9.3480][10.5164:5303](),[10.5303][9.3624:3656](),[9.3624][9.3624:3656](),[9.3656][10.5304:5357](),[10.5357][9.3704:3765](),[9.3704][9.3704:3765]()
    match message {
    Message::FilesChanged => {
    dbg!("FilesChanged");
    repo::refresh(&mut state.repo);
    // Re-index cursor selection
    if let Some(selection) = state.cursor.selection.as_ref() {
    // Try to find the file with the same name. If not found, remove
    // selection
    state.cursor.selection = match selection {
    cursor::Selection::UntrackedFile { ix: _, path } => state
    .repo
    .untracked_files
    .iter()
    .enumerate()
    .find(|(_ix, file_path)| *file_path == path)
    .map(|(ix, path)| cursor::Selection::UntrackedFile {
    ix,
    path: path.clone(),
    }),
    cursor::Selection::ChangedFile { ix: _, path } => state
    .repo
    .changed_files
    .iter()
    .enumerate()
    .find(|(_ix, file)| &file.path == path)
    .map(|(ix, file)| cursor::Selection::ChangedFile {
    ix,
    path: file.path.clone(),
    }),
    };
    }
    [9.2512]
    [13.1273]
    match msg {
    Message::RepoTaskExited => {
    error!("Repo task exited");
    Task::none()
    }
    Message::FromRepo(msg) => {
    info!("Repo sent msg {msg}");
    update_from_repo(state, msg)
    }
    Message::ToRepo(msg) => {
    let _ = state.repo_tx_in.send(msg);
  • replacement in crates/flowers_ui/src/main.rs at line 150
    [6.791][9.3766:4037]()
    state.cursor.selection = match state.cursor.selection.as_ref() {
    Some(cursor::Selection::UntrackedFile { ix, path: _ }) => {
    let new_selection =
    if state.repo.untracked_files.len().saturating_sub(1)
    [6.791]
    [9.4037]
    if let Some(repo) = state.repo.as_ref() {
    state.cursor.selection = match state.cursor.selection.as_ref() {
    Some(cursor::Selection::UntrackedFile { ix, path: _ }) => {
    let new_selection =
    if repo.untracked_files.len().saturating_sub(1)
    == *ix
    {
    if repo.changed_files.is_empty() {
    let ix = 0;
    untracked_file_selection(repo, ix)
    } else {
    let ix = 0;
    changed_file_selection(repo, ix)
    }
    } else {
    let ix = ix + 1;
    untracked_file_selection(repo, ix)
    };
    Some(new_selection)
    }
    Some(cursor::Selection::ChangedFile { ix, path: _ }) => {
    let new_selection = if repo
    .changed_files
    .len()
    .saturating_sub(1)
  • replacement in crates/flowers_ui/src/main.rs at line 177
    [9.4098][9.4098:4167]()
    if state.repo.changed_files.is_empty() {
    [9.4098]
    [9.4167]
    if repo.untracked_files.is_empty() {
  • replacement in crates/flowers_ui/src/main.rs at line 179
    [9.4211][10.5358:5426]()
    untracked_file_selection(state, ix)
    [9.4211]
    [9.4272]
    changed_file_selection(repo, ix)
  • replacement in crates/flowers_ui/src/main.rs at line 182
    [9.4353][10.5427:5493]()
    changed_file_selection(state, ix)
    [9.4353]
    [9.4412]
    untracked_file_selection(repo, ix)
  • replacement in crates/flowers_ui/src/main.rs at line 186
    [9.4488][10.5494:5558]()
    untracked_file_selection(state, ix)
    [9.4488]
    [9.4545]
    changed_file_selection(repo, ix)
  • replacement in crates/flowers_ui/src/main.rs at line 188
    [9.4572][9.4572:4744](),[7.400][6.1262:1399](),[9.4744][6.1262:1399](),[6.1262][6.1262:1399](),[6.1399][9.4745:4860](),[9.4860][10.5559:5625]()
    Some(new_selection)
    }
    Some(cursor::Selection::ChangedFile { ix, path: _ }) => {
    let new_selection =
    if state.repo.changed_files.len().saturating_sub(1)
    == *ix
    {
    if state.repo.untracked_files.is_empty() {
    let ix = 0;
    changed_file_selection(state, ix)
    [9.4572]
    [9.4919]
    Some(new_selection)
    }
    None => {
    if repo.untracked_files.is_empty() {
    if repo.changed_files.is_empty() {
    None
  • replacement in crates/flowers_ui/src/main.rs at line 196
    [9.5000][10.5626:5694]()
    untracked_file_selection(state, ix)
    [9.5000]
    [9.5061]
    Some(changed_file_selection(repo, ix))
  • edit in crates/flowers_ui/src/main.rs at line 198
    [9.5091][6.1454:1487](),[6.1454][6.1454:1487](),[6.1487][7.401:446](),[7.446][10.5695:5757](),[10.5757][9.5147:5419](),[9.5147][9.5147:5419]()
    } else {
    let ix = ix + 1;
    changed_file_selection(state, ix)
    };
    Some(new_selection)
    }
    None => {
    if state.repo.untracked_files.is_empty() {
    if state.repo.changed_files.is_empty() {
    None
  • replacement in crates/flowers_ui/src/main.rs at line 200
    [9.5492][10.5758:5826]()
    Some(changed_file_selection(state, ix))
    [9.5492]
    [6.1547]
    Some(untracked_file_selection(repo, ix))
  • edit in crates/flowers_ui/src/main.rs at line 202
    [6.1573][9.5554:5619](),[9.5619][10.5827:5893]()
    } else {
    let ix = 0;
    Some(untracked_file_selection(state, ix))
  • replacement in crates/flowers_ui/src/main.rs at line 203
    [6.1595][9.5679:5697](),[9.5697][13.1299:1314]()
    }
    };
    [6.1595]
    [13.1314]
    };
    }
  • replacement in crates/flowers_ui/src/main.rs at line 208
    [6.1711][9.5712:5919](),[9.5919][6.1874:1939](),[6.1874][6.1874:1939](),[6.1939][9.5920:5995](),[9.5995][10.5894:5958]()
    state.cursor.selection = match state.cursor.selection.as_ref() {
    Some(cursor::Selection::UntrackedFile { ix, path: _ }) => {
    let new_selection = if 0 == *ix {
    if state.repo.changed_files.is_empty() {
    let ix = state.repo.untracked_files.len() - 1;
    untracked_file_selection(state, ix)
    [6.1711]
    [6.1994]
    if let Some(repo) = state.repo.as_ref() {
    state.cursor.selection = match state.cursor.selection.as_ref() {
    Some(cursor::Selection::UntrackedFile { ix, path: _ }) => {
    let new_selection = if 0 == *ix {
    if repo.changed_files.is_empty() {
    let ix = repo.untracked_files.len() - 1;
    untracked_file_selection(repo, ix)
    } else {
    let ix = repo.changed_files.len() - 1;
    changed_file_selection(repo, ix)
    }
  • replacement in crates/flowers_ui/src/main.rs at line 220
    [6.2027][7.574:647](),[7.647][10.5959:6021](),[7.774][6.2178:2204](),[10.6021][6.2178:2204](),[9.6108][6.2178:2204](),[6.2178][6.2178:2204](),[6.2204][9.6109:6179](),[9.6179][10.6022:6082](),[10.6082][9.6232:6581](),[9.6232][9.6232:6581](),[9.6581][10.6083:6145]()
    let ix = state.repo.changed_files.len() - 1;
    changed_file_selection(state, ix)
    }
    } else {
    let ix = ix - 1;
    untracked_file_selection(state, ix)
    };
    Some(new_selection)
    }
    Some(cursor::Selection::ChangedFile { ix, path: _ }) => {
    let new_selection = if 0 == *ix {
    if state.repo.untracked_files.is_empty() {
    let ix = state.repo.changed_files.len() - 1;
    changed_file_selection(state, ix)
    [6.2027]
    [6.2378]
    let ix = ix - 1;
    untracked_file_selection(repo, ix)
    };
    Some(new_selection)
    }
    Some(cursor::Selection::ChangedFile { ix, path: _ }) => {
    let new_selection = if 0 == *ix {
    if repo.untracked_files.is_empty() {
    let ix = repo.changed_files.len() - 1;
    changed_file_selection(repo, ix)
    } else {
    let ix = repo.untracked_files.len() - 1;
    untracked_file_selection(repo, ix)
    }
  • replacement in crates/flowers_ui/src/main.rs at line 235
    [6.2411][9.6637:6712](),[9.6712][10.6146:6210](),[10.6210][9.6769:6865](),[9.6769][9.6769:6865](),[9.6865][10.6211:6269](),[10.6269][9.6916:7184](),[9.6916][9.6916:7184]()
    let ix = state.repo.untracked_files.len() - 1;
    untracked_file_selection(state, ix)
    }
    } else {
    let ix = ix - 1;
    changed_file_selection(state, ix)
    };
    Some(new_selection)
    }
    None => {
    if state.repo.changed_files.is_empty() {
    if state.repo.untracked_files.is_empty() {
    None
    [6.2411]
    [9.7184]
    let ix = ix - 1;
    changed_file_selection(repo, ix)
    };
    Some(new_selection)
    }
    None => {
    if repo.changed_files.is_empty() {
    if repo.untracked_files.is_empty() {
    None
    } else {
    let ix = repo.untracked_files.len() - 1;
    Some(untracked_file_selection(repo, ix))
    }
  • replacement in crates/flowers_ui/src/main.rs at line 249
    [9.7217][9.7217:7292](),[9.7292][10.6270:6340]()
    let ix = state.repo.untracked_files.len() - 1;
    Some(untracked_file_selection(state, ix))
    [9.7217]
    [6.2471]
    let ix = repo.changed_files.len() - 1;
    Some(changed_file_selection(repo, ix))
  • edit in crates/flowers_ui/src/main.rs at line 252
    [6.2497][9.7356:7454](),[9.7454][10.6341:6405]()
    } else {
    let ix = state.repo.changed_files.len() - 1;
    Some(changed_file_selection(state, ix))
  • replacement in crates/flowers_ui/src/main.rs at line 253
    [6.2519][9.7512:7530](),[9.7530][13.1340:1355]()
    }
    };
    [6.2519]
    [13.1355]
    };
    }
  • replacement in crates/flowers_ui/src/main.rs at line 262
    [10.6445][10.6445:6580](),[10.6580][13.1461:1516](),[13.1516][10.6636:7252](),[10.6636][10.6636:7252]()
    if let Some(cursor::Selection::UntrackedFile { ix, path }) =
    state.cursor.selection.as_ref()
    {
    repo::add(&mut state.repo.repo, path);
    let removed = state.repo.untracked_files.remove(path);
    debug_assert!(
    removed,
    "{:?}, path: {path}",
    state.repo.untracked_files
    );
    let file = repo::ChangedFile {
    path: path.clone(),
    diff: repo::ChangedFileDiff::Add,
    };
    state.repo.changed_files.insert(file);
    // Select the next untracked file, if any
    state.cursor.selection =
    if state.repo.untracked_files.is_empty() {
    [10.6445]
    [10.7252]
    if let Some(repo) = state.repo.as_mut() {
    if let Some(cursor::Selection::UntrackedFile { ix, path }) =
    state.cursor.selection.as_ref()
    {
    let _ =
    state.repo_tx_in.send(repo::MsgIn::AddUntrackedFile {
    path: path.clone(),
    });
    let removed = repo.untracked_files.remove(path);
    debug_assert!(
    removed,
    "{:?}, path: {path}",
    repo.untracked_files
    );
    let file = repo::ChangedFile {
    path: path.clone(),
    diff: repo::ChangedFileDiff::Add,
    };
    repo.changed_files.insert(file);
    // Select the next untracked file, if any
    state.cursor.selection = if repo.untracked_files.is_empty()
    {
  • replacement in crates/flowers_ui/src/main.rs at line 287
    [10.7310][10.7310:7490]()
    let ix =
    cmp::min(*ix, state.repo.untracked_files.len() - 1);
    Some(untracked_file_selection(state, ix))
    [10.7310]
    [10.7490]
    let ix = cmp::min(*ix, repo.untracked_files.len() - 1);
    Some(untracked_file_selection(repo, ix))
  • edit in crates/flowers_ui/src/main.rs at line 290
    [10.7513]
    [10.7513]
    }
  • replacement in crates/flowers_ui/src/main.rs at line 295
    [11.684][11.684:960](),[11.960][13.1543:1601](),[13.1601][11.1019:2009](),[11.1019][11.1019:2009]()
    if let Some(cursor::Selection::ChangedFile { ix, path }) =
    state.cursor.selection.as_ref()
    {
    let file = state.repo.changed_files.iter().nth(*ix).unwrap();
    if let repo::ChangedFileDiff::Add = &file.diff {
    repo::rm(&mut state.repo.repo, path);
    let file = file.clone();
    // Remove from changed files
    let removed = state.repo.changed_files.remove(&file);
    debug_assert!(
    removed,
    "{:?} not found in {:?}",
    file, state.repo.changed_files
    );
    // Update untracked files
    state.repo.untracked_files.insert(file.path);
    // Select the next changed file, if any
    state.cursor.selection =
    if state.repo.changed_files.is_empty() {
    None
    } else {
    let ix = cmp::min(
    *ix,
    state.repo.changed_files.len() - 1,
    );
    Some(changed_file_selection(state, ix))
    };
    [11.684]
    [11.2009]
    if let Some(repo) = state.repo.as_mut() {
    if let Some(cursor::Selection::ChangedFile { ix, path }) =
    state.cursor.selection.as_ref()
    {
    let file = repo.changed_files.iter().nth(*ix).unwrap();
    if let repo::ChangedFileDiff::Add = &file.diff {
    let _ =
    state.repo_tx_in.send(repo::MsgIn::RmAddedFile {
    path: path.clone(),
    });
    let file = file.clone();
    // Remove from changed files
    let removed = repo.changed_files.remove(&file);
    debug_assert!(
    removed,
    "{:?} not found in {:?}",
    file, repo.changed_files
    );
    // Update untracked files
    repo.untracked_files.insert(file.path);
    // 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(changed_file_selection(repo, ix))
    };
    }
  • replacement in crates/flowers_ui/src/main.rs at line 331
    [12.294][12.294:603]()
    if state.record_msg.is_some() {
    println!("Already recording");
    } else if state.repo.changed_files.is_empty() {
    println!("Trying to record with no changed file");
    } else {
    state.record_msg = Some(text_editor::Content::new());
    [12.294]
    [11.2027]
    if let Some(repo) = state.repo.as_ref() {
    if state.record_msg.is_some() {
    info!("Requested to record, but already recording");
    } else if repo.changed_files.is_empty() {
    info!("Trying to record with no changed files");
    } else {
    state.record_msg = Some(text_editor::Content::new());
    }
  • replacement in crates/flowers_ui/src/main.rs at line 348
    [12.782][12.782:906](),[12.906][13.1680:1709](),[13.1709][12.906:954](),[12.906][12.906:954]()
    Message::Save => match state.record_msg.as_ref() {
    None => {
    println!("Not recording");
    Task::none()
    }
    Some(record_msg) => {
    [12.782]
    [13.1710]
    Message::Save => {
    if let Some(record_msg) = state.record_msg.as_ref() {
  • replacement in crates/flowers_ui/src/main.rs at line 352
    [13.1815][12.1061:1118](),[12.1061][12.1061:1118](),[12.1118][13.1816:1849]()
    println!("Message cannot be empty");
    Task::none()
    [13.1815]
    [12.1118]
    info!("Cannot record with an empty message");
  • replacement in crates/flowers_ui/src/main.rs at line 355
    [12.1188][13.1850:2108]()
    // TODO: Make a record
    // Task::future(async move {
    // repo::record(&state.repo, msg).await;
    // Message::MadeRecord
    // })
    todo!()
    [12.1188]
    [12.1247]
    state.repo.as_mut().unwrap().changed_files =
    BTreeSet::new();
    let _ = state.repo_tx_in.send(repo::MsgIn::Record { msg });
  • replacement in crates/flowers_ui/src/main.rs at line 360
    [12.1279][12.1279:1290](),[12.1290][13.2109:2149]()
    },
    Message::MadeRecord => todo!(),
    [12.1279]
    [6.2657]
    Task::none()
    }
    }
    }
    fn update_from_repo(state: &mut State, msg: repo::MsgOut) -> Task<Message> {
    match msg {
    repo::MsgOut::Init(repo) => {
    state.repo = Some(repo);
    Task::none()
    }
    repo::MsgOut::RefreshedChangedAndUntrackedFiles {
    untracked_files,
    changed_files,
    } => {
    let repo = state.repo.as_mut().unwrap();
    repo.untracked_files = untracked_files;
    repo.changed_files = changed_files;
    // Re-index cursor selection
    if let Some(selection) = state.cursor.selection.as_ref() {
    // Try to find the file with the same name. If not found, remove
    // selection
    state.cursor.selection = match selection {
    cursor::Selection::UntrackedFile { ix: _, path } => repo
    .untracked_files
    .iter()
    .enumerate()
    .find(|(_ix, file_path)| *file_path == path)
    .map(|(ix, path)| cursor::Selection::UntrackedFile {
    ix,
    path: path.clone(),
    }),
    cursor::Selection::ChangedFile { ix: _, path } => repo
    .changed_files
    .iter()
    .enumerate()
    .find(|(_ix, file)| &file.path == path)
    .map(|(ix, file)| cursor::Selection::ChangedFile {
    ix,
    path: file.path.clone(),
    }),
    };
    }
    Task::none()
    }
  • replacement in crates/flowers_ui/src/main.rs at line 439
    [4.1501][5.5140:5242](),[6.3313][5.5242:5356](),[5.5242][5.5242:5356](),[5.5356][6.3314:3337](),[6.3337][9.7545:7648](),[9.7648][6.3518:3529](),[6.3518][6.3518:3529](),[6.3529][5.5394:5403](),[5.5394][5.5394:5403]()
    let dir_name = repo::dir_name(&state.repo);
    let channel = repo::current_channel(&state.repo);
    let repo_info = Element::from(row([
    Element::from(text(dir_name)),
    Element::from(text(": ")),
    Element::from(
    button(text(channel)), /* TODO
    * .on_press(Message) */
    ),
    ]));
    [4.1501]
    [6.3530]
    if let Some(repo) = state.repo.as_ref() {
    let repo_info = Element::from(row([
    Element::from(text(&repo.dir_name)),
    Element::from(text(": ")),
    Element::from(
    button(text(&repo.channel)), /* TODO
    * .on_press(Message) */
    ),
    ]));
  • replacement in crates/flowers_ui/src/main.rs at line 449
    [6.3531][9.7649:7755]()
    let untracked_files =
    Element::from(column(state.repo.untracked_files.iter().enumerate().map(
    [6.3531]
    [9.7755]
    let untracked_files =
    Element::from(column(repo.untracked_files.iter().enumerate().map(
  • replacement in crates/flowers_ui/src/main.rs at line 465
    [9.8341][6.3531:3633](),[6.3531][6.3531:3633]()
    let changed_files =
    Element::from(column(state.repo.changed_files.iter().enumerate().map(
    [9.8341]
    [10.7599]
    let changed_files =
    Element::from(column(repo.changed_files.iter().enumerate().map(
  • replacement in crates/flowers_ui/src/main.rs at line 481
    [6.4169][5.5588:5731](),[5.5588][5.5588:5731]()
    let log = Element::from(column(
    ["todo: commits"]
    .iter()
    .map(|hash| Element::from(text(*hash))),
    ));
    [6.4169]
    [6.4170]
    let log = Element::from(column(
    ["todo: commits"]
    .iter()
    .map(|hash| Element::from(text(*hash))),
    ));
    let record_msg_editor =
    if let Some(record_msg) = state.record_msg.as_ref() {
    Element::from(
    text_editor(record_msg)
    .placeholder("Type something here...")
    .on_action(Message::EditRecordMsg),
    )
    } else {
    Element::from(row([]))
    };
  • replacement in crates/flowers_ui/src/main.rs at line 498
    [6.4171][12.1678:1940]()
    let record_msg_editor = if let Some(record_msg) = state.record_msg.as_ref()
    {
    Element::from(
    text_editor(record_msg)
    .placeholder("Type something here...")
    .on_action(Message::EditRecordMsg),
    )
    [6.4171]
    [12.1940]
    Element::from(row([
    Element::from(
    column([
    repo_info,
    Element::from(horizontal_rule(1)),
    Element::from(text("Untracked:")),
    untracked_files,
    Element::from(horizontal_rule(1)),
    Element::from(text("Changed:")),
    changed_files,
    Element::from(horizontal_rule(1)),
    log,
    ])
    .width(Length::FillPortion(1)),
    ),
    Element::from(
    column([record_msg_editor]).width(Length::FillPortion(1)),
    ),
    ]))
  • replacement in crates/flowers_ui/src/main.rs at line 518
    [12.1953][12.1953:1992](),[12.1992][5.5731:5826](),[6.4171][5.5731:5826](),[5.5731][5.5731:5826](),[5.5826][9.8532:8667](),[9.8667][5.5826:5877](),[5.5826][5.5826:5877](),[5.5877][9.8668:8717](),[9.8717][5.5877:6050](),[5.5877][5.5877:6050](),[5.6050][12.1993:2098](),[12.2098][5.6115:6123](),[5.6115][5.6115:6123]()
    Element::from(row([]))
    };
    Element::from(row([
    Element::from(
    column([
    repo_info,
    Element::from(horizontal_rule(1)),
    Element::from(text("Untracked:")),
    untracked_files,
    Element::from(horizontal_rule(1)),
    Element::from(text("Changed:")),
    changed_files,
    Element::from(horizontal_rule(1)),
    log,
    ])
    .width(Length::FillPortion(1)),
    ),
    Element::from(
    column([record_msg_editor]).width(Length::FillPortion(1)),
    ),
    ]))
    [12.1953]
    [6.4172]
    Element::from(text("Loading repo..."))
    }
  • edit in crates/flowers_ui/Cargo.toml at line 27
    [8.1847]
    [8.1847]
    workspace = true
    [dependencies.tracing]
  • edit in crates/flowers_ui/Cargo.toml at line 30
    [8.1864]
    [dependencies.tracing-subscriber]
    workspace = true
  • edit in Cargo.toml at line 32
    [2.4346]
    [2.4347]
    features = ["tokio"]
  • edit in Cargo.toml at line 61
    [3.6285]
    [workspace.dependencies.tracing]
    version = "0.1"
    [workspace.dependencies.tracing-subscriber]
    version = "0.3"
    features = ["env-filter"]
    [profile.dev]
    opt-level = 1 # Async seems to have big performance hit without this
  • edit in Cargo.lock at line 1361
    [8.3121]
    [2.30026]
    "tracing",
    "tracing-subscriber",
  • replacement in Cargo.lock at line 1628
    [4.10215][4.10215:10251]()
    "regex-automata",
    "regex-syntax",
    [4.10215]
    [4.10251]
    "regex-automata 0.4.9",
    "regex-syntax 0.8.5",
  • edit in Cargo.lock at line 1867
    [2.40121]
    [2.40121]
    "tokio",
  • replacement in Cargo.lock at line 1992
    [4.11567][4.11567:11586]()
    "regex-automata",
    [4.11567]
    [4.11586]
    "regex-automata 0.4.9",
  • edit in Cargo.lock at line 2166
    [4.12317]
    [2.44952]
    "tracing",
  • edit in Cargo.lock at line 2178
    [10.8169]
    [2.45044]
    "tokio",
  • edit in Cargo.lock at line 2318
    [2.47168]
    [2.47168]
    ]
    [[package]]
    name = "matchers"
    version = "0.1.0"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
    dependencies = [
    "regex-automata 0.1.10",
  • edit in Cargo.lock at line 2516
    [8.5651]
    [8.5651]
    [[package]]
    name = "nu-ansi-term"
    version = "0.46.0"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
    dependencies = [
    "overload",
    "winapi",
    ]
  • edit in Cargo.lock at line 2834
    [2.57188]
    [2.57188]
    [[package]]
    name = "overload"
    version = "0.1.1"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
  • replacement in Cargo.lock at line 3350
    [4.16427][4.16427:16463]()
    "regex-automata",
    "regex-syntax",
    [4.16427]
    [2.67123]
    "regex-automata 0.4.9",
    "regex-syntax 0.8.5",
    ]
    [[package]]
    name = "regex-automata"
    version = "0.1.10"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
    dependencies = [
    "regex-syntax 0.6.29",
  • replacement in Cargo.lock at line 3371
    [4.16694][4.16694:16711]()
    "regex-syntax",
    [4.16694]
    [4.16711]
    "regex-syntax 0.8.5",
  • edit in Cargo.lock at line 3373
    [4.16713]
    [4.16713]
    [[package]]
    name = "regex-syntax"
    version = "0.6.29"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
  • edit in Cargo.lock at line 3626
    [4.18727]
    [2.70894]
    ]
    [[package]]
    name = "sharded-slab"
    version = "0.1.7"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
    dependencies = [
    "lazy_static",
  • edit in Cargo.lock at line 3976
    [4.19615]
    [4.19615]
    ]
    [[package]]
    name = "thread_local"
    version = "1.1.8"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
    dependencies = [
    "cfg-if",
    "once_cell",
  • edit in Cargo.lock at line 4172
    [2.79782]
    [2.79782]
    dependencies = [
    "once_cell",
    "valuable",
    ]
    [[package]]
    name = "tracing-log"
    version = "0.2.0"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
    dependencies = [
    "log",
    "once_cell",
    "tracing-core",
    ]
    [[package]]
    name = "tracing-subscriber"
    version = "0.3.19"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
  • edit in Cargo.lock at line 4194
    [2.79799]
    [2.79799]
    "matchers",
    "nu-ansi-term",
  • edit in Cargo.lock at line 4197
    [2.79813]
    [2.79813]
    "regex",
    "sharded-slab",
    "smallvec",
    "thread_local",
    "tracing",
    "tracing-core",
    "tracing-log",
  • edit in Cargo.lock at line 4319
    [2.82852]
    [2.82852]
    name = "valuable"
    version = "0.1.1"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
    [[package]]