This change should make terminal interactions less error-prone, nicer looking and easier to maintain, but further polish is still needed, as many pre-existing issues regarding status output have not been fully resolved.
JUYSZJSHULJFR4HUJF72TEKKFMBPG4ZOGAGOJ2BX6P3D4DRZAU5QC 6HQHOC2ZD2BZICJXOQT4SS4AUY75TC5FTEE3UWBIGI4WGRYJACHQC DFK4BTAA6IHIYARZWQH6LLBMWL2CMENP2MOYBQCKQMCTT72ZXNKAC S4LQTDJIXHLNCOVARYKRDSE4H22L5HBQ3AOLYBBNCIS42OOMP24QC BHFLDCFR6ZOUVAZUOYUSYACWDVY52GAXR6Q4TR4WMCHHFGECXQ3AC 2FZSXUGKG4F3ARCC6J5A5VUWPULYG22U3E5WNIUJJNCTD3ZVUHQQC ISZ65SPQXSDGK6T6VQJJTD54KD3R5GOQ3GIMRALPJU6JPUSHUXMQC 5XCNW4EVBUVSZPDPGLHQOZJOUWMMSCLK7QTXF7GNS3L5Q2G2MLCAC XQHABMC2FOMH7SZIYVYAR5MNH2DK2AOUCX2RJKZM3PDG2H5JIXYQC Z6ASIMORLV437IEFIUYXIRYSGUTIBMPJNIVF6XQMGRP6WV6R4YPAC LZOGKBJXRQJKXHYNNENJFGNLP5SHIXGSV6HDB7UVOP7FSA5EUNCQC SXEYMYF7P4RZMZ46WPL4IZUTSQ2ATBWYZX7QNVMS3SGOYXYOHAGQC BNPSVXIC72C3WT33YKCH766OBLLNCS7POX6U6JXZSQQPJF2M22MQC 2D7P2VKJASU7QDQZHGCLBIT6G2V5WUFYLWTCEVVEI2EZHGM6XYRAC GNMZNKB46GTPTWBR452FITHPBCMYPSDLV5VZQSY7BX6OJHWTWTZAC 3OF3DHLM6FGCN7GTKZUE5U5VL3HB3SCDAJ5AL3KD6MDVQ4VLJQUQC MXQ3U2DPIALVG7HZOVC7U3HBQBGIPTLLU2ASTZICYF7KIQ7SYGLQC DO2Y5TY5JQISUHCVNPI2FXO7WWZVJQ3LGPWF4DNADMGZRIO6PT2QC L4JXJHWXYNCL4QGJXNKKTOKKTAXKKXBJUUY7HFZGEUZ5A2V5H34QC ISCWVXO6UN37V2QMR75ZWS7H5E7WYZCGR6TX3EDFOFRVCAPMIVUAC KTTKF3RWYAK2YSH2DYYW5QVG4KSNGWUBJBFHKE24OJ7LFCBF5FEAC WLUID7NANDWTN5GOECNEKFTLZF3MUVS7K26YWLYLSGJ56G63NV4QC UDHP4ZVBQZT2VBURB2MDCU2IZDNMCAFSIUKWRBDQ5BWMFKSN2LYQC TYAKEAJLABCZQDYAI4YBGIJNQ7HJS4DVULEGPCZOGJPJUYYNR6TAC 76PCXGML77EZWTRI5E6KHLVRAFTJ2AB5YRN5EKOYNAPKTWY2KCGAC MU5GSJAW65PEG3BRYUKZ7O37BPHW3MOX3S5E2RFOXKGUOJEEDQ5AC TKEVOH7HXON7SOBGXTUDHAHO2U2GPTQRNESP6ERKUQAS526OZIRAC IBPVOKM5MXTGB2P7LCD75MISAYUNDPEKQAUEVCXJJWLWCX2TJZBAC 4HTHYIA3GLMUBUMAI7CQMZA4KE47EUXOP24XLUEYFRMOG57CJLMAC C3L2TLQWREYOM3YHL37L7PS74YGLHBEDQRSCVMYIU6HKBEPNN2SAC Q45QHPO4HDTEZF2W4UDZSYYQ46BPEIWSW4GJILZR5HTJNLKXJABQC A3RM526Y7LUXNYW4TL56YKQ5GVOK2R5D7JJVTSQ6TT5MEXIR6YAAC HXEIH4UQ6EX3MAY33JK4WQUE5GUSZ673OX57JKNFXC2N2QLTXKXAC I24UEJQLCH2SOXA4UHIYWTRDCHSOPU7AFTRUOTX7HZIAV4AZKYEQC ZWVYH7WPYOGDKWODFSAJ6R5U64DON2AVVJ2XZJKHAOMLJEFTYF3QC ZDK3GNDBWXJ2OXFDYB72ZCEBGLBF4MKE5K3PVHDZATHJ7HJIDPRQC I52XSRUH5RVHQBFWVMAQPTUSPAJ4KNVID2RMI3UGCVKFLYUO6WZAC 5SLOJYHGPMZVCOE3IS7ICNMJJYX3RBT6CDG5MAV6T4CJIOW7YZ6QC AI73GKAO5QBPR6YGW7H5UNZYAEGYGIHAFO6DM2DWCPMVYLHE547QC FBXYP7QM7SG6P2JDJVQPPCRKJE3GVYXNQ5GVV4GRDUNG6Q4ZRDJQC IQ4FCHPZYGTZHCQHUIRCMUI5LCHIDSJCM2AZXGRJARWLCPPLXZOQC 367UBQ6KNAKUEWG32R4QRJ6H7IE7NAZFOPTC3ZOE4Z6E44RV3ISQC JZADJIA3P3EOKPBGEKEXJVGWHNF2SIHYNNMB3XFNPBU4BTVGM3YQC TI7PCK7JLPU4KYE65XIMBUPPY7DRPVDAETPDFMG3VAQGGDRGQHPQC ZSFJT4SFIAS7WBODRZOFKKG4SVYBC5PC6XY75WYN7CCQ3SMV7IUQC YN63NUZO4LVJ7XPMURDULTXBVJKW5MVCTZ24R7Z52QMHO3HPDUVQC XSRTXUAS3DXJA42TZESMETFVTKU2OBUDGDE4N5F2CVWI4CLOUJ4AC EUZFFJSOWV4PXDFFPDAFBHFUUMOFEU6ST7JH57YYRRR2SEOXLN6QC 6ZPDI7QGISBFIIJWV4J4JGCZKW2OZA57ZEQSP5YXWRFW3J6PGMVAC Y6EVFMTA6FOH3OQH6QCSWMI3F6SYZT2FSHO6GF4M3ICENDCWFM4QC LGEJSLTYI7Y2CYC3AN6ECMT3D3MTWCAKZPVQEG5MPM2OBW5FQ46AC MDADYULS5AWVMTJDGYCGNQTN6T7XJDRUBDTFILDY5MLF6I2PE5NAC 2K7JLB4Z7BS5VFNWD4DO3MKYU7VNPA5MTVHVSDI3FQZ5ICM6XM6QC J2D66R2DQVRQVQAFFIR4F7RI3BHQFANY2HEGUFRBNUPFVUZAXUTQC use std::borrow::Cow;use std::io::Write;use std::sync::{Arc, Mutex};pub struct Cursors {pub inner: Arc<Mutex<InnerCursors>>,}pub struct InnerCursors {drawn: usize,n_post: usize,n_pre: usize,w: usize,stop: bool,}impl Cursors {pub fn new() -> Self {let inner = Arc::new(Mutex::new(InnerCursors {drawn: 0,cursors: Vec::new(),n_post: 0,n_pre: 0,stop: false,w: 0,}));{let mut inner = if let Ok(inner) = inner_.lock() {inner} else {break;};if inner.stop {inner.render().unwrap();break;} else {inner.render().unwrap();}}std::thread::sleep(std::time::Duration::from_millis(100));}pub fn stop(&self) {if let Ok(mut n) = self.inner.lock() {n.stop = true}}self.stop();}pub fn borrow_mut(&self,) -> Result<std::sync::MutexGuard<InnerCursors>,std::sync::PoisonError<std::sync::MutexGuard<'_, InnerCursors>>,> {let mut m = self.inner.lock()?;m.n_pre = 0;Ok(m)}}#[allow(dead_code)]pub enum Cursor {Static {pre: Cow<'static, str>,},Bar {pre: Cow<'static, str>,n: usize,i: usize,},Spin {pre: Cow<'static, str>,i: usize,},}impl Cursor {fn pre(&self) -> &str {match self {Cursor::Static { pre } => pre,Cursor::Bar { pre, .. } => pre,Cursor::Spin { pre, .. } => pre,}}fn n(&self) -> usize {match self {Cursor::Bar { n, .. } => {let mut n = *n;let mut r = 6;while n > 0 {n /= 10;r += 2}r}_ => 0,}}pub fn incr(&mut self) {match self {Cursor::Bar { i, .. } => *i += 1,_ => {}}}fn render<W: std::io::Write>(&mut self,stdout: &mut W,npre: usize,npost: usize,w: usize,) -> Result<(), std::io::Error> {match self {Cursor::Static { pre } => {for _ in 0..npre - pre.chars().count() {stdout.write_all(b" ")?;}stdout.write_all(pre.as_bytes())?;// Fil the rest of the line with spaces.for _ in 0..w - npre {stdout.write_all(b" ")?;}Ok(())}Cursor::Bar { pre, i, n } => {for _ in 0..npre - pre.chars().count() {stdout.write_all(b" ")?;}// Comupte the appropriate width for the bar.// Output the bar.write!(stdout, "{} [", pre)?;let wb = (w as usize).min(50);}}write!(stdout, "] {}/{}", *i, *n)?;for _ in 0..nw {stdout.write_all(b" ")?;}Ok(())}Cursor::Spin { pre, i } => {for _ in 0..npre - pre.chars().count() {stdout.write_all(b" ")?;}stdout.write_all(pre.as_bytes())?;stdout.write_all(b" ")?;const SYM: [&str; 8] = ["←", "↖", "↑", "↗", "→", "↘", "↓", "↙"];stdout.write_all(SYM[*i].as_bytes())?;*i = (*i + 1) % SYM.len();// Fill the rest of the line with spaces.for _ in 0..w - npre - 2 {stdout.write_all(b" ")?;}Ok(())}}}}impl InnerCursors {fn render(&mut self) -> Result<(), std::io::Error> {use terminal_size::*;let mut stdout = std::io::stdout();if let Some((Width(w), _)) = terminal_size() {if self.n_pre == 0 {self.n_post = 0;for c in self.cursors.iter() {let n_pre = c.pre().chars().count();self.n_pre = self.n_pre.max(n_pre);self.n_post = self.n_post.max(c.n());}}stdout.write_all(b"\x1B[F")?;}for c in self.cursors.iter_mut() {// Clear the end of the line and move to the next one.stdout.write_all(b"\x1B[K\n")?;}self.drawn = self.cursors.len();// Erase the terminal after the cursor.stdout.write_all(b"\x1B[J")?;stdout.flush()?;}Ok(())}}c.render(&mut stdout, self.n_pre, self.n_post, w)?;self.w = w;let w = w as usize;for _ in 0..self.drawn {pub fn push(&mut self, c: Cursor) -> usize {let r = self.cursors.len();self.cursors.push(c);r}let nw = w + npost - wb - 6;if *n <= 1 {for _ in 0..wb as usize {}} else {for j in 0..wb as usize {if j < k {write!(stdout, "=")?;} else if j == k {write!(stdout, ">")?;} else {write!(stdout, " ")?}let k = (wb as usize * *i) / *n;stdout.write_all(filler.as_bytes())?;let filler = if *i == *n { "=" } else { " " };let w_digits = {let mut n = *n;let mut nd = if n == 0 { 1 } else { 0 } + if *i == 0 { 1 } else { 0 };while n > 0 {n /= 10;nd += 1}};let w = w - npre - npost - w_digits;nd * 2_ => {}}}pub fn incr_len(&mut self) {match self {Cursor::Bar { n, .. } => *n += 1,m.stop = false;debug!("borrow_mut");self.restart();let mut t = self.t.lock().unwrap();if let Some(t) = t.take() {t.join().unwrap();}pub fn join(&self) {debug!("join");debug!("stop");}));let cursors = Cursors {inner,t: Mutex::new(None),};cursors.restart();cursors}fn restart(&self) {debug!("restart");let mut t = self.t.lock().unwrap();if t.is_some() {return;}let inner_ = self.inner.clone();*t = Some(std::thread::spawn(move || loop {}impl std::ops::Index<usize> for InnerCursors {type Output = Cursor;fn index(&self, i: usize) -> &Self::Output {self.cursors.index(i)}}impl std::ops::IndexMut<usize> for InnerCursors {fn index_mut(&mut self, i: usize) -> &mut Self::Output {self.cursors.index_mut(i)}cursors: Vec<Cursor>,t: Mutex<Option<std::thread::JoinHandle<()>>>,lazy_static::lazy_static! {pub static ref PROGRESS: crate::progress::Cursors = crate::progress::Cursors::new();}use log::*;
let mut pro = PROGRESS.borrow_mut().unwrap();let pro_a = pro.push(crate::progress::Cursor::Bar {i: 0,n: to_apply.len(),pre: "Downloading changes".into(),});let pro_b = if do_apply {Some(pro.push(crate::progress::Cursor::Bar {i: 0,n: to_apply.len(),pre: "Applying".into(),}))
let apply_len = to_apply.len() as u64;let download_bar = ProgressBar::new(apply_len, DOWNLOAD_MESSAGE)?;let apply_bar = if do_apply {ProgressBar::new(apply_len, APPLY_MESSAGE)?
let mut progress = PROGRESS.borrow_mut().unwrap();let pro_n = {progress.push(crate::progress::Cursor::Bar {i: 0,n: 0,pre: "Completing changes".into(),})};std::mem::drop(progress);let t = tokio::spawn(async move {self_.download_changes(pro_n, &mut recv_hash, &mut send_sig, &mut changes_dir, true).await?;Ok::<_, anyhow::Error>(self_)});
let dummy_bar = ProgressBar::hidden();let _completion_spinner = Spinner::new(COMPLETE_MESSAGE)?;let t: tokio::task::JoinHandle<Result<RemoteRepo, anyhow::Error>> =tokio::spawn(async move {self_.download_changes(dummy_bar,&mut recv_hash,&mut send_sig,&mut changes_dir,true,).await?;Ok::<_, anyhow::Error>(self_)});