YBJRDOTCX3ZRDB5EVXJBR55FX3CADCSIGMYWNYVC2PD5W3GXR3DQC 6YZAVBWU6E5FYOI5JGEIPXGZLIKAW6LS2AOFIQWEE5DMOPPCD5PQC KLR5FRIBS6UOH3S3XAOE22TJACVSVOY7TOLW22DIWNGY27S6WZRAC IQDCHWCP47LL46EXQLQGHQPGFYIHQLMQBHA57RWJCIOX5UEUIQAQC SWWE2R6MVBX5CNM6X3WLXZTSRTU53PBJL7WJSFVF77XBPXDX4COAC WT3GA27PQ2AOAIGK65O3Q4DMX4AZDVNULBLRL6GF4QW6QCASUEAAC DVKSPF7R5QBWRHNADU7LK37OVZHOHNDSRETUVY6GNXWE74SHXSUAC UB2ITZJSDADVINSQEZ3HA6PVGA7OA6JYFG5GMSO7Y7LOXJC4FI7AC EC3TVL4X6VZZVLOKUN63LC73ADPHBHMZO7QMDXGX2ZPURVI4B4XQC KT5UYXGKEEXUHURNOYFVIG7WQ3Y3SJZMM2TP4OSW6NXSXQ5XXRHAC ELG3UDT6OJFEYSJR7HZEC65IUWBMGPPPCXEW3CDW5T74R6KC5LIAC S2NVIFXRFER4SRA37WCT5XTXHDHAL5WIGGKY4A4XOTPLTKTZSRGQC W7IUT3ZVMFH77IGKLAL7WX7IVVTGTY3FKEJ3WHMP3KI37B6NENLQC impl Deref for State {type Target = pijul::Repository;
/// 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();
fn deref(&self) -> &Self::Target {&self.repo
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;}
pub type Diff = LocalChange<Hunk<Option<Hash>, Local>, Author>;
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}
let path_str = path_slash::PathExt::to_slash_lossy(path.as_path());
// NOTE: This has to be the file path without the repo's path prefixlet file_path = PathBuf::from(file_path_str);let path_str = path_slash::PathExt::to_slash_lossy(file_path.as_path());
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());
let file_path = PathBuf::from(file_path_str);let path_str = path_slash::PathExt::to_slash_lossy(file_path.as_path());
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}}
[dependencies.tracing]workspace = true
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();
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);
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 }};
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 }};
|state: &State, ix: usize| -> cursor::Selection {let path = state.repo.changed_files.iter().nth(ix).unwrap().path.clone();
|repo: &repo::State, ix: usize| -> cursor::Selection {let path = repo.changed_files.iter().nth(ix).unwrap().path.clone();
match message {Message::FilesChanged => {dbg!("FilesChanged");repo::refresh(&mut state.repo);// Re-index cursor selectionif let Some(selection) = state.cursor.selection.as_ref() {// Try to find the file with the same name. If not found, remove// selectionstate.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(),}),};}
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);
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)
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)
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)
Some(new_selection)}None => {if repo.untracked_files.is_empty() {if repo.changed_files.is_empty() {None
} 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
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)
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)}
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)
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)}
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
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))}
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 anystate.cursor.selection =if state.repo.untracked_files.is_empty() {
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 anystate.cursor.selection = if repo.untracked_files.is_empty(){
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 fileslet removed = state.repo.changed_files.remove(&file);debug_assert!(removed,"{:?} not found in {:?}",file, state.repo.changed_files);// Update untracked filesstate.repo.untracked_files.insert(file.path);// Select the next changed file, if anystate.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))};
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 fileslet removed = repo.changed_files.remove(&file);debug_assert!(removed,"{:?} not found in {:?}",file, repo.changed_files);// Update untracked filesrepo.untracked_files.insert(file.path);// Select the next changed file, if anystate.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))};}
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());
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());}
Message::Save => match state.record_msg.as_ref() {None => {println!("Not recording");Task::none()}Some(record_msg) => {
Message::Save => {if let Some(record_msg) = state.record_msg.as_ref() {
// TODO: Make a record// Task::future(async move {// repo::record(&state.repo, msg).await;// Message::MadeRecord// })todo!()
state.repo.as_mut().unwrap().changed_files =BTreeSet::new();let _ = state.repo_tx_in.send(repo::MsgIn::Record { msg });
},Message::MadeRecord => todo!(),
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 selectionif let Some(selection) = state.cursor.selection.as_ref() {// Try to find the file with the same name. If not found, remove// selectionstate.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()}
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) */),]));
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) */),]));
let log = Element::from(column(["todo: commits"].iter().map(|hash| Element::from(text(*hash))),));
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([]))};
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),)
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)),),]))
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)),),]))
Element::from(text("Loading repo..."))}
[dependencies.tracing-subscriber]workspace = true
[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
"regex-automata","regex-syntax",
"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",
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"