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_)
});