mirror of
https://github.com/denoland/deno.git
synced 2024-11-22 04:51:22 +00:00
feat: refresh "Download" progress bar with a spinner (#24913)
This commit adds a spinner to "Download" progress bar and makes it multiline, showing up to 4 lines of documents being downloaded.
This commit is contained in:
parent
4c56353594
commit
6e8612f319
@ -223,23 +223,23 @@ impl DrawThreadRenderer for ProgressBarInner {
|
||||
if state.entries.is_empty() {
|
||||
return String::new();
|
||||
}
|
||||
let preferred_entry = state
|
||||
let display_entries = state
|
||||
.entries
|
||||
.iter()
|
||||
.find(|e| e.percent() > 0f64)
|
||||
.or_else(|| state.entries.iter().last())
|
||||
.unwrap();
|
||||
.map(|e| ProgressDataDisplayEntry {
|
||||
prompt: e.prompt,
|
||||
message: e.message.to_string(),
|
||||
position: e.position(),
|
||||
total_size: e.total_size(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
ProgressData {
|
||||
duration: state.start_time.elapsed(),
|
||||
terminal_width: size.cols,
|
||||
pending_entries: state.entries.len(),
|
||||
total_entries: state.total_entries,
|
||||
display_entry: ProgressDataDisplayEntry {
|
||||
prompt: preferred_entry.prompt,
|
||||
message: preferred_entry.message.clone(),
|
||||
position: preferred_entry.position(),
|
||||
total_size: preferred_entry.total_size(),
|
||||
},
|
||||
display_entries,
|
||||
percent_done: {
|
||||
let mut total_percent_sum = 0f64;
|
||||
for entry in &state.entries {
|
||||
@ -273,7 +273,7 @@ impl ProgressBar {
|
||||
Arc::new(renderer::BarProgressBarRenderer)
|
||||
}
|
||||
ProgressBarStyle::TextOnly => {
|
||||
Arc::new(renderer::TextOnlyProgressBarRenderer)
|
||||
Arc::new(renderer::TextOnlyProgressBarRenderer::default())
|
||||
}
|
||||
}),
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::time::Duration;
|
||||
|
||||
use deno_terminal::colors;
|
||||
@ -19,7 +21,7 @@ pub struct ProgressDataDisplayEntry {
|
||||
#[derive(Clone)]
|
||||
pub struct ProgressData {
|
||||
pub terminal_width: u32,
|
||||
pub display_entry: ProgressDataDisplayEntry,
|
||||
pub display_entries: Vec<ProgressDataDisplayEntry>,
|
||||
pub pending_entries: usize,
|
||||
pub percent_done: f64,
|
||||
pub total_entries: usize,
|
||||
@ -36,9 +38,13 @@ pub struct BarProgressBarRenderer;
|
||||
|
||||
impl ProgressBarRenderer for BarProgressBarRenderer {
|
||||
fn render(&self, data: ProgressData) -> String {
|
||||
// In `ProgressBarRenderer` we only care about first entry.
|
||||
let Some(display_entry) = &data.display_entries.first() else {
|
||||
return String::new();
|
||||
};
|
||||
let (bytes_text, bytes_text_max_width) = {
|
||||
let total_size = data.display_entry.total_size;
|
||||
let pos = data.display_entry.position;
|
||||
let total_size = display_entry.total_size;
|
||||
let pos = display_entry.position;
|
||||
if total_size == 0 {
|
||||
(String::new(), 0)
|
||||
} else {
|
||||
@ -69,11 +75,11 @@ impl ProgressBarRenderer for BarProgressBarRenderer {
|
||||
|
||||
let elapsed_text = get_elapsed_text(data.duration);
|
||||
let mut text = String::new();
|
||||
if !data.display_entry.message.is_empty() {
|
||||
if !display_entry.message.is_empty() {
|
||||
text.push_str(&format!(
|
||||
"{} {}{}\n",
|
||||
colors::green("Download"),
|
||||
data.display_entry.message,
|
||||
display_entry.message,
|
||||
bytes_text,
|
||||
));
|
||||
}
|
||||
@ -106,7 +112,7 @@ impl ProgressBarRenderer for BarProgressBarRenderer {
|
||||
text.push(']');
|
||||
|
||||
// suffix
|
||||
if data.display_entry.message.is_empty() {
|
||||
if display_entry.message.is_empty() {
|
||||
text.push_str(&colors::gray(bytes_text).to_string());
|
||||
}
|
||||
text.push_str(&colors::gray(total_text).to_string());
|
||||
@ -116,13 +122,61 @@ impl ProgressBarRenderer for BarProgressBarRenderer {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TextOnlyProgressBarRenderer;
|
||||
pub struct TextOnlyProgressBarRenderer {
|
||||
last_tick: AtomicUsize,
|
||||
start_time: std::time::Instant,
|
||||
}
|
||||
|
||||
impl Default for TextOnlyProgressBarRenderer {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
last_tick: Default::default(),
|
||||
start_time: std::time::Instant::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const SPINNER_CHARS: [&str; 8] = ["⣷", "⣯", "⣟", "⡿", "⢿", "⣻", "⣽", "⣾"];
|
||||
impl ProgressBarRenderer for TextOnlyProgressBarRenderer {
|
||||
fn render(&self, data: ProgressData) -> String {
|
||||
let last_tick = {
|
||||
let last_tick = self.last_tick.load(Ordering::Relaxed);
|
||||
let last_tick = (last_tick + 1) % 8;
|
||||
self.last_tick.store(last_tick, Ordering::Relaxed);
|
||||
last_tick
|
||||
};
|
||||
let current_time = std::time::Instant::now();
|
||||
|
||||
let mut display_str = format!(
|
||||
"{} {} ",
|
||||
data.display_entries[0].prompt.as_text(),
|
||||
SPINNER_CHARS[last_tick]
|
||||
);
|
||||
|
||||
let elapsed_time = current_time - self.start_time;
|
||||
let fmt_elapsed_time = get_elapsed_text(elapsed_time);
|
||||
|
||||
let total_text = if data.total_entries <= 1 {
|
||||
String::new()
|
||||
} else {
|
||||
format!(
|
||||
" {}/{}",
|
||||
data.total_entries - data.pending_entries,
|
||||
data.total_entries
|
||||
)
|
||||
};
|
||||
|
||||
display_str.push_str(&format!("{}{}\n", fmt_elapsed_time, total_text));
|
||||
|
||||
for i in 0..4 {
|
||||
let Some(display_entry) = data.display_entries.get(i) else {
|
||||
display_str.push('\n');
|
||||
continue;
|
||||
};
|
||||
|
||||
let bytes_text = {
|
||||
let total_size = data.display_entry.total_size;
|
||||
let pos = data.display_entry.position;
|
||||
let total_size = display_entry.total_size;
|
||||
let pos = display_entry.position;
|
||||
if total_size == 0 {
|
||||
String::new()
|
||||
} else {
|
||||
@ -133,23 +187,17 @@ impl ProgressBarRenderer for TextOnlyProgressBarRenderer {
|
||||
)
|
||||
}
|
||||
};
|
||||
let total_text = if data.total_entries <= 1 {
|
||||
String::new()
|
||||
} else {
|
||||
format!(
|
||||
" ({}/{})",
|
||||
data.total_entries - data.pending_entries,
|
||||
data.total_entries
|
||||
)
|
||||
};
|
||||
|
||||
format!(
|
||||
"{} {}{}{}",
|
||||
data.display_entry.prompt.as_text(),
|
||||
data.display_entry.message,
|
||||
colors::gray(bytes_text),
|
||||
colors::gray(total_text),
|
||||
)
|
||||
let message = display_entry
|
||||
.message
|
||||
.replace("https://registry.npmjs.org/", "npm:")
|
||||
.replace("https://jsr.io/", "jsr:");
|
||||
display_str.push_str(
|
||||
&colors::gray(format!(" - {}{}\n", message, bytes_text)).to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
display_str
|
||||
}
|
||||
}
|
||||
|
||||
@ -165,6 +213,7 @@ mod test {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::time::Duration;
|
||||
use test_util::assert_contains;
|
||||
|
||||
#[test]
|
||||
fn should_get_elapsed_text() {
|
||||
@ -197,12 +246,12 @@ mod test {
|
||||
fn should_render_bar_progress() {
|
||||
let renderer = BarProgressBarRenderer;
|
||||
let mut data = ProgressData {
|
||||
display_entry: ProgressDataDisplayEntry {
|
||||
display_entries: vec![ProgressDataDisplayEntry {
|
||||
prompt: ProgressMessagePrompt::Download,
|
||||
message: "data".to_string(),
|
||||
position: 0,
|
||||
total_size: 10 * BYTES_TO_KIB,
|
||||
},
|
||||
}],
|
||||
duration: Duration::from_secs(1),
|
||||
pending_entries: 1,
|
||||
total_entries: 1,
|
||||
@ -220,8 +269,8 @@ mod test {
|
||||
);
|
||||
|
||||
data.percent_done = 0.5f64;
|
||||
data.display_entry.position = 5 * BYTES_TO_KIB;
|
||||
data.display_entry.message = String::new();
|
||||
data.display_entries[0].position = 5 * BYTES_TO_KIB;
|
||||
data.display_entries[0].message = "".to_string();
|
||||
data.total_entries = 3;
|
||||
let text = renderer.render(data.clone());
|
||||
let text = test_util::strip_ansi_codes(&text);
|
||||
@ -235,14 +284,14 @@ mod test {
|
||||
|
||||
data.terminal_width = 50;
|
||||
data.pending_entries = 0;
|
||||
data.display_entry.position = 10 * BYTES_TO_KIB;
|
||||
data.display_entries[0].position = 10 * BYTES_TO_KIB;
|
||||
data.percent_done = 1.0f64;
|
||||
let text = renderer.render(data.clone());
|
||||
let text = test_util::strip_ansi_codes(&text);
|
||||
assert_eq!(text, "[00:01] [###########] 10.00KiB/10.00KiB (3/3)",);
|
||||
|
||||
data.display_entry.position = 0;
|
||||
data.display_entry.total_size = 0;
|
||||
data.display_entries[0].position = 0;
|
||||
data.display_entries[0].total_size = 0;
|
||||
data.pending_entries = 0;
|
||||
data.total_entries = 1;
|
||||
let text = renderer.render(data);
|
||||
@ -252,14 +301,14 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn should_render_text_only_progress() {
|
||||
let renderer = TextOnlyProgressBarRenderer;
|
||||
let renderer = TextOnlyProgressBarRenderer::default();
|
||||
let mut data = ProgressData {
|
||||
display_entry: ProgressDataDisplayEntry {
|
||||
display_entries: vec![ProgressDataDisplayEntry {
|
||||
prompt: ProgressMessagePrompt::Blocking,
|
||||
message: "data".to_string(),
|
||||
position: 0,
|
||||
total_size: 10 * BYTES_TO_KIB,
|
||||
},
|
||||
}],
|
||||
duration: Duration::from_secs(1),
|
||||
pending_entries: 1,
|
||||
total_entries: 3,
|
||||
@ -268,14 +317,16 @@ mod test {
|
||||
};
|
||||
let text = renderer.render(data.clone());
|
||||
let text = test_util::strip_ansi_codes(&text);
|
||||
assert_eq!(text, "Blocking data 0.00KiB/10.00KiB (2/3)");
|
||||
assert_contains!(text, "Blocking ⣯");
|
||||
assert_contains!(text, "2/3\n - data 0.00KiB/10.00KiB\n\n\n\n");
|
||||
|
||||
data.pending_entries = 0;
|
||||
data.total_entries = 1;
|
||||
data.display_entry.position = 0;
|
||||
data.display_entry.total_size = 0;
|
||||
data.display_entries[0].position = 0;
|
||||
data.display_entries[0].total_size = 0;
|
||||
let text = renderer.render(data);
|
||||
let text = test_util::strip_ansi_codes(&text);
|
||||
assert_eq!(text, "Blocking data");
|
||||
assert_contains!(text, "Blocking ⣟");
|
||||
assert_contains!(text, "\n - data\n\n\n\n");
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user