aboutsummaryrefslogtreecommitdiff
path: root/src/output.rs
blob: bd6c03c0e5dae36c9d377b26b561e6686b35d235 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
// output.rs

// Copyright (C) 2020 The Nitrocli Developers
// SPDX-License-Identifier: GPL-3.0-or-later

use anyhow::Context as _;

use progressing::Baring as _;

use termion::cursor::DetectCursorPos as _;
use termion::raw::IntoRawMode as _;

use crate::Context;

/// A progress bar that can be printed to an interactive output.
pub struct ProgressBar {
  /// Whether to redraw the entire progress bar in the next call to `draw`.
  redraw: bool,
  /// The current progress of the progress bar (0 <= progress <= 100).
  progress: u8,
  /// Toggled on every call to `draw` to print a pulsing indicator.
  toggle: bool,
  /// Whether this progress bar finished.
  finished: bool,
}

impl ProgressBar {
  /// Creates a new empty progress bar.
  pub fn new() -> ProgressBar {
    ProgressBar {
      redraw: true,
      progress: 0,
      toggle: false,
      finished: false,
    }
  }

  /// Whether this progress bar is finished.
  pub fn is_finished(&self) -> bool {
    self.finished
  }

  /// Updates the progress bar with the given progress (0 <= progress <= 100).
  pub fn update(&mut self, progress: u8) -> anyhow::Result<()> {
    anyhow::ensure!(!self.finished, "Tried to update finished progress bar");
    anyhow::ensure!(
      progress <= 100,
      "Progress bar value out of range: {}",
      progress
    );
    if progress != self.progress {
      self.redraw = true;
      self.progress = progress;
    }
    self.toggle = !self.toggle;
    Ok(())
  }

  /// Finish this progress bar.
  ///
  /// A finished progress bar may no longer be updated.
  pub fn finish(&mut self) {
    self.finished = true;
    self.redraw = true;
    self.progress = 100;
  }

  /// Print the progress bar to the stdout set in the given context.
  ///
  /// On every call of this method (as long as the progress bar is not
  /// finished), a pulsing indicator is printed to show that the process
  /// is still running. If there was progress since the last call to
  /// `draw`, or if this is the first call, this function will also
  /// print the progress bar itself.
  pub fn draw(&self, ctx: &mut Context<'_>) -> anyhow::Result<()> {
    if !ctx.is_tty {
      return Ok(());
    }

    let pos = ctx
      .stdout
      .into_raw_mode()
      .context("Failed to activate raw mode")?
      .cursor_pos()
      .context("Failed to query cursor position")?;

    let progress_char = if self.toggle && !self.finished {
      "."
    } else {
      " "
    };

    if self.redraw {
      let mut progress_bar = progressing::mapping::Bar::with_range(0, 100);
      progress_bar.set(self.progress);

      print!(ctx, "{}", termion::clear::CurrentLine)?;
      print!(ctx, "{}", termion::cursor::Goto(1, pos.1))?;
      print!(ctx, " {} {}", progress_char, progress_bar)?;
      if self.finished {
        println!(ctx)?;
      }
    } else {
      print!(ctx, "{}{}", termion::cursor::Goto(2, pos.1), progress_char)?;
    }

    ctx.stdout.flush()?;
    Ok(())
  }
}