Giter Site home page Giter Site logo

ProgressBar.WriteLine does not work correctly on multiline writes or when lines are longer then ConsoleWidth about shellprogressbar HOT 3 OPEN

EddyToo avatar EddyToo commented on August 13, 2024
ProgressBar.WriteLine does not work correctly on multiline writes or when lines are longer then ConsoleWidth

from shellprogressbar.

Comments (3)

EddyToo avatar EddyToo commented on August 13, 2024

Suggested patch to resolve this issue and #79

diff --git a/src/ShellProgressBar.Example/Examples/MessageBeforeAndAfterExample.cs b/src/ShellProgressBar.Example/Examples/MessageBeforeAndAfterExample.cs
index 4280eef..eaaf98e 100644
--- a/src/ShellProgressBar.Example/Examples/MessageBeforeAndAfterExample.cs
+++ b/src/ShellProgressBar.Example/Examples/MessageBeforeAndAfterExample.cs
@@ -8,7 +8,7 @@ namespace ShellProgressBar.Example.Examples
 		protected override Task StartAsync()
 		{
 			Console.WriteLine("This should not be overwritten");
-			const int totalTicks = 10;
+			int totalTicks = Console.WindowHeight;
 			var options = new ProgressBarOptions
 			{
 				ForegroundColor = ConsoleColor.Yellow,
@@ -18,9 +18,27 @@ namespace ShellProgressBar.Example.Examples
 			};
 			using (var pbar = new ProgressBar(totalTicks, "showing off styling", options))
 			{
-				TickToCompletion(pbar, totalTicks, sleep: 500, i =>
+				TickToCompletion(pbar, totalTicks, sleep: 250, i =>
 				{
-					pbar.WriteErrorLine($"This should appear above:{i}");
+					if (i % 5 == 0)
+					{
+						// Single line
+						pbar.WriteErrorLine($"[{i}] This{Environment.NewLine}[{i}] is{Environment.NewLine}[{i}] over{Environment.NewLine}[{i}] 4 lines");
+						return;
+					}
+					if (i % 4 == 0)
+					{
+						// Single line
+						pbar.WriteErrorLine($"[{i}] This has{Environment.NewLine}[{i}] 2 lines.");
+						return;
+					}
+					if (i % 3 == 0)
+					{
+						// Single line
+						pbar.WriteErrorLine($"[{i}] This is a very long line {new string('.', Console.BufferWidth)} and should be split over 2 lines");
+						return;
+					}
+					pbar.WriteErrorLine($"[{i}] This should appear above");
 				});
 			}
 
diff --git a/src/ShellProgressBar.Example/Program.cs b/src/ShellProgressBar.Example/Program.cs
index b947538..0acc86e 100644
--- a/src/ShellProgressBar.Example/Program.cs
+++ b/src/ShellProgressBar.Example/Program.cs
@@ -66,6 +66,9 @@ namespace ShellProgressBar.Example
 				case "test":
 					await RunTestCases(token);
 					return;
+				case "scrolltest":
+					await RunTestCases(token, Console.WindowHeight+5);
+					return;
 				case "example":
 					var nth = args.Length > 1 ? int.Parse(args[1]) : 0;
 					await RunExample(nth, token);
@@ -88,12 +91,16 @@ namespace ShellProgressBar.Example
 			await example.Start(token);
 		}
 
-		private static async Task RunTestCases(CancellationToken token)
+		private static async Task RunTestCases(CancellationToken token, int writeNumOfRowBefore = 0)
 		{
 			var i = 0;
 			foreach (var example in TestCases)
 			{
 				if (i > 0) Console.Clear(); //not necessary but for demo/recording purposes.
+
+				for (int r = 0; r< writeNumOfRowBefore; r++)
+					Console.WriteLine($"Writing output before test. Row {r+1}/{writeNumOfRowBefore}");
+
 				await example.Start(token);
 				i++;
 			}
diff --git a/src/ShellProgressBar/ProgressBar.cs b/src/ShellProgressBar/ProgressBar.cs
index a76e525..9208fa8 100644
--- a/src/ShellProgressBar/ProgressBar.cs
+++ b/src/ShellProgressBar/ProgressBar.cs
@@ -2,7 +2,6 @@
 using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.Linq;
-using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
 using System.Threading;
 using System.Threading.Tasks;
@@ -15,11 +14,10 @@ namespace ShellProgressBar
 
 		private readonly ConsoleColor _originalColor;
 		private readonly Func<ConsoleOutLine, int> _writeMessageToConsole;
-		private readonly int _originalWindowTop;
-		private readonly int _originalWindowHeight;
 		private readonly bool _startedRedirected;
 		private int _originalCursorTop;
 		private int _isDisposed;
+		private int _lastDrawBottomPos;
 
 		private Timer _timer;
 		private int _visibleDescendants = 0;
@@ -41,8 +39,6 @@ namespace ShellProgressBar
 			try
 			{
 				_originalCursorTop = Console.CursorTop;
-				_originalWindowTop = Console.WindowTop;
-				_originalWindowHeight = Console.WindowHeight + _originalWindowTop;
 				_originalColor = Console.ForegroundColor;
 			}
 			catch
@@ -56,7 +52,7 @@ namespace ShellProgressBar
 			if (this.Options.EnableTaskBarProgress)
 				TaskbarProgress.SetState(TaskbarProgress.TaskbarStates.Normal);
 
-			if (this.Options.DisplayTimeInRealTime)
+			if (this.Options.DisplayTimeInRealTime) 
 				_timer = new Timer((s) => OnTimerTick(), null, 500, 500);
 			else //draw once
 				_timer = new Timer((s) =>
@@ -102,18 +98,22 @@ namespace ShellProgressBar
 
 		private void EnsureMainProgressBarVisible(int extraBars = 0)
 		{
+			var lastVisibleRow = Console.WindowHeight + Console.WindowTop;
+
 			var pbarHeight = this.Options.DenseProgressBar ? 1 : 2;
-			var neededPadding = Math.Min(_originalWindowHeight - pbarHeight, (1 + extraBars) * pbarHeight);
-			var difference = _originalWindowHeight - _originalCursorTop;
-			var write = difference <= neededPadding ? Math.Max(0, Math.Max(neededPadding, difference)) : 0;
+			var neededPadding = Math.Min(lastVisibleRow - pbarHeight, (1 + extraBars) * pbarHeight);
+			var difference = lastVisibleRow - _originalCursorTop;
+			var write = difference <= neededPadding ? Math.Min(Console.WindowHeight, Math.Max(0, Math.Max(neededPadding, difference))) : 0;
+
+			if (write == 0)
+				return;
 
 			var written = 0;
 			for (; written < write; written++)
 				Console.WriteLine();
-			if (written == 0) return;
 
-			Console.CursorTop = _originalWindowHeight - (written);
-			_originalCursorTop = Console.CursorTop - 1;
+			Console.CursorTop = Console.WindowHeight + Console.WindowTop - write;
+			_originalCursorTop = Console.CursorTop -1;
 		}
 
 		private void GrowDrawingAreaBasedOnChildren() => EnsureMainProgressBarVisible(_visibleDescendants);
@@ -345,7 +345,12 @@ namespace ShellProgressBar
 
 			DrawChildren(this.Children, indentation, ref cursorTop, Options.PercentageFormat);
 
-			ResetToBottom(ref cursorTop);
+			if (Console.CursorTop < _lastDrawBottomPos)
+			{
+				// The bar shrunk. Need to clean the remaining rows
+				ClearLines(_lastDrawBottomPos - Console.CursorTop);
+			}
+			_lastDrawBottomPos = Console.CursorTop;
 
 			Console.SetCursorPosition(0, _originalCursorTop);
 			Console.ForegroundColor = _originalColor;
@@ -355,35 +360,60 @@ namespace ShellProgressBar
 			_timer = null;
 		}
 
+		private static void ClearLines(int numOfLines)
+		{
+			// Use bufferwidth and not only the visible width. (currently identical on all platforms)
+			Console.Write(new string(' ', Console.BufferWidth * numOfLines));
+		}
+
+		private static string _resetString = "";
+		private static void ClearCurrentLine()
+		{
+			if (_resetString.Length != Console.BufferWidth + 2)
+			{
+				// Use buffer width and not only the visible width. (currently identical on all platforms)
+				_resetString = $"\r{new string(' ', Console.BufferWidth)}\r";
+			}
+			Console.Write(_resetString);
+		}
+
 		private void WriteConsoleLine(ConsoleOutLine m)
 		{
-			var resetString = new string(' ', Console.WindowWidth);
-			Console.Write(resetString);
-			Console.Write("\r");
 			var foreground = Console.ForegroundColor;
 			var background = Console.BackgroundColor;
-			var written = _writeMessageToConsole(m);
+			ClearCurrentLine();
+			var moved = _writeMessageToConsole(m);
 			Console.ForegroundColor = foreground;
 			Console.BackgroundColor = background;
-			_originalCursorTop += written;
+			_originalCursorTop += moved;
 		}
 
 		private static int DefaultConsoleWrite(ConsoleOutLine line)
 		{
-			if (line.Error) Console.Error.WriteLine(line.Line);
-			else Console.WriteLine(line.Line);
-			return 1;
-		}
+			var fromPos = Console.CursorTop;
 
-		private void ResetToBottom(ref int cursorTop)
-		{
-			var resetString = new string(' ', Console.WindowWidth);
-			var windowHeight = _originalWindowHeight;
-			if (cursorTop >= (windowHeight - 1)) return;
-			do
+			// First line was already cleared by WriteConsoleLine().
+			// Would be cleaner to do it here, but would break backwards compatibility for those
+			// who implemented their own writer function.
+			bool isClearedLine = true;
+			foreach (var outLine in line.Line.SplitToConsoleLines())
 			{
-				Console.Write(resetString);
-			} while (++cursorTop < (windowHeight - 1));
+				// Skip slower line clearing if we scrolled on last write
+				if (!isClearedLine)
+					ClearCurrentLine();
+
+				int lastCursorTop = Console.CursorTop;
+				if (line.Error)
+					Console.Error.WriteLine(outLine);
+				else
+					Console.WriteLine(outLine);
+
+				// If the cursorTop is still on same position we are at the end of the buffer and scrolling happened.
+				isClearedLine = lastCursorTop == Console.CursorTop;
+			}
+
+			// Return how many rows the cursor actually moved by.
+			return Console.CursorTop - fromPos;
 		}
 
 		private static void DrawChildren(IEnumerable<ChildProgressBar> children, Indentation[] indentation,
@@ -392,12 +422,12 @@ namespace ShellProgressBar
 			var view = children.Where(c => !c.Collapse).Select((c, i) => new {c, i}).ToList();
 			if (!view.Any()) return;
 
-			var windowHeight = Console.WindowHeight;
+			var lastVisibleRow = Console.WindowHeight + Console.WindowTop;
 			var lastChild = view.Max(t => t.i);
 			foreach (var tuple in view)
 			{
-				//Dont bother drawing children that would fall off the screen
-				if (cursorTop >= (windowHeight - 2))
+				// Dont bother drawing children that would fall off the screen and don't want to scroll top out of view
+				 if (cursorTop >= (lastVisibleRow - 2))
 					return;
 
 				var child = tuple.c;
@@ -500,7 +530,7 @@ namespace ShellProgressBar
 			{
 				var pbarHeight = this.Options.DenseProgressBar ? 1 : 2;
 				var openDescendantsPadding = (_visibleDescendants * pbarHeight);
-				var newCursorTop = Math.Min(_originalWindowHeight, _originalCursorTop + pbarHeight + openDescendantsPadding);
+				var newCursorTop = Math.Min(Console.WindowHeight+Console.WindowTop, _originalCursorTop + pbarHeight + openDescendantsPadding);
 				Console.CursorVisible = true;
 				Console.SetCursorPosition(0, newCursorTop);
 			}
diff --git a/src/ShellProgressBar/StringExtensions.cs b/src/ShellProgressBar/StringExtensions.cs
index bc3071b..0d00102 100644
--- a/src/ShellProgressBar/StringExtensions.cs
+++ b/src/ShellProgressBar/StringExtensions.cs
@@ -12,5 +12,32 @@ namespace ShellProgressBar
                 return phrase;
             return phrase.Substring(0, length - 3) + "...";
         }
+
+        /// <summary>
+        /// Splits a string into it's indiviudal lines and then again splits these individual lines
+        /// into multiple lines if they exceed the width of the console.
+        /// </summary>
+        /// <param name="str"></param>
+        /// <returns></returns>
+        public static IEnumerable<string> SplitToConsoleLines(this string str)
+        {
+	        int width = Console.BufferWidth;
+	        var lines = str.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
+
+	        foreach (var line in lines)
+	        {
+		        if (line.Length > width)
+		        {
+			        for (int i = 0; i < line.Length; i += width)
+			        {
+				        yield return line.Substring(i, Math.Min(width, line.Length - i));
+			        }
+		        }
+		        else
+		        {
+			        yield return line;
+		        }
+	        }
+        }
     }
 }

from shellprogressbar.

KoalaBear84 avatar KoalaBear84 commented on August 13, 2024

It would be great if issues with this are being resolved, as it is almost unusable to use the WriteLine. I was glad I saw the ProgressBar.WriteLine method, but after that already smashed to the floor again because it isn't usable 😂

from shellprogressbar.

tishige avatar tishige commented on August 13, 2024

Thank you!!

from shellprogressbar.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.