use std::fmt::Display;
use std::io::Write;
use std::ops::Deref;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use ansi_escapes::{CursorLeft, CursorPrevLine, EraseDown};
fn redraw(ansi: bool, state: &impl Display) {
let stderr = std::io::stderr();
let mut stderr = stderr.lock();
let contents = format!("{}", state);
if ansi {
let line_count = contents.chars().filter(|c| *c == '\n').count();
write!(&mut stderr, "{}{}{}", EraseDown, contents, CursorLeft).unwrap();
for _ in 0..line_count {
write!(&mut stderr, "{}", CursorPrevLine).unwrap();
}
} else {
writeln!(&mut stderr, "{}", contents).unwrap();
}
}
fn clear(ansi: bool) {
if ansi {
let stderr = std::io::stderr();
let mut stderr = stderr.lock();
write!(&mut stderr, "{}", EraseDown).unwrap();
}
}
struct State<D> {
data: D,
visible: AtomicBool,
}
impl<D> State<D> {
pub fn new(inner: D) -> State<D> {
State {
data: inner,
visible: AtomicBool::new(false),
}
}
}
pub struct Options {
pub refresh_period: Duration,
pub initially_visible: bool,
pub enable_ansi_escapes: bool,
}
impl Default for Options {
fn default() -> Self {
let is_tty = atty::is(atty::Stream::Stderr);
let refresh_period_ms = if is_tty { 100 } else { 1000 };
Options {
refresh_period: Duration::from_millis(refresh_period_ms),
initially_visible: true,
enable_ansi_escapes: is_tty,
}
}
}
pub struct StatusLine<D: Display> {
state: Arc<State<D>>,
options: Options,
}
impl<D: Display + Send + Sync + 'static> StatusLine<D> {
pub fn new(data: D) -> StatusLine<D> {
Self::with_options(data, Default::default())
}
pub fn with_options(data: D, options: Options) -> StatusLine<D> {
let state = Arc::new(State::new(data));
state
.visible
.store(options.initially_visible, Ordering::Release);
let state_ref = state.clone();
thread::spawn(move || {
while Arc::strong_count(&state_ref) > 1 {
if state_ref.visible.load(Ordering::Acquire) {
redraw(options.enable_ansi_escapes, &state_ref.data);
}
thread::sleep(options.refresh_period);
}
});
StatusLine { state, options }
}
}
impl<D: Display> StatusLine<D> {
pub fn refresh(&self) {
redraw(self.options.enable_ansi_escapes, &self.state.data);
}
pub fn set_visible(&self, visible: bool) {
let was_visible = self.state.visible.swap(visible, Ordering::Release);
if !visible && was_visible {
clear(self.options.enable_ansi_escapes)
} else if visible && !was_visible {
redraw(self.options.enable_ansi_escapes, &self.state.data)
}
}
pub fn is_visible(&self) -> bool {
self.state.visible.load(Ordering::Acquire)
}
}
impl<D: Display> Deref for StatusLine<D> {
type Target = D;
fn deref(&self) -> &Self::Target {
&self.state.data
}
}
impl<D: Display> Drop for StatusLine<D> {
fn drop(&mut self) {
if self.is_visible() {
clear(self.options.enable_ansi_escapes)
}
}
}