Skip to content

Instantly share code, notes, and snippets.

@DanielSWolf
Last active October 2, 2025 15:42
Show Gist options
  • Save DanielSWolf/0ab6a96899cc5377bf54 to your computer and use it in GitHub Desktop.
Save DanielSWolf/0ab6a96899cc5377bf54 to your computer and use it in GitHub Desktop.

Revisions

  1. DanielSWolf revised this gist Jul 25, 2015. No changes.
  2. DanielSWolf revised this gist Jul 25, 2015. No changes.
  3. DanielSWolf revised this gist Jul 25, 2015. No changes.
  4. DanielSWolf revised this gist Jul 2, 2015. 1 changed file with 0 additions and 2 deletions.
    2 changes: 0 additions & 2 deletions Program.cs
    Original file line number Diff line number Diff line change
    @@ -4,7 +4,6 @@
    static class Program {

    static void Main() {
    Thread.Sleep(5000);
    Console.Write("Performing some task... ");
    using (var progress = new ProgressBar()) {
    for (int i = 0; i <= 100; i++) {
    @@ -13,7 +12,6 @@ static void Main() {
    }
    }
    Console.WriteLine("Done.");
    Console.ReadLine();
    }

    }
  5. DanielSWolf created this gist Jul 2, 2015.
    19 changes: 19 additions & 0 deletions Program.cs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,19 @@
    using System;
    using System.Threading;

    static class Program {

    static void Main() {
    Thread.Sleep(5000);
    Console.Write("Performing some task... ");
    using (var progress = new ProgressBar()) {
    for (int i = 0; i <= 100; i++) {
    progress.Report((double) i / 100);
    Thread.Sleep(20);
    }
    }
    Console.WriteLine("Done.");
    Console.ReadLine();
    }

    }
    90 changes: 90 additions & 0 deletions ProgressBar.cs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,90 @@
    using System;
    using System.Text;
    using System.Threading;

    /// <summary>
    /// An ASCII progress bar
    /// </summary>
    public class ProgressBar : IDisposable, IProgress<double> {
    private const int blockCount = 10;
    private readonly TimeSpan animationInterval = TimeSpan.FromSeconds(1.0 / 8);
    private const string animation = @"|/-\";

    private readonly Timer timer;

    private double currentProgress = 0;
    private string currentText = string.Empty;
    private bool disposed = false;
    private int animationIndex = 0;

    public ProgressBar() {
    timer = new Timer(TimerHandler);

    // A progress bar is only for temporary display in a console window.
    // If the console output is redirected to a file, draw nothing.
    // Otherwise, we'll end up with a lot of garbage in the target file.
    if (!Console.IsOutputRedirected) {
    ResetTimer();
    }
    }

    public void Report(double value) {
    // Make sure value is in [0..1] range
    value = Math.Max(0, Math.Min(1, value));
    Interlocked.Exchange(ref currentProgress, value);
    }

    private void TimerHandler(object state) {
    lock (timer) {
    if (disposed) return;

    int progressBlockCount = (int) (currentProgress * blockCount);
    int percent = (int) (currentProgress * 100);
    string text = string.Format("[{0}{1}] {2,3}% {3}",
    new string('#', progressBlockCount), new string('-', blockCount - progressBlockCount),
    percent,
    animation[animationIndex++ % animation.Length]);
    UpdateText(text);

    ResetTimer();
    }
    }

    private void UpdateText(string text) {
    // Get length of common portion
    int commonPrefixLength = 0;
    int commonLength = Math.Min(currentText.Length, text.Length);
    while (commonPrefixLength < commonLength && text[commonPrefixLength] == currentText[commonPrefixLength]) {
    commonPrefixLength++;
    }

    // Backtrack to the first differing character
    StringBuilder outputBuilder = new StringBuilder();
    outputBuilder.Append('\b', currentText.Length - commonPrefixLength);

    // Output new suffix
    outputBuilder.Append(text.Substring(commonPrefixLength));

    // If the new text is shorter than the old one: delete overlapping characters
    int overlapCount = currentText.Length - text.Length;
    if (overlapCount > 0) {
    outputBuilder.Append(' ', overlapCount);
    outputBuilder.Append('\b', overlapCount);
    }

    Console.Write(outputBuilder);
    currentText = text;
    }

    private void ResetTimer() {
    timer.Change(animationInterval, TimeSpan.FromMilliseconds(-1));
    }

    public void Dispose() {
    lock (timer) {
    disposed = true;
    UpdateText(string.Empty);
    }
    }

    }