2024-08-26 00:53:50 +03:00
|
|
|
use std::{
|
|
|
|
fmt, fs,
|
|
|
|
io::{self, BufRead, StdoutLock, Write},
|
|
|
|
};
|
2024-08-08 03:45:18 +03:00
|
|
|
|
2024-08-24 01:14:12 +03:00
|
|
|
use crossterm::{
|
|
|
|
cursor::MoveTo,
|
2024-08-26 00:53:50 +03:00
|
|
|
style::{Attribute, Color, ResetColor, SetAttribute, SetForegroundColor},
|
2024-08-24 01:14:12 +03:00
|
|
|
terminal::{Clear, ClearType},
|
2024-08-26 00:53:50 +03:00
|
|
|
Command, QueueableCommand,
|
2024-08-24 01:14:12 +03:00
|
|
|
};
|
|
|
|
|
2024-08-24 18:17:56 +03:00
|
|
|
/// Terminal progress bar to be used when not using Ratataui.
|
|
|
|
pub fn progress_bar(
|
|
|
|
stdout: &mut StdoutLock,
|
|
|
|
progress: u16,
|
|
|
|
total: u16,
|
|
|
|
line_width: u16,
|
|
|
|
) -> io::Result<()> {
|
|
|
|
debug_assert!(progress <= total);
|
|
|
|
|
|
|
|
const PREFIX: &[u8] = b"Progress: [";
|
|
|
|
const PREFIX_WIDTH: u16 = PREFIX.len() as u16;
|
|
|
|
// Leaving the last char empty (_) for `total` > 99.
|
|
|
|
const POSTFIX_WIDTH: u16 = "] xxx/xx exercises_".len() as u16;
|
|
|
|
const WRAPPER_WIDTH: u16 = PREFIX_WIDTH + POSTFIX_WIDTH;
|
|
|
|
const MIN_LINE_WIDTH: u16 = WRAPPER_WIDTH + 4;
|
|
|
|
|
|
|
|
if line_width < MIN_LINE_WIDTH {
|
|
|
|
return write!(stdout, "Progress: {progress}/{total} exercises");
|
|
|
|
}
|
|
|
|
|
|
|
|
stdout.write_all(PREFIX)?;
|
|
|
|
|
|
|
|
let width = line_width - WRAPPER_WIDTH;
|
|
|
|
let filled = (width * progress) / total;
|
|
|
|
|
|
|
|
stdout.queue(SetForegroundColor(Color::Green))?;
|
|
|
|
for _ in 0..filled {
|
|
|
|
stdout.write_all(b"#")?;
|
|
|
|
}
|
|
|
|
|
|
|
|
if filled < width {
|
|
|
|
stdout.write_all(b">")?;
|
|
|
|
}
|
|
|
|
|
|
|
|
let width_minus_filled = width - filled;
|
|
|
|
if width_minus_filled > 1 {
|
|
|
|
let red_part_width = width_minus_filled - 1;
|
|
|
|
stdout.queue(SetForegroundColor(Color::Red))?;
|
|
|
|
for _ in 0..red_part_width {
|
|
|
|
stdout.write_all(b"-")?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
stdout.queue(ResetColor)?;
|
|
|
|
write!(stdout, "] {progress:>3}/{total} exercises")
|
|
|
|
}
|
|
|
|
|
2024-08-08 03:45:18 +03:00
|
|
|
pub fn clear_terminal(stdout: &mut StdoutLock) -> io::Result<()> {
|
2024-08-24 01:14:12 +03:00
|
|
|
stdout
|
|
|
|
.queue(MoveTo(0, 0))?
|
|
|
|
.queue(Clear(ClearType::All))?
|
|
|
|
.queue(Clear(ClearType::Purge))
|
|
|
|
.map(|_| ())
|
2024-08-08 03:45:18 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn press_enter_prompt(stdout: &mut StdoutLock) -> io::Result<()> {
|
|
|
|
stdout.flush()?;
|
|
|
|
io::stdin().lock().read_until(b'\n', &mut Vec::new())?;
|
|
|
|
stdout.write_all(b"\n")?;
|
|
|
|
Ok(())
|
|
|
|
}
|
2024-08-26 00:53:50 +03:00
|
|
|
|
|
|
|
pub fn terminal_file_link(stdout: &mut StdoutLock, path: &str, color: Color) -> io::Result<()> {
|
|
|
|
let canonical_path = fs::canonicalize(path).ok();
|
|
|
|
|
|
|
|
let Some(canonical_path) = canonical_path.as_deref().and_then(|p| p.to_str()) else {
|
|
|
|
return stdout.write_all(path.as_bytes());
|
|
|
|
};
|
|
|
|
|
|
|
|
// Windows itself can't handle its verbatim paths.
|
|
|
|
#[cfg(windows)]
|
|
|
|
let canonical_path = if canonical_path.len() > 5 && &canonical_path[0..4] == r"\\?\" {
|
|
|
|
&canonical_path[4..]
|
|
|
|
} else {
|
|
|
|
canonical_path
|
|
|
|
};
|
|
|
|
|
|
|
|
stdout
|
|
|
|
.queue(SetForegroundColor(color))?
|
|
|
|
.queue(SetAttribute(Attribute::Underlined))?;
|
|
|
|
write!(
|
|
|
|
stdout,
|
|
|
|
"\x1b]8;;file://{canonical_path}\x1b\\{path}\x1b]8;;\x1b\\",
|
|
|
|
)?;
|
|
|
|
stdout.queue(ResetColor)?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn write_ansi(output: &mut Vec<u8>, command: impl Command) {
|
|
|
|
struct FmtWriter<'a>(&'a mut Vec<u8>);
|
|
|
|
|
|
|
|
impl fmt::Write for FmtWriter<'_> {
|
|
|
|
fn write_str(&mut self, s: &str) -> fmt::Result {
|
|
|
|
self.0.extend_from_slice(s.as_bytes());
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let _ = command.write_ansi(&mut FmtWriter(output));
|
|
|
|
}
|