Skip to content

Instantly share code, notes, and snippets.

@neremin
Forked from DanielSWolf/Program.cs
Last active April 27, 2018 13:11
Show Gist options
  • Save neremin/f9905f708c11855f472891acd57bf7d6 to your computer and use it in GitHub Desktop.
Save neremin/f9905f708c11855f472891acd57bf7d6 to your computer and use it in GitHub Desktop.
Console progress bar. Code is under the MIT License: http://opensource.org/licenses/MIT
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;
private bool indeterminate = false;
public ProgressBar(bool indeterminate = false)
{
timer = new Timer(TimerHandler);
this.indeterminate = indeterminate;
// 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;
string text = string.Empty;
if (indeterminate)
{
text = string.Format("[{0}]", animation[animationIndex++ % animation.Length]);
}
else
{
int progressBlockCount = (int)(currentProgress * blockCount);
int percent = (int)(currentProgress * 100);
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);
}
}
}
using System;
using System.Threading;
static class Program {
static void Main() {
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.");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment