Although it may not seem like much (visually), I believe that this new implementation should be much more flexible and should allow many more modules to be spun off into their individual crates.
Q45BC4OC3NSRLJANNOBHENZCFSF7SG2MMYMZR4247CPZXOJ5VSSAC
6HQHOC2ZD2BZICJXOQT4SS4AUY75TC5FTEE3UWBIGI4WGRYJACHQC
DFK4BTAA6IHIYARZWQH6LLBMWL2CMENP2MOYBQCKQMCTT72ZXNKAC
S4LQTDJIXHLNCOVARYKRDSE4H22L5HBQ3AOLYBBNCIS42OOMP24QC
BHFLDCFR6ZOUVAZUOYUSYACWDVY52GAXR6Q4TR4WMCHHFGECXQ3AC
2FZSXUGKG4F3ARCC6J5A5VUWPULYG22U3E5WNIUJJNCTD3ZVUHQQC
5XCNW4EVBUVSZPDPGLHQOZJOUWMMSCLK7QTXF7GNS3L5Q2G2MLCAC
Z6ASIMORLV437IEFIUYXIRYSGUTIBMPJNIVF6XQMGRP6WV6R4YPAC
DAD6VPVMOCIO7MRJLYCBBV4W4YTBM2O3LLNMHWSZ65TBOY5QCXZAC
2MVT53CF3CC4FHPJWUNS3ZCCPRO26ROA5JAYCRGQQ4SH3SO2YGSAC
ENWJBQGQUL3KLYPVGYP2ZSDB5ZUXLTY4W6NCHUA6VTHRRNPRUDHAC
SXEYMYF7P4RZMZ46WPL4IZUTSQ2ATBWYZX7QNVMS3SGOYXYOHAGQC
BNPSVXIC72C3WT33YKCH766OBLLNCS7POX6U6JXZSQQPJF2M22MQC
2D7P2VKJASU7QDQZHGCLBIT6G2V5WUFYLWTCEVVEI2EZHGM6XYRAC
GNMZNKB46GTPTWBR452FITHPBCMYPSDLV5VZQSY7BX6OJHWTWTZAC
3OF3DHLM6FGCN7GTKZUE5U5VL3HB3SCDAJ5AL3KD6MDVQ4VLJQUQC
MXQ3U2DPIALVG7HZOVC7U3HBQBGIPTLLU2ASTZICYF7KIQ7SYGLQC
KWD6K4F73WZGP7GZLKZHXF7364QNCVNFWEYCCLFPQINMLPZBTWAQC
L4JXJHWXYNCL4QGJXNKKTOKKTAXKKXBJUUY7HFZGEUZ5A2V5H34QC
ISCWVXO6UN37V2QMR75ZWS7H5E7WYZCGR6TX3EDFOFRVCAPMIVUAC
DO2Y5TY5JQISUHCVNPI2FXO7WWZVJQ3LGPWF4DNADMGZRIO6PT2QC
KTTKF3RWYAK2YSH2DYYW5QVG4KSNGWUBJBFHKE24OJ7LFCBF5FEAC
WLUID7NANDWTN5GOECNEKFTLZF3MUVS7K26YWLYLSGJ56G63NV4QC
MU5GSJAW65PEG3BRYUKZ7O37BPHW3MOX3S5E2RFOXKGUOJEEDQ5AC
UDHP4ZVBQZT2VBURB2MDCU2IZDNMCAFSIUKWRBDQ5BWMFKSN2LYQC
TYAKEAJLABCZQDYAI4YBGIJNQ7HJS4DVULEGPCZOGJPJUYYNR6TAC
76PCXGML77EZWTRI5E6KHLVRAFTJ2AB5YRN5EKOYNAPKTWY2KCGAC
TKEVOH7HXON7SOBGXTUDHAHO2U2GPTQRNESP6ERKUQAS526OZIRAC
IBPVOKM5MXTGB2P7LCD75MISAYUNDPEKQAUEVCXJJWLWCX2TJZBAC
4HTHYIA3GLMUBUMAI7CQMZA4KE47EUXOP24XLUEYFRMOG57CJLMAC
C3L2TLQWREYOM3YHL37L7PS74YGLHBEDQRSCVMYIU6HKBEPNN2SAC
Q45QHPO4HDTEZF2W4UDZSYYQ46BPEIWSW4GJILZR5HTJNLKXJABQC
A3RM526Y7LUXNYW4TL56YKQ5GVOK2R5D7JJVTSQ6TT5MEXIR6YAAC
HXEIH4UQ6EX3MAY33JK4WQUE5GUSZ673OX57JKNFXC2N2QLTXKXAC
I24UEJQLCH2SOXA4UHIYWTRDCHSOPU7AFTRUOTX7HZIAV4AZKYEQC
ZWVYH7WPYOGDKWODFSAJ6R5U64DON2AVVJ2XZJKHAOMLJEFTYF3QC
ZDK3GNDBWXJ2OXFDYB72ZCEBGLBF4MKE5K3PVHDZATHJ7HJIDPRQC
I52XSRUH5RVHQBFWVMAQPTUSPAJ4KNVID2RMI3UGCVKFLYUO6WZAC
5SLOJYHGPMZVCOE3IS7ICNMJJYX3RBT6CDG5MAV6T4CJIOW7YZ6QC
OIOMXESDNMLOTMNYCZZBYSBAQTYPAXXMUHTLA2AYCMNHZMPSLX2AC
H4AU6QRPRDRFW3V7NN5CJ6DHLEUBYGNLRZ5GYV6ULBGRMOPCJQXQC
FBXYP7QM7SG6P2JDJVQPPCRKJE3GVYXNQ5GVV4GRDUNG6Q4ZRDJQC
LZOGKBJXRQJKXHYNNENJFGNLP5SHIXGSV6HDB7UVOP7FSA5EUNCQC
IQ4FCHPZYGTZHCQHUIRCMUI5LCHIDSJCM2AZXGRJARWLCPPLXZOQC
367UBQ6KNAKUEWG32R4QRJ6H7IE7NAZFOPTC3ZOE4Z6E44RV3ISQC
JZADJIA3P3EOKPBGEKEXJVGWHNF2SIHYNNMB3XFNPBU4BTVGM3YQC
TI7PCK7JLPU4KYE65XIMBUPPY7DRPVDAETPDFMG3VAQGGDRGQHPQC
FVQYZQFL7WHSC3UUPJ4IWTP7SKTDQ4K6K5HY4EDK3JKXG3CQNZEAC
6ZPDI7QGISBFIIJWV4J4JGCZKW2OZA57ZEQSP5YXWRFW3J6PGMVAC
ZSFJT4SFIAS7WBODRZOFKKG4SVYBC5PC6XY75WYN7CCQ3SMV7IUQC
YN63NUZO4LVJ7XPMURDULTXBVJKW5MVCTZ24R7Z52QMHO3HPDUVQC
XSRTXUAS3DXJA42TZESMETFVTKU2OBUDGDE4N5F2CVWI4CLOUJ4AC
EUZFFJSOWV4PXDFFPDAFBHFUUMOFEU6ST7JH57YYRRR2SEOXLN6QC
Y6EVFMTA6FOH3OQH6QCSWMI3F6SYZT2FSHO6GF4M3ICENDCWFM4QC
LGEJSLTYI7Y2CYC3AN6ECMT3D3MTWCAKZPVQEG5MPM2OBW5FQ46AC
C4MJ7D7QCOFGIHQSDV3UC76DTSE5M7JSLGN5MROMSVKFVQRFSP5QC
ISZ65SPQXSDGK6T6VQJJTD54KD3R5GOQ3GIMRALPJU6JPUSHUXMQC
MDADYULS5AWVMTJDGYCGNQTN6T7XJDRUBDTFILDY5MLF6I2PE5NAC
6B5SYXGBWMM5GMSOHD6GZITBNUYICSI2TXSRACJU6Q72BEU7XFHQC
2K7JLB4Z7BS5VFNWD4DO3MKYU7VNPA5MTVHVSDI3FQZ5ICM6XM6QC
TNN56XYKX4QRHA4FWCF5F3JVG52FIAC76EEYYANDKEE4IAWQKPEQC
SGXOEWHUET7RVS5HANSYE7QRQAG56NIKTC56MLPZ5XBCXODEWUXQC
STG7MO5MLMKFJYJQDAXL6YSUJE2BTXOMWUQHXBWFHDFJNNJRP5AQC
EJ7TFFOWLM5EXYX57NJZZX3NLPBLLMRX7CGJYC75DJZ5LYXOQPJAC
use std::time::Duration;
use indicatif::MultiProgress;
use lazy_static::lazy_static;
pub use indicatif::ProgressStyle;
lazy_static! {
static ref MULTI_PROGRESS: MultiProgress = MultiProgress::new();
}
pub const DOWNLOAD_MESSAGE: &str = "Downloading changes";
pub const APPLY_MESSAGE: &str = "Applying changes";
pub const UPLOAD_MESSAGE: &str = "Uploading changes";
pub const COMPLETE_MESSAGE: &str = "Completing changes";
pub const OUTPUT_MESSAGE: &str = "Outputting repository";
#[derive(Clone, Debug)]
pub struct ProgressBar(Option<indicatif::ProgressBar>);
impl ProgressBar {
pub fn new(len: u64, message: &str) -> Result<Self, anyhow::Error> {
let style =
ProgressStyle::with_template("{msg:<20} [{bar:50}] {pos}/{len} [{elapsed_precise}]")?
.progress_chars("=> ");
let progress_bar = indicatif::ProgressBar::new(len)
.with_style(style)
.with_message(message.to_owned());
MULTI_PROGRESS.add(progress_bar.clone());
progress_bar.enable_steady_tick(Duration::from_millis(15));
Ok(Self(Some(progress_bar)))
}
pub fn hidden() -> Self {
Self(None)
}
pub fn inc(&self, delta: u64) {
if let Some(progress) = self.0.clone() {
progress.inc(delta);
}
}
pub fn inner(&self) -> Option<indicatif::ProgressBar> {
self.0.clone()
}
pub fn finish(&self) {
if let Some(progress) = self.0.clone() {
progress.finish();
}
}
}
#[derive(Clone, Debug)]
pub struct Spinner(indicatif::ProgressBar);
impl Spinner {
pub fn new(message: &str) -> Result<Self, anyhow::Error> {
let style = ProgressStyle::with_template("{msg}{spinner}")?
.tick_strings(&[". ", ".. ", "...", " "]);
let spinner = indicatif::ProgressBar::new_spinner()
.with_style(style)
.with_message(message.to_owned());
spinner.enable_steady_tick(Duration::from_millis(200));
MULTI_PROGRESS.add(spinner.clone());
Ok(Self(spinner))
}
pub fn finish(&self) -> Result<(), anyhow::Error> {
self.0.set_style(ProgressStyle::with_template("{msg}")?);
self.0
.finish_with_message(format!("{}... done!", self.0.message()));
Ok(())
}
}
pub mod progress;
[package]
name = "pijul-term"
description = "Terminal visualisations for the pijul binary"
version = "0.0.1"
authors = ["Finchie"]
edition = "2018"
repository = "https://nest.pijul.com/pijul/pijul"
license = "GPL-2.0"
include = [ "Cargo.toml", "src" ]
[dependencies]
anyhow = { version = "1.0.71", features = ["backtrace"] }
indicatif = { version = "0.17.5", features = ["improved_unicode"] }
lazy_static = "1.4.0"
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().try_into()?;
let download_progress = ProgressBar::new(apply_len, DOWNLOAD_MESSAGE)?;
let apply_progress = if do_apply {
ProgressBar::new(apply_len, APPLY_MESSAGE)?
let pro_n = {
let mut pro = PROGRESS.borrow_mut().unwrap();
pro.push(crate::progress::Cursor::Bar {
i: 0,
n: tag.len(),
pre: "Downloading changes".into(),
})
};
let download_progress = ProgressBar::new(tag.len().try_into()?, DOWNLOAD_MESSAGE)?;
let cloned_download_progress = download_progress.clone();
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_progress = 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_progress,
&mut recv_hash,
&mut send_sig,
&mut changes_dir,
true,
)
.await?;
Ok::<_, anyhow::Error>(self_)
});
if let Ok((config, _)) = Global::load() {
let color_choice = config.colors.unwrap_or_default();
match color_choice {
Choice::Auto | Choice::Always => Ok(Box::new(theme::ColorfulTheme::default())),
Choice::Never => Ok(Box::new(theme::SimpleTheme)),
}
} else {
Ok(Box::new(theme::ColorfulTheme::default()))
match color_choice() {
Choice::Auto | Choice::Always => Ok(Box::new(theme::ColorfulTheme::default())),
Choice::Never => Ok(Box::new(theme::SimpleTheme)),
let mut pro = PROGRESS.borrow_mut().unwrap();
let n = pro.push(crate::progress::Cursor::Bar {
i: 0,
n: to_download.len(),
pre: "Applying".into(),
});
std::mem::drop(pro);
let apply_progress = ProgressBar::new(to_download.len().try_into()?, APPLY_MESSAGE)?;
name = "indicatif"
version = "0.17.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ff8cc23a7393a397ed1d7f56e6365cba772aba9f9912ab968b03043c395d057"
dependencies = [
"console",
"instant",
"number_prefix",
"portable-atomic",
"unicode-segmentation",
"unicode-width",
]
[[package]]