├── doc
├── pbar-osx.gif
├── children-osx.gif
├── pbar-windows.gif
├── styling-osx.gif
├── styling-windows.gif
├── bar-on-bottom-osx.gif
├── update-on-tick-osx.gif
├── children-no-collapse-osx.gif
└── children-no-collapse-windows.gif
├── src
├── nuget-icon.png
├── .build
│ ├── MSBuild.Community.Tasks.dll
│ └── MSBuild.Community.Tasks.targets
├── ShellProgressBar
│ ├── ProgressBarHeight.cs
│ ├── ConsoleOutLine.cs
│ ├── StringExtensions.cs
│ ├── IProgressBar.cs
│ ├── Progress.cs
│ ├── FixedDurationBar.cs
│ ├── ShellProgressBar.csproj
│ ├── IndeterminateProgressBar.cs
│ ├── IndeterminateChildProgressBar.cs
│ ├── TaskbarProgress.cs
│ ├── ChildProgressBar.cs
│ ├── ProgressBarOptions.cs
│ ├── ProgressBarBase.cs
│ └── ProgressBar.cs
├── ShellProgressBar.Example
│ ├── IProgressBarExample.cs
│ ├── TestCases
│ │ ├── NeverTicksExample.cs
│ │ ├── NeverCompletesExample.cs
│ │ ├── LongRunningExample.cs
│ │ ├── ZeroMaxTicksExample.cs
│ │ ├── NegativeMaxTicksExample.cs
│ │ ├── TicksOverflowExample.cs
│ │ ├── DrawsOnlyOnTickExample.cs
│ │ ├── ThreadedTicksOverflowExample.cs
│ │ ├── UpdatesMaxTicksExample.cs
│ │ ├── NestedProgressBarPerStepProgress.cs
│ │ └── DeeplyNestedProgressBarTreeExample.cs
│ ├── Examples
│ │ ├── DontDisplayInRealTimeExample.cs
│ │ ├── ProgressBarOnBottomExample.cs
│ │ ├── StylingExample.cs
│ │ ├── MessageBeforeAndAfterExample.cs
│ │ ├── ChildrenExample.cs
│ │ ├── ExampleBase.cs
│ │ ├── IndeterminateProgressExample.cs
│ │ ├── IntegrationWithIProgressPercentageExample.cs
│ │ ├── AlternateFinishedColorExample.cs
│ │ ├── IntegrationWithIProgressExample.cs
│ │ ├── ChildrenNoCollapseExample.cs
│ │ ├── EstimatedDurationExample.cs
│ │ ├── FixedDurationExample.cs
│ │ ├── IndeterminateChildrenNoCollapse.cs
│ │ ├── DownloadPogressExample.cs
│ │ └── PersistMessageExample.cs
│ ├── ShellProgressBar.Example.csproj
│ └── Program.cs
└── ShellProgressBar.sln
├── test
└── ShellProgressBar.Tests
│ ├── UnitTest1.cs
│ └── ShellProgressBar.Tests.csproj
├── .editorconfig
├── LICENSE
├── .gitattributes
├── .gitignore
└── README.md
/doc/pbar-osx.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mpdreamz/shellprogressbar/HEAD/doc/pbar-osx.gif
--------------------------------------------------------------------------------
/src/nuget-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mpdreamz/shellprogressbar/HEAD/src/nuget-icon.png
--------------------------------------------------------------------------------
/doc/children-osx.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mpdreamz/shellprogressbar/HEAD/doc/children-osx.gif
--------------------------------------------------------------------------------
/doc/pbar-windows.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mpdreamz/shellprogressbar/HEAD/doc/pbar-windows.gif
--------------------------------------------------------------------------------
/doc/styling-osx.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mpdreamz/shellprogressbar/HEAD/doc/styling-osx.gif
--------------------------------------------------------------------------------
/doc/styling-windows.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mpdreamz/shellprogressbar/HEAD/doc/styling-windows.gif
--------------------------------------------------------------------------------
/doc/bar-on-bottom-osx.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mpdreamz/shellprogressbar/HEAD/doc/bar-on-bottom-osx.gif
--------------------------------------------------------------------------------
/doc/update-on-tick-osx.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mpdreamz/shellprogressbar/HEAD/doc/update-on-tick-osx.gif
--------------------------------------------------------------------------------
/doc/children-no-collapse-osx.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mpdreamz/shellprogressbar/HEAD/doc/children-no-collapse-osx.gif
--------------------------------------------------------------------------------
/doc/children-no-collapse-windows.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mpdreamz/shellprogressbar/HEAD/doc/children-no-collapse-windows.gif
--------------------------------------------------------------------------------
/src/.build/MSBuild.Community.Tasks.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mpdreamz/shellprogressbar/HEAD/src/.build/MSBuild.Community.Tasks.dll
--------------------------------------------------------------------------------
/src/ShellProgressBar/ProgressBarHeight.cs:
--------------------------------------------------------------------------------
1 | namespace ShellProgressBar
2 | {
3 | public enum ProgressBarHeight
4 | {
5 | Increment, Decrement
6 | }
7 | }
--------------------------------------------------------------------------------
/src/ShellProgressBar.Example/IProgressBarExample.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 |
4 | namespace ShellProgressBar.Example
5 | {
6 | public interface IProgressBarExample
7 | {
8 | Task Start(CancellationToken token);
9 | }
10 | }
--------------------------------------------------------------------------------
/src/ShellProgressBar/ConsoleOutLine.cs:
--------------------------------------------------------------------------------
1 | namespace ShellProgressBar
2 | {
3 | public struct ConsoleOutLine
4 | {
5 | public bool Error { get; }
6 | public string Line { get; }
7 |
8 | public ConsoleOutLine(string line, bool error = false)
9 | {
10 | Error = error;
11 | Line = line;
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/test/ShellProgressBar.Tests/UnitTest1.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Xunit;
3 |
4 | namespace ShellProgressBar.Tests
5 | {
6 | public class UnitTest1
7 | {
8 | [Fact]
9 | public void Test1()
10 | {
11 | using var pbar = new ProgressBar(1000, "task");
12 | pbar.Tick();
13 | pbar.WriteLine("Asdad");
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root=true
2 |
3 | [*.cs]
4 | trim_trailing_whitespace=true
5 | insert_final_newline=true
6 |
7 | [*]
8 | indent_style = tab
9 | indent_size = 4
10 |
11 | [*.cshtml]
12 | indent_style = tab
13 | indent_size = 4
14 |
15 | [*.{fs,fsx}]
16 | indent_style = space
17 | indent_size = 4
18 |
19 | [*.{md,markdown,json,js,csproj,fsproj,targets}]
20 | indent_style = space
21 | indent_size = 2
22 |
--------------------------------------------------------------------------------
/src/ShellProgressBar.Example/TestCases/NeverTicksExample.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 |
4 | namespace ShellProgressBar.Example.TestCases
5 | {
6 | public class NeverTicksExample : IProgressBarExample
7 | {
8 | public Task Start(CancellationToken token)
9 | {
10 | var ticks = 10;
11 | using (var pbar = new ProgressBar(ticks, "A console progress bar that never ticks"))
12 | {
13 | }
14 | return Task.FromResult(1);
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/src/ShellProgressBar/StringExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace ShellProgressBar
6 | {
7 | internal static class StringExtensions
8 | {
9 | public static string Excerpt(string phrase, int length = 60)
10 | {
11 | if (string.IsNullOrEmpty(phrase) || phrase.Length < length)
12 | return phrase;
13 | return phrase.Substring(0, length - 3) + "...";
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/ShellProgressBar.Example/TestCases/NeverCompletesExample.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 |
4 | namespace ShellProgressBar.Example.TestCases
5 | {
6 | public class NeverCompletesExample : IProgressBarExample
7 | {
8 | public Task Start(CancellationToken token)
9 | {
10 | var ticks = 5;
11 | using (var pbar = new ProgressBar(ticks, "A console progress bar does not complete"))
12 | {
13 | pbar.Tick();
14 | pbar.Tick();
15 | pbar.Tick();
16 | pbar.Tick();
17 | }
18 | return Task.FromResult(1);
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/src/ShellProgressBar.Example/TestCases/LongRunningExample.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 |
5 | namespace ShellProgressBar.Example.TestCases
6 | {
7 | public class LongRunningExample : IProgressBarExample
8 | {
9 | public Task Start(CancellationToken token)
10 | {
11 | var ticks = 100;
12 | using (var pbar = new ProgressBar(ticks, "my long running operation", ConsoleColor.Green))
13 | {
14 | for (var i = 0; i < ticks; i++)
15 | {
16 | pbar.Tick("step " + i);
17 | Thread.Sleep(50);
18 | }
19 | }
20 | return Task.FromResult(1);
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/src/ShellProgressBar.Example/TestCases/ZeroMaxTicksExample.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 |
5 | namespace ShellProgressBar.Example.TestCases
6 | {
7 | public class ZeroMaxTicksExample : IProgressBarExample
8 | {
9 | public Task Start(CancellationToken token)
10 | {
11 | var ticks = 0;
12 | using (var pbar = new ProgressBar(ticks, "my operation with zero ticks", ConsoleColor.Cyan))
13 | {
14 | for (var i = 0; i < ticks; i++)
15 | {
16 | pbar.Tick("step " + i);
17 | Thread.Sleep(50);
18 | }
19 | }
20 | return Task.FromResult(1);
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/src/ShellProgressBar.Example/Examples/DontDisplayInRealTimeExample.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 |
3 | namespace ShellProgressBar.Example.Examples
4 | {
5 | public class DontDisplayInRealTimeExample : ExampleBase
6 | {
7 | protected override Task StartAsync()
8 | {
9 | const int totalTicks = 5;
10 | var options = new ProgressBarOptions
11 | {
12 | DisplayTimeInRealTime = false
13 | };
14 | using (var pbar = new ProgressBar(totalTicks, "only draw progress on tick", options))
15 | {
16 | TickToCompletion(pbar, totalTicks, sleep: 1750);
17 | }
18 |
19 | return Task.CompletedTask;
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/ShellProgressBar.Example/TestCases/NegativeMaxTicksExample.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 |
5 | namespace ShellProgressBar.Example.TestCases
6 | {
7 | public class NegativeMaxTicksExample : IProgressBarExample
8 | {
9 | public Task Start(CancellationToken token)
10 | {
11 | var ticks = -100;
12 | using (var pbar = new ProgressBar(ticks, "my operation with negative ticks", ConsoleColor.Cyan))
13 | {
14 | for (var i = 0; i < ticks; i++)
15 | {
16 | pbar.Tick("step " + i);
17 | Thread.Sleep(50);
18 | }
19 | }
20 | return Task.FromResult(1);
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/src/ShellProgressBar.Example/TestCases/TicksOverflowExample.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 |
5 | namespace ShellProgressBar.Example.TestCases
6 | {
7 | public class TicksOverflowExample : IProgressBarExample
8 | {
9 | public Task Start(CancellationToken token)
10 | {
11 | var ticks = 10;
12 | using (var pbar = new ProgressBar(ticks, "My operation that ticks to often", ConsoleColor.Cyan))
13 | {
14 | for (var i = 0; i < ticks*10; i++)
15 | {
16 | pbar.Tick("too many steps " + i);
17 | Thread.Sleep(50);
18 | }
19 | }
20 | return Task.FromResult(1);
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/src/ShellProgressBar.Example/Examples/ProgressBarOnBottomExample.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 |
4 | namespace ShellProgressBar.Example.Examples
5 | {
6 | public class ProgressBarOnBottomExample : ExampleBase
7 | {
8 | protected override Task StartAsync()
9 | {
10 | const int totalTicks = 10;
11 | var options = new ProgressBarOptions
12 | {
13 | ProgressCharacter = '─',
14 | ProgressBarOnBottom = true
15 | };
16 | using var pbar = new ProgressBar(totalTicks, "progress bar is on the bottom now", options);
17 | TickToCompletion(pbar, totalTicks, sleep: 500);
18 |
19 | return Task.CompletedTask;
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/ShellProgressBar.Example/TestCases/DrawsOnlyOnTickExample.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 |
4 | namespace ShellProgressBar.Example.TestCases
5 | {
6 | public class DrawsOnlyOnTickExample : IProgressBarExample
7 | {
8 | public Task Start(CancellationToken token)
9 | {
10 | var ticks = 5;
11 | var updateOnTicksOnlyOptions = new ProgressBarOptions {DisplayTimeInRealTime = false};
12 | using (var pbar = new ProgressBar(ticks, "only update time on ticks", updateOnTicksOnlyOptions))
13 | {
14 | for (var i = 0; i < ticks; i++)
15 | {
16 | pbar.Tick("only update time on ticks, current: " + i);
17 | Thread.Sleep(1750);
18 | }
19 | }
20 | return Task.FromResult(1);
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/ShellProgressBar.Example/TestCases/ThreadedTicksOverflowExample.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 |
6 | namespace ShellProgressBar.Example.TestCases
7 | {
8 | public class ThreadedTicksOverflowExample : IProgressBarExample
9 | {
10 | public Task Start(CancellationToken token)
11 | {
12 | var ticks = 200;
13 | using (var pbar = new ProgressBar(ticks/10, "My operation that ticks to often using threads", ConsoleColor.Cyan))
14 | {
15 | var threads = Enumerable.Range(0, ticks).Select(i => new Thread(() => pbar.Tick("threaded tick " + i))).ToList();
16 | foreach (var thread in threads) thread.Start();
17 | foreach (var thread in threads) thread.Join();
18 | }
19 | return Task.FromResult(1);
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/src/ShellProgressBar.Example/TestCases/UpdatesMaxTicksExample.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 |
5 | namespace ShellProgressBar.Example.TestCases
6 | {
7 | public class UpdatesMaxTicksExample : IProgressBarExample
8 | {
9 | public Task Start(CancellationToken token)
10 | {
11 | var ticks = 10;
12 | using (var pbar = new ProgressBar(ticks, "My operation that updates maxTicks", ConsoleColor.Cyan))
13 | {
14 | var sleep = 1000;
15 | for (var i = 0; i < ticks; i++)
16 | {
17 | pbar.Tick("Updating maximum ticks " + i);
18 | if (i == 5)
19 | {
20 | ticks = 120;
21 | pbar.MaxTicks = ticks;
22 | sleep = 50;
23 | }
24 | Thread.Sleep(sleep);
25 | }
26 | }
27 | return Task.FromResult(1);
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/ShellProgressBar.Example/ShellProgressBar.Example.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net6.0
6 | shellprogressbar-example
7 | ShellProgressBar.Example
8 | $(CurrentVersion)
9 | $(CurrentVersion)
10 | $(CurrentAssemblyVersion)
11 | $(CurrentAssemblyFileVersion)
12 | False
13 | latest
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/ShellProgressBar.Example/Examples/StylingExample.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 |
4 | namespace ShellProgressBar.Example.Examples
5 | {
6 | public class StylingExample : ExampleBase
7 | {
8 | protected override Task StartAsync()
9 | {
10 | const int totalTicks = 10;
11 | var options = new ProgressBarOptions
12 | {
13 | ForegroundColor = ConsoleColor.Yellow,
14 | ForegroundColorDone = ConsoleColor.DarkGreen,
15 | BackgroundColor = ConsoleColor.DarkGray,
16 | BackgroundCharacter = '\u2593'
17 | };
18 | using var pbar = new ProgressBar(totalTicks, "showing off styling", options);
19 | TickToCompletion(pbar, totalTicks, sleep: 500, i =>
20 | {
21 | if (i > 5) pbar.ForegroundColor = ConsoleColor.Red;
22 | });
23 |
24 | return Task.CompletedTask;
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/ShellProgressBar.Example/Examples/MessageBeforeAndAfterExample.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 |
4 | namespace ShellProgressBar.Example.Examples
5 | {
6 | public class MessageBeforeAndAfterExample : ExampleBase
7 | {
8 | protected override Task StartAsync()
9 | {
10 | Console.WriteLine("This should not be overwritten");
11 | const int totalTicks = 10;
12 | var options = new ProgressBarOptions
13 | {
14 | ForegroundColor = ConsoleColor.Yellow,
15 | ForegroundColorDone = ConsoleColor.DarkGreen,
16 | BackgroundColor = ConsoleColor.DarkGray,
17 | BackgroundCharacter = '\u2593'
18 | };
19 | using (var pbar = new ProgressBar(totalTicks, "showing off styling", options))
20 | {
21 | TickToCompletion(pbar, totalTicks, sleep: 500, i =>
22 | {
23 | pbar.WriteErrorLine($"This should appear above:{i}");
24 | });
25 | }
26 |
27 | Console.WriteLine("This should not be overwritten either afterwards");
28 | return Task.CompletedTask;
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/ShellProgressBar.Example/Examples/ChildrenExample.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 |
4 | namespace ShellProgressBar.Example.Examples
5 | {
6 | public class ChildrenExample : ExampleBase
7 | {
8 | protected override Task StartAsync()
9 | {
10 | const int totalTicks = 10;
11 | var options = new ProgressBarOptions
12 | {
13 | ForegroundColor = ConsoleColor.Yellow,
14 | BackgroundColor = ConsoleColor.DarkGray,
15 | ProgressCharacter = '─'
16 | };
17 | var childOptions = new ProgressBarOptions
18 | {
19 | ForegroundColor = ConsoleColor.Green,
20 | BackgroundColor = ConsoleColor.DarkGray,
21 | ProgressCharacter = '─'
22 | };
23 | using var pbar = new ProgressBar(totalTicks, "main progressbar", options);
24 | TickToCompletion(pbar, totalTicks, sleep: 10, childAction: i =>
25 | {
26 | using var child = pbar.Spawn(totalTicks, "child actions", childOptions);
27 | TickToCompletion(child, totalTicks, sleep: 100);
28 | });
29 | return Task.CompletedTask;
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/ShellProgressBar/IProgressBar.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace ShellProgressBar
4 | {
5 | public interface IProgressBar : IDisposable
6 | {
7 | ChildProgressBar Spawn(int maxTicks, string message, ProgressBarOptions options = null);
8 |
9 | void Tick(string message = null);
10 | void Tick(int newTickCount, string message = null);
11 |
12 | int MaxTicks { get; set; }
13 | string Message { get; set; }
14 |
15 | double Percentage { get; }
16 | int CurrentTick { get; }
17 |
18 | ConsoleColor ForegroundColor { get; set; }
19 |
20 | ///
21 | /// This writes a new line above the progress bar to .
22 | /// Use to update the message inside the progress bar
23 | ///
24 | void WriteLine(string message);
25 |
26 | /// This writes a new line above the progress bar to
27 | void WriteErrorLine(string message);
28 |
29 | IProgress AsProgress(Func message = null, Func percentage = null);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/ShellProgressBar.Example/Examples/ExampleBase.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 |
5 | namespace ShellProgressBar.Example.Examples
6 | {
7 | public abstract class ExampleBase : IProgressBarExample
8 | {
9 | private bool RequestToQuit { get; set; }
10 |
11 | protected void TickToCompletion(IProgressBar pbar, int ticks, int sleep = 1750, Action childAction = null)
12 | {
13 | var initialMessage = pbar.Message;
14 | for (var i = 0; i < ticks && !RequestToQuit; i++)
15 | {
16 | pbar.Message = $"Start {i + 1} of {ticks} {Console.CursorTop}/{Console.WindowHeight}: {initialMessage}";
17 | childAction?.Invoke(i);
18 | Thread.Sleep(sleep);
19 | pbar.Tick($"End {i + 1} of {ticks} {Console.CursorTop}/{Console.WindowHeight}: {initialMessage}");
20 | }
21 | }
22 |
23 | public async Task Start(CancellationToken token)
24 | {
25 | RequestToQuit = false;
26 | token.Register(() => RequestToQuit = true);
27 |
28 | await this.StartAsync();
29 | }
30 |
31 | protected abstract Task StartAsync();
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/ShellProgressBar.Example/Examples/IndeterminateProgressExample.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 |
5 | namespace ShellProgressBar.Example.Examples
6 | {
7 | public class IndeterminateProgressExample : ExampleBase
8 | {
9 | protected override Task StartAsync()
10 | {
11 | var options = new ProgressBarOptions
12 | {
13 | ForegroundColor = ConsoleColor.Yellow,
14 | ForegroundColorDone = ConsoleColor.DarkGreen,
15 | BackgroundColor = ConsoleColor.DarkGray,
16 | BackgroundCharacter = '\u2593'
17 | };
18 |
19 | using (var pbar = new IndeterminateProgressBar("Indeterminate", options))
20 | {
21 | Task.Run(
22 | () =>
23 | {
24 | for (var i = 0; i < 1_000; i++)
25 | {
26 | pbar.Message= $"The progress is beating to its own drum (indeterminate) {i}";
27 | Task.Delay(10).Wait();
28 | }
29 | }).Wait();
30 | pbar.Finished();
31 | pbar.Message= "Finished! Moving on to the next in 5 seconds.";
32 | }
33 |
34 | return Task.CompletedTask;
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/ShellProgressBar.Example/Examples/IntegrationWithIProgressPercentageExample.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 |
8 | namespace ShellProgressBar.Example.Examples
9 | {
10 | public class IntegrationWithIProgressPercentageExample : IProgressBarExample
11 | {
12 | public Task Start(CancellationToken token)
13 | {
14 | using (var pbar = new ProgressBar(100, "A console progress that integrates with IProgress"))
15 | {
16 | ProcessFiles(pbar.AsProgress());
17 | }
18 | return Task.FromResult(1);
19 | }
20 |
21 | public static void ProcessFiles(IProgress progress)
22 | {
23 | var files = Enumerable.Range(1, 10).Select(e => new FileInfo($"Data{e:D2}.csv")).ToList();
24 | var i = 0;
25 | foreach (var file in files)
26 | {
27 | DoWork(file);
28 | progress?.Report(++i / (float)files.Count);
29 | }
30 | }
31 |
32 | private static void DoWork(FileInfo file)
33 | {
34 | Thread.Sleep(200);
35 | }
36 | }
37 | }
--------------------------------------------------------------------------------
/src/ShellProgressBar.Example/Examples/AlternateFinishedColorExample.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 |
5 | namespace ShellProgressBar.Example.Examples
6 | {
7 | public class AlternateFinishedColorExample : ExampleBase
8 | {
9 | protected override async Task StartAsync()
10 | {
11 | var options = new ProgressBarOptions
12 | {
13 | ForegroundColor = ConsoleColor.Yellow,
14 | ForegroundColorDone = ConsoleColor.DarkGreen,
15 | ForegroundColorError = ConsoleColor.Red,
16 | BackgroundColor = ConsoleColor.DarkGray,
17 | BackgroundCharacter = '\u2593'
18 | };
19 |
20 | using var pbar = new ProgressBar(100, "100 ticks", options);
21 | await Task.Run(
22 | () =>
23 | {
24 | for (var i = 0; i < 10; i++)
25 | {
26 | Task.Delay(10).Wait();
27 | pbar.Tick($"Step {i}");
28 | }
29 | pbar.WriteErrorLine("The task ran into an issue!");
30 | // OR pbar.ObservedError = true;
31 | });
32 | pbar.Message= "Indicate the task is done, but the status is not Green.";
33 | }
34 |
35 |
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/ShellProgressBar.Example/Examples/IntegrationWithIProgressExample.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 |
8 | namespace ShellProgressBar.Example.Examples
9 | {
10 | public class IntegrationWithIProgressExample : IProgressBarExample
11 | {
12 | public Task Start(CancellationToken token)
13 | {
14 | var files = Enumerable.Range(1, 10).Select(e => new FileInfo($"Data{e:D2}.csv")).ToList();
15 | using (var pbar = new ProgressBar(files.Count, "A console progress that integrates with IProgress"))
16 | {
17 | ProcessFiles(files, pbar.AsProgress(e => $"Processed {e.Name}"));
18 | }
19 | return Task.FromResult(1);
20 | }
21 |
22 | public static void ProcessFiles(IEnumerable files, IProgress progress)
23 | {
24 | foreach (var file in files)
25 | {
26 | DoWork(file);
27 | progress?.Report(file);
28 | }
29 | }
30 |
31 | private static void DoWork(FileInfo file)
32 | {
33 | Thread.Sleep(200);
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/src/ShellProgressBar.Example/Examples/ChildrenNoCollapseExample.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 |
4 | namespace ShellProgressBar.Example.Examples
5 | {
6 | public class ChildrenNoCollapseExample : ExampleBase
7 | {
8 | protected override Task StartAsync()
9 | {
10 | const int totalTicks = 10;
11 | var options = new ProgressBarOptions
12 | {
13 | ForegroundColor = ConsoleColor.Yellow,
14 | BackgroundColor = ConsoleColor.DarkYellow,
15 | ProgressCharacter = '─'
16 | };
17 | var childOptions = new ProgressBarOptions
18 | {
19 | ForegroundColor = ConsoleColor.Green,
20 | BackgroundColor = ConsoleColor.DarkGreen,
21 | ProgressCharacter = '─',
22 | CollapseWhenFinished = false
23 | };
24 | using var pbar = new ProgressBar(totalTicks, "main progressbar", options);
25 | TickToCompletion(pbar, totalTicks, sleep: 10, childAction: i =>
26 | {
27 | using var child = pbar.Spawn(totalTicks, "child actions", childOptions);
28 | TickToCompletion(child, totalTicks, sleep: 100);
29 | });
30 |
31 | return Task.CompletedTask;
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2013 Martijn Laarman
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/src/ShellProgressBar/Progress.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace ShellProgressBar
4 | {
5 | internal class Progress : IProgress, IDisposable
6 | {
7 | private readonly WeakReference _progressBar;
8 | private readonly Func _message;
9 | private readonly Func _percentage;
10 |
11 | public Progress(IProgressBar progressBar, Func message, Func percentage)
12 | {
13 | _progressBar = new WeakReference(progressBar);
14 | _message = message;
15 | _percentage = percentage ?? (value => value as double? ?? value as float?);
16 | }
17 |
18 | public void Report(T value)
19 | {
20 | if (!_progressBar.TryGetTarget(out var progressBar)) return;
21 |
22 | var message = _message?.Invoke(value);
23 | var percentage = _percentage(value);
24 | if (percentage.HasValue)
25 | progressBar.Tick((int)(percentage * progressBar.MaxTicks), message);
26 | else
27 | progressBar.Tick(message);
28 | }
29 |
30 | public void Dispose()
31 | {
32 | if (_progressBar.TryGetTarget(out var progressBar))
33 | {
34 | progressBar.Dispose();
35 | }
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/test/ShellProgressBar.Tests/ShellProgressBar.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 |
6 | false
7 |
8 | latest
9 |
10 |
11 |
12 |
13 |
14 |
15 | runtime; build; native; contentfiles; analyzers; buildtransitive
16 | all
17 |
18 |
19 | runtime; build; native; contentfiles; analyzers; buildtransitive
20 | all
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/ShellProgressBar.Example/Examples/EstimatedDurationExample.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 |
5 | namespace ShellProgressBar.Example.Examples
6 | {
7 | public class EstimatedDurationExample : ExampleBase
8 | {
9 | protected override Task StartAsync()
10 | {
11 | const int totalTicks = 10;
12 | var options = new ProgressBarOptions
13 | {
14 | ProgressCharacter = '─',
15 | ShowEstimatedDuration = true
16 | };
17 | using (var pbar = new ProgressBar(totalTicks, "you can set the estimated duration too", options))
18 | {
19 | pbar.EstimatedDuration = TimeSpan.FromMilliseconds(totalTicks * 500);
20 |
21 | var initialMessage = pbar.Message;
22 | for (var i = 0; i < totalTicks; i++)
23 | {
24 | pbar.Message = $"Start {i + 1} of {totalTicks}: {initialMessage}";
25 | Thread.Sleep(500);
26 |
27 | // Simulate changing estimated durations while progress increases
28 | var estimatedDuration =
29 | TimeSpan.FromMilliseconds(500 * totalTicks) + TimeSpan.FromMilliseconds(300 * i);
30 | pbar.Tick(estimatedDuration, $"End {i + 1} of {totalTicks}: {initialMessage}");
31 | }
32 | }
33 |
34 | return Task.CompletedTask;
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/ShellProgressBar.Example/Examples/FixedDurationExample.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 |
5 | namespace ShellProgressBar.Example.Examples
6 | {
7 | public class FixedDurationExample : ExampleBase
8 | {
9 | protected override Task StartAsync()
10 | {
11 | var options = new ProgressBarOptions
12 | {
13 | ForegroundColor = ConsoleColor.Yellow,
14 | ForegroundColorDone = ConsoleColor.DarkGreen,
15 | BackgroundColor = ConsoleColor.DarkGray,
16 | BackgroundCharacter = '\u2593'
17 | };
18 | var wait = TimeSpan.FromSeconds(5);
19 | using (var pbar = new FixedDurationBar(wait, "", options))
20 | {
21 | var t = new Thread(()=> LongRunningTask(pbar));
22 | t.Start();
23 |
24 | if (!pbar.CompletedHandle.WaitOne(wait))
25 | Console.Error.WriteLine($"{nameof(FixedDurationBar)} did not signal {nameof(FixedDurationBar.CompletedHandle)} after {wait}");
26 |
27 | }
28 |
29 | return Task.CompletedTask;
30 | }
31 |
32 | private static void LongRunningTask(FixedDurationBar bar)
33 | {
34 | for (var i = 0; i < 1_000_000; i++)
35 | {
36 | bar.Message = $"{i} events";
37 | if (bar.IsCompleted) break;
38 | Thread.Sleep(1);
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/ShellProgressBar.Example/Examples/IndeterminateChildrenNoCollapse.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 |
5 | namespace ShellProgressBar.Example.Examples
6 | {
7 | public class IndeterminateChildrenNoCollapseExample : ExampleBase
8 | {
9 | protected override async Task StartAsync()
10 | {
11 | const int totalChildren = 2;
12 | Random random = new Random();
13 | var options = new ProgressBarOptions
14 | {
15 | ForegroundColor = ConsoleColor.Yellow,
16 | BackgroundColor = ConsoleColor.DarkGray,
17 | ProgressCharacter = '─'
18 | };
19 | var childOptions = new ProgressBarOptions
20 | {
21 | ForegroundColor = ConsoleColor.Green,
22 | BackgroundColor = ConsoleColor.DarkGray,
23 | ProgressCharacter = '─',
24 | CollapseWhenFinished = false
25 | };
26 | using (var pbar = new ProgressBar(totalChildren, "main progressbar", options))
27 | {
28 | for (int i = 1; i <= totalChildren; i++)
29 | {
30 | pbar.Message = $"Start {i} of {totalChildren}: main progressbar";
31 | using (var child = pbar.SpawnIndeterminate("child action " + i, childOptions))
32 | {
33 | await Task.Delay(1000 * random.Next(5, 15));
34 | child.Finished();
35 | }
36 | pbar.Tick();
37 | }
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/ShellProgressBar/FixedDurationBar.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 |
4 | namespace ShellProgressBar
5 | {
6 | public class FixedDurationBar : ProgressBar
7 | {
8 | public bool IsCompleted { get; private set; }
9 |
10 | private readonly ManualResetEvent _completedHandle = new ManualResetEvent(false);
11 | public WaitHandle CompletedHandle => _completedHandle;
12 |
13 | public FixedDurationBar(TimeSpan duration, string message, ConsoleColor color) : this(duration, message, new ProgressBarOptions {ForegroundColor = color}) { }
14 |
15 | public FixedDurationBar(TimeSpan duration, string message, ProgressBarOptions options = null)
16 | : base((int)Math.Ceiling(duration.TotalSeconds) * 2, message, options)
17 | {
18 | if (!this.Options.DisplayTimeInRealTime)
19 | throw new ArgumentException(
20 | $"{nameof(ProgressBarOptions)}.{nameof(ProgressBarOptions.DisplayTimeInRealTime)} has to be true for {nameof(FixedDurationBar)}", nameof(options)
21 | );
22 | }
23 |
24 | private long _seenTicks = 0;
25 | protected override void OnTimerTick()
26 | {
27 | Interlocked.Increment(ref _seenTicks);
28 | this.Tick();
29 | base.OnTimerTick();
30 | }
31 |
32 | protected override void OnDone()
33 | {
34 | this.IsCompleted = true;
35 | this._completedHandle.Set();
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/ShellProgressBar/ShellProgressBar.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ShellProgressBar
5 | ShellProgressBar
6 | netstandard1.3;netstandard2.0;net461
7 | true
8 |
9 |
10 | ShellProgressBar
11 | Cross platform simple and complex progressbars on the command line!
12 | Martijn Laarman
13 | MIT
14 | https://github.com/Mpdreamz/shellprogressbar
15 | https://raw.github.com/Mpdreamz/shellprogressbar/master/src/nuget-icon.png
16 | console;shell;terminal;progress;progressbar
17 |
18 |
19 |
20 | all
21 | runtime; build; native; contentfiles; analyzers; buildtransitive
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/ShellProgressBar/IndeterminateProgressBar.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 |
4 | namespace ShellProgressBar
5 | {
6 | public class IndeterminateProgressBar : ProgressBar
7 | {
8 | private const int MaxTicksForIndeterminate = 20;
9 |
10 | public IndeterminateProgressBar(string message, ConsoleColor color) : this(
11 | message,
12 | new ProgressBarOptions { ForegroundColor = color })
13 | {
14 | }
15 |
16 | public IndeterminateProgressBar(string message, ProgressBarOptions options = null) : base(
17 | MaxTicksForIndeterminate,
18 | message,
19 | options)
20 | {
21 | if (options == null)
22 | {
23 | options = new ProgressBarOptions();
24 | }
25 |
26 | options.DisableBottomPercentage = true;
27 | options.DisplayTimeInRealTime = true;
28 |
29 | if (!this.Options.DisplayTimeInRealTime)
30 | throw new ArgumentException(
31 | $"{nameof(ProgressBarOptions)}.{nameof(ProgressBarOptions.DisplayTimeInRealTime)} has to be true for {nameof(FixedDurationBar)}",
32 | nameof(options)
33 | );
34 | }
35 |
36 | private long _seenTicks = 0;
37 |
38 | protected override void OnTimerTick()
39 | {
40 | Interlocked.Increment(ref _seenTicks);
41 | if (_seenTicks == MaxTicksForIndeterminate - 1)
42 | {
43 | this.Tick(0);
44 | Interlocked.Exchange(ref _seenTicks, 0);
45 | }
46 | else
47 | {
48 | this.Tick();
49 | }
50 |
51 | base.OnTimerTick();
52 | }
53 |
54 | public void Finished()
55 | {
56 | Tick(MaxTicksForIndeterminate);
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/ShellProgressBar/IndeterminateChildProgressBar.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Concurrent;
3 | using System.Threading;
4 |
5 | namespace ShellProgressBar
6 | {
7 | public class IndeterminateChildProgressBar : ChildProgressBar
8 | {
9 | private const int MaxTicksForIndeterminate = 20;
10 | private Timer _timer;
11 | internal IndeterminateChildProgressBar(
12 | string message,
13 | Action scheduleDraw,
14 | Action writeLine,
15 | Action writeError,
16 | ProgressBarOptions options = null,
17 | Action growth = null
18 | )
19 | : base(MaxTicksForIndeterminate, message, scheduleDraw, writeLine, writeError, options, growth)
20 | {
21 | if (options == null)
22 | {
23 | options = new ProgressBarOptions();
24 | }
25 |
26 | options.DisableBottomPercentage = true;
27 | _timer = new Timer((s) => OnTimerTick(), null, 500, 500);
28 | }
29 |
30 | private long _seenTicks = 0;
31 |
32 | protected void OnTimerTick()
33 | {
34 | Interlocked.Increment(ref _seenTicks);
35 | if (_seenTicks == MaxTicksForIndeterminate - 1)
36 | {
37 | this.Tick(0);
38 | Interlocked.Exchange(ref _seenTicks, 0);
39 | }
40 | else
41 | {
42 | this.Tick();
43 | }
44 | DisplayProgress();
45 | }
46 |
47 | public void Finished()
48 | {
49 | _timer.Change(Timeout.Infinite, Timeout.Infinite);
50 | _timer.Dispose();
51 | Tick(MaxTicksForIndeterminate);
52 | }
53 |
54 | public void Dispose()
55 | {
56 | if (_timer != null) _timer.Dispose();
57 | foreach (var c in this.Children) c.Dispose();
58 | OnDone();
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/ShellProgressBar.Example/Examples/DownloadPogressExample.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Net;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 |
7 | namespace ShellProgressBar.Example.Examples
8 | {
9 | public class DownloadProgressExample : ExampleBase
10 | {
11 | protected override Task StartAsync()
12 | {
13 | var files = new string[]
14 | {
15 | "https://github.com/Mpdreamz/shellprogressbar/archive/4.3.0.zip",
16 | "https://github.com/Mpdreamz/shellprogressbar/archive/4.2.0.zip",
17 | "https://github.com/Mpdreamz/shellprogressbar/archive/4.1.0.zip",
18 | "https://github.com/Mpdreamz/shellprogressbar/archive/4.0.0.zip",
19 | };
20 | var childOptions = new ProgressBarOptions
21 | {
22 | ForegroundColor = ConsoleColor.Yellow,
23 | ProgressCharacter = '\u2593'
24 | };
25 | using var pbar = new ProgressBar(files.Length, "downloading");
26 | foreach (var (file, i) in files.Select((f, i) => (f, i)))
27 | {
28 | byte[] data = null;
29 | using var child = pbar.Spawn(100, "page: " + i, childOptions);
30 | try
31 | {
32 | #pragma warning disable CS0618
33 | #pragma warning disable SYSLIB0014
34 | using var client = new WebClient();
35 | #pragma warning restore CS0618
36 | #pragma warning restore SYSLIB0014
37 | client.DownloadProgressChanged += (o, args) => child.Tick(args.ProgressPercentage);
38 | client.DownloadDataCompleted += (o, args) => data = args.Result;
39 | client.DownloadDataAsync(new Uri(file));
40 | while (client.IsBusy)
41 | {
42 | Thread.Sleep(1);
43 | }
44 |
45 | pbar.Tick();
46 | }
47 | catch (WebException error)
48 | {
49 | pbar.WriteLine(error.Message);
50 | }
51 | }
52 |
53 | return Task.CompletedTask;
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/ShellProgressBar.Example/TestCases/NestedProgressBarPerStepProgress.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 |
6 | namespace ShellProgressBar.Example.TestCases
7 | {
8 | public class NestedProgressBarPerStepProgress : IProgressBarExample
9 | {
10 | public Task Start(CancellationToken token)
11 | {
12 | var outerTicks = 10;
13 | using (var pbar = new ProgressBar(outerTicks, "outer progress", ConsoleColor.Cyan))
14 | {
15 | for (var i = 0; i < outerTicks; i++)
16 | {
17 | InnerProgressBars(pbar);
18 | pbar.Tick();
19 | }
20 | }
21 | return Task.FromResult(1);
22 | }
23 |
24 | private static void InnerProgressBars(ProgressBar pbar)
25 | {
26 | var innerProgressBars = Enumerable.Range(0, new Random().Next(2, 6))
27 | .Select(s => pbar.Spawn(new Random().Next(2, 5), $"inner bar {s}"))
28 | .ToList();
29 |
30 | var maxTicks = innerProgressBars.Max(p => p.MaxTicks);
31 |
32 | for (var ii = 0; ii < maxTicks; ii++)
33 | {
34 | foreach (var p in innerProgressBars)
35 | {
36 | InnerInnerProgressBars(p);
37 | p.Tick();
38 | }
39 |
40 |
41 | Thread.Sleep(4);
42 | }
43 | foreach (var p in innerProgressBars) p.Dispose();
44 | }
45 |
46 | private static void InnerInnerProgressBars(ChildProgressBar pbar)
47 | {
48 | var progressBarOption = new ProgressBarOptions { ForegroundColor = ConsoleColor.Yellow };
49 | var innerProgressBars = Enumerable.Range(0, new Random().Next(1, 3))
50 | .Select(s => pbar.Spawn(new Random().Next(5, 10), $"inner bar {s}", progressBarOption))
51 | .ToList();
52 | if (!innerProgressBars.Any()) return;
53 |
54 | var maxTicks = innerProgressBars.Max(p => p.MaxTicks);
55 |
56 | for (var ii = 0; ii < maxTicks; ii++)
57 | {
58 | foreach (var p in innerProgressBars)
59 | p.Tick();
60 | }
61 | foreach (var p in innerProgressBars) p.Dispose();
62 | }
63 | }
64 | }
--------------------------------------------------------------------------------
/src/ShellProgressBar.Example/Examples/PersistMessageExample.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 |
5 | namespace ShellProgressBar.Example.Examples
6 | {
7 | public class PersistMessageExample : ExampleBase
8 | {
9 | protected override Task StartAsync()
10 | {
11 | var options = new ProgressBarOptions
12 | {
13 | ForegroundColor = ConsoleColor.Yellow,
14 | ForegroundColorDone = ConsoleColor.DarkGreen,
15 | ForegroundColorError = ConsoleColor.Red,
16 | BackgroundColor = ConsoleColor.DarkGray,
17 | BackgroundCharacter = '\u2593',
18 | WriteQueuedMessage = o =>
19 | {
20 | var writer = o.Error ? Console.Error : Console.Out;
21 | var c = o.Error ? ConsoleColor.DarkRed : ConsoleColor.Blue;
22 | if (o.Line.StartsWith("Report 500"))
23 | {
24 | Console.ForegroundColor = ConsoleColor.Yellow;
25 | writer.WriteLine("Add an extra message, because why not");
26 |
27 | Console.ForegroundColor = c;
28 | writer.WriteLine(o.Line);
29 | return 2; //signal to the progressbar we wrote two messages
30 | }
31 | Console.ForegroundColor = c;
32 | writer.WriteLine(o.Line);
33 | return 1;
34 | }
35 | };
36 | var wait = TimeSpan.FromSeconds(6);
37 | using var pbar = new FixedDurationBar(wait, "", options);
38 | var t = new Thread(()=> LongRunningTask(pbar));
39 | t.Start();
40 |
41 | if (!pbar.CompletedHandle.WaitOne(wait.Subtract(TimeSpan.FromSeconds(.5))))
42 | {
43 | pbar.WriteErrorLine($"{nameof(FixedDurationBar)} did not signal {nameof(FixedDurationBar.CompletedHandle)} after {wait}");
44 | pbar.Dispose();
45 | }
46 | return Task.CompletedTask;
47 | }
48 |
49 | private static void LongRunningTask(FixedDurationBar bar)
50 | {
51 | for (var i = 0; i < 1_000_000; i++)
52 | {
53 | bar.Message = $"{i} events";
54 | if (bar.IsCompleted || bar.ObservedError) break;
55 | if (i % 500 == 0) bar.WriteLine($"Report {i} to console above the progressbar");
56 | Thread.Sleep(1);
57 | }
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/ShellProgressBar/TaskbarProgress.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 |
4 | namespace ShellProgressBar
5 | {
6 | public static class TaskbarProgress
7 | {
8 | public enum TaskbarStates
9 | {
10 | NoProgress = 0,
11 | Indeterminate = 0x1,
12 | Normal = 0x2,
13 | Error = 0x4,
14 | Paused = 0x8
15 | }
16 |
17 | [ComImport()]
18 | [Guid("ea1afb91-9e28-4b86-90e9-9e9f8a5eefaf")]
19 | [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
20 | private interface ITaskbarList3
21 | {
22 | // ITaskbarList
23 | [PreserveSig]
24 | void HrInit();
25 |
26 | [PreserveSig]
27 | void AddTab(IntPtr hwnd);
28 |
29 | [PreserveSig]
30 | void DeleteTab(IntPtr hwnd);
31 |
32 | [PreserveSig]
33 | void ActivateTab(IntPtr hwnd);
34 |
35 | [PreserveSig]
36 | void SetActiveAlt(IntPtr hwnd);
37 |
38 | // ITaskbarList2
39 | [PreserveSig]
40 | void MarkFullscreenWindow(IntPtr hwnd, [MarshalAs(UnmanagedType.Bool)] bool fFullscreen);
41 |
42 | // ITaskbarList3
43 | [PreserveSig]
44 | void SetProgressValue(IntPtr hwnd, UInt64 ullCompleted, UInt64 ullTotal);
45 |
46 | [PreserveSig]
47 | void SetProgressState(IntPtr hwnd, TaskbarStates state);
48 | }
49 |
50 | [ComImport]
51 | [Guid("56fdf344-fd6d-11d0-958a-006097c9a090")]
52 | [ClassInterface(ClassInterfaceType.None)]
53 | private class TaskbarInstance
54 | {}
55 |
56 | [DllImport("kernel32.dll")]
57 | static extern IntPtr GetConsoleWindow();
58 |
59 | private static readonly ITaskbarList3 _taskbarInstance = (ITaskbarList3) new TaskbarInstance();
60 | private static readonly bool _taskbarSupported = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
61 |
62 | public static void SetState(TaskbarStates taskbarState)
63 | {
64 | if (_taskbarSupported)
65 | _taskbarInstance.SetProgressState(GetConsoleWindow(), taskbarState);
66 | }
67 |
68 | public static void SetValue(double progressValue, double progressMax)
69 | {
70 | if (_taskbarSupported)
71 | _taskbarInstance.SetProgressValue(GetConsoleWindow(), (ulong) progressValue, (ulong) progressMax);
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/ShellProgressBar/ChildProgressBar.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Concurrent;
3 | using System.Threading;
4 |
5 | namespace ShellProgressBar
6 | {
7 | public class ChildProgressBar : ProgressBarBase, IProgressBar
8 | {
9 | private readonly Action _scheduleDraw;
10 | private readonly Action _writeLine;
11 | private readonly Action _writeError;
12 | private readonly Action _growth;
13 |
14 | public DateTime StartDate { get; } = DateTime.Now;
15 |
16 | protected override void DisplayProgress() => _scheduleDraw?.Invoke();
17 |
18 | internal ChildProgressBar(
19 | int maxTicks,
20 | string message,
21 | Action scheduleDraw,
22 | Action writeLine,
23 | Action writeError,
24 | ProgressBarOptions options = null,
25 | Action growth = null
26 | )
27 | : base(maxTicks, message, options)
28 | {
29 | _scheduleDraw = scheduleDraw;
30 | _writeLine = writeLine;
31 | _writeError = writeError;
32 | _growth = growth;
33 | _growth?.Invoke(ProgressBarHeight.Increment);
34 | }
35 |
36 | protected override void Grow(ProgressBarHeight direction) => _growth?.Invoke(direction);
37 |
38 | private bool _calledDone;
39 | private readonly object _callOnce = new object();
40 | protected override void OnDone()
41 | {
42 | if (_calledDone) return;
43 | lock(_callOnce)
44 | {
45 | if (_calledDone) return;
46 |
47 | if (this.EndTime == null)
48 | this.EndTime = DateTime.Now;
49 |
50 | if (this.Options.CollapseWhenFinished)
51 | _growth?.Invoke(ProgressBarHeight.Decrement);
52 |
53 | _calledDone = true;
54 | }
55 | }
56 |
57 | public override void WriteLine(string message) => _writeLine(message);
58 | public override void WriteErrorLine(string message) => _writeError(message);
59 |
60 | public void Dispose()
61 | {
62 | foreach (var c in this.Children) c.Dispose();
63 | OnDone();
64 | }
65 |
66 | public IProgress AsProgress(Func message = null, Func percentage = null)
67 | {
68 | return new Progress(this, message, percentage);
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/ShellProgressBar.Example/TestCases/DeeplyNestedProgressBarTreeExample.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 |
6 | namespace ShellProgressBar.Example.TestCases
7 | {
8 | public class DeeplyNestedProgressBarTreeExample : IProgressBarExample
9 | {
10 | public Task Start(CancellationToken token)
11 | {
12 | var random = new Random();
13 |
14 | var numberOfSteps = 7;
15 |
16 | var overProgressOptions = new ProgressBarOptions
17 | {
18 | DenseProgressBar = true,
19 | ProgressCharacter = '─',
20 | BackgroundColor = ConsoleColor.DarkGray,
21 | EnableTaskBarProgress = RuntimeInformation.IsOSPlatform(OSPlatform.Windows),
22 | };
23 |
24 | using (var pbar = new ProgressBar(numberOfSteps, "overall progress", overProgressOptions))
25 | {
26 | var stepBarOptions = new ProgressBarOptions
27 | {
28 | DenseProgressBar = true,
29 | ForegroundColor = ConsoleColor.Cyan,
30 | ForegroundColorDone = ConsoleColor.DarkGreen,
31 | ProgressCharacter = '─',
32 | BackgroundColor = ConsoleColor.DarkGray,
33 | CollapseWhenFinished = true,
34 | };
35 | Parallel.For(0, numberOfSteps, (i) =>
36 | {
37 | var workBarOptions = new ProgressBarOptions
38 | {
39 | DenseProgressBar = true,
40 | ForegroundColor = ConsoleColor.Yellow,
41 | ProgressCharacter = '─',
42 | BackgroundColor = ConsoleColor.DarkGray,
43 | };
44 | var childSteps = random.Next(1, 5);
45 | using (var childProgress = pbar.Spawn(childSteps, $"step {i} progress", stepBarOptions))
46 | Parallel.For(0, childSteps, (ci) =>
47 | {
48 | var childTicks = random.Next(50, 250);
49 | using (var innerChildProgress = childProgress.Spawn(childTicks, $"step {i}::{ci} progress", workBarOptions))
50 | {
51 | for (var r = 0; r < childTicks; r++)
52 | {
53 | innerChildProgress.Tick();
54 | Program.BusyWait(50);
55 | }
56 | }
57 | childProgress.Tick();
58 | });
59 |
60 | pbar.Tick();
61 | });
62 | }
63 | return Task.FromResult(1);
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/src/ShellProgressBar.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 14
4 | VisualStudioVersion = 14.0.24720.0
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShellProgressBar", "ShellProgressBar\ShellProgressBar.csproj", "{4767E390-2159-470C-AE49-E75E2DDF80A4}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShellProgressBar.Example", "ShellProgressBar.Example\ShellProgressBar.Example.csproj", "{25EEC6B1-4113-41F4-8181-674E4855F40A}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".build", ".build", "{DEEB298E-2B68-4DDD-9BBA-8E4BFCB2AA30}"
11 | ProjectSection(SolutionItems) = preProject
12 | .build\MSBuild.Community.Tasks.dll = .build\MSBuild.Community.Tasks.dll
13 | .build\MSBuild.Community.Tasks.targets = .build\MSBuild.Community.Tasks.targets
14 | EndProjectSection
15 | EndProject
16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{314A5D2B-11E8-4BF3-BB64-B4E15399591C}"
17 | ProjectSection(SolutionItems) = preProject
18 | ..\build.bat = ..\build.bat
19 | ..\README.md = ..\README.md
20 | EndProjectSection
21 | EndProject
22 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShellProgressBar.Tests", "..\test\ShellProgressBar.Tests\ShellProgressBar.Tests.csproj", "{7F6B9B22-0375-46C4-ADEB-30F5BF6DB7B2}"
23 | EndProject
24 | Global
25 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
26 | Debug|Any CPU = Debug|Any CPU
27 | Release|Any CPU = Release|Any CPU
28 | EndGlobalSection
29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
30 | {4767E390-2159-470C-AE49-E75E2DDF80A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
31 | {4767E390-2159-470C-AE49-E75E2DDF80A4}.Debug|Any CPU.Build.0 = Debug|Any CPU
32 | {4767E390-2159-470C-AE49-E75E2DDF80A4}.Release|Any CPU.ActiveCfg = Release|Any CPU
33 | {4767E390-2159-470C-AE49-E75E2DDF80A4}.Release|Any CPU.Build.0 = Release|Any CPU
34 | {25EEC6B1-4113-41F4-8181-674E4855F40A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
35 | {25EEC6B1-4113-41F4-8181-674E4855F40A}.Debug|Any CPU.Build.0 = Debug|Any CPU
36 | {25EEC6B1-4113-41F4-8181-674E4855F40A}.Release|Any CPU.ActiveCfg = Release|Any CPU
37 | {25EEC6B1-4113-41F4-8181-674E4855F40A}.Release|Any CPU.Build.0 = Release|Any CPU
38 | {7F6B9B22-0375-46C4-ADEB-30F5BF6DB7B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
39 | {7F6B9B22-0375-46C4-ADEB-30F5BF6DB7B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
40 | {7F6B9B22-0375-46C4-ADEB-30F5BF6DB7B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
41 | {7F6B9B22-0375-46C4-ADEB-30F5BF6DB7B2}.Release|Any CPU.Build.0 = Release|Any CPU
42 | EndGlobalSection
43 | GlobalSection(SolutionProperties) = preSolution
44 | HideSolutionNode = FALSE
45 | EndGlobalSection
46 | EndGlobal
47 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.sln.docstates
8 | .idea
9 |
10 | # Build results
11 |
12 | [Dd]ebug/
13 | [Rr]elease/
14 | x64/
15 | */build/
16 | !build
17 | [Bb]in/
18 | [Oo]bj/
19 |
20 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets
21 | !packages/*/build/
22 |
23 | # MSTest test Results
24 | [Tt]est[Rr]esult*/
25 | [Bb]uild[Ll]og.*
26 |
27 | *_i.c
28 | *_p.c
29 | *.ilk
30 | *.meta
31 | *.obj
32 | *.pch
33 | *.pdb
34 | *.pgc
35 | *.pgd
36 | *.rsp
37 | *.sbr
38 | *.tlb
39 | *.tli
40 | *.tlh
41 | *.tmp
42 | *.tmp_proj
43 | *.log
44 | *.vspscc
45 | *.vssscc
46 | .builds
47 | *.pidb
48 | *.log
49 | *.scc
50 |
51 | # Visual C++ cache files
52 | ipch/
53 | *.aps
54 | *.ncb
55 | *.opensdf
56 | *.sdf
57 | *.cachefile
58 |
59 | # Visual Studio profiler
60 | *.psess
61 | *.vsp
62 | *.vspx
63 |
64 | # Guidance Automation Toolkit
65 | *.gpState
66 |
67 | # ReSharper is a .NET coding add-in
68 | _ReSharper*/
69 | *.[Rr]e[Ss]harper
70 |
71 | # TeamCity is a build add-in
72 | _TeamCity*
73 |
74 | # DotCover is a Code Coverage Tool
75 | *.dotCover
76 |
77 | # NCrunch
78 | *.ncrunch*
79 | .*crunch*.local.xml
80 |
81 | # Installshield output folder
82 | [Ee]xpress/
83 |
84 | # DocProject is a documentation generator add-in
85 | DocProject/buildhelp/
86 | DocProject/Help/*.HxT
87 | DocProject/Help/*.HxC
88 | DocProject/Help/*.hhc
89 | DocProject/Help/*.hhk
90 | DocProject/Help/*.hhp
91 | DocProject/Help/Html2
92 | DocProject/Help/html
93 |
94 | # Click-Once directory
95 | publish/
96 |
97 | # Publish Web Output
98 | *.Publish.xml
99 | *.pubxml
100 |
101 | # Windows Azure Build Output
102 | csx
103 | *.build.csdef
104 |
105 | # Windows Store app package directory
106 | AppPackages/
107 |
108 | # Others
109 | sql/
110 | *.Cache
111 | ClientBin/
112 | [Ss]tyle[Cc]op.*
113 | ~$*
114 | *~
115 | *.dbmdl
116 | *.[Pp]ublish.xml
117 | *.pfx
118 | *.publishsettings
119 |
120 | # RIA/Silverlight projects
121 | Generated_Code/
122 |
123 | # Backup & report files from converting an old project file to a newer
124 | # Visual Studio version. Backup files are not needed, because we have git ;-)
125 | _UpgradeReport_Files/
126 | Backup*/
127 | UpgradeLog*.XML
128 | UpgradeLog*.htm
129 |
130 | # SQL Server files
131 | App_Data/*.mdf
132 | App_Data/*.ldf
133 |
134 | # =========================
135 | # Windows detritus
136 | # =========================
137 |
138 | # Windows image file caches
139 | Thumbs.db
140 | ehthumbs.db
141 |
142 | # Folder config file
143 | Desktop.ini
144 |
145 | # Recycle Bin used on file shares
146 | $RECYCLE.BIN/
147 |
148 | # Mac crap
149 | .DS_Store
150 |
151 | src/packages
152 | build/tmp
153 | build/tmp/*
154 | build/_out/*
155 | !build/keys
156 | !build/tools/*
157 | !build/tools/scriptcs/*
158 |
159 | .vs
160 |
--------------------------------------------------------------------------------
/src/ShellProgressBar.Example/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 | using ShellProgressBar.Example.Examples;
7 | using ShellProgressBar.Example.TestCases;
8 |
9 | namespace ShellProgressBar.Example
10 | {
11 | class Program
12 | {
13 | private static readonly IList TestCases = new List
14 | {
15 | new PersistMessageExample(),
16 | new FixedDurationExample(),
17 | new DeeplyNestedProgressBarTreeExample(),
18 | new NestedProgressBarPerStepProgress(),
19 | new DrawsOnlyOnTickExample(),
20 | new ThreadedTicksOverflowExample(),
21 | new TicksOverflowExample(),
22 | new NegativeMaxTicksExample(),
23 | new ZeroMaxTicksExample(),
24 | new LongRunningExample(),
25 | new NeverCompletesExample(),
26 | new UpdatesMaxTicksExample(),
27 | new NeverTicksExample(),
28 | new EstimatedDurationExample(),
29 | new IndeterminateProgressExample(),
30 | new IndeterminateChildrenNoCollapseExample(),
31 | new AlternateFinishedColorExample()
32 | };
33 |
34 | private static readonly IList Examples = new List
35 | {
36 | new DontDisplayInRealTimeExample(),
37 | new StylingExample(),
38 | new ProgressBarOnBottomExample(),
39 | new ChildrenExample(),
40 | new ChildrenNoCollapseExample(),
41 | new IntegrationWithIProgressExample(),
42 | new IntegrationWithIProgressPercentageExample(),
43 | new MessageBeforeAndAfterExample(),
44 | new DeeplyNestedProgressBarTreeExample(),
45 | new EstimatedDurationExample(),
46 | new DownloadProgressExample(),
47 | new AlternateFinishedColorExample()
48 | };
49 |
50 | public static async Task Main(string[] args)
51 | {
52 | var cts = new CancellationTokenSource();
53 | Console.CancelKeyPress += (s, e) =>
54 | {
55 | cts.Cancel();
56 | };
57 |
58 | await MainAsync(args, cts.Token);
59 | }
60 |
61 | private static async Task MainAsync(string[] args, CancellationToken token)
62 | {
63 | var command = args.Length > 0 ? args[0] : "test";
64 | switch (command)
65 | {
66 | case "test":
67 | await RunTestCases(token);
68 | return;
69 | case "example":
70 | var nth = args.Length > 1 ? int.Parse(args[1]) : 0;
71 | await RunExample(nth, token);
72 | return;
73 | default:
74 | await Console.Error.WriteLineAsync($"Unknown command:{command}");
75 | return;
76 | }
77 | }
78 |
79 | private static async Task RunExample(int nth, CancellationToken token)
80 | {
81 | if (nth > Examples.Count - 1 || nth < 0)
82 | {
83 | await Console.Error.WriteLineAsync($"There are only {Examples.Count} examples, {nth} is not valid");
84 | }
85 |
86 | var example = Examples[nth];
87 |
88 | await example.Start(token);
89 | }
90 |
91 | private static async Task RunTestCases(CancellationToken token)
92 | {
93 | var i = 0;
94 | foreach (var example in TestCases)
95 | {
96 | if (i > 0) Console.Clear(); //not necessary but for demo/recording purposes.
97 | await example.Start(token);
98 | i++;
99 | }
100 | Console.Write("Shown all examples!");
101 | }
102 |
103 | public static void BusyWait(int milliseconds)
104 | {
105 | Thread.Sleep(milliseconds);
106 | }
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/ShellProgressBar/ProgressBarOptions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 |
4 | namespace ShellProgressBar
5 | {
6 | ///
7 | /// Control the behaviour of your progressbar
8 | ///
9 | public class ProgressBarOptions
10 | {
11 | private bool _enableTaskBarProgress;
12 | public static readonly ProgressBarOptions Default = new ProgressBarOptions();
13 |
14 | public static string ProgressMessageEncodingName { get; set; }
15 |
16 | public string MessageEncodingName
17 | {
18 | get
19 | {
20 | return ProgressMessageEncodingName;
21 | }
22 | set
23 | {
24 | ProgressMessageEncodingName = value;
25 | }
26 | }
27 |
28 | /// The foreground color of the progress bar, message and time
29 | public ConsoleColor ForegroundColor { get; set; } = ConsoleColor.Green;
30 |
31 | /// The foreground color the progressbar has reached a 100 percent
32 | public ConsoleColor? ForegroundColorDone { get; set; }
33 |
34 | ///
35 | /// The foreground color the progressbar when it has observed an error
36 | /// If set this takes priority over any other color as soon as an error is observed
37 | /// Use either or to
38 | /// put the progressbar in errored state
39 | ///
40 | public ConsoleColor? ForegroundColorError { get; set; }
41 |
42 | /// The background color of the remainder of the progressbar
43 | public ConsoleColor? BackgroundColor { get; set; }
44 |
45 | /// The character to use to draw the progressbar
46 | public char ProgressCharacter { get; set; } = '\u2588';
47 |
48 | ///
49 | /// The character to use for the background of the progress defaults to
50 | ///
51 | public char? BackgroundCharacter { get; set; }
52 |
53 | ///
54 | /// When true will redraw the progressbar using a timer, otherwise only update when
55 | /// is called.
56 | /// Defaults to true
57 | ///
58 | public bool DisplayTimeInRealTime { get; set; } = true;
59 |
60 | ///
61 | /// Collapse the progressbar when done, very useful for child progressbars
62 | /// Defaults to true
63 | ///
64 | public bool CollapseWhenFinished { get; set; } = false;
65 |
66 | ///
67 | /// By default the text and time information is displayed at the bottom and the progress bar at the top.
68 | /// This setting swaps their position
69 | ///
70 | public bool ProgressBarOnBottom { get; set; }
71 |
72 | ///
73 | /// Progressbar is written on a single line
74 | ///
75 | public bool DenseProgressBar { get; set; }
76 |
77 | ///
78 | /// Whether to show the estimated time. It can be set when
79 | /// is called or the property
80 | /// is set.
81 | ///
82 | public bool ShowEstimatedDuration { get; set; }
83 |
84 | ///
85 | /// Whether to show the percentage number
86 | ///
87 | public bool DisableBottomPercentage { get; set; } = false;
88 |
89 | /// Set percentage decimal format. By default is {0:N2}.
90 | public string PercentageFormat { get; set; } = "{0:N2}% ";
91 |
92 | ///
93 | /// Use Windows' task bar to display progress.
94 | ///
95 | ///
96 | /// This feature is available on the Windows platform.
97 | ///
98 | public bool EnableTaskBarProgress
99 | {
100 | get => _enableTaskBarProgress;
101 | set
102 | {
103 | if (value && !RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
104 | throw new NotSupportedException("Task bar progress only works on Windows");
105 |
106 | _enableTaskBarProgress = value;
107 | }
108 | }
109 |
110 | ///
111 | /// Take ownership of writing a message that is intended to be displayed above the progressbar.
112 | /// The delegate is expected to return the number of messages written to the console as a result of the string argument.
113 | /// Use case: pretty print or change the console colors, the progressbar will reset back
114 | ///
115 | public Func WriteQueuedMessage { get; set; }
116 |
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/ShellProgressBar/ProgressBarBase.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Concurrent;
3 | using System.Text;
4 | using System.Threading;
5 |
6 | namespace ShellProgressBar
7 | {
8 | public abstract class ProgressBarBase
9 | {
10 | static ProgressBarBase()
11 | {
12 | Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
13 | }
14 |
15 | protected readonly DateTime _startDate = DateTime.Now;
16 | private int _maxTicks;
17 | private int _currentTick;
18 | private string _message;
19 | private TimeSpan _estimatedDuration;
20 |
21 | protected ProgressBarBase(int maxTicks, string message, ProgressBarOptions options)
22 | {
23 | this._maxTicks = Math.Max(0, maxTicks);
24 | this._message = message;
25 | this.Options = options ?? ProgressBarOptions.Default;
26 | }
27 |
28 | internal ProgressBarOptions Options { get; }
29 | internal ConcurrentBag Children { get; } = new ConcurrentBag();
30 |
31 | protected abstract void DisplayProgress();
32 |
33 | protected virtual void Grow(ProgressBarHeight direction) { }
34 |
35 | protected virtual void OnDone() { }
36 |
37 | public DateTime? EndTime { get; protected set; }
38 |
39 | public bool ObservedError { get; set; }
40 |
41 | private ConsoleColor? _dynamicForegroundColor = null;
42 | public ConsoleColor ForegroundColor
43 | {
44 | get
45 | {
46 | var realColor = _dynamicForegroundColor ?? this.Options.ForegroundColor;
47 | if (this.ObservedError && this.Options.ForegroundColorError.HasValue)
48 | return this.Options.ForegroundColorError.Value;
49 |
50 | return EndTime.HasValue
51 | ? this.Options.ForegroundColorDone ?? realColor
52 | : realColor;
53 | }
54 | set => _dynamicForegroundColor = value;
55 | }
56 |
57 | public int CurrentTick => _currentTick;
58 |
59 | public int MaxTicks
60 | {
61 | get => _maxTicks;
62 | set
63 | {
64 | Interlocked.Exchange(ref _maxTicks, value);
65 | DisplayProgress();
66 | }
67 | }
68 |
69 | public string Message
70 | {
71 | get => _message;
72 | set
73 | {
74 | Interlocked.Exchange(ref _message, value);
75 | DisplayProgress();
76 | }
77 | }
78 |
79 | public TimeSpan EstimatedDuration
80 | {
81 | get => _estimatedDuration;
82 | set
83 | {
84 | _estimatedDuration = value;
85 | DisplayProgress();
86 | }
87 | }
88 |
89 | public double Percentage
90 | {
91 | get
92 | {
93 | var percentage = Math.Max(0, Math.Min(100, (100.0 / this._maxTicks) * this._currentTick));
94 | // Gracefully handle if the percentage is NaN due to division by 0
95 | if (double.IsNaN(percentage) || percentage < 0) percentage = 100;
96 | return percentage;
97 | }
98 | }
99 |
100 | public bool Collapse => this.EndTime.HasValue && this.Options.CollapseWhenFinished;
101 |
102 | public ChildProgressBar Spawn(int maxTicks, string message, ProgressBarOptions options = null)
103 | {
104 | // if this bar collapses all child progressbar will collapse
105 | if (options?.CollapseWhenFinished == false && this.Options.CollapseWhenFinished)
106 | options.CollapseWhenFinished = true;
107 |
108 | var pbar = new ChildProgressBar(
109 | maxTicks, message, DisplayProgress, WriteLine, WriteErrorLine, options ?? this.Options, d => this.Grow(d)
110 | );
111 | this.Children.Add(pbar);
112 | DisplayProgress();
113 | return pbar;
114 | }
115 |
116 | public IndeterminateChildProgressBar SpawnIndeterminate(string message, ProgressBarOptions options = null)
117 | {
118 | // if this bar collapses all child progressbar will collapse
119 | if (options?.CollapseWhenFinished == false && this.Options.CollapseWhenFinished)
120 | options.CollapseWhenFinished = true;
121 |
122 | var pbar = new IndeterminateChildProgressBar(
123 | message, DisplayProgress, WriteLine, WriteErrorLine, options ?? this.Options, d => this.Grow(d)
124 | );
125 | this.Children.Add(pbar);
126 | DisplayProgress();
127 | return pbar;
128 | }
129 |
130 | public abstract void WriteLine(string message);
131 | public abstract void WriteErrorLine(string message);
132 |
133 |
134 | public void Tick(string message = null)
135 | {
136 | Interlocked.Increment(ref _currentTick);
137 | FinishTick(message);
138 | }
139 |
140 | public void Tick(int newTickCount, string message = null)
141 | {
142 | Interlocked.Exchange(ref _currentTick, newTickCount);
143 | FinishTick(message);
144 | }
145 |
146 | public void Tick(TimeSpan estimatedDuration, string message = null)
147 | {
148 | Interlocked.Increment(ref _currentTick);
149 | _estimatedDuration = estimatedDuration;
150 |
151 | FinishTick(message);
152 | }
153 | public void Tick(int newTickCount, TimeSpan estimatedDuration, string message = null)
154 | {
155 | Interlocked.Exchange(ref _currentTick, newTickCount);
156 | _estimatedDuration = estimatedDuration;
157 |
158 | FinishTick(message);
159 | }
160 |
161 | private void FinishTick(string message)
162 | {
163 | if (message != null)
164 | Interlocked.Exchange(ref _message, message);
165 |
166 | if (_currentTick >= _maxTicks)
167 | {
168 | this.EndTime = DateTime.Now;
169 | this.OnDone();
170 | }
171 | DisplayProgress();
172 | }
173 |
174 | protected static string GetDurationString(TimeSpan duration)
175 | {
176 | if (duration.Days > 0)
177 | {
178 | return $"{duration.Days}D {duration.Hours:00}:{duration.Minutes:00}:{duration.Seconds:00}";
179 | }
180 | return $"{duration.Hours:00}:{duration.Minutes:00}:{duration.Seconds:00}";
181 | }
182 | }
183 | }
184 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ShellProgressBar
2 | ===================
3 | visualize (concurrent) progress in your console application
4 |
5 | This is a great little library to visualize long running command line tasks.
6 |
7 | .NET Core ready!
8 |
9 | It also supports spawning child progress bars which allows you to visualize dependencies and concurrency rather nicely.
10 |
11 | Tested on OSX
12 |
13 | 
14 |
15 | and Windows
16 |
17 | 
18 |
19 | (Powershell works too, see example further down)
20 |
21 | # Install
22 |
23 | Get it on nuget: http://www.nuget.org/packages/ShellProgressBar/
24 |
25 | # Usage
26 |
27 | Usage is really straightforward
28 |
29 | ```csharp
30 | const int totalTicks = 10;
31 | var options = new ProgressBarOptions
32 | {
33 | ProgressCharacter = '─',
34 | ProgressBarOnBottom = true
35 | };
36 | using (var pbar = new ProgressBar(totalTicks, "Initial message", options))
37 | {
38 | pbar.Tick(); //will advance pbar to 1 out of 10.
39 | //we can also advance and update the progressbar text
40 | pbar.Tick("Step 2 of 10");
41 | }
42 | ```
43 |
44 | ## Reporting progression
45 |
46 | There are two ways to report progression. You can use the `Tick()` function as described above. Alternatively you can report progression through an [`IProgress`](https://docs.microsoft.com/en-us/dotnet/api/system.iprogress-1) instance that you obtain by calling `AsProgress()` on the progress bar object.
47 |
48 | For a simple case where the progress type is a `float` value between 0.0 and 1.0 that represents the completion percentage, use `progressBar.AsProgress()`:
49 |
50 | ```csharp
51 | using ProgressBar progressBar = new ProgressBar(10000, "My Progress Message");
52 | IProgress progress = progressBar.AsProgress();
53 | progress.Report(0.25); // Advances the progress bar to 25%
54 | ```
55 |
56 | See `IntegrationWithIProgressExample.cs` and `IntegrationWithIProgressPercentageExample.cs` in the [src/ShellProgressBar.Example/Examples](src/ShellProgressBar.Example/Examples) directory for full examples.
57 |
58 | ## Options
59 |
60 | ### Progress bar position
61 |
62 | ```csharp
63 | const int totalTicks = 10;
64 | var options = new ProgressBarOptions
65 | {
66 | ProgressCharacter = '─',
67 | ProgressBarOnBottom = true
68 | };
69 | using (var pbar = new ProgressBar(totalTicks, "progress bar is on the bottom now", options))
70 | {
71 | TickToCompletion(pbar, totalTicks, sleep: 500);
72 | }
73 | ```
74 |
75 | By default the progress bar is at the top and the message at the bottom.
76 | This can be flipped around if so desired.
77 |
78 | 
79 |
80 | ### Styling changes
81 |
82 | ```csharp
83 | const int totalTicks = 10;
84 | var options = new ProgressBarOptions
85 | {
86 | ForegroundColor = ConsoleColor.Yellow,
87 | ForegroundColorDone = ConsoleColor.DarkGreen,
88 | BackgroundColor = ConsoleColor.DarkGray,
89 | BackgroundCharacter = '\u2593'
90 | };
91 | using (var pbar = new ProgressBar(totalTicks, "showing off styling", options))
92 | {
93 | TickToCompletion(pbar, totalTicks, sleep: 500);
94 | }
95 | ```
96 |
97 | Many aspects can be styled including foreground color, background (inactive portion)
98 | and changing the color on completion.
99 |
100 | 
101 |
102 |
103 | ### No real time update
104 |
105 | By default a timer will draw the screen every 500ms. You can configure the progressbar
106 | to only be drawn when `.Tick()` is called.
107 |
108 | ```csharp
109 | const int totalTicks = 5;
110 | var options = new ProgressBarOptions
111 | {
112 | DisplayTimeInRealTime = false
113 | };
114 | using (var pbar = new ProgressBar(totalTicks, "only draw progress on tick", options))
115 | {
116 | TickToCompletion(pbar, totalTicks, sleep:1750);
117 | }
118 | ```
119 |
120 | If you look at the time passed you will see it skips `02:00`
121 |
122 |
123 | 
124 |
125 | ### Descendant progressbars
126 |
127 | A progressbar can spawn child progress bars and each child can spawn
128 | its own progressbars. Each child can have its own styling options.
129 |
130 | This is great to visualize concurrent running tasks.
131 |
132 | ```csharp
133 | const int totalTicks = 10;
134 | var options = new ProgressBarOptions
135 | {
136 | ForegroundColor = ConsoleColor.Yellow,
137 | BackgroundColor = ConsoleColor.DarkYellow,
138 | ProgressCharacter = '─'
139 | };
140 | var childOptions = new ProgressBarOptions
141 | {
142 | ForegroundColor = ConsoleColor.Green,
143 | BackgroundColor = ConsoleColor.DarkGreen,
144 | ProgressCharacter = '─'
145 | };
146 | using (var pbar = new ProgressBar(totalTicks, "main progressbar", options))
147 | {
148 | TickToCompletion(pbar, totalTicks, sleep: 10, childAction: () =>
149 | {
150 | using (var child = pbar.Spawn(totalTicks, "child actions", childOptions))
151 | {
152 | TickToCompletion(child, totalTicks, sleep: 100);
153 | }
154 | });
155 | }
156 | ```
157 |
158 | 
159 |
160 | By default children will collapse when done, making room for new/concurrent progressbars.
161 |
162 | You can keep them around by specifying `CollapseWhenFinished = false`
163 |
164 | ```csharp
165 | var childOptions = new ProgressBarOptions
166 | {
167 | ForegroundColor = ConsoleColor.Green,
168 | BackgroundColor = ConsoleColor.DarkGreen,
169 | ProgressCharacter = '─',
170 | CollapseWhenFinished = false
171 | };
172 | ```
173 |
174 | 
175 |
176 |
177 | # FixedDurationBar
178 |
179 | `ProgressBar` is great for visualizing tasks with an unknown runtime. If you have a task that you know takes a fixed amount of time there is also a `FixedDurationBar` subclass.
180 | `FixedDurationBar` will `Tick()` automatically but other then that all the options and usage are the same. Except it relies on the real time update feature so disabling that
181 | will throw.
182 |
183 | `FixedDurationBar` exposes an `IsCompleted` and `CompletedHandle`
184 |
185 |
186 | ### Credits
187 |
188 | The initial implementation was inspired by this article.
189 | http://www.bytechaser.com/en/articles/ckcwh8nsyt/display-progress-bar-in-console-application-in-c.aspx
190 |
191 | And obviously anyone who sends a PR to this repository :+1:
192 |
--------------------------------------------------------------------------------
/src/.build/MSBuild.Community.Tasks.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | $(MSBuildExtensionsPath)\MSBuildCommunityTasks
7 | $([MSBUILD]::Unescape($(MSBuildCommunityTasksPath)\MSBuild.Community.Tasks.dll))
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 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
--------------------------------------------------------------------------------
/src/ShellProgressBar/ProgressBar.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Concurrent;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Runtime.CompilerServices;
6 | using System.Runtime.InteropServices;
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 |
10 | namespace ShellProgressBar
11 | {
12 | public class ProgressBar : ProgressBarBase, IProgressBar
13 | {
14 | private static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
15 |
16 | private readonly ConsoleColor _originalColor;
17 | private readonly Func _writeMessageToConsole;
18 | private readonly int _originalWindowTop;
19 | private readonly int _originalWindowHeight;
20 | private readonly bool _startedRedirected;
21 | private int _originalCursorTop;
22 | private int _isDisposed;
23 |
24 | private Timer _timer;
25 | private int _visibleDescendants = 0;
26 | private readonly AutoResetEvent _displayProgressEvent;
27 | private readonly Task _displayProgress;
28 |
29 | public ProgressBar(int maxTicks, string message, ConsoleColor color)
30 | : this(maxTicks, message, new ProgressBarOptions {ForegroundColor = color})
31 | {
32 | }
33 |
34 | public ProgressBar(int maxTicks, string message, ProgressBarOptions options = null)
35 | : base(maxTicks, message, options)
36 | {
37 |
38 | _writeMessageToConsole = this.Options.WriteQueuedMessage ?? DefaultConsoleWrite;
39 | _startedRedirected = Console.IsOutputRedirected;
40 |
41 | try
42 | {
43 | _originalCursorTop = Console.CursorTop;
44 | _originalWindowTop = Console.WindowTop;
45 | _originalWindowHeight = Console.WindowHeight + _originalWindowTop;
46 | _originalColor = Console.ForegroundColor;
47 | }
48 | catch
49 | {
50 | _startedRedirected = true;
51 | }
52 |
53 | if (!_startedRedirected)
54 | Console.CursorVisible = false;
55 |
56 | if (this.Options.EnableTaskBarProgress)
57 | TaskbarProgress.SetState(TaskbarProgress.TaskbarStates.Normal);
58 |
59 | if (this.Options.DisplayTimeInRealTime)
60 | _timer = new Timer((s) => OnTimerTick(), null, 500, 500);
61 | else //draw once
62 | _timer = new Timer((s) =>
63 | {
64 | _timer.Dispose();
65 | DisplayProgress();
66 | }, null, 0, 1000);
67 |
68 | _displayProgressEvent = new AutoResetEvent(false);
69 | _displayProgress = Task.Run(() =>
70 | {
71 | while (_isDisposed == 0)
72 | {
73 | if (!_displayProgressEvent.WaitOne(TimeSpan.FromSeconds(10)))
74 | continue;
75 | if (_isDisposed > 0) return;
76 | try
77 | {
78 | UpdateProgress();
79 | }
80 | catch
81 | {
82 | //don't want to crash background thread
83 | }
84 | }
85 | });
86 | }
87 |
88 | protected virtual void OnTimerTick() => DisplayProgress();
89 |
90 | protected override void Grow(ProgressBarHeight direction)
91 | {
92 | switch (direction)
93 | {
94 | case ProgressBarHeight.Increment:
95 | Interlocked.Increment(ref _visibleDescendants);
96 | break;
97 | case ProgressBarHeight.Decrement:
98 | Interlocked.Decrement(ref _visibleDescendants);
99 | break;
100 | }
101 | }
102 |
103 | private void EnsureMainProgressBarVisible(int extraBars = 0)
104 | {
105 | var pbarHeight = this.Options.DenseProgressBar ? 1 : 2;
106 | var neededPadding = Math.Min(_originalWindowHeight - pbarHeight, (1 + extraBars) * pbarHeight);
107 | var difference = _originalWindowHeight - _originalCursorTop;
108 | var write = difference <= neededPadding ? Math.Max(0, Math.Max(neededPadding, difference)) : 0;
109 |
110 | var written = 0;
111 | for (; written < write; written++)
112 | Console.WriteLine();
113 | if (written == 0) return;
114 |
115 | Console.CursorTop = _originalWindowHeight - (written);
116 | _originalCursorTop = Console.CursorTop - 1;
117 | }
118 |
119 | private void GrowDrawingAreaBasedOnChildren() => EnsureMainProgressBarVisible(_visibleDescendants);
120 |
121 | private struct Indentation
122 | {
123 | public Indentation(ConsoleColor color, bool lastChild)
124 | {
125 | this.ConsoleColor = color;
126 | this.LastChild = lastChild;
127 | }
128 |
129 | public string Glyph => !LastChild ? "├─" : "└─";
130 |
131 | public readonly ConsoleColor ConsoleColor;
132 | public readonly bool LastChild;
133 | }
134 |
135 | private static void CondensedProgressBar(
136 | double percentage,
137 | string message,
138 | char progressCharacter,
139 | char? progressBackgroundCharacter,
140 | ConsoleColor? backgroundColor,
141 | Indentation[] indentation,
142 | bool progressBarOnTop)
143 | {
144 | var depth = indentation.Length;
145 | var messageWidth = 30;
146 | var maxCharacterWidth = Console.WindowWidth - (depth * 2) + 2;
147 | var truncatedMessage = StringExtensions.Excerpt(message, messageWidth - 2) + " ";
148 | var width = (Console.WindowWidth - (depth * 2) + 2) - truncatedMessage.Length;
149 |
150 | if (!string.IsNullOrWhiteSpace(ProgressBarOptions.ProgressMessageEncodingName))
151 | {
152 | width = width + message.Length - System.Text.Encoding.GetEncoding(ProgressBarOptions.ProgressMessageEncodingName).GetBytes(message).Length;
153 | }
154 |
155 | var newWidth = (int) ((width * percentage) / 100d);
156 | var progBar = new string(progressCharacter, newWidth);
157 | DrawBottomHalfPrefix(indentation, depth);
158 | Console.Write(truncatedMessage);
159 | Console.Write(progBar);
160 | if (backgroundColor.HasValue)
161 | {
162 | Console.ForegroundColor = backgroundColor.Value;
163 | Console.Write(new string(progressBackgroundCharacter ?? progressCharacter, width - newWidth));
164 | }
165 | else Console.Write(new string(' ', width - newWidth));
166 |
167 | Console.ForegroundColor = indentation[depth - 1].ConsoleColor;
168 | }
169 |
170 |
171 | private static void ProgressBarBottomHalf(double percentage, DateTime startDate, DateTime? endDate,
172 | string message, Indentation[] indentation, bool progressBarOnBottom, bool showEstimatedDuration,
173 | TimeSpan estimatedDuration, bool disableBottomPercentage, string percentageFormat)
174 | {
175 | var depth = indentation.Length;
176 | var maxCharacterWidth = Console.WindowWidth - (depth * 2) + 2;
177 | var duration = ((endDate ?? DateTime.Now) - startDate);
178 | var durationString = GetDurationString(duration);
179 |
180 | if (showEstimatedDuration)
181 | durationString +=
182 | $" / {GetDurationString(estimatedDuration)}";
183 |
184 | var column1Width = Console.WindowWidth - durationString.Length - (depth * 2) + 2;
185 | var column2Width = durationString.Length;
186 |
187 | if (!string.IsNullOrWhiteSpace(ProgressBarOptions.ProgressMessageEncodingName))
188 | {
189 | column1Width = column1Width + message.Length - System.Text.Encoding.GetEncoding(ProgressBarOptions.ProgressMessageEncodingName).GetBytes(message).Length;
190 | }
191 |
192 | if (progressBarOnBottom)
193 | DrawTopHalfPrefix(indentation, depth);
194 | else
195 | DrawBottomHalfPrefix(indentation, depth);
196 |
197 | var format = $"{{0, -{column1Width}}}{{1,{column2Width}}}";
198 | var percentageFormatedString = string.Format(percentageFormat, percentage);
199 | var truncatedMessage = StringExtensions.Excerpt(percentageFormatedString + message, column1Width);
200 |
201 | if (disableBottomPercentage)
202 | {
203 | truncatedMessage = StringExtensions.Excerpt(message, column1Width);
204 | }
205 |
206 | var formatted = string.Format(format, truncatedMessage, durationString);
207 | var m = formatted + new string(' ', Math.Max(0, maxCharacterWidth - formatted.Length));
208 | Console.Write(m);
209 | }
210 |
211 | private static void DrawBottomHalfPrefix(Indentation[] indentation, int depth)
212 | {
213 | for (var i = 1; i < depth; i++)
214 | {
215 | var ind = indentation[i];
216 | Console.ForegroundColor = indentation[i - 1].ConsoleColor;
217 | if (!ind.LastChild)
218 | Console.Write(i == (depth - 1) ? ind.Glyph : "│ ");
219 | else
220 | Console.Write(i == (depth - 1) ? ind.Glyph : " ");
221 | }
222 |
223 | Console.ForegroundColor = indentation[depth - 1].ConsoleColor;
224 | }
225 |
226 | private static void ProgressBarTopHalf(
227 | double percentage,
228 | char progressCharacter,
229 | char? progressBackgroundCharacter,
230 | ConsoleColor? backgroundColor,
231 | Indentation[] indentation, bool progressBarOnTop)
232 | {
233 | var depth = indentation.Length;
234 | var width = Console.WindowWidth - (depth * 2) + 2;
235 |
236 | if (progressBarOnTop)
237 | DrawBottomHalfPrefix(indentation, depth);
238 | else
239 | DrawTopHalfPrefix(indentation, depth);
240 |
241 | var newWidth = (int) ((width * percentage) / 100d);
242 | var progBar = new string(progressCharacter, newWidth);
243 | Console.Write(progBar);
244 | if (backgroundColor.HasValue)
245 | {
246 | Console.ForegroundColor = backgroundColor.Value;
247 | Console.Write(new string(progressBackgroundCharacter ?? progressCharacter, width - newWidth));
248 | }
249 | else Console.Write(new string(' ', width - newWidth));
250 |
251 | Console.ForegroundColor = indentation[depth - 1].ConsoleColor;
252 | }
253 |
254 | private static void DrawTopHalfPrefix(Indentation[] indentation, int depth)
255 | {
256 | for (var i = 1; i < depth; i++)
257 | {
258 | var ind = indentation[i];
259 | Console.ForegroundColor = indentation[i - 1].ConsoleColor;
260 | if (ind.LastChild && i != (depth - 1))
261 | Console.Write(" ");
262 | else
263 | Console.Write("│ ");
264 | }
265 |
266 | Console.ForegroundColor = indentation[depth - 1].ConsoleColor;
267 | }
268 |
269 | protected override void DisplayProgress() => _displayProgressEvent.Set();
270 |
271 | private readonly ConcurrentQueue _stickyMessages = new ConcurrentQueue();
272 |
273 | public override void WriteLine(string message)
274 | {
275 | _stickyMessages.Enqueue(new ConsoleOutLine(message));
276 | DisplayProgress();
277 | }
278 | public override void WriteErrorLine(string message)
279 | {
280 | this.ObservedError = true;
281 | _stickyMessages.Enqueue(new ConsoleOutLine(message, error: true));
282 | DisplayProgress();
283 | }
284 |
285 | private void UpdateProgress()
286 | {
287 | var mainPercentage = this.Percentage;
288 |
289 | if (this.Options.EnableTaskBarProgress)
290 | TaskbarProgress.SetValue(mainPercentage, 100);
291 |
292 | // write queued console messages, displayprogress is signaled straight after but
293 | // just in case make sure we never write more then 5 in a display progress tick
294 | for (var i = 0; i < 5 && _stickyMessages.TryDequeue(out var m); i++)
295 | WriteConsoleLine(m);
296 |
297 | if (_startedRedirected) return;
298 |
299 | Console.CursorVisible = false;
300 | Console.ForegroundColor = this.ForegroundColor;
301 |
302 | GrowDrawingAreaBasedOnChildren();
303 | var cursorTop = _originalCursorTop;
304 | var indentation = new[] {new Indentation(this.ForegroundColor, true)};
305 |
306 | void TopHalf()
307 | {
308 | ProgressBarTopHalf(mainPercentage,
309 | this.Options.ProgressCharacter,
310 | this.Options.BackgroundCharacter,
311 | this.Options.BackgroundColor,
312 | indentation,
313 | this.Options.ProgressBarOnBottom
314 | );
315 | }
316 |
317 | if (this.Options.DenseProgressBar)
318 | {
319 | CondensedProgressBar(mainPercentage,
320 | this.Message,
321 | this.Options.ProgressCharacter,
322 | this.Options.BackgroundCharacter,
323 | this.Options.BackgroundColor,
324 | indentation,
325 | this.Options.ProgressBarOnBottom
326 | );
327 |
328 | }
329 | else if (this.Options.ProgressBarOnBottom)
330 | {
331 | ProgressBarBottomHalf(mainPercentage, this._startDate, null, this.Message, indentation,
332 | this.Options.ProgressBarOnBottom, Options.ShowEstimatedDuration, EstimatedDuration, this.Options.DisableBottomPercentage,
333 | Options.PercentageFormat);
334 | Console.SetCursorPosition(0, ++cursorTop);
335 | TopHalf();
336 | }
337 | else
338 | {
339 | TopHalf();
340 | Console.SetCursorPosition(0, ++cursorTop);
341 | ProgressBarBottomHalf(mainPercentage, this._startDate, null, this.Message, indentation,
342 | this.Options.ProgressBarOnBottom, Options.ShowEstimatedDuration, EstimatedDuration, this.Options.DisableBottomPercentage,
343 | Options.PercentageFormat);
344 | }
345 |
346 | DrawChildren(this.Children, indentation, ref cursorTop, Options.PercentageFormat);
347 |
348 | ResetToBottom(ref cursorTop);
349 |
350 | Console.SetCursorPosition(0, _originalCursorTop);
351 | Console.ForegroundColor = _originalColor;
352 |
353 | if (!(mainPercentage >= 100)) return;
354 | _timer?.Dispose();
355 | _timer = null;
356 | }
357 |
358 | private void WriteConsoleLine(ConsoleOutLine m)
359 | {
360 | var resetString = new string(' ', Console.WindowWidth);
361 | Console.Write(resetString);
362 | Console.Write("\r");
363 | var foreground = Console.ForegroundColor;
364 | var background = Console.BackgroundColor;
365 | var written = _writeMessageToConsole(m);
366 | Console.ForegroundColor = foreground;
367 | Console.BackgroundColor = background;
368 | _originalCursorTop += written;
369 | }
370 |
371 | private static int DefaultConsoleWrite(ConsoleOutLine line)
372 | {
373 | if (line.Error) Console.Error.WriteLine(line.Line);
374 | else Console.WriteLine(line.Line);
375 | return 1;
376 | }
377 |
378 | private void ResetToBottom(ref int cursorTop)
379 | {
380 | var resetString = new string(' ', Console.WindowWidth);
381 | var windowHeight = _originalWindowHeight;
382 | if (cursorTop >= (windowHeight - 1)) return;
383 | do
384 | {
385 | Console.Write(resetString);
386 | } while (++cursorTop < (windowHeight - 1));
387 | }
388 |
389 | private static void DrawChildren(IEnumerable children, Indentation[] indentation,
390 | ref int cursorTop, string percentageFormat)
391 | {
392 | var view = children.Where(c => !c.Collapse).Select((c, i) => new {c, i}).ToList();
393 | if (!view.Any()) return;
394 |
395 | var windowHeight = Console.WindowHeight;
396 | var lastChild = view.Max(t => t.i);
397 | foreach (var tuple in view)
398 | {
399 | //Dont bother drawing children that would fall off the screen
400 | if (cursorTop >= (windowHeight - 2))
401 | return;
402 |
403 | var child = tuple.c;
404 | var currentIndentation = new Indentation(child.ForegroundColor, tuple.i == lastChild);
405 | var childIndentation = NewIndentation(indentation, currentIndentation);
406 |
407 | var percentage = child.Percentage;
408 | Console.ForegroundColor = child.ForegroundColor;
409 |
410 | void TopHalf()
411 | {
412 | ProgressBarTopHalf(percentage,
413 | child.Options.ProgressCharacter,
414 | child.Options.BackgroundCharacter,
415 | child.Options.BackgroundColor,
416 | childIndentation,
417 | child.Options.ProgressBarOnBottom
418 | );
419 | }
420 |
421 | Console.SetCursorPosition(0, ++cursorTop);
422 |
423 | if (child.Options.DenseProgressBar)
424 | {
425 | CondensedProgressBar(percentage,
426 | child.Message,
427 | child.Options.ProgressCharacter,
428 | child.Options.BackgroundCharacter,
429 | child.Options.BackgroundColor,
430 | childIndentation,
431 | child.Options.ProgressBarOnBottom
432 | );
433 | }
434 | else if (child.Options.ProgressBarOnBottom)
435 | {
436 | ProgressBarBottomHalf(percentage, child.StartDate, child.EndTime, child.Message, childIndentation,
437 | child.Options.ProgressBarOnBottom, child.Options.ShowEstimatedDuration,
438 | child.EstimatedDuration, child.Options.DisableBottomPercentage,
439 | percentageFormat);
440 | Console.SetCursorPosition(0, ++cursorTop);
441 | TopHalf();
442 | }
443 | else
444 | {
445 | TopHalf();
446 | Console.SetCursorPosition(0, ++cursorTop);
447 | ProgressBarBottomHalf(percentage, child.StartDate, child.EndTime, child.Message, childIndentation,
448 | child.Options.ProgressBarOnBottom, child.Options.ShowEstimatedDuration,
449 | child.EstimatedDuration, child.Options.DisableBottomPercentage,
450 | percentageFormat);
451 | }
452 |
453 | DrawChildren(child.Children, childIndentation, ref cursorTop, percentageFormat);
454 | }
455 | }
456 |
457 | private static Indentation[] NewIndentation(Indentation[] array, Indentation append)
458 | {
459 | var result = new Indentation[array.Length + 1];
460 | Array.Copy(array, result, array.Length);
461 | result[array.Length] = append;
462 | return result;
463 | }
464 |
465 | public void Dispose()
466 | {
467 | if (Interlocked.CompareExchange(ref _isDisposed, 1, 0) != 0)
468 | return;
469 |
470 | _timer?.Dispose();
471 | _timer = null;
472 |
473 | // make sure background task is stopped before we clean up
474 | _displayProgressEvent.Set();
475 | _displayProgress.Wait();
476 |
477 | // update one last time - needed because background task might have
478 | // been already in progress before Dispose was called and it might
479 | // have been running for a very long time due to poor performance
480 | // of System.Console
481 | UpdateProgress();
482 |
483 | //make sure we pop all pending messages
484 | while (_stickyMessages.TryDequeue(out var m))
485 | WriteConsoleLine(m);
486 |
487 | if (this.EndTime == null)
488 | this.EndTime = DateTime.Now;
489 |
490 | if (this.Options.EnableTaskBarProgress)
491 | TaskbarProgress.SetState(TaskbarProgress.TaskbarStates.NoProgress);
492 |
493 | try
494 | {
495 | foreach (var c in this.Children) c.Dispose();
496 | }
497 | catch { }
498 |
499 | try
500 | {
501 | var pbarHeight = this.Options.DenseProgressBar ? 1 : 2;
502 | var openDescendantsPadding = (_visibleDescendants * pbarHeight);
503 | var newCursorTop = Math.Min(_originalWindowHeight, _originalCursorTop + pbarHeight + openDescendantsPadding);
504 | Console.CursorVisible = true;
505 | Console.SetCursorPosition(0, newCursorTop);
506 | }
507 | //This is bad and I should feel bad, but i rather eat pbar exceptions in production then causing false negatives
508 | catch { }
509 | }
510 |
511 | public IProgress AsProgress(Func message = null, Func percentage = null)
512 | {
513 | return new Progress(this, message, percentage);
514 | }
515 | }
516 | }
517 |
--------------------------------------------------------------------------------