瀏覽代碼

Merges in #3019 changes. Makes OnResizeNeeded non-virtual. If we find a use-case where someone wants to override it we can change this back.

Tig Kindel 1 年之前
父節點
當前提交
a6092aedc6

+ 1 - 0
Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs

@@ -83,6 +83,7 @@ namespace Terminal.Gui {
 		public override void GenerateSuggestions (AutocompleteContext context)
 		{
 			if (_suspendSuggestions) {
+				_suspendSuggestions = false;
 				return;
 			}
 			base.GenerateSuggestions (context);

+ 15 - 10
Terminal.Gui/Text/TextFormatter.cs

@@ -1102,7 +1102,7 @@ namespace Terminal.Gui {
 				_text = EnableNeedsFormat (value);
 
 				if ((AutoSize && Alignment != TextAlignment.Justified && VerticalAlignment != VerticalTextAlignment.Justified) || (textWasNull && Size.IsEmpty)) {
-					Size = CalcRect (0, 0, _text, _textDirection, TabWidth).Size;
+					Size = CalcRect (0, 0, _text, Direction, TabWidth).Size;
 				}
 
 				//if (_text != null && _text.GetRuneCount () > 0 && (Size.Width == 0 || Size.Height == 0 || Size.Width != _text.GetColumns ())) {
@@ -1129,7 +1129,7 @@ namespace Terminal.Gui {
 			set {
 				_autoSize = EnableNeedsFormat (value);
 				if (_autoSize && Alignment != TextAlignment.Justified && VerticalAlignment != VerticalTextAlignment.Justified) {
-					Size = CalcRect (0, 0, Text, _textDirection, TabWidth).Size;
+					Size = CalcRect (0, 0, _text, Direction, TabWidth).Size;
 				}
 			}
 		}
@@ -1169,7 +1169,12 @@ namespace Terminal.Gui {
 		/// <value>The text vertical alignment.</value>
 		public TextDirection Direction {
 			get => _textDirection;
-			set => _textDirection = EnableNeedsFormat (value);
+			set {
+				_textDirection = EnableNeedsFormat (value);
+				if (AutoSize && Alignment != TextAlignment.Justified && VerticalAlignment != VerticalTextAlignment.Justified) {
+					Size = CalcRect (0, 0, Text, Direction, TabWidth).Size;
+				}
+			}
 		}
 
 		/// <summary>
@@ -1250,7 +1255,7 @@ namespace Terminal.Gui {
 			get => _size;
 			set {
 				if (AutoSize && Alignment != TextAlignment.Justified && VerticalAlignment != VerticalTextAlignment.Justified) {
-					_size = EnableNeedsFormat (CalcRect (0, 0, Text, _textDirection, TabWidth).Size);
+					_size = EnableNeedsFormat (CalcRect (0, 0, Text, Direction, TabWidth).Size);
 				} else {
 					_size = EnableNeedsFormat (value);
 				}
@@ -1329,7 +1334,7 @@ namespace Terminal.Gui {
 						shown_text = ReplaceHotKeyWithTag (shown_text, _hotKeyPos);
 					}
 
-					if (IsVerticalDirection (_textDirection)) {
+					if (IsVerticalDirection (Direction)) {
 						var colsWidth = GetSumMaxCharWidth (shown_text, 0, 1, TabWidth);
 						_lines = Format (shown_text, Size.Height, VerticalAlignment == VerticalTextAlignment.Justified, Size.Width > colsWidth && WordWrap,
 							PreserveTrailingSpaces, TabWidth, Direction, MultiLine);
@@ -1434,7 +1439,7 @@ namespace Terminal.Gui {
 			// Use "Lines" to ensure a Format (don't use "lines"))
 
 			var linesFormated = Lines;
-			switch (_textDirection) {
+			switch (Direction) {
 			case TextDirection.TopBottom_RightLeft:
 			case TextDirection.LeftRight_BottomTop:
 			case TextDirection.RightLeft_BottomTop:
@@ -1443,7 +1448,7 @@ namespace Terminal.Gui {
 				break;
 			}
 
-			var isVertical = IsVerticalDirection (_textDirection);
+			var isVertical = IsVerticalDirection (Direction);
 			var maxBounds = bounds;
 			if (driver != null) {
 				maxBounds = containerBounds == default
@@ -1475,7 +1480,7 @@ namespace Terminal.Gui {
 
 				var runes = _lines [line].ToRunes ();
 
-				switch (_textDirection) {
+				switch (Direction) {
 				case TextDirection.RightLeft_BottomTop:
 				case TextDirection.RightLeft_TopBottom:
 				case TextDirection.BottomTop_LeftRight:
@@ -1488,7 +1493,7 @@ namespace Terminal.Gui {
 
 				int x, y;
 				// Horizontal Alignment
-				if (_textAlignment == TextAlignment.Right || (_textAlignment == TextAlignment.Justified && !IsLeftToRight (_textDirection))) {
+				if (_textAlignment == TextAlignment.Right || (_textAlignment == TextAlignment.Justified && !IsLeftToRight (Direction))) {
 					if (isVertical) {
 						var runesWidth = GetSumMaxCharWidth (Lines, line, TabWidth);
 						x = bounds.Right - runesWidth;
@@ -1521,7 +1526,7 @@ namespace Terminal.Gui {
 				}
 
 				// Vertical Alignment
-				if (_textVerticalAlignment == VerticalTextAlignment.Bottom || (_textVerticalAlignment == VerticalTextAlignment.Justified && !IsTopToBottom (_textDirection))) {
+				if (_textVerticalAlignment == VerticalTextAlignment.Bottom || (_textVerticalAlignment == VerticalTextAlignment.Justified && !IsTopToBottom (Direction))) {
 					if (isVertical) {
 						y = bounds.Bottom - runes.Length;
 					} else {

+ 5 - 5
Terminal.Gui/View/Layout/ViewLayout.cs

@@ -569,14 +569,14 @@ public partial class View {
 	/// </summary>
 	/// <remarks>
 	///         <para>
-	///         Sets the <see cref="Frame"/>.
-	///         </para>
-	///         <para>
-	///         Can be overridden if the view resize behavior is different than the default.
+	///         Determines the relative bounds of the <see cref="View"/> and its <see cref="Frame"/>s, and then calls
+	///         <see cref="SetRelativeLayout(Rect)"/> to update the view. 
 	///         </para>
 	/// </remarks>
-	protected virtual void OnResizeNeeded ()
+	internal void OnResizeNeeded ()
 	{
+		// TODO: Identify a real-world use-case where this API should be virtual. 
+		// TODO: Until then leave it `internal` and non-virtual
 		// First try SuperView.Bounds, then Application.Top, then Driver.Bounds.
 		// Finally, if none of those are valid, use int.MaxValue (for Unit tests).
 		var relativeBounds = SuperView is { IsInitialized: true }        ? SuperView.Bounds :

+ 134 - 110
Terminal.Gui/View/ViewDrawing.cs

@@ -1,5 +1,4 @@
 using System;
-using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 
@@ -8,9 +7,12 @@ namespace Terminal.Gui;
 public partial class View {
 	ColorScheme _colorScheme;
 
+	// The view-relative region that needs to be redrawn. Marked internal for unit tests.
+	internal Rect _needsDisplayRect = Rect.Empty;
+
 	/// <summary>
 	/// The color scheme for this view, if it is not defined, it returns the <see cref="SuperView"/>'s
-	/// color scheme. 
+	/// color scheme.
 	/// </summary>
 	public virtual ColorScheme ColorScheme {
 		get {
@@ -27,12 +29,47 @@ public partial class View {
 		}
 	}
 
+	/// <summary>
+	/// Gets or sets whether the view needs to be redrawn.
+	/// </summary>
+	public bool NeedsDisplay {
+		get => _needsDisplayRect != Rect.Empty;
+		set {
+			if (value) {
+				SetNeedsDisplay ();
+			} else {
+				ClearNeedsDisplay ();
+			}
+		}
+	}
+
+	/// <summary>
+	/// Gets whether any Subviews need to be redrawn.
+	/// </summary>
+	public bool SubViewNeedsDisplay { get; private set; }
+
+	/// <summary>
+	/// The canvas that any line drawing that is to be shared by subviews of this view should add lines to.
+	/// </summary>
+	/// <remarks><see cref="Border"/> adds border lines to this LineCanvas.</remarks>
+	public LineCanvas LineCanvas { get; } = new ();
+
+	/// <summary>
+	/// Gets or sets whether this View will use it's SuperView's <see cref="LineCanvas"/> for
+	/// rendering any border lines. If <see langword="true"/> the rendering of any borders drawn
+	/// by this Frame will be done by it's parent's SuperView. If <see langword="false"/> (the default)
+	/// this View's <see cref="OnDrawFrames()"/> method will be called to render the borders.
+	/// </summary>
+	public virtual bool SuperViewRendersLineCanvas { get; set; } = false;
+
 	/// <summary>
 	/// Determines the current <see cref="ColorScheme"/> based on the <see cref="Enabled"/> value.
 	/// </summary>
-	/// <returns><see cref="Terminal.Gui.ColorScheme.Normal"/> if <see cref="Enabled"/> is <see langword="true"/>
+	/// <returns>
+	/// <see cref="Terminal.Gui.ColorScheme.Normal"/> if <see cref="Enabled"/> is <see langword="true"/>
 	/// or <see cref="Terminal.Gui.ColorScheme.Disabled"/> if <see cref="Enabled"/> is <see langword="false"/>.
-	/// If it's overridden can return other values.</returns>
+	/// If it's overridden can return other values.
+	/// </returns>
 	public virtual Attribute GetNormalColor ()
 	{
 		var cs = ColorScheme;
@@ -45,17 +82,21 @@ public partial class View {
 	/// <summary>
 	/// Determines the current <see cref="ColorScheme"/> based on the <see cref="Enabled"/> value.
 	/// </summary>
-	/// <returns><see cref="Terminal.Gui.ColorScheme.Focus"/> if <see cref="Enabled"/> is <see langword="true"/>
+	/// <returns>
+	/// <see cref="Terminal.Gui.ColorScheme.Focus"/> if <see cref="Enabled"/> is <see langword="true"/>
 	/// or <see cref="Terminal.Gui.ColorScheme.Disabled"/> if <see cref="Enabled"/> is <see langword="false"/>.
-	/// If it's overridden can return other values.</returns>
+	/// If it's overridden can return other values.
+	/// </returns>
 	public virtual Attribute GetFocusColor () => Enabled ? ColorScheme.Focus : ColorScheme.Disabled;
 
 	/// <summary>
 	/// Determines the current <see cref="ColorScheme"/> based on the <see cref="Enabled"/> value.
 	/// </summary>
-	/// <returns><see cref="Terminal.Gui.ColorScheme.HotNormal"/> if <see cref="Enabled"/> is <see langword="true"/>
+	/// <returns>
+	/// <see cref="Terminal.Gui.ColorScheme.HotNormal"/> if <see cref="Enabled"/> is <see langword="true"/>
 	/// or <see cref="Terminal.Gui.ColorScheme.Disabled"/> if <see cref="Enabled"/> is <see langword="false"/>.
-	/// If it's overridden can return other values.</returns>
+	/// If it's overridden can return other values.
+	/// </returns>
 	public virtual Attribute GetHotNormalColor () => Enabled ? ColorScheme.HotNormal : ColorScheme.Disabled;
 
 	/// <summary>
@@ -81,25 +122,8 @@ public partial class View {
 	/// </summary>
 	protected void ClearNeedsDisplay ()
 	{
-		_needsDisplayRect = Rect.Empty;
-		_subViewNeedsDisplay = false;
-	}
-
-	// The view-relative region that needs to be redrawn. Marked internal for unit tests.
-	internal Rect _needsDisplayRect = Rect.Empty;
-
-	/// <summary>
-	/// Gets or sets whether the view needs to be redrawn.
-	/// </summary>
-	public bool NeedsDisplay {
-		get => _needsDisplayRect != Rect.Empty;
-		set {
-			if (value) {
-				SetNeedsDisplay ();
-			} else {
-				ClearNeedsDisplay ();
-			}
-		}
+		_needsDisplayRect   = Rect.Empty;
+		SubViewNeedsDisplay = false;
 	}
 
 	/// <summary>
@@ -133,18 +157,18 @@ public partial class View {
 		if (_needsDisplayRect.IsEmpty) {
 			_needsDisplayRect = region;
 		} else {
-			int x = Math.Min (_needsDisplayRect.X, region.X);
-			int y = Math.Min (_needsDisplayRect.Y, region.Y);
-			int w = Math.Max (_needsDisplayRect.Width, region.Width);
-			int h = Math.Max (_needsDisplayRect.Height, region.Height);
+			var x = Math.Min (_needsDisplayRect.X, region.X);
+			var y = Math.Min (_needsDisplayRect.Y, region.Y);
+			var w = Math.Max (_needsDisplayRect.Width,  region.Width);
+			var h = Math.Max (_needsDisplayRect.Height, region.Height);
 			_needsDisplayRect = new Rect (x, y, w, h);
 		}
 		_superView?.SetSubViewNeedsDisplay ();
 
 		if (_needsDisplayRect.X < Bounds.X ||
-		_needsDisplayRect.Y < Bounds.Y ||
-		_needsDisplayRect.Width > Bounds.Width ||
-		_needsDisplayRect.Height > Bounds.Height) {
+		    _needsDisplayRect.Y < Bounds.Y ||
+		    _needsDisplayRect.Width > Bounds.Width ||
+		    _needsDisplayRect.Height > Bounds.Height) {
 			Margin?.SetNeedsDisplay (Margin.Bounds);
 			Border?.SetNeedsDisplay (Border.Bounds);
 			Padding?.SetNeedsDisplay (Padding.Bounds);
@@ -164,31 +188,24 @@ public partial class View {
 		}
 	}
 
-	/// <summary>
-	/// Gets whether any Subviews need to be redrawn.
-	/// </summary>
-	public bool SubViewNeedsDisplay => _subViewNeedsDisplay;
-
-	bool _subViewNeedsDisplay;
-
 	/// <summary>
 	/// Indicates that any Subviews (in the <see cref="Subviews"/> list) need to be repainted.
 	/// </summary>
 	public void SetSubViewNeedsDisplay ()
 	{
-		_subViewNeedsDisplay = true;
-		if (_superView != null && !_superView._subViewNeedsDisplay) {
+		SubViewNeedsDisplay = true;
+		if (_superView != null && !_superView.SubViewNeedsDisplay) {
 			_superView.SetSubViewNeedsDisplay ();
 		}
 	}
 
 	/// <summary>
-	///   Clears the <see cref="Bounds"/> with the normal background color.
+	/// Clears the <see cref="Bounds"/> with the normal background color.
 	/// </summary>
 	/// <remarks>
-	///   <para>
-	///     This clears the Bounds used by this view.
-	///   </para>
+	///         <para>
+	///         This clears the Bounds used by this view.
+	///         </para>
 	/// </remarks>
 	public void Clear ()
 	{
@@ -202,7 +219,7 @@ public partial class View {
 	// "View APIs only deal with View-relative coords". This is only used by ComboBox which can
 	// be refactored to use the View-relative version.
 	/// <summary>
-	///   Clears the specified screen-relative rectangle with the normal background. 
+	/// Clears the specified screen-relative rectangle with the normal background.
 	/// </summary>
 	/// <remarks>
 	/// </remarks>
@@ -220,10 +237,10 @@ public partial class View {
 	// Clips a rectangle in screen coordinates to the dimensions currently available on the screen
 	internal Rect ScreenClip (Rect regionScreen)
 	{
-		int x = regionScreen.X < 0 ? 0 : regionScreen.X;
-		int y = regionScreen.Y < 0 ? 0 : regionScreen.Y;
-		int w = regionScreen.X + regionScreen.Width >= Driver.Cols ? Driver.Cols - regionScreen.X : regionScreen.Width;
-		int h = regionScreen.Y + regionScreen.Height >= Driver.Rows ? Driver.Rows - regionScreen.Y : regionScreen.Height;
+		var x = regionScreen.X < 0 ? 0 : regionScreen.X;
+		var y = regionScreen.Y < 0 ? 0 : regionScreen.Y;
+		var w = regionScreen.X + regionScreen.Width >= Driver.Cols ? Driver.Cols - regionScreen.X : regionScreen.Width;
+		var h = regionScreen.Y + regionScreen.Height >= Driver.Rows ? Driver.Rows - regionScreen.Y : regionScreen.Height;
 
 		return new Rect (x, y, w, h);
 	}
@@ -231,11 +248,15 @@ public partial class View {
 	/// <summary>
 	/// Expands the <see cref="ConsoleDriver"/>'s clip region to include <see cref="Bounds"/>.
 	/// </summary>
-	/// <returns>The current screen-relative clip region, which can be then re-applied by setting <see cref="ConsoleDriver.Clip"/>.</returns>
+	/// <returns>
+	/// The current screen-relative clip region, which can be then re-applied by setting
+	/// <see cref="ConsoleDriver.Clip"/>.
+	/// </returns>
 	/// <remarks>
-	/// <para>
-	/// If <see cref="ConsoleDriver.Clip"/> and <see cref="Bounds"/> do not intersect, the clip region will be set to <see cref="Rect.Empty"/>.
-	/// </para>
+	///         <para>
+	///         If <see cref="ConsoleDriver.Clip"/> and <see cref="Bounds"/> do not intersect, the clip region will be set to
+	///         <see cref="Rect.Empty"/>.
+	///         </para>
 	/// </remarks>
 	public Rect ClipToBounds ()
 	{
@@ -251,14 +272,17 @@ public partial class View {
 	/// <param name="hotColor">Hot color.</param>
 	/// <param name="normalColor">Normal color.</param>
 	/// <remarks>
-	/// <para>The hotkey is any character following the hotkey specifier, which is the underscore ('_') character by default.</para>
-	/// <para>The hotkey specifier can be changed via <see cref="HotKeySpecifier"/></para>
+	///         <para>
+	///         The hotkey is any character following the hotkey specifier, which is the underscore ('_') character by
+	///         default.
+	///         </para>
+	///         <para>The hotkey specifier can be changed via <see cref="HotKeySpecifier"/></para>
 	/// </remarks>
 	public void DrawHotString (string text, Attribute hotColor, Attribute normalColor)
 	{
 		var hotkeySpec = HotKeySpecifier == (Rune)0xffff ? (Rune)'_' : HotKeySpecifier;
 		Application.Driver.SetAttribute (normalColor);
-		foreach (char rune in text) {
+		foreach (var rune in text) {
 			if (rune == hotkeySpec.Value) {
 				Application.Driver.SetAttribute (hotColor);
 				continue;
@@ -272,7 +296,10 @@ public partial class View {
 	/// Utility function to draw strings that contains a hotkey using a <see cref="ColorScheme"/> and the "focused" state.
 	/// </summary>
 	/// <param name="text">String to display, the underscore before a letter flags the next letter as the hotkey.</param>
-	/// <param name="focused">If set to <see langword="true"/> this uses the focused colors from the color scheme, otherwise the regular ones.</param>
+	/// <param name="focused">
+	/// If set to <see langword="true"/> this uses the focused colors from the color scheme, otherwise
+	/// the regular ones.
+	/// </param>
 	/// <param name="scheme">The color scheme to use.</param>
 	public void DrawHotString (string text, bool focused, ColorScheme scheme)
 	{
@@ -295,28 +322,15 @@ public partial class View {
 			return;
 		}
 
-		BoundsToScreen (col, row, out int rCol, out int rRow, false);
+		BoundsToScreen (col, row, out var rCol, out var rRow, false);
 		Driver?.Move (rCol, rRow);
 	}
 
-	/// <summary>
-	/// The canvas that any line drawing that is to be shared by subviews of this view should add lines to.
-	/// </summary>
-	/// <remarks><see cref="Border"/> adds border lines to this LineCanvas.</remarks>
-	public LineCanvas LineCanvas { get; } = new ();
-
-	/// <summary>
-	/// Gets or sets whether this View will use it's SuperView's <see cref="LineCanvas"/> for
-	/// rendering any border lines. If <see langword="true"/> the rendering of any borders drawn
-	/// by this Frame will be done by it's parent's SuperView. If <see langword="false"/> (the default)
-	/// this View's <see cref="OnDrawFrames()"/> method will be called to render the borders.
-	/// </summary>
-	public virtual bool SuperViewRendersLineCanvas { get; set; } = false;
-
 	// TODO: Make this cancelable
 	/// <summary>
-	/// Prepares <see cref="View.LineCanvas"/>. If <see cref="SuperViewRendersLineCanvas"/> is true, only the <see cref="LineCanvas"/> of 
-	/// this view's subviews will be rendered. If <see cref="SuperViewRendersLineCanvas"/> is false (the default), this 
+	/// Prepares <see cref="View.LineCanvas"/>. If <see cref="SuperViewRendersLineCanvas"/> is true, only the
+	/// <see cref="LineCanvas"/> of
+	/// this view's subviews will be rendered. If <see cref="SuperViewRendersLineCanvas"/> is false (the default), this
 	/// method will cause the <see cref="LineCanvas"/> be prepared to be rendered.
 	/// </summary>
 	/// <returns></returns>
@@ -336,21 +350,22 @@ public partial class View {
 	}
 
 	/// <summary>
-	/// Draws the view. Causes the following virtual methods to be called (along with their related events): 
+	/// Draws the view. Causes the following virtual methods to be called (along with their related events):
 	/// <see cref="OnDrawContent"/>, <see cref="OnDrawContentComplete"/>.
 	/// </summary>
 	/// <remarks>
-	/// <para>
-	///    Always use <see cref="Bounds"/> (view-relative) when calling <see cref="OnDrawContent(Rect)"/>, NOT <see cref="Frame"/> (superview-relative).
-	/// </para>
-	/// <para>
-	///    Views should set the color that they want to use on entry, as otherwise this will inherit
-	///    the last color that was set globally on the driver.
-	/// </para>
-	/// <para>
-	///    Overrides of <see cref="OnDrawContent(Rect)"/> must ensure they do not set <c>Driver.Clip</c> to a clip region
-	///    larger than the <ref name="Bounds"/> property, as this will cause the driver to clip the entire region.
-	/// </para>
+	///         <para>
+	///         Always use <see cref="Bounds"/> (view-relative) when calling <see cref="OnDrawContent(Rect)"/>, NOT
+	///         <see cref="Frame"/> (superview-relative).
+	///         </para>
+	///         <para>
+	///         Views should set the color that they want to use on entry, as otherwise this will inherit
+	///         the last color that was set globally on the driver.
+	///         </para>
+	///         <para>
+	///         Overrides of <see cref="OnDrawContent(Rect)"/> must ensure they do not set <c>Driver.Clip</c> to a clip region
+	///         larger than the <ref name="Bounds"/> property, as this will cause the driver to clip the entire region.
+	///         </para>
 	/// </remarks>
 	public void Draw ()
 	{
@@ -387,8 +402,9 @@ public partial class View {
 
 	// TODO: Make this cancelable
 	/// <summary>
-	/// Renders <see cref="View.LineCanvas"/>. If <see cref="SuperViewRendersLineCanvas"/> is true, only the <see cref="LineCanvas"/> of 
-	/// this view's subviews will be rendered. If <see cref="SuperViewRendersLineCanvas"/> is false (the default), this 
+	/// Renders <see cref="View.LineCanvas"/>. If <see cref="SuperViewRendersLineCanvas"/> is true, only the
+	/// <see cref="LineCanvas"/> of
+	/// this view's subviews will be rendered. If <see cref="SuperViewRendersLineCanvas"/> is false (the default), this
 	/// method will cause the <see cref="LineCanvas"/> to be rendered.
 	/// </summary>
 	/// <returns></returns>
@@ -411,7 +427,7 @@ public partial class View {
 		}
 
 		if (Subviews.Any (s => s.SuperViewRendersLineCanvas)) {
-			foreach (var subview in Subviews.Where (s => s.SuperViewRendersLineCanvas == true)) {
+			foreach (var subview in Subviews.Where (s => s.SuperViewRendersLineCanvas)) {
 				// Combine the LineCanvas'
 				LineCanvas.Merge (subview.LineCanvas);
 				subview.LineCanvas.Clear ();
@@ -434,21 +450,25 @@ public partial class View {
 	/// Event invoked when the content area of the View is to be drawn.
 	/// </summary>
 	/// <remarks>
-	/// <para>
-	/// Will be invoked before any subviews added with <see cref="Add(View)"/> have been drawn.
-	/// </para>
-	/// <para>
-	/// Rect provides the view-relative rectangle describing the currently visible viewport into the <see cref="View"/>.
-	/// </para>
+	///         <para>
+	///         Will be invoked before any subviews added with <see cref="Add(View)"/> have been drawn.
+	///         </para>
+	///         <para>
+	///         Rect provides the view-relative rectangle describing the currently visible viewport into the <see cref="View"/>
+	///         .
+	///         </para>
 	/// </remarks>
 	public event EventHandler<DrawEventArgs> DrawContent;
 
 	/// <summary>
-	/// Enables overrides to draw infinitely scrolled content and/or a background behind added controls. 
+	/// Enables overrides to draw infinitely scrolled content and/or a background behind added controls.
 	/// </summary>
-	/// <param name="contentArea">The view-relative rectangle describing the currently visible viewport into the <see cref="View"/></param>
+	/// <param name="contentArea">
+	/// The view-relative rectangle describing the currently visible viewport into the
+	/// <see cref="View"/>
+	/// </param>
 	/// <remarks>
-	/// This method will be called before any subviews added with <see cref="Add(View)"/> have been drawn. 
+	/// This method will be called before any subviews added with <see cref="Add(View)"/> have been drawn.
 	/// </remarks>
 	public virtual void OnDrawContent (Rect contentArea)
 	{
@@ -475,8 +495,8 @@ public partial class View {
 			var subviewsNeedingDraw = _subviews.Where (
 				view => view.Visible &&
 					(view.NeedsDisplay ||
-					view.SubViewNeedsDisplay ||
-					view.LayoutNeeded)
+					 view.SubViewNeedsDisplay ||
+					 view.LayoutNeeded)
 			);
 
 			foreach (var view in subviewsNeedingDraw) {
@@ -499,19 +519,23 @@ public partial class View {
 	/// Event invoked when the content area of the View is completed drawing.
 	/// </summary>
 	/// <remarks>
-	/// <para>
-	/// Will be invoked after any subviews removed with <see cref="Remove(View)"/> have been completed drawing.
-	/// </para>
-	/// <para>
-	/// Rect provides the view-relative rectangle describing the currently visible viewport into the <see cref="View"/>.
-	/// </para>
+	///         <para>
+	///         Will be invoked after any subviews removed with <see cref="Remove(View)"/> have been completed drawing.
+	///         </para>
+	///         <para>
+	///         Rect provides the view-relative rectangle describing the currently visible viewport into the <see cref="View"/>
+	///         .
+	///         </para>
 	/// </remarks>
 	public event EventHandler<DrawEventArgs> DrawContentComplete;
 
 	/// <summary>
 	/// Enables overrides after completed drawing infinitely scrolled content and/or a background behind removed controls.
 	/// </summary>
-	/// <param name="contentArea">The view-relative rectangle describing the currently visible viewport into the <see cref="View"/></param>
+	/// <param name="contentArea">
+	/// The view-relative rectangle describing the currently visible viewport into the
+	/// <see cref="View"/>
+	/// </param>
 	/// <remarks>
 	/// This method will be called after any subviews removed with <see cref="Remove(View)"/> have been completed drawing.
 	/// </remarks>

+ 608 - 607
Terminal.Gui/View/ViewSubViews.cs

@@ -1,724 +1,725 @@
 using System;
 using System.Collections.Generic;
-using System.ComponentModel;
-using System.Linq;
-using System.Text;
-
-namespace Terminal.Gui {
-	public partial class View {
-		static readonly IList<View> _empty = new List<View> (0).AsReadOnly ();
-
-		View _superView = null;
-
-		/// <summary>
-		/// Returns the container for this view, or null if this view has not been added to a container.
-		/// </summary>
-		/// <value>The super view.</value>
-		public virtual View SuperView {
-			get {
-				return _superView;
-			}
-			set {
-				throw new NotImplementedException ();
-			}
-		}
 
-		List<View> _subviews; // This is null, and allocated on demand.
-		/// <summary>
-		/// This returns a list of the subviews contained by this view.
-		/// </summary>
-		/// <value>The subviews.</value>
-		public IList<View> Subviews => _subviews?.AsReadOnly () ?? _empty;
-
-		// Internally, we use InternalSubviews rather than subviews, as we do not expect us
-		// to make the same mistakes our users make when they poke at the Subviews.
-		internal IList<View> InternalSubviews => _subviews ?? _empty;
-
-		/// <summary>
-		/// Returns a value indicating if this View is currently on Top (Active)
-		/// </summary>
-		public bool IsCurrentTop => Application.Current == this;
-
-		/// <summary>
-		/// Event fired when this view is added to another.
-		/// </summary>
-		public event EventHandler<SuperViewChangedEventArgs> Added;
-
-		internal bool _addingView;
-
-		/// <summary>
-		///   Adds a subview (child) to this view.
-		/// </summary>
-		/// <remarks>
-		/// The Views that have been added to this view can be retrieved via the <see cref="Subviews"/> property. 
-		/// See also <seealso cref="Remove(View)"/> <seealso cref="RemoveAll"/> 
-		/// </remarks>
-		public virtual void Add (View view)
-		{
-			if (view == null) {
-				return;
-			}
-			if (_subviews == null) {
-				_subviews = new List<View> ();
-			}
-			if (_tabIndexes == null) {
-				_tabIndexes = new List<View> ();
-			}
-			_subviews.Add (view);
-			_tabIndexes.Add (view);
-			view._superView = this;
-			if (view.CanFocus) {
-				_addingView = true;
-				if (SuperView?.CanFocus == false) {
-					SuperView._addingView = true;
-					SuperView.CanFocus = true;
-					SuperView._addingView = false;
-				}
-				CanFocus = true;
-				view._tabIndex = _tabIndexes.IndexOf (view);
-				_addingView = false;
-			}
-			if (view.Enabled && !Enabled) {
-				view._oldEnabled = true;
-				view.Enabled = false;
-			}
+namespace Terminal.Gui; 
 
-			OnAdded (new SuperViewChangedEventArgs (this, view));
-			if (IsInitialized && !view.IsInitialized) {
-				view.BeginInit ();
-				view.EndInit ();
-			}
+public partial class View {
+	static readonly IList<View> _empty = new List<View> (0).AsReadOnly ();
 
-			SetNeedsLayout ();
-			SetNeedsDisplay ();
+	internal bool _addingView;
+
+	List<View> _subviews; // This is null, and allocated on demand.
+
+	View _superView;
+
+	/// <summary>
+	/// Returns the container for this view, or null if this view has not been added to a container.
+	/// </summary>
+	/// <value>The super view.</value>
+	public virtual View SuperView {
+		get => _superView;
+		set => throw new NotImplementedException ();
+	}
+
+	/// <summary>
+	/// This returns a list of the subviews contained by this view.
+	/// </summary>
+	/// <value>The subviews.</value>
+	public IList<View> Subviews => _subviews?.AsReadOnly () ?? _empty;
+
+	// Internally, we use InternalSubviews rather than subviews, as we do not expect us
+	// to make the same mistakes our users make when they poke at the Subviews.
+	internal IList<View> InternalSubviews => _subviews ?? _empty;
+
+	/// <summary>
+	/// Returns a value indicating if this View is currently on Top (Active)
+	/// </summary>
+	public bool IsCurrentTop => Application.Current == this;
+
+	/// <summary>
+	/// Indicates whether the view was added to <see cref="SuperView"/>.
+	/// </summary>
+	public bool IsAdded { get; private set; }
+
+	/// <summary>
+	/// Event fired when this view is added to another.
+	/// </summary>
+	public event EventHandler<SuperViewChangedEventArgs> Added;
+
+	/// <summary>
+	/// Adds a subview (child) to this view.
+	/// </summary>
+	/// <remarks>
+	/// The Views that have been added to this view can be retrieved via the <see cref="Subviews"/> property.
+	/// See also <seealso cref="Remove(View)"/> <seealso cref="RemoveAll"/>
+	/// </remarks>
+	public virtual void Add (View view)
+	{
+		if (view == null) {
+			return;
+		}
+		if (_subviews == null) {
+			_subviews = new List<View> ();
+		}
+		if (_tabIndexes == null) {
+			_tabIndexes = new List<View> ();
+		}
+		_subviews.Add (view);
+		_tabIndexes.Add (view);
+		view._superView = this;
+		if (view.CanFocus) {
+			_addingView = true;
+			if (SuperView?.CanFocus == false) {
+				SuperView._addingView = true;
+				SuperView.CanFocus    = true;
+				SuperView._addingView = false;
+			}
+			CanFocus       = true;
+			view._tabIndex = _tabIndexes.IndexOf (view);
+			_addingView    = false;
+		}
+		if (view.Enabled && !Enabled) {
+			view._oldEnabled = true;
+			view.Enabled     = false;
 		}
 
-		/// <summary>
-		/// Adds the specified views (children) to the view.
-		/// </summary>
-		/// <param name="views">Array of one or more views (can be optional parameter).</param>
-		/// <remarks>
-		/// The Views that have been added to this view can be retrieved via the <see cref="Subviews"/> property. 
-		/// See also <seealso cref="Remove(View)"/> <seealso cref="RemoveAll"/> 
-		/// </remarks>
-		public void Add (params View [] views)
-		{
-			if (views == null) {
-				return;
-			}
-			foreach (var view in views) {
-				Add (view);
-			}
+		OnAdded (new SuperViewChangedEventArgs (this, view));
+		if (IsInitialized && !view.IsInitialized) {
+			view.BeginInit ();
+			view.EndInit ();
 		}
 
-		/// <summary>
-		/// Method invoked when a subview is being added to this view.
-		/// </summary>
-		/// <param name="e">Event where <see cref="ViewEventArgs.View"/> is the subview being added.</param>
-		public virtual void OnAdded (SuperViewChangedEventArgs e)
-		{
-			var view = e.Child;
-			view.IsAdded = true;
-			view.OnResizeNeeded ();
-			view.Added?.Invoke (this, e);
-		}
-
-		/// <summary>
-		/// Indicates whether the view was added to <see cref="SuperView"/>.
-		/// </summary>
-		public bool IsAdded { get; private set; }
-
-		/// <summary>
-		/// Event fired when this view is removed from another.
-		/// </summary>
-		public event EventHandler<SuperViewChangedEventArgs> Removed;
-
-		/// <summary>
-		///   Removes all subviews (children) added via <see cref="Add(View)"/> or <see cref="Add(View[])"/> from this View.
-		/// </summary>
-		public virtual void RemoveAll ()
-		{
-			if (_subviews == null) {
-				return;
-			}
+		SetNeedsLayout ();
+		SetNeedsDisplay ();
+	}
 
-			while (_subviews.Count > 0) {
-				Remove (_subviews [0]);
-			}
+	/// <summary>
+	/// Adds the specified views (children) to the view.
+	/// </summary>
+	/// <param name="views">Array of one or more views (can be optional parameter).</param>
+	/// <remarks>
+	/// The Views that have been added to this view can be retrieved via the <see cref="Subviews"/> property.
+	/// See also <seealso cref="Remove(View)"/> <seealso cref="RemoveAll"/>
+	/// </remarks>
+	public void Add (params View [] views)
+	{
+		if (views == null) {
+			return;
+		}
+		foreach (var view in views) {
+			Add (view);
 		}
+	}
 
-		/// <summary>
-		///   Removes a subview added via <see cref="Add(View)"/> or <see cref="Add(View[])"/> from this View.
-		/// </summary>
-		/// <remarks>
-		/// </remarks>
-		public virtual void Remove (View view)
-		{
-			if (view == null || _subviews == null) return;
-
-			var touched = view.Frame;
-			_subviews.Remove (view);
-			_tabIndexes.Remove (view);
-			view._superView = null;
-			view._tabIndex = -1;
-			SetNeedsLayout ();
-			SetNeedsDisplay ();
+	/// <summary>
+	/// Method invoked when a subview is being added to this view.
+	/// </summary>
+	/// <param name="e">Event where <see cref="ViewEventArgs.View"/> is the subview being added.</param>
+	public virtual void OnAdded (SuperViewChangedEventArgs e)
+	{
+		var view = e.Child;
+		view.IsAdded = true;
+		view.OnResizeNeeded ();
+		view.Added?.Invoke (this, e);
+	}
 
-			foreach (var v in _subviews) {
-				if (v.Frame.IntersectsWith (touched))
-					view.SetNeedsDisplay ();
-			}
-			OnRemoved (new SuperViewChangedEventArgs (this, view));
-			if (_focused == view) {
-				_focused = null;
-			}
+	/// <summary>
+	/// Event fired when this view is removed from another.
+	/// </summary>
+	public event EventHandler<SuperViewChangedEventArgs> Removed;
+
+	/// <summary>
+	/// Removes all subviews (children) added via <see cref="Add(View)"/> or <see cref="Add(View[])"/> from this View.
+	/// </summary>
+	public virtual void RemoveAll ()
+	{
+		if (_subviews == null) {
+			return;
 		}
 
-		/// <summary>
-		/// Method invoked when a subview is being removed from this view.
-		/// </summary>
-		/// <param name="e">Event args describing the subview being removed.</param>
-		public virtual void OnRemoved (SuperViewChangedEventArgs e)
-		{
-			var view = e.Child;
-			view.IsAdded = false;
-			view.Removed?.Invoke (this, e);
+		while (_subviews.Count > 0) {
+			Remove (_subviews [0]);
 		}
+	}
 
+	/// <summary>
+	/// Removes a subview added via <see cref="Add(View)"/> or <see cref="Add(View[])"/> from this View.
+	/// </summary>
+	/// <remarks>
+	/// </remarks>
+	public virtual void Remove (View view)
+	{
+		if (view == null || _subviews == null) {
+			return;
+		}
 
-		void PerformActionForSubview (View subview, Action<View> action)
-		{
-			if (_subviews.Contains (subview)) {
-				action (subview);
-			}
+		var touched = view.Frame;
+		_subviews.Remove (view);
+		_tabIndexes.Remove (view);
+		view._superView = null;
+		view._tabIndex  = -1;
+		SetNeedsLayout ();
+		SetNeedsDisplay ();
 
-			SetNeedsDisplay ();
-			subview.SetNeedsDisplay ();
-		}
-
-		/// <summary>
-		/// Brings the specified subview to the front so it is drawn on top of any other views.
-		/// </summary>
-		/// <param name="subview">The subview to send to the front</param>
-		/// <remarks>
-		///   <seealso cref="SendSubviewToBack"/>.
-		/// </remarks>
-		public void BringSubviewToFront (View subview)
-		{
-			PerformActionForSubview (subview, x => {
-				_subviews.Remove (x);
-				_subviews.Add (x);
-			});
-		}
-
-		/// <summary>
-		/// Sends the specified subview to the front so it is the first view drawn
-		/// </summary>
-		/// <param name="subview">The subview to send to the front</param>
-		/// <remarks>
-		///   <seealso cref="BringSubviewToFront(View)"/>.
-		/// </remarks>
-		public void SendSubviewToBack (View subview)
-		{
-			PerformActionForSubview (subview, x => {
-				_subviews.Remove (x);
-				_subviews.Insert (0, subview);
-			});
-		}
-
-		/// <summary>
-		/// Moves the subview backwards in the hierarchy, only one step
-		/// </summary>
-		/// <param name="subview">The subview to send backwards</param>
-		/// <remarks>
-		/// If you want to send the view all the way to the back use SendSubviewToBack.
-		/// </remarks>
-		public void SendSubviewBackwards (View subview)
-		{
-			PerformActionForSubview (subview, x => {
-				var idx = _subviews.IndexOf (x);
-				if (idx > 0) {
-					_subviews.Remove (x);
-					_subviews.Insert (idx - 1, x);
-				}
-			});
-		}
-
-		/// <summary>
-		/// Moves the subview backwards in the hierarchy, only one step
-		/// </summary>
-		/// <param name="subview">The subview to send backwards</param>
-		/// <remarks>
-		/// If you want to send the view all the way to the back use SendSubviewToBack.
-		/// </remarks>
-		public void BringSubviewForward (View subview)
-		{
-			PerformActionForSubview (subview, x => {
-				var idx = _subviews.IndexOf (x);
-				if (idx + 1 < _subviews.Count) {
-					_subviews.Remove (x);
-					_subviews.Insert (idx + 1, x);
-				}
-			});
-		}
-
-		/// <summary>
-		/// Get the top superview of a given <see cref="View"/>.
-		/// </summary>
-		/// <returns>The superview view.</returns>
-		public View GetTopSuperView (View view = null, View superview = null)
-		{
-			View top = superview ?? Application.Top;
-			for (var v = view?.SuperView ?? (this?.SuperView); v != null; v = v.SuperView) {
-				top = v;
-				if (top == superview) {
-					break;
-				}
+		foreach (var v in _subviews) {
+			if (v.Frame.IntersectsWith (touched)) {
+				view.SetNeedsDisplay ();
 			}
-
-			return top;
 		}
+		OnRemoved (new SuperViewChangedEventArgs (this, view));
+		if (Focused == view) {
+			Focused = null;
+		}
+	}
 
+	/// <summary>
+	/// Method invoked when a subview is being removed from this view.
+	/// </summary>
+	/// <param name="e">Event args describing the subview being removed.</param>
+	public virtual void OnRemoved (SuperViewChangedEventArgs e)
+	{
+		var view = e.Child;
+		view.IsAdded = false;
+		view.Removed?.Invoke (this, e);
+	}
 
 
-		#region Focus
-		View _focused = null;
+	void PerformActionForSubview (View subview, Action<View> action)
+	{
+		if (_subviews.Contains (subview)) {
+			action (subview);
+		}
 
-		internal enum Direction {
-			Forward,
-			Backward
+		SetNeedsDisplay ();
+		subview.SetNeedsDisplay ();
+	}
+
+	/// <summary>
+	/// Brings the specified subview to the front so it is drawn on top of any other views.
+	/// </summary>
+	/// <param name="subview">The subview to send to the front</param>
+	/// <remarks>
+	/// <seealso cref="SendSubviewToBack"/>.
+	/// </remarks>
+	public void BringSubviewToFront (View subview) => PerformActionForSubview (subview, x => {
+		_subviews.Remove (x);
+		_subviews.Add (x);
+	});
+
+	/// <summary>
+	/// Sends the specified subview to the front so it is the first view drawn
+	/// </summary>
+	/// <param name="subview">The subview to send to the front</param>
+	/// <remarks>
+	/// <seealso cref="BringSubviewToFront(View)"/>.
+	/// </remarks>
+	public void SendSubviewToBack (View subview) => PerformActionForSubview (subview, x => {
+		_subviews.Remove (x);
+		_subviews.Insert (0, subview);
+	});
+
+	/// <summary>
+	/// Moves the subview backwards in the hierarchy, only one step
+	/// </summary>
+	/// <param name="subview">The subview to send backwards</param>
+	/// <remarks>
+	/// If you want to send the view all the way to the back use SendSubviewToBack.
+	/// </remarks>
+	public void SendSubviewBackwards (View subview) => PerformActionForSubview (subview, x => {
+		var idx = _subviews.IndexOf (x);
+		if (idx > 0) {
+			_subviews.Remove (x);
+			_subviews.Insert (idx - 1, x);
+		}
+	});
+
+	/// <summary>
+	/// Moves the subview backwards in the hierarchy, only one step
+	/// </summary>
+	/// <param name="subview">The subview to send backwards</param>
+	/// <remarks>
+	/// If you want to send the view all the way to the back use SendSubviewToBack.
+	/// </remarks>
+	public void BringSubviewForward (View subview) => PerformActionForSubview (subview, x => {
+		var idx = _subviews.IndexOf (x);
+		if (idx + 1 < _subviews.Count) {
+			_subviews.Remove (x);
+			_subviews.Insert (idx + 1, x);
+		}
+	});
+
+	/// <summary>
+	/// Get the top superview of a given <see cref="View"/>.
+	/// </summary>
+	/// <returns>The superview view.</returns>
+	public View GetTopSuperView (View view = null, View superview = null)
+	{
+		var top = superview ?? Application.Top;
+		for (var v = view?.SuperView ?? this?.SuperView; v != null; v = v.SuperView) {
+			top = v;
+			if (top == superview) {
+				break;
+			}
 		}
 
-		/// <summary>
-		/// Event fired when the view gets focus.
-		/// </summary>
-		public event EventHandler<FocusEventArgs> Enter;
+		return top;
+	}
+
 
-		/// <summary>
-		/// Event fired when the view looses focus.
-		/// </summary>
-		public event EventHandler<FocusEventArgs> Leave;
 
-		Direction _focusDirection;
-		internal Direction FocusDirection {
-			get => SuperView?.FocusDirection ?? _focusDirection;
-			set {
-				if (SuperView != null)
-					SuperView.FocusDirection = value;
-				else
-					_focusDirection = value;
+	#region Focus
+	internal enum Direction {
+		Forward,
+		Backward
+	}
+
+	/// <summary>
+	/// Event fired when the view gets focus.
+	/// </summary>
+	public event EventHandler<FocusEventArgs> Enter;
+
+	/// <summary>
+	/// Event fired when the view looses focus.
+	/// </summary>
+	public event EventHandler<FocusEventArgs> Leave;
+
+	Direction _focusDirection;
+
+	internal Direction FocusDirection {
+		get => SuperView?.FocusDirection ?? _focusDirection;
+		set {
+			if (SuperView != null) {
+				SuperView.FocusDirection = value;
+			} else {
+				_focusDirection = value;
 			}
 		}
+	}
 
 
-		// BUGBUG: v2 - Seems weird that this is in View and not Responder.
-		bool _hasFocus;
+	// BUGBUG: v2 - Seems weird that this is in View and not Responder.
+	bool _hasFocus;
 
-		/// <inheritdoc/>
-		public override bool HasFocus => _hasFocus;
+	/// <inheritdoc/>
+	public override bool HasFocus => _hasFocus;
 
-		void SetHasFocus (bool value, View view, bool force = false)
-		{
-			if (_hasFocus != value || force) {
-				_hasFocus = value;
-				if (value) {
-					OnEnter (view);
-				} else {
-					OnLeave (view);
-				}
-				SetNeedsDisplay ();
+	void SetHasFocus (bool value, View view, bool force = false)
+	{
+		if (_hasFocus != value || force) {
+			_hasFocus = value;
+			if (value) {
+				OnEnter (view);
+			} else {
+				OnLeave (view);
 			}
+			SetNeedsDisplay ();
+		}
 
-			// Remove focus down the chain of subviews if focus is removed
-			if (!value && _focused != null) {
-				var f = _focused;
-				f.OnLeave (view);
-				f.SetHasFocus (false, view);
-				_focused = null;
-			}
+		// Remove focus down the chain of subviews if focus is removed
+		if (!value && Focused != null) {
+			var f = Focused;
+			f.OnLeave (view);
+			f.SetHasFocus (false, view);
+			Focused = null;
 		}
+	}
 
-		/// <summary>
-		/// Event fired when the <see cref="CanFocus"/> value is being changed.
-		/// </summary>
-		public event EventHandler CanFocusChanged;
+	/// <summary>
+	/// Event fired when the <see cref="CanFocus"/> value is being changed.
+	/// </summary>
+	public event EventHandler CanFocusChanged;
 
-		/// <inheritdoc/>
-		public override void OnCanFocusChanged () => CanFocusChanged?.Invoke (this, EventArgs.Empty);
+	/// <inheritdoc/>
+	public override void OnCanFocusChanged () => CanFocusChanged?.Invoke (this, EventArgs.Empty);
+
+	bool _oldCanFocus;
+
+	/// <inheritdoc/>
+	public override bool CanFocus {
+		get => base.CanFocus;
+		set {
+			if (!_addingView && IsInitialized && SuperView?.CanFocus == false && value) {
+				throw new InvalidOperationException ("Cannot set CanFocus to true if the SuperView CanFocus is false!");
+			}
+			if (base.CanFocus != value) {
+				base.CanFocus = value;
 
-		bool _oldCanFocus;
-		/// <inheritdoc/>
-		public override bool CanFocus {
-			get => base.CanFocus;
-			set {
-				if (!_addingView && IsInitialized && SuperView?.CanFocus == false && value) {
-					throw new InvalidOperationException ("Cannot set CanFocus to true if the SuperView CanFocus is false!");
+				switch (value) {
+				case false when _tabIndex > -1:
+					TabIndex = -1;
+					break;
+				case true when SuperView?.CanFocus == false && _addingView:
+					SuperView.CanFocus = true;
+					break;
 				}
-				if (base.CanFocus != value) {
-					base.CanFocus = value;
-
-					switch (value) {
-					case false when _tabIndex > -1:
-						TabIndex = -1;
-						break;
-					case true when SuperView?.CanFocus == false && _addingView:
-						SuperView.CanFocus = true;
-						break;
-					}
 
-					if (value && _tabIndex == -1) {
-						TabIndex = SuperView != null ? SuperView._tabIndexes.IndexOf (this) : -1;
-					}
-					TabStop = value;
+				if (value && _tabIndex == -1) {
+					TabIndex = SuperView != null ? SuperView._tabIndexes.IndexOf (this) : -1;
+				}
+				TabStop = value;
 
-					if (!value && SuperView?.Focused == this) {
-						SuperView._focused = null;
-					}
-					if (!value && HasFocus) {
-						SetHasFocus (false, this);
-						SuperView?.EnsureFocus ();
-						if (SuperView != null && SuperView.Focused == null) {
-							SuperView.FocusNext ();
-							if (SuperView.Focused == null && Application.Current != null) {
-								Application.Current.FocusNext ();
-							}
-							Application.BringOverlappedTopToFront ();
+				if (!value && SuperView?.Focused == this) {
+					SuperView.Focused = null;
+				}
+				if (!value && HasFocus) {
+					SetHasFocus (false, this);
+					SuperView?.EnsureFocus ();
+					if (SuperView != null && SuperView.Focused == null) {
+						SuperView.FocusNext ();
+						if (SuperView.Focused == null && Application.Current != null) {
+							Application.Current.FocusNext ();
 						}
+						Application.BringOverlappedTopToFront ();
 					}
-					if (_subviews != null && IsInitialized) {
-						foreach (var view in _subviews) {
-							if (view.CanFocus != value) {
-								if (!value) {
-									view._oldCanFocus = view.CanFocus;
-									view._oldTabIndex = view._tabIndex;
-									view.CanFocus = false;
-									view._tabIndex = -1;
-								} else {
-									if (_addingView) {
-										view._addingView = true;
-									}
-									view.CanFocus = view._oldCanFocus;
-									view._tabIndex = view._oldTabIndex;
-									view._addingView = false;
+				}
+				if (_subviews != null && IsInitialized) {
+					foreach (var view in _subviews) {
+						if (view.CanFocus != value) {
+							if (!value) {
+								view._oldCanFocus = view.CanFocus;
+								view._oldTabIndex = view._tabIndex;
+								view.CanFocus     = false;
+								view._tabIndex    = -1;
+							} else {
+								if (_addingView) {
+									view._addingView = true;
 								}
+								view.CanFocus    = view._oldCanFocus;
+								view._tabIndex   = view._oldTabIndex;
+								view._addingView = false;
 							}
 						}
 					}
-					OnCanFocusChanged ();
-					SetNeedsDisplay ();
 				}
+				OnCanFocusChanged ();
+				SetNeedsDisplay ();
 			}
 		}
+	}
 
 
-		/// <inheritdoc/>
-		public override bool OnEnter (View view)
-		{
-			var args = new FocusEventArgs (view);
-			Enter?.Invoke (this, args);
-			if (args.Handled) {
-				return true;
-			}
-			if (base.OnEnter (view)) {
-				return true;
-			}
+	/// <inheritdoc/>
+	public override bool OnEnter (View view)
+	{
+		var args = new FocusEventArgs (view);
+		Enter?.Invoke (this, args);
+		if (args.Handled) {
+			return true;
+		}
+		if (base.OnEnter (view)) {
+			return true;
+		}
 
-			return false;
+		return false;
+	}
+
+	/// <inheritdoc/>
+	public override bool OnLeave (View view)
+	{
+		var args = new FocusEventArgs (view);
+		Leave?.Invoke (this, args);
+		if (args.Handled) {
+			return true;
+		}
+		if (base.OnLeave (view)) {
+			return true;
 		}
 
-		/// <inheritdoc/>
-		public override bool OnLeave (View view)
-		{
-			var args = new FocusEventArgs (view);
-			Leave?.Invoke (this, args);
-			if (args.Handled) {
-				return true;
+		Driver?.SetCursorVisibility (CursorVisibility.Invisible);
+		return false;
+	}
+
+	/// <summary>
+	/// Returns the currently focused view inside this view, or null if nothing is focused.
+	/// </summary>
+	/// <value>The focused.</value>
+	public View Focused { get; private set; }
+
+	/// <summary>
+	/// Returns the most focused view in the chain of subviews (the leaf view that has the focus).
+	/// </summary>
+	/// <value>The most focused View.</value>
+	public View MostFocused {
+		get {
+			if (Focused == null) {
+				return null;
 			}
-			if (base.OnLeave (view)) {
-				return true;
+			var most = Focused.MostFocused;
+			if (most != null) {
+				return most;
 			}
-
-			Driver?.SetCursorVisibility (CursorVisibility.Invisible);
-			return false;
+			return Focused;
 		}
+	}
 
-		/// <summary>
-		/// Returns the currently focused view inside this view, or null if nothing is focused.
-		/// </summary>
-		/// <value>The focused.</value>
-		public View Focused => _focused;
-
-		/// <summary>
-		/// Returns the most focused view in the chain of subviews (the leaf view that has the focus).
-		/// </summary>
-		/// <value>The most focused View.</value>
-		public View MostFocused {
-			get {
-				if (Focused == null)
-					return null;
-				var most = Focused.MostFocused;
-				if (most != null)
-					return most;
-				return Focused;
-			}
+	/// <summary>
+	/// Causes the specified subview to have focus.
+	/// </summary>
+	/// <param name="view">View.</param>
+	void SetFocus (View view)
+	{
+		if (view == null) {
+			return;
+		}
+		//Console.WriteLine ($"Request to focus {view}");
+		if (!view.CanFocus || !view.Visible || !view.Enabled) {
+			return;
 		}
+		if (Focused?._hasFocus == true && Focused == view) {
+			return;
+		}
+		if (Focused?._hasFocus == true && Focused?.SuperView == view || view == this) {
 
-		/// <summary>
-		/// Causes the specified subview to have focus.
-		/// </summary>
-		/// <param name="view">View.</param>
-		void SetFocus (View view)
-		{
-			if (view == null) {
-				return;
-			}
-			//Console.WriteLine ($"Request to focus {view}");
-			if (!view.CanFocus || !view.Visible || !view.Enabled) {
-				return;
+			if (!view._hasFocus) {
+				view._hasFocus = true;
 			}
-			if (_focused?._hasFocus == true && _focused == view) {
-				return;
+			return;
+		}
+		// Make sure that this view is a subview
+		View c;
+		for (c = view._superView; c != null; c = c._superView) {
+			if (c == this) {
+				break;
 			}
-			if ((_focused?._hasFocus == true && _focused?.SuperView == view) || view == this) {
+		}
+		if (c == null) {
+			throw new ArgumentException ("the specified view is not part of the hierarchy of this view");
+		}
 
-				if (!view._hasFocus) {
-					view._hasFocus = true;
-				}
-				return;
-			}
-			// Make sure that this view is a subview
-			View c;
-			for (c = view._superView; c != null; c = c._superView)
-				if (c == this)
-					break;
-			if (c == null)
-				throw new ArgumentException ("the specified view is not part of the hierarchy of this view");
+		if (Focused != null) {
+			Focused.SetHasFocus (false, view);
+		}
 
-			if (_focused != null)
-				_focused.SetHasFocus (false, view);
+		var f = Focused;
+		Focused = view;
+		Focused.SetHasFocus (true, f);
+		Focused.EnsureFocus ();
 
-			var f = _focused;
-			_focused = view;
-			_focused.SetHasFocus (true, f);
-			_focused.EnsureFocus ();
+		// Send focus upwards
+		if (SuperView != null) {
+			SuperView.SetFocus (this);
+		} else {
+			SetFocus (this);
+		}
+	}
 
-			// Send focus upwards
-			if (SuperView != null) {
-				SuperView.SetFocus (this);
-			} else {
-				SetFocus (this);
+	/// <summary>
+	/// Causes the specified view and the entire parent hierarchy to have the focused order updated.
+	/// </summary>
+	public void SetFocus ()
+	{
+		if (!CanBeVisible (this) || !Enabled) {
+			if (HasFocus) {
+				SetHasFocus (false, this);
 			}
+			return;
 		}
 
-		/// <summary>
-		/// Causes the specified view and the entire parent hierarchy to have the focused order updated.
-		/// </summary>
-		public void SetFocus ()
-		{
-			if (!CanBeVisible (this) || !Enabled) {
-				if (HasFocus) {
-					SetHasFocus (false, this);
-				}
-				return;
-			}
+		if (SuperView != null) {
+			SuperView.SetFocus (this);
+		} else {
+			SetFocus (this);
+		}
+	}
 
-			if (SuperView != null) {
-				SuperView.SetFocus (this);
+	/// <summary>
+	/// Finds the first view in the hierarchy that wants to get the focus if nothing is currently focused, otherwise, does
+	/// nothing.
+	/// </summary>
+	public void EnsureFocus ()
+	{
+		if (Focused == null && _subviews?.Count > 0) {
+			if (FocusDirection == Direction.Forward) {
+				FocusFirst ();
 			} else {
-				SetFocus (this);
+				FocusLast ();
 			}
 		}
+	}
 
-		/// <summary>
-		/// Finds the first view in the hierarchy that wants to get the focus if nothing is currently focused, otherwise, does nothing.
-		/// </summary>
-		public void EnsureFocus ()
-		{
-			if (_focused == null && _subviews?.Count > 0) {
-				if (FocusDirection == Direction.Forward) {
-					FocusFirst ();
-				} else {
-					FocusLast ();
-				}
-			}
+	/// <summary>
+	/// Focuses the first focusable subview if one exists.
+	/// </summary>
+	public void FocusFirst ()
+	{
+		if (!CanBeVisible (this)) {
+			return;
 		}
 
-		/// <summary>
-		/// Focuses the first focusable subview if one exists.
-		/// </summary>
-		public void FocusFirst ()
-		{
-			if (!CanBeVisible (this)) {
-				return;
-			}
+		if (_tabIndexes == null) {
+			SuperView?.SetFocus (this);
+			return;
+		}
 
-			if (_tabIndexes == null) {
-				SuperView?.SetFocus (this);
+		foreach (var view in _tabIndexes) {
+			if (view.CanFocus && view._tabStop && view.Visible && view.Enabled) {
+				SetFocus (view);
 				return;
 			}
+		}
+	}
 
-			foreach (var view in _tabIndexes) {
-				if (view.CanFocus && view._tabStop && view.Visible && view.Enabled) {
-					SetFocus (view);
-					return;
-				}
-			}
+	/// <summary>
+	/// Focuses the last focusable subview if one exists.
+	/// </summary>
+	public void FocusLast ()
+	{
+		if (!CanBeVisible (this)) {
+			return;
 		}
 
-		/// <summary>
-		/// Focuses the last focusable subview if one exists.
-		/// </summary>
-		public void FocusLast ()
-		{
-			if (!CanBeVisible (this)) {
-				return;
-			}
+		if (_tabIndexes == null) {
+			SuperView?.SetFocus (this);
+			return;
+		}
 
-			if (_tabIndexes == null) {
-				SuperView?.SetFocus (this);
+		for (var i = _tabIndexes.Count; i > 0;) {
+			i--;
+
+			var v = _tabIndexes [i];
+			if (v.CanFocus && v._tabStop && v.Visible && v.Enabled) {
+				SetFocus (v);
 				return;
 			}
+		}
+	}
 
-			for (var i = _tabIndexes.Count; i > 0;) {
-				i--;
+	/// <summary>
+	/// Focuses the previous view.
+	/// </summary>
+	/// <returns><see langword="true"/> if previous was focused, <see langword="false"/> otherwise.</returns>
+	public bool FocusPrev ()
+	{
+		if (!CanBeVisible (this)) {
+			return false;
+		}
 
-				var v = _tabIndexes [i];
-				if (v.CanFocus && v._tabStop && v.Visible && v.Enabled) {
-					SetFocus (v);
-					return;
-				}
-			}
+		FocusDirection = Direction.Backward;
+		if (_tabIndexes == null || _tabIndexes.Count == 0) {
+			return false;
 		}
 
-		/// <summary>
-		/// Focuses the previous view.
-		/// </summary>
-		/// <returns><see langword="true"/> if previous was focused, <see langword="false"/> otherwise.</returns>
-		public bool FocusPrev ()
-		{
-			if (!CanBeVisible (this)) {
-				return false;
-			}
+		if (Focused == null) {
+			FocusLast ();
+			return Focused != null;
+		}
 
-			FocusDirection = Direction.Backward;
-			if (_tabIndexes == null || _tabIndexes.Count == 0)
-				return false;
+		var focusedIdx = -1;
+		for (var i = _tabIndexes.Count; i > 0;) {
+			i--;
+			var w = _tabIndexes [i];
 
-			if (_focused == null) {
-				FocusLast ();
-				return _focused != null;
+			if (w.HasFocus) {
+				if (w.FocusPrev ()) {
+					return true;
+				}
+				focusedIdx = i;
+				continue;
 			}
+			if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled) {
+				Focused.SetHasFocus (false, w);
 
-			var focusedIdx = -1;
-			for (var i = _tabIndexes.Count; i > 0;) {
-				i--;
-				var w = _tabIndexes [i];
-
-				if (w.HasFocus) {
-					if (w.FocusPrev ())
-						return true;
-					focusedIdx = i;
-					continue;
+				if (w.CanFocus && w._tabStop && w.Visible && w.Enabled) {
+					w.FocusLast ();
 				}
-				if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled) {
-					_focused.SetHasFocus (false, w);
-
-					if (w.CanFocus && w._tabStop && w.Visible && w.Enabled)
-						w.FocusLast ();
 
-					SetFocus (w);
-					return true;
-				}
-			}
-			if (_focused != null) {
-				_focused.SetHasFocus (false, this);
-				_focused = null;
+				SetFocus (w);
+				return true;
 			}
-			return false;
 		}
+		if (Focused != null) {
+			Focused.SetHasFocus (false, this);
+			Focused = null;
+		}
+		return false;
+	}
 
-		/// <summary>
-		/// Focuses the next view.
-		/// </summary>
-		/// <returns><see langword="true"/> if next was focused, <see langword="false"/> otherwise.</returns>
-		public bool FocusNext ()
-		{
-			if (!CanBeVisible (this)) {
-				return false;
-			}
-
-			FocusDirection = Direction.Forward;
-			if (_tabIndexes == null || _tabIndexes.Count == 0)
-				return false;
+	/// <summary>
+	/// Focuses the next view.
+	/// </summary>
+	/// <returns><see langword="true"/> if next was focused, <see langword="false"/> otherwise.</returns>
+	public bool FocusNext ()
+	{
+		if (!CanBeVisible (this)) {
+			return false;
+		}
 
-			if (_focused == null) {
-				FocusFirst ();
-				return _focused != null;
-			}
-			var focusedIdx = -1;
-			for (var i = 0; i < _tabIndexes.Count; i++) {
-				var w = _tabIndexes [i];
-
-				if (w.HasFocus) {
-					if (w.FocusNext ())
-						return true;
-					focusedIdx = i;
-					continue;
-				}
-				if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled) {
-					_focused.SetHasFocus (false, w);
+		FocusDirection = Direction.Forward;
+		if (_tabIndexes == null || _tabIndexes.Count == 0) {
+			return false;
+		}
 
-					if (w.CanFocus && w._tabStop && w.Visible && w.Enabled)
-						w.FocusFirst ();
+		if (Focused == null) {
+			FocusFirst ();
+			return Focused != null;
+		}
+		var focusedIdx = -1;
+		for (var i = 0; i < _tabIndexes.Count; i++) {
+			var w = _tabIndexes [i];
 
-					SetFocus (w);
+			if (w.HasFocus) {
+				if (w.FocusNext ()) {
 					return true;
 				}
+				focusedIdx = i;
+				continue;
 			}
-			if (_focused != null) {
-				_focused.SetHasFocus (false, this);
-				_focused = null;
-			}
-			return false;
-		}
+			if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled) {
+				Focused.SetHasFocus (false, w);
 
-		View GetMostFocused (View view)
-		{
-			if (view == null) {
-				return null;
+				if (w.CanFocus && w._tabStop && w.Visible && w.Enabled) {
+					w.FocusFirst ();
+				}
+
+				SetFocus (w);
+				return true;
 			}
+		}
+		if (Focused != null) {
+			Focused.SetHasFocus (false, this);
+			Focused = null;
+		}
+		return false;
+	}
 
-			return view._focused != null ? GetMostFocused (view._focused) : view;
+	View GetMostFocused (View view)
+	{
+		if (view == null) {
+			return null;
 		}
 
-		/// <summary>
-		///   Positions the cursor in the right position based on the currently focused view in the chain.
-		/// </summary>
-		///    Views that are focusable should override <see cref="PositionCursor"/> to ensure
-		///    the cursor is placed in a location that makes sense. Unix terminals do not have
-		///    a way of hiding the cursor, so it can be distracting to have the cursor left at
-		///    the last focused view. Views should make sure that they place the cursor
-		///    in a visually sensible place.
-		public virtual void PositionCursor ()
-		{
-			if (!CanBeVisible (this) || !Enabled) {
-				return;
-			}
+		return view.Focused != null ? GetMostFocused (view.Focused) : view;
+	}
 
-			// BUGBUG: v2 - This needs to support children of Frames too
+	/// <summary>
+	/// Positions the cursor in the right position based on the currently focused view in the chain.
+	/// </summary>
+	/// Views that are focusable should override
+	/// <see cref="PositionCursor"/>
+	/// to ensure
+	/// the cursor is placed in a location that makes sense. Unix terminals do not have
+	/// a way of hiding the cursor, so it can be distracting to have the cursor left at
+	/// the last focused view. Views should make sure that they place the cursor
+	/// in a visually sensible place.
+	public virtual void PositionCursor ()
+	{
+		if (!CanBeVisible (this) || !Enabled) {
+			return;
+		}
 
-			if (_focused == null && SuperView != null) {
-				SuperView.EnsureFocus ();
-			} else if (_focused?.Visible == true && _focused?.Enabled == true && _focused?.Frame.Width > 0 && _focused.Frame.Height > 0) {
-				_focused.PositionCursor ();
-			} else if (_focused?.Visible == true && _focused?.Enabled == false) {
-				_focused = null;
-			} else if (CanFocus && HasFocus && Visible && Frame.Width > 0 && Frame.Height > 0) {
-				Move (TextFormatter.HotKeyPos == -1 ? 0 : TextFormatter.CursorPosition, 0);
-			} else {
-				Move (_frame.X, _frame.Y);
-			}
+		// BUGBUG: v2 - This needs to support children of Frames too
+
+		if (Focused == null && SuperView != null) {
+			SuperView.EnsureFocus ();
+		} else if (Focused?.Visible == true && Focused?.Enabled == true && Focused?.Frame.Width > 0 && Focused.Frame.Height > 0) {
+			Focused.PositionCursor ();
+		} else if (Focused?.Visible == true && Focused?.Enabled == false) {
+			Focused = null;
+		} else if (CanFocus && HasFocus && Visible && Frame.Width > 0 && Frame.Height > 0) {
+			Move (TextFormatter.HotKeyPos == -1 ? 0 : TextFormatter.CursorPosition, 0);
+		} else {
+			Move (_frame.X, _frame.Y);
 		}
-		#endregion Focus
 	}
-}
+	#endregion Focus
+}

+ 35 - 27
Terminal.Gui/View/ViewText.cs

@@ -190,37 +190,43 @@ public partial class View {
 			}
 			sizeRequired = Bounds.Size;
 
-			if (!AutoSize && !string.IsNullOrEmpty (TextFormatter.Text)) {
-				switch (TextFormatter.IsVerticalDirection (TextDirection)) {
-				case true:
-					var colWidth = TextFormatter.GetSumMaxCharWidth (new List<string> { TextFormatter.Text }, 0, 1);
-					// TODO: v2 - This uses frame.Width; it should only use Bounds
-					if (_frame.Width < colWidth &&
-					    (Width == null ||
-					     Bounds.Width >= 0 &&
-					     Width is Dim.DimAbsolute &&
-					     Width.Anchor (0) >= 0 &&
-					     Width.Anchor (0) < colWidth)) {
-						sizeRequired = new Size (colWidth, Bounds.Height);
-						return true;
-					}
-					break;
-				default:
-					if (_frame.Height < 1 &&
-					    (Height == null ||
-					     Height is Dim.DimAbsolute &&
-					     Height.Anchor (0) == 0)) {
-						sizeRequired = new Size (Bounds.Width, 1);
-						return true;
-					}
-					break;
+			if (AutoSize || string.IsNullOrEmpty (TextFormatter.Text)) {
+				return false;
+			}
+			
+			switch (TextFormatter.IsVerticalDirection (TextDirection)) {
+			case true:
+				var colWidth = TextFormatter.GetSumMaxCharWidth (new List<string> { TextFormatter.Text }, 0, 1);
+				// TODO: v2 - This uses frame.Width; it should only use Bounds
+				if (_frame.Width < colWidth &&
+				    (Width == null ||
+				     Bounds.Width >= 0 &&
+				     Width is Dim.DimAbsolute &&
+				     Width.Anchor (0) >= 0 &&
+				     Width.Anchor (0) < colWidth)) {
+					sizeRequired = new Size (colWidth, Bounds.Height);
+					return true;
+				}
+				break;
+			default:
+				if (_frame.Height < 1 &&
+				    (Height == null ||
+				     Height is Dim.DimAbsolute &&
+				     Height.Anchor (0) == 0)) {
+					sizeRequired = new Size (Bounds.Width, 1);
+					return true;
 				}
+				break;
 			}
 			return false;
 		}
 
 		if (GetMinimumSizeOfText (out var size)) {
+			// TODO: This is a hack.
+			//_width  = size.Width;
+			//_height = size.Height;
 			_frame = new Rect (_frame.Location, size);
+			//throw new InvalidOperationException ("This is a hack.");
 			return true;
 		}
 		return false;
@@ -275,10 +281,12 @@ public partial class View {
 	{
 		if (!IsInitialized) {
 			TextFormatter.Size = Size.Empty;
+			return;
 		}
 
 		if (string.IsNullOrEmpty (TextFormatter.Text)) {
 			TextFormatter.Size = Bounds.Size;
+			return;
 		}
 
 		TextFormatter.Size = new Size (Bounds.Size.Width + GetHotKeySpecifierLength (),
@@ -299,9 +307,9 @@ public partial class View {
 			x = Bounds.X;
 			y = Bounds.Y;
 		}
-		var rect = TextFormatter.CalcRect (x, y, TextFormatter.Text, TextFormatter.Direction);
-		var newWidth = rect.Size.Width - GetHotKeySpecifierLength () + Margin.Thickness.Horizontal + Border.Thickness.Horizontal + Padding.Thickness.Horizontal;
-		var newHeight = rect.Size.Height - GetHotKeySpecifierLength (false) + Margin.Thickness.Vertical + Border.Thickness.Vertical + Padding.Thickness.Vertical;
+		var rect = TextFormatter.CalcRect (x, y, TextFormatter.Text, TextFormatter.Direction); 
+		int newWidth = rect.Size.Width - GetHotKeySpecifierLength () + (Margin == null ? 0 : Margin.Thickness.Horizontal + Border.Thickness.Horizontal + Padding.Thickness.Horizontal);
+		int newHeight = rect.Size.Height - GetHotKeySpecifierLength (false) + (Margin == null ? 0 : Margin.Thickness.Vertical + Border.Thickness.Vertical + Padding.Thickness.Vertical);
 		return new Size (newWidth, newHeight);
 	}
 

+ 98 - 123
Terminal.Gui/Views/Label.cs

@@ -1,141 +1,116 @@
-//
-// Label.cs: Label control
-//
-// Authors:
-//   Miguel de Icaza ([email protected])
-//
+using System;
+
+namespace Terminal.Gui; 
+
+/// <summary>
+/// The Label <see cref="View"/> displays a string at a given position and supports multiple lines separated by newline
+/// characters.
+/// Multi-line Labels support word wrap.
+/// </summary>
+/// <remarks>
+/// The <see cref="Label"/> view is functionality identical to <see cref="View"/> and is included for API backwards
+/// compatibility.
+/// </remarks>
+public class Label : View {
+	/// <inheritdoc/>
+	public Label () => SetInitialProperties ();
+
+	/// <inheritdoc/>
+	public Label (Rect frame, bool autosize = false) : base (frame) => SetInitialProperties (autosize);
+
+	/// <inheritdoc/>
+	public Label (string text, bool autosize = true) : base (text) => SetInitialProperties (autosize);
+
+	/// <inheritdoc/>
+	public Label (Rect rect, string text, bool autosize = false) : base (rect, text) => SetInitialProperties (autosize);
+
+	/// <inheritdoc/>
+	public Label (int x, int y, string text, bool autosize = true) : base (x, y, text) => SetInitialProperties (autosize);
+
+	/// <inheritdoc/>
+	public Label (string text, TextDirection direction, bool autosize = true)
+		: base (text, direction) => SetInitialProperties (autosize);
+
+	void SetInitialProperties (bool autosize = true)
+	{
+		Height   = 1;
+		AutoSize = autosize;
+		// Things this view knows how to do
+		AddCommand (Command.Default, () => {
+			// BUGBUG: This is a hack, but it does work.
+			var can = CanFocus;
+			CanFocus = true;
+			SetFocus ();
+			SuperView.FocusNext ();
+			CanFocus = can;
+			return true;
+		});
+		AddCommand (Command.Accept, () => AcceptKey ());
+
+		// Default key bindings for this view
+		KeyBindings.Add (KeyCode.Space, Command.Accept);
+	}
 
-using System;
-using System.Text;
+	bool AcceptKey ()
+	{
+		if (!HasFocus) {
+			SetFocus ();
+		}
+		OnClicked ();
+		return true;
+	}
 
-namespace Terminal.Gui {
 	/// <summary>
-	/// The Label <see cref="View"/> displays a string at a given position and supports multiple lines separated by newline characters.
-	/// Multi-line Labels support word wrap.
+	/// The event fired when the user clicks the primary mouse button within the Bounds of this <see cref="View"/>
+	/// or if the user presses the action key while this view is focused. (TODO: IsDefault)
 	/// </summary>
 	/// <remarks>
-	/// The <see cref="Label"/> view is functionality identical to <see cref="View"/> and is included for API backwards compatibility.
+	/// Client code can hook up to this event, it is
+	/// raised when the button is activated either with
+	/// the mouse or the keyboard.
 	/// </remarks>
-	public class Label : View {
-		/// <inheritdoc/>
-		public Label ()
-		{
-			SetInitialProperties ();
-		}
-
-		/// <inheritdoc/>
-		public Label (Rect frame, bool autosize = false) : base (frame)
-		{
-			SetInitialProperties (autosize);
-		}
+	public event EventHandler Clicked;
 
-		/// <inheritdoc/>
-		public Label (string text, bool autosize = true) : base (text)
-		{
-			SetInitialProperties (autosize);
-		}
-
-		/// <inheritdoc/>
-		public Label (Rect rect, string text, bool autosize = false) : base (rect, text)
-		{
-			SetInitialProperties (autosize);
-		}
-
-		/// <inheritdoc/>
-		public Label (int x, int y, string text, bool autosize = true) : base (x, y, text)
-		{
-			SetInitialProperties (autosize);
-		}
-
-		/// <inheritdoc/>
-		public Label (string text, TextDirection direction, bool autosize = true)
-			: base (text, direction)
-		{
-			SetInitialProperties (autosize);
+	/// <summary>
+	/// Method invoked when a mouse event is generated
+	/// </summary>
+	/// <param name="mouseEvent"></param>
+	/// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
+	public override bool OnMouseEvent (MouseEvent mouseEvent)
+	{
+		var args = new MouseEventEventArgs (mouseEvent);
+		if (OnMouseClick (args)) {
+			return true;
 		}
-
-		void SetInitialProperties (bool autosize = true)
-		{
-			Height = 1;
-			AutoSize = autosize;
-			// Things this view knows how to do
-			AddCommand (Command.Default, () => {
-				// BUGBUG: This is a hack, but it does work.
-				var can = CanFocus;
-				CanFocus = true;
-				SetFocus ();
-				SuperView.FocusNext ();
-				CanFocus = can;
-				return true;
-			});
-			AddCommand (Command.Accept, () => AcceptKey ());
-
-			// Default key bindings for this view
-			KeyBindings.Add (KeyCode.Space, Command.Accept);
+		if (MouseEvent (mouseEvent)) {
+			return true;
 		}
 
-		bool AcceptKey ()
-		{
-			if (!HasFocus) {
+		if (mouseEvent.Flags == MouseFlags.Button1Clicked) {
+			if (!HasFocus && SuperView != null) {
+				if (!SuperView.HasFocus) {
+					SuperView.SetFocus ();
+				}
 				SetFocus ();
+				SetNeedsDisplay ();
 			}
+
 			OnClicked ();
 			return true;
 		}
-		
-		/// <summary>
-		///   The event fired when the user clicks the primary mouse button within the Bounds of this <see cref="View"/>
-		///   or if the user presses the action key while this view is focused. (TODO: IsDefault)
-		/// </summary>
-		/// <remarks>
-		///   Client code can hook up to this event, it is
-		///   raised when the button is activated either with
-		///   the mouse or the keyboard.
-		/// </remarks>
-		public event EventHandler Clicked;
-
-		/// <summary>
-		/// Method invoked when a mouse event is generated
-		/// </summary>
-		/// <param name="mouseEvent"></param>
-		/// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
-		public override bool OnMouseEvent (MouseEvent mouseEvent)
-		{
-			MouseEventEventArgs args = new MouseEventEventArgs (mouseEvent);
-			if (OnMouseClick (args))
-				return true;
-			if (MouseEvent (mouseEvent))
-				return true;
-
-			if (mouseEvent.Flags == MouseFlags.Button1Clicked) {
-				if (!HasFocus && SuperView != null) {
-					if (!SuperView.HasFocus) {
-						SuperView.SetFocus ();
-					}
-					SetFocus ();
-					SetNeedsDisplay ();
-				}
-
-				OnClicked ();
-				return true;
-			}
-			return false;
-		}
-
-		///<inheritdoc/>
-		public override bool OnEnter (View view)
-		{
-			Application.Driver.SetCursorVisibility (CursorVisibility.Invisible);
+		return false;
+	}
 
-			return base.OnEnter (view);
-		}
+	///<inheritdoc/>
+	public override bool OnEnter (View view)
+	{
+		Application.Driver.SetCursorVisibility (CursorVisibility.Invisible);
 
-		/// <summary>
-		/// Virtual method to invoke the <see cref="Clicked"/> event.
-		/// </summary>
-		public virtual void OnClicked ()
-		{
-			Clicked?.Invoke (this, EventArgs.Empty);
-		}
+		return base.OnEnter (view);
 	}
-}
+
+	/// <summary>
+	/// Virtual method to invoke the <see cref="Clicked"/> event.
+	/// </summary>
+	public virtual void OnClicked () => Clicked?.Invoke (this, EventArgs.Empty);
+}

+ 314 - 287
UICatalog/Scenarios/ListColumns.cs

@@ -3,331 +3,358 @@ using System.Collections;
 using System.Collections.Generic;
 using System.Data;
 using Terminal.Gui;
-using static Terminal.Gui.TableView;
-
-namespace UICatalog.Scenarios {
-
-	[ScenarioMetadata (Name: "ListColumns", Description: "Implements a columned list via a data table.")]
-	[ScenarioCategory ("TableView")]
-	[ScenarioCategory ("Controls")]
-	[ScenarioCategory ("Dialogs")]
-	[ScenarioCategory ("Text and Formatting")]
-	[ScenarioCategory ("Top Level Windows")]
-	public class ListColumns : Scenario {
-		TableView listColView;
-		DataTable currentTable;
-		private MenuItem _miCellLines;
-		private MenuItem _miExpandLastColumn;
-		private MenuItem _miAlwaysUseNormalColorForVerticalCellLines;
-		private MenuItem _miSmoothScrolling;
-		private MenuItem _miAlternatingColors;
-		private MenuItem _miCursor;
-		private MenuItem _miTopline;
-		private MenuItem _miBottomline;
-		private MenuItem _miOrientVertical;
-		private MenuItem _miScrollParallel;
-
-		ColorScheme alternatingColorScheme;
-
-		public override void Setup ()
-		{
-			Win.Title = this.GetName ();
-			Win.Y = 1; // menu
-			Win.Height = Dim.Fill (1); // status bar
-
-			this.listColView = new TableView () {
-				X = 0,
-				Y = 0,
-				Width = Dim.Fill (),
-				Height = Dim.Fill (1),
-				Style = new TableStyle {
-					ShowHeaders = false,
-					ShowHorizontalHeaderOverline = false,
-					ShowHorizontalHeaderUnderline = false,
-					ShowHorizontalBottomline = false,
-					ExpandLastColumn = false,
-				}
-			};
-			var listColStyle = new ListColumnStyle ();
-
-			var menu = new MenuBar (new MenuBarItem [] {
-				new MenuBarItem ("_File", new MenuItem [] {
-					new MenuItem ("Open_BigListExample", "", () => OpenSimpleList (true)),
-					new MenuItem ("Open_SmListExample", "", () => OpenSimpleList (false)),
-					new MenuItem ("_CloseExample", "", () => CloseExample ()),
-					new MenuItem ("_Quit", "", () => Quit()),
-				}),
-				new MenuBarItem ("_View", new MenuItem [] {
-					_miTopline = new MenuItem ("_TopLine", "", () => ToggleTopline ()) { Checked = listColView.Style.ShowHorizontalHeaderOverline, CheckType = MenuItemCheckStyle.Checked },
-					_miBottomline = new MenuItem ("_BottomLine", "", () => ToggleBottomline ()) { Checked = listColView.Style.ShowHorizontalBottomline, CheckType = MenuItemCheckStyle.Checked },
-					_miCellLines = new MenuItem ("_CellLines", "", () => ToggleCellLines ()) { Checked = listColView.Style.ShowVerticalCellLines, CheckType = MenuItemCheckStyle.Checked },
-					_miExpandLastColumn = new MenuItem ("_ExpandLastColumn", "", () => ToggleExpandLastColumn ()) { Checked = listColView.Style.ExpandLastColumn, CheckType = MenuItemCheckStyle.Checked },
-					_miAlwaysUseNormalColorForVerticalCellLines = new MenuItem ("_AlwaysUseNormalColorForVerticalCellLines", "", () => ToggleAlwaysUseNormalColorForVerticalCellLines ()) { Checked = listColView.Style.AlwaysUseNormalColorForVerticalCellLines, CheckType = MenuItemCheckStyle.Checked },
-					_miSmoothScrolling = new MenuItem ("_SmoothHorizontalScrolling", "", () => ToggleSmoothScrolling ()) { Checked = listColView.Style.SmoothHorizontalScrolling, CheckType = MenuItemCheckStyle.Checked },
-					_miAlternatingColors = new MenuItem ("Alternating Colors", "", () => ToggleAlternatingColors ()) { CheckType = MenuItemCheckStyle.Checked},
-					_miCursor = new MenuItem ("Invert Selected Cell First Character", "", () => ToggleInvertSelectedCellFirstCharacter ()) { Checked = listColView.Style.InvertSelectedCellFirstCharacter,CheckType = MenuItemCheckStyle.Checked},
-				}),
-				new MenuBarItem ("_List", new MenuItem [] {
-					//new MenuItem ("_Hide Headers", "", HideHeaders),
-					_miOrientVertical = new MenuItem ("_OrientVertical", "", () => ToggleVerticalOrientation ()) { Checked = listColStyle.Orientation == Orientation.Vertical, CheckType = MenuItemCheckStyle.Checked },
-					_miScrollParallel = new MenuItem ("_ScrollParallel", "", () => ToggleScrollParallel ()) { Checked = listColStyle.ScrollParallel, CheckType = MenuItemCheckStyle.Checked },
-					new MenuItem ("Set _Max Cell Width", "", SetListMaxWidth),
-					new MenuItem ("Set Mi_n Cell Width", "", SetListMinWidth),
-				}),
-			});
-
-			Application.Top.Add (menu);
-
-			var statusBar = new StatusBar (new StatusItem [] {
-				new StatusItem(KeyCode.F2, "~F2~ OpenBigListEx", () => OpenSimpleList (true)),
-				new StatusItem(KeyCode.F3, "~F3~ CloseExample", () => CloseExample ()),
-				new StatusItem(KeyCode.F4, "~F4~ OpenSmListEx", () => OpenSimpleList (false)),
-				new StatusItem(Application.QuitKey, $"{Application.QuitKey} to Quit", () => Quit()),
-			});
-			Application.Top.Add (statusBar);
-
-			Win.Add (listColView);
-
-			var selectedCellLabel = new Label () {
-				X = 0,
-				Y = Pos.Bottom (listColView),
-				Text = "0,0",
-				Width = Dim.Fill (),
-				TextAlignment = TextAlignment.Right
-
-			};
-
-			Win.Add (selectedCellLabel);
-
-			listColView.SelectedCellChanged += (s, e) => { selectedCellLabel.Text = $"{listColView.SelectedRow},{listColView.SelectedColumn}"; };
-			listColView.KeyDown += TableViewKeyPress;
-
-			SetupScrollBar ();
-
-			alternatingColorScheme = new ColorScheme () {
-
-				Disabled = Win.ColorScheme.Disabled,
-				HotFocus = Win.ColorScheme.HotFocus,
-				Focus = Win.ColorScheme.Focus,
-				Normal = new Attribute (Color.White, Color.BrightBlue)
-			};
-
-			// if user clicks the mouse in TableView
-			listColView.MouseClick += (s, e) => {
-
-				listColView.ScreenToCell (e.MouseEvent.X, e.MouseEvent.Y, out int? clickedCol);
-			};
-
-			listColView.KeyBindings.Add (KeyCode.Space, Command.ToggleChecked);
-		}
-
-		private void SetupScrollBar ()
-		{
-			var scrollBar = new ScrollBarView (listColView, true); // (listColView, true, true);
 
-			scrollBar.ChangedPosition += (s, e) => {
-				listColView.RowOffset = scrollBar.Position;
-				if (listColView.RowOffset != scrollBar.Position) {
-					scrollBar.Position = listColView.RowOffset;
-				}
-				listColView.SetNeedsDisplay ();
-			};
-			/*
-			scrollBar.OtherScrollBarView.ChangedPosition += (s,e) => {
-				listColView.ColumnOffset = scrollBar.OtherScrollBarView.Position;
-				if (listColView.ColumnOffset != scrollBar.OtherScrollBarView.Position) {
-					scrollBar.OtherScrollBarView.Position = listColView.ColumnOffset;
+namespace UICatalog.Scenarios;
+
+[ScenarioMetadata ("ListColumns", "Implements a columned list via a data table.")]
+[ScenarioCategory ("TableView")]
+[ScenarioCategory ("Controls")]
+[ScenarioCategory ("Dialogs")]
+[ScenarioCategory ("Text and Formatting")]
+[ScenarioCategory ("Top Level Windows")]
+public class ListColumns : Scenario {
+	MenuItem _miAlternatingColors;
+	MenuItem _miAlwaysUseNormalColorForVerticalCellLines;
+	MenuItem _miBottomline;
+	MenuItem _miCellLines;
+	MenuItem _miCursor;
+	MenuItem _miExpandLastColumn;
+	MenuItem _miOrientVertical;
+	MenuItem _miScrollParallel;
+	MenuItem _miSmoothScrolling;
+	MenuItem _miTopline;
+
+	ColorScheme alternatingColorScheme;
+	DataTable currentTable;
+	TableView listColView;
+
+	public override void Setup ()
+	{
+		Win.Title = GetName ();
+		Win.Y = 1;                 // menu
+		Win.Height = Dim.Fill (1); // status bar
+
+		listColView = new TableView {
+			X = 0,
+			Y = 0,
+			Width = Dim.Fill (),
+			Height = Dim.Fill (1),
+			Style = new TableStyle {
+				ShowHeaders = false,
+				ShowHorizontalHeaderOverline = false,
+				ShowHorizontalHeaderUnderline = false,
+				ShowHorizontalBottomline = false,
+				ExpandLastColumn = false
+			}
+		};
+		var listColStyle = new ListColumnStyle ();
+
+		var menu = new MenuBar (new MenuBarItem [] {
+			new ("_File", new MenuItem [] {
+				new ("Open_BigListExample", "", () => OpenSimpleList (true)),
+				new ("Open_SmListExample", "", () => OpenSimpleList (false)),
+				new ("_CloseExample", "", () => CloseExample ()),
+				new ("_Quit", "", () => Quit ())
+			}),
+			new ("_View", new [] {
+				_miTopline = new MenuItem ("_TopLine", "", () => ToggleTopline ()) {
+					Checked = listColView.Style.ShowHorizontalHeaderOverline,
+					CheckType = MenuItemCheckStyle.Checked
+				},
+				_miBottomline = new MenuItem ("_BottomLine", "", () => ToggleBottomline ()) {
+					Checked = listColView.Style.ShowHorizontalBottomline,
+					CheckType = MenuItemCheckStyle.Checked
+				},
+				_miCellLines = new MenuItem ("_CellLines", "", () => ToggleCellLines ()) {
+					Checked = listColView.Style.ShowVerticalCellLines,
+					CheckType = MenuItemCheckStyle.Checked
+				},
+				_miExpandLastColumn = new MenuItem ("_ExpandLastColumn", "", () => ToggleExpandLastColumn ()) {
+					Checked = listColView.Style.ExpandLastColumn,
+					CheckType = MenuItemCheckStyle.Checked
+				},
+				_miAlwaysUseNormalColorForVerticalCellLines =
+					new MenuItem ("_AlwaysUseNormalColorForVerticalCellLines", "",
+						() => ToggleAlwaysUseNormalColorForVerticalCellLines ()) {
+						Checked = listColView.Style.AlwaysUseNormalColorForVerticalCellLines,
+						CheckType = MenuItemCheckStyle.Checked
+					},
+				_miSmoothScrolling = new MenuItem ("_SmoothHorizontalScrolling", "", () => ToggleSmoothScrolling ()) {
+					Checked = listColView.Style.SmoothHorizontalScrolling,
+					CheckType = MenuItemCheckStyle.Checked
+				},
+				_miAlternatingColors = new MenuItem ("Alternating Colors", "", () => ToggleAlternatingColors ())
+					{ CheckType = MenuItemCheckStyle.Checked },
+				_miCursor = new MenuItem ("Invert Selected Cell First Character", "",
+					() => ToggleInvertSelectedCellFirstCharacter ()) {
+					Checked = listColView.Style.InvertSelectedCellFirstCharacter,
+					CheckType = MenuItemCheckStyle.Checked
 				}
-				listColView.SetNeedsDisplay ();
-			};
-			*/
+			}),
+			new ("_List", new [] {
+				//new MenuItem ("_Hide Headers", "", HideHeaders),
+				_miOrientVertical = new MenuItem ("_OrientVertical", "", () => ToggleVerticalOrientation ()) {
+					Checked = listColStyle.Orientation == Orientation.Vertical,
+					CheckType = MenuItemCheckStyle.Checked
+				},
+				_miScrollParallel = new MenuItem ("_ScrollParallel", "", () => ToggleScrollParallel ())
+					{ Checked = listColStyle.ScrollParallel, CheckType = MenuItemCheckStyle.Checked },
+				new ("Set _Max Cell Width", "", SetListMaxWidth),
+				new ("Set Mi_n Cell Width", "", SetListMinWidth)
+			})
+		});
+
+		Application.Top.Add (menu);
+
+		var statusBar = new StatusBar (new StatusItem [] {
+			new (KeyCode.F2, "~F2~ OpenBigListEx", () => OpenSimpleList (true)),
+			new (KeyCode.F3, "~F3~ CloseExample", () => CloseExample ()),
+			new (KeyCode.F4, "~F4~ OpenSmListEx", () => OpenSimpleList (false)),
+			new (Application.QuitKey, $"{Application.QuitKey} to Quit", () => Quit ())
+		});
+		Application.Top.Add (statusBar);
+
+		Win.Add (listColView);
+
+		var selectedCellLabel = new Label {
+			X = 0,
+			Y = Pos.Bottom (listColView),
+			Text = "0,0",
+			Width = Dim.Fill (),
+			TextAlignment = TextAlignment.Right
+
+		};
+
+		Win.Add (selectedCellLabel);
+
+		listColView.SelectedCellChanged += (s, e) => { selectedCellLabel.Text = $"{listColView.SelectedRow},{listColView.SelectedColumn}"; };
+		listColView.KeyDown += TableViewKeyPress;
+
+		SetupScrollBar ();
+
+		alternatingColorScheme = new ColorScheme {
+
+			Disabled = Win.ColorScheme.Disabled,
+			HotFocus = Win.ColorScheme.HotFocus,
+			Focus = Win.ColorScheme.Focus,
+			Normal = new Attribute (Color.White, Color.BrightBlue)
+		};
+
+		// if user clicks the mouse in TableView
+		listColView.MouseClick += (s, e) => {
+
+			listColView.ScreenToCell (e.MouseEvent.X, e.MouseEvent.Y, out var clickedCol);
+		};
+
+		listColView.KeyBindings.Add (KeyCode.Space, Command.ToggleChecked);
+	}
+
+	void SetupScrollBar ()
+	{
+		var scrollBar = new ScrollBarView (listColView, true); // (listColView, true, true);
 
-			listColView.DrawContent += (s, e) => {
-				scrollBar.Size = listColView.Table?.Rows ?? 0;
+		scrollBar.ChangedPosition += (s, e) => {
+			listColView.RowOffset = scrollBar.Position;
+			if (listColView.RowOffset != scrollBar.Position) {
 				scrollBar.Position = listColView.RowOffset;
-				//scrollBar.OtherScrollBarView.Size = listColView.Table?.Columns - 1 ?? 0;
-				//scrollBar.OtherScrollBarView.Position = listColView.ColumnOffset;
-				scrollBar.Refresh ();
-			};
+			}
+			listColView.SetNeedsDisplay ();
+		};
+		/*
+		scrollBar.OtherScrollBarView.ChangedPosition += (s,e) => {
+			listColView.ColumnOffset = scrollBar.OtherScrollBarView.Position;
+			if (listColView.ColumnOffset != scrollBar.OtherScrollBarView.Position) {
+				scrollBar.OtherScrollBarView.Position = listColView.ColumnOffset;
+			}
+			listColView.SetNeedsDisplay ();
+		};
+		*/
 
-		}
+		listColView.DrawContent += (s, e) => {
+			scrollBar.Size = listColView.Table?.Rows ?? 0;
+			scrollBar.Position = listColView.RowOffset;
+			//scrollBar.OtherScrollBarView.Size = listColView.Table?.Columns - 1 ?? 0;
+			//scrollBar.OtherScrollBarView.Position = listColView.ColumnOffset;
+			scrollBar.Refresh ();
+		};
 
-		private void TableViewKeyPress (object sender, Key e)
-		{
-			if (e.KeyCode == KeyCode.Delete) {
+	}
 
-				// set all selected cells to null
-				foreach (var pt in listColView.GetAllSelectedCells ()) {
-					currentTable.Rows [pt.Y] [pt.X] = DBNull.Value;
-				}
+	void TableViewKeyPress (object sender, Key e)
+	{
+		if (e.KeyCode == KeyCode.Delete) {
 
-				listColView.Update ();
-				e.Handled = true;
+			// set all selected cells to null
+			foreach (var pt in listColView.GetAllSelectedCells ()) {
+				currentTable.Rows [pt.Y] [pt.X] = DBNull.Value;
 			}
 
-		}
-
-		private void ToggleTopline ()
-		{
-			_miTopline.Checked = !_miTopline.Checked;
-			listColView.Style.ShowHorizontalHeaderOverline = (bool)_miTopline.Checked;
-			listColView.Update ();
-		}
-		private void ToggleBottomline ()
-		{
-			_miBottomline.Checked = !_miBottomline.Checked;
-			listColView.Style.ShowHorizontalBottomline = (bool)_miBottomline.Checked;
 			listColView.Update ();
+			e.Handled = true;
 		}
-		private void ToggleExpandLastColumn ()
-		{
-			_miExpandLastColumn.Checked = !_miExpandLastColumn.Checked;
-			listColView.Style.ExpandLastColumn = (bool)_miExpandLastColumn.Checked;
 
-			listColView.Update ();
+	}
 
-		}
+	void ToggleTopline ()
+	{
+		_miTopline.Checked = !_miTopline.Checked;
+		listColView.Style.ShowHorizontalHeaderOverline = (bool)_miTopline.Checked;
+		listColView.Update ();
+	}
 
-		private void ToggleAlwaysUseNormalColorForVerticalCellLines ()
-		{
-			_miAlwaysUseNormalColorForVerticalCellLines.Checked = !_miAlwaysUseNormalColorForVerticalCellLines.Checked;
-			listColView.Style.AlwaysUseNormalColorForVerticalCellLines = (bool)_miAlwaysUseNormalColorForVerticalCellLines.Checked;
+	void ToggleBottomline ()
+	{
+		_miBottomline.Checked = !_miBottomline.Checked;
+		listColView.Style.ShowHorizontalBottomline = (bool)_miBottomline.Checked;
+		listColView.Update ();
+	}
 
-			listColView.Update ();
-		}
-		private void ToggleSmoothScrolling ()
-		{
-			_miSmoothScrolling.Checked = !_miSmoothScrolling.Checked;
-			listColView.Style.SmoothHorizontalScrolling = (bool)_miSmoothScrolling.Checked;
+	void ToggleExpandLastColumn ()
+	{
+		_miExpandLastColumn.Checked = !_miExpandLastColumn.Checked;
+		listColView.Style.ExpandLastColumn = (bool)_miExpandLastColumn.Checked;
 
-			listColView.Update ();
+		listColView.Update ();
 
-		}
-		private void ToggleCellLines ()
-		{
-			_miCellLines.Checked = !_miCellLines.Checked;
-			listColView.Style.ShowVerticalCellLines = (bool)_miCellLines.Checked;
-			listColView.Update ();
-		}
-		private void ToggleAlternatingColors ()
-		{
-			//toggle menu item
-			_miAlternatingColors.Checked = !_miAlternatingColors.Checked;
-
-			if (_miAlternatingColors.Checked == true) {
-				listColView.Style.RowColorGetter = (a) => { return a.RowIndex % 2 == 0 ? alternatingColorScheme : null; };
-			} else {
-				listColView.Style.RowColorGetter = null;
-			}
-			listColView.SetNeedsDisplay ();
-		}
+	}
 
-		private void ToggleInvertSelectedCellFirstCharacter ()
-		{
-			//toggle menu item
-			_miCursor.Checked = !_miCursor.Checked;
-			listColView.Style.InvertSelectedCellFirstCharacter = (bool)_miCursor.Checked;
-			listColView.SetNeedsDisplay ();
-		}
+	void ToggleAlwaysUseNormalColorForVerticalCellLines ()
+	{
+		_miAlwaysUseNormalColorForVerticalCellLines.Checked = !_miAlwaysUseNormalColorForVerticalCellLines.Checked;
+		listColView.Style.AlwaysUseNormalColorForVerticalCellLines = (bool)_miAlwaysUseNormalColorForVerticalCellLines.Checked;
 
-		private void ToggleVerticalOrientation ()
-		{
-			_miOrientVertical.Checked = !_miOrientVertical.Checked;
-			if ((ListTableSource)listColView.Table != null) {
-				((ListTableSource)listColView.Table).Style.Orientation = (bool)_miOrientVertical.Checked ? Orientation.Vertical : Orientation.Horizontal;
-				listColView.SetNeedsDisplay ();
-			}
-		}
+		listColView.Update ();
+	}
 
-		private void ToggleScrollParallel ()
-		{
-			_miScrollParallel.Checked = !_miScrollParallel.Checked;
-			if ((ListTableSource)listColView.Table != null) {
-				((ListTableSource)listColView.Table).Style.ScrollParallel = (bool)_miScrollParallel.Checked;
-				listColView.SetNeedsDisplay ();
-			}
+	void ToggleSmoothScrolling ()
+	{
+		_miSmoothScrolling.Checked = !_miSmoothScrolling.Checked;
+		listColView.Style.SmoothHorizontalScrolling = (bool)_miSmoothScrolling.Checked;
+
+		listColView.Update ();
+
+	}
+
+	void ToggleCellLines ()
+	{
+		_miCellLines.Checked = !_miCellLines.Checked;
+		listColView.Style.ShowVerticalCellLines = (bool)_miCellLines.Checked;
+		listColView.Update ();
+	}
+
+	void ToggleAlternatingColors ()
+	{
+		//toggle menu item
+		_miAlternatingColors.Checked = !_miAlternatingColors.Checked;
+
+		if (_miAlternatingColors.Checked == true) {
+			listColView.Style.RowColorGetter = a => { return a.RowIndex % 2 == 0 ? alternatingColorScheme : null; };
+		} else {
+			listColView.Style.RowColorGetter = null;
 		}
+		listColView.SetNeedsDisplay ();
+	}
+
+	void ToggleInvertSelectedCellFirstCharacter ()
+	{
+		//toggle menu item
+		_miCursor.Checked = !_miCursor.Checked;
+		listColView.Style.InvertSelectedCellFirstCharacter = (bool)_miCursor.Checked;
+		listColView.SetNeedsDisplay ();
+	}
 
-		private void SetListMinWidth ()
-		{
-			RunListWidthDialog ("MinCellWidth", (s, v) => s.MinCellWidth = v, (s) => s.MinCellWidth);
+	void ToggleVerticalOrientation ()
+	{
+		_miOrientVertical.Checked = !_miOrientVertical.Checked;
+		if ((ListTableSource)listColView.Table != null) {
+			((ListTableSource)listColView.Table).Style.Orientation = (bool)_miOrientVertical.Checked ? Orientation.Vertical : Orientation.Horizontal;
 			listColView.SetNeedsDisplay ();
 		}
+	}
 
-		private void SetListMaxWidth ()
-		{
-			RunListWidthDialog ("MaxCellWidth", (s, v) => s.MaxCellWidth = v, (s) => s.MaxCellWidth);
+	void ToggleScrollParallel ()
+	{
+		_miScrollParallel.Checked = !_miScrollParallel.Checked;
+		if ((ListTableSource)listColView.Table != null) {
+			((ListTableSource)listColView.Table).Style.ScrollParallel = (bool)_miScrollParallel.Checked;
 			listColView.SetNeedsDisplay ();
 		}
+	}
 
-		private void RunListWidthDialog (string prompt, Action<TableView, int> setter, Func<TableView, int> getter)
-		{
-			var accepted = false;
-			var ok = new Button ("Ok", is_default: true);
-			ok.Clicked += (s, e) => { accepted = true; Application.RequestStop (); };
-			var cancel = new Button ("Cancel");
-			cancel.Clicked += (s, e) => { Application.RequestStop (); };
-			var d = new Dialog (ok, cancel) { Title = prompt };
-
-			var tf = new TextField () {
-				Text = getter (listColView).ToString (),
-				X = 0,
-				Y = 1,
-				Width = Dim.Fill ()
-			};
-
-			d.Add (tf);
-			tf.SetFocus ();
-
-			Application.Run (d);
-
-			if (accepted) {
-
-				try {
-					setter (listColView, int.Parse (tf.Text));
-				} catch (Exception ex) {
-					MessageBox.ErrorQuery (60, 20, "Failed to set", ex.Message, "Ok");
-				}
-			}
-		}
+	void SetListMinWidth ()
+	{
+		RunListWidthDialog ("MinCellWidth", (s, v) => s.MinCellWidth = v, s => s.MinCellWidth);
+		listColView.SetNeedsDisplay ();
+	}
 
-		private void CloseExample ()
-		{
-			listColView.Table = null;
-		}
+	void SetListMaxWidth ()
+	{
+		RunListWidthDialog ("MaxCellWidth", (s, v) => s.MaxCellWidth = v, s => s.MaxCellWidth);
+		listColView.SetNeedsDisplay ();
+	}
 
-		private void Quit ()
-		{
+	void RunListWidthDialog (string prompt, Action<TableView, int> setter, Func<TableView, int> getter)
+	{
+		var accepted = false;
+		var ok = new Button ("Ok", true);
+		ok.Clicked += (s, e) => {
+			accepted = true;
 			Application.RequestStop ();
-		}
+		};
+		var cancel = new Button ("Cancel");
+		cancel.Clicked += (s, e) => { Application.RequestStop (); };
+		var d = new Dialog (ok, cancel) { Title = prompt };
 
-		private void OpenSimpleList (bool big)
-		{
-			SetTable (BuildSimpleList (big ? 1023 : 31));
-		}
+		var tf = new TextField {
+			Text = getter (listColView).ToString (),
+			X = 0,
+			Y = 1,
+			Width = Dim.Fill ()
+		};
+
+		d.Add (tf);
+		tf.SetFocus ();
+
+		Application.Run (d);
+
+		if (accepted) {
 
-		private void SetTable (IList list)
-		{
-			listColView.Table = new ListTableSource (list, listColView);
-			if ((ListTableSource)listColView.Table != null) {
-				currentTable = ((ListTableSource)listColView.Table).DataTable;
+			try {
+				setter (listColView, int.Parse (tf.Text));
+			} catch (Exception ex) {
+				MessageBox.ErrorQuery (60, 20, "Failed to set", ex.Message, "Ok");
 			}
 		}
+	}
 
-		/// <summary>
-		/// Builds a simple list in which values are the index.  This helps testing that scrolling etc is working correctly and not skipping out values when paging
-		/// </summary>
-		/// <param name="items"></param>
-		/// <returns></returns>
-		public static IList BuildSimpleList (int items)
-		{
-			var list = new List<object> ();
-
-			for (int i = 0; i < items; i++) {
-				list.Add ("Item " + i);
-			}
+	void CloseExample () => listColView.Table = null;
+
+	void Quit () => Application.RequestStop ();
+
+	void OpenSimpleList (bool big) => SetTable (BuildSimpleList (big ? 1023 : 31));
 
-			return list;
+	void SetTable (IList list)
+	{
+		listColView.Table = new ListTableSource (list, listColView);
+		if ((ListTableSource)listColView.Table != null) {
+			currentTable = ((ListTableSource)listColView.Table).DataTable;
 		}
 	}
+
+	/// <summary>
+	/// Builds a simple list in which values are the index.  This helps testing that scrolling etc is working correctly and not
+	/// skipping out values when paging
+	/// </summary>
+	/// <param name="items"></param>
+	/// <returns></returns>
+	public static IList BuildSimpleList (int items)
+	{
+		var list = new List<object> ();
+
+		for (var i = 0; i < items; i++) {
+			list.Add ("Item " + i);
+		}
+
+		return list;
+	}
 }

文件差異過大導致無法顯示
+ 732 - 763
UnitTests/Dialogs/DialogTests.cs


+ 1701 - 1620
UnitTests/Text/TextFormatterTests.cs

@@ -1,100 +1,96 @@
-using System.Text;
-using System;
+using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Text;
 using Xunit;
 using Xunit.Abstractions;
-
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 
-namespace Terminal.Gui.TextTests {
-	public class TextFormatterTests {
-		readonly ITestOutputHelper output;
+namespace Terminal.Gui.TextTests; 
 
-		public TextFormatterTests (ITestOutputHelper output)
-		{
-			this.output = output;
-		}
+public class TextFormatterTests {
+	readonly ITestOutputHelper output;
 
-		[Fact]
-		public void Basic_Usage ()
-		{
-			var testText = "test";
-			var expectedSize = new Size ();
-			var testBounds = new Rect (0, 0, 100, 1);
-			var tf = new TextFormatter ();
-
-			tf.Text = testText;
-			expectedSize = new Size (testText.Length, 1);
-			Assert.Equal (testText, tf.Text);
-			Assert.Equal (TextAlignment.Left, tf.Alignment);
-			Assert.Equal (expectedSize, tf.Size);
-			tf.Draw (testBounds, new Attribute (), new Attribute ());
-			Assert.Equal (expectedSize, tf.Size);
-			Assert.NotEmpty (tf.Lines);
-
-			tf.Alignment = TextAlignment.Right;
-			expectedSize = new Size (testText.Length, 1);
-			Assert.Equal (testText, tf.Text);
-			Assert.Equal (TextAlignment.Right, tf.Alignment);
-			Assert.Equal (expectedSize, tf.Size);
-			tf.Draw (testBounds, new Attribute (), new Attribute ());
-			Assert.Equal (expectedSize, tf.Size);
-			Assert.NotEmpty (tf.Lines);
-
-			tf.Alignment = TextAlignment.Right;
-			expectedSize = new Size (testText.Length * 2, 1);
-			tf.Size = expectedSize;
-			Assert.Equal (testText, tf.Text);
-			Assert.Equal (TextAlignment.Right, tf.Alignment);
-			Assert.Equal (expectedSize, tf.Size);
-			tf.Draw (testBounds, new Attribute (), new Attribute ());
-			Assert.Equal (expectedSize, tf.Size);
-			Assert.NotEmpty (tf.Lines);
-
-			tf.Alignment = TextAlignment.Centered;
-			expectedSize = new Size (testText.Length * 2, 1);
-			tf.Size = expectedSize;
-			Assert.Equal (testText, tf.Text);
-			Assert.Equal (TextAlignment.Centered, tf.Alignment);
-			Assert.Equal (expectedSize, tf.Size);
-			tf.Draw (testBounds, new Attribute (), new Attribute ());
-			Assert.Equal (expectedSize, tf.Size);
-			Assert.NotEmpty (tf.Lines);
-		}
+	public TextFormatterTests (ITestOutputHelper output) => this.output = output;
 
-		[Theory]
-		[InlineData (TextDirection.LeftRight_TopBottom, false)]
-		[InlineData (TextDirection.LeftRight_TopBottom, true)]
-		[InlineData (TextDirection.TopBottom_LeftRight, false)]
-		[InlineData (TextDirection.TopBottom_LeftRight, true)]
-		public void TestSize_TextChange (TextDirection textDirection, bool autoSize)
-		{
-			var tf = new TextFormatter () { Direction = textDirection, Text = "你", AutoSize = autoSize };
-			Assert.Equal (2, tf.Size.Width);
-			Assert.Equal (1, tf.Size.Height);
-			tf.Text = "你你";
-			if (autoSize) {
-				if (textDirection == TextDirection.LeftRight_TopBottom) {
-					Assert.Equal (4, tf.Size.Width);
-					Assert.Equal (1, tf.Size.Height);
-				} else {
-					Assert.Equal (2, tf.Size.Width);
-					Assert.Equal (2, tf.Size.Height);
-				}
-			} else {
-				Assert.Equal (2, tf.Size.Width);
-				Assert.Equal (1, tf.Size.Height);
+	public static IEnumerable<object []> CMGlyphs =>
+		new List<object []> {
+			new object [] { $"{CM.Glyphs.LeftBracket} Say Hello 你 {CM.Glyphs.RightBracket}", 16, 15 }
+		};
+
+	public static IEnumerable<object []> FormatEnvironmentNewLine =>
+		new List<object []> {
+			new object [] { $"Line1{Environment.NewLine}Line2{Environment.NewLine}Line3{Environment.NewLine}", 60, new [] { "Line1", "Line2", "Line3" } }
+		};
+
+	public static IEnumerable<object []> SplitEnvironmentNewLine =>
+		new List<object []> {
+			new object [] { $"First Line 界{Environment.NewLine}Second Line 界{Environment.NewLine}Third Line 界", new [] { "First Line 界", "Second Line 界", "Third Line 界" } },
+			new object [] {
+				$"First Line 界{Environment.NewLine}Second Line 界{Environment.NewLine}Third Line 界{Environment.NewLine}", new [] { "First Line 界", "Second Line 界", "Third Line 界", "" }
 			}
-		}
+		};
 
-		[Theory]
-		[InlineData (TextDirection.LeftRight_TopBottom)]
-		[InlineData (TextDirection.TopBottom_LeftRight)]
-		public void TestSize_AutoSizeChange (TextDirection textDirection)
-		{
-			var tf = new TextFormatter () { Direction = textDirection, Text = "你你" };
+	[Fact]
+	public void Basic_Usage ()
+	{
+		var testText = "test";
+		var expectedSize = new Size ();
+		var testBounds = new Rect (0, 0, 100, 1);
+		var tf = new TextFormatter ();
+
+		tf.Text = testText;
+		expectedSize = new Size (testText.Length, 1);
+		Assert.Equal (testText,           tf.Text);
+		Assert.Equal (TextAlignment.Left, tf.Alignment);
+		Assert.Equal (expectedSize,       tf.Size);
+		tf.Draw (testBounds, new Attribute (), new Attribute ());
+		Assert.Equal (expectedSize, tf.Size);
+		Assert.NotEmpty (tf.Lines);
+
+		tf.Alignment = TextAlignment.Right;
+		expectedSize = new Size (testText.Length, 1);
+		Assert.Equal (testText,            tf.Text);
+		Assert.Equal (TextAlignment.Right, tf.Alignment);
+		Assert.Equal (expectedSize,        tf.Size);
+		tf.Draw (testBounds, new Attribute (), new Attribute ());
+		Assert.Equal (expectedSize, tf.Size);
+		Assert.NotEmpty (tf.Lines);
+
+		tf.Alignment = TextAlignment.Right;
+		expectedSize = new Size (testText.Length * 2, 1);
+		tf.Size = expectedSize;
+		Assert.Equal (testText,            tf.Text);
+		Assert.Equal (TextAlignment.Right, tf.Alignment);
+		Assert.Equal (expectedSize,        tf.Size);
+		tf.Draw (testBounds, new Attribute (), new Attribute ());
+		Assert.Equal (expectedSize, tf.Size);
+		Assert.NotEmpty (tf.Lines);
+
+		tf.Alignment = TextAlignment.Centered;
+		expectedSize = new Size (testText.Length * 2, 1);
+		tf.Size = expectedSize;
+		Assert.Equal (testText,               tf.Text);
+		Assert.Equal (TextAlignment.Centered, tf.Alignment);
+		Assert.Equal (expectedSize,           tf.Size);
+		tf.Draw (testBounds, new Attribute (), new Attribute ());
+		Assert.Equal (expectedSize, tf.Size);
+		Assert.NotEmpty (tf.Lines);
+	}
+
+	[Theory]
+	[InlineData (TextDirection.LeftRight_TopBottom, false)]
+	[InlineData (TextDirection.LeftRight_TopBottom, true)]
+	[InlineData (TextDirection.TopBottom_LeftRight, false)]
+	[InlineData (TextDirection.TopBottom_LeftRight, true)]
+	public void TestSize_TextChange (TextDirection textDirection, bool autoSize)
+	{
+		var tf = new TextFormatter { Direction = textDirection, Text = "你", AutoSize = autoSize };
+		Assert.Equal (2, tf.Size.Width);
+		Assert.Equal (1, tf.Size.Height);
+		tf.Text = "你你";
+		if (autoSize) {
 			if (textDirection == TextDirection.LeftRight_TopBottom) {
 				Assert.Equal (4, tf.Size.Width);
 				Assert.Equal (1, tf.Size.Height);
@@ -102,29 +98,58 @@ namespace Terminal.Gui.TextTests {
 				Assert.Equal (2, tf.Size.Width);
 				Assert.Equal (2, tf.Size.Height);
 			}
-			Assert.False (tf.AutoSize);
+		} else {
+			Assert.Equal (2, tf.Size.Width);
+			Assert.Equal (1, tf.Size.Height);
+		}
+	}
 
-			tf.Size = new Size (1, 1);
-			Assert.Equal (1, tf.Size.Width);
+	[Theory]
+	[InlineData (TextDirection.LeftRight_TopBottom)]
+	[InlineData (TextDirection.TopBottom_LeftRight)]
+	public void TestSize_AutoSizeChange (TextDirection textDirection)
+	{
+		var tf = new TextFormatter { Direction = textDirection, Text = "你你" };
+		if (textDirection == TextDirection.LeftRight_TopBottom) {
+			Assert.Equal (4, tf.Size.Width);
 			Assert.Equal (1, tf.Size.Height);
-			tf.AutoSize = true;
-			if (textDirection == TextDirection.LeftRight_TopBottom) {
-				Assert.Equal (4, tf.Size.Width);
-				Assert.Equal (1, tf.Size.Height);
-			} else {
-				Assert.Equal (2, tf.Size.Width);
-				Assert.Equal (2, tf.Size.Height);
-			}
+		} else {
+			Assert.Equal (2, tf.Size.Width);
+			Assert.Equal (2, tf.Size.Height);
+		}
+		Assert.False (tf.AutoSize);
+
+		tf.Size = new Size (1, 1);
+		Assert.Equal (1, tf.Size.Width);
+		Assert.Equal (1, tf.Size.Height);
+		tf.AutoSize = true;
+		if (textDirection == TextDirection.LeftRight_TopBottom) {
+			Assert.Equal (4, tf.Size.Width);
+			Assert.Equal (1, tf.Size.Height);
+		} else {
+			Assert.Equal (2, tf.Size.Width);
+			Assert.Equal (2, tf.Size.Height);
 		}
+	}
 
-		[Theory]
-		[InlineData (TextDirection.LeftRight_TopBottom, false)]
-		[InlineData (TextDirection.LeftRight_TopBottom, true)]
-		[InlineData (TextDirection.TopBottom_LeftRight, false)]
-		[InlineData (TextDirection.TopBottom_LeftRight, true)]
-		public void TestSize_SizeChange_AutoSize_True_Or_False (TextDirection textDirection, bool autoSize)
-		{
-			var tf = new TextFormatter () { Direction = textDirection, Text = "你你", AutoSize = autoSize };
+	[Theory]
+	[InlineData (TextDirection.LeftRight_TopBottom, false)]
+	[InlineData (TextDirection.LeftRight_TopBottom, true)]
+	[InlineData (TextDirection.TopBottom_LeftRight, false)]
+	[InlineData (TextDirection.TopBottom_LeftRight, true)]
+	public void TestSize_SizeChange_AutoSize_True_Or_False (TextDirection textDirection, bool autoSize)
+	{
+		var tf = new TextFormatter { Direction = textDirection, Text = "你你", AutoSize = autoSize };
+		if (textDirection == TextDirection.LeftRight_TopBottom) {
+			Assert.Equal (4, tf.Size.Width);
+			Assert.Equal (1, tf.Size.Height);
+		} else {
+			Assert.Equal (2, tf.Size.Width);
+			Assert.Equal (2, tf.Size.Height);
+		}
+
+		tf.Size = new Size (1, 1);
+		if (autoSize) {
 			if (textDirection == TextDirection.LeftRight_TopBottom) {
 				Assert.Equal (4, tf.Size.Width);
 				Assert.Equal (1, tf.Size.Height);
@@ -132,1639 +157,1695 @@ namespace Terminal.Gui.TextTests {
 				Assert.Equal (2, tf.Size.Width);
 				Assert.Equal (2, tf.Size.Height);
 			}
-
-			tf.Size = new Size (1, 1);
-			if (autoSize) {
-				if (textDirection == TextDirection.LeftRight_TopBottom) {
-					Assert.Equal (4, tf.Size.Width);
-					Assert.Equal (1, tf.Size.Height);
-				} else {
-					Assert.Equal (2, tf.Size.Width);
-					Assert.Equal (2, tf.Size.Height);
-				}
-			} else {
-				Assert.Equal (1, tf.Size.Width);
-				Assert.Equal (1, tf.Size.Height);
-			}
+		} else {
+			Assert.Equal (1, tf.Size.Width);
+			Assert.Equal (1, tf.Size.Height);
 		}
+	}
 
-		[Theory]
-		[InlineData (TextAlignment.Left, false)]
-		[InlineData (TextAlignment.Centered, true)]
-		[InlineData (TextAlignment.Right, false)]
-		[InlineData (TextAlignment.Justified, true)]
-		public void TestSize_SizeChange_AutoSize_True_Or_False_Horizontal (TextAlignment textAlignment, bool autoSize)
-		{
-			var tf = new TextFormatter () { Direction = TextDirection.LeftRight_TopBottom, Text = "你你", Alignment = textAlignment, AutoSize = autoSize };
-				Assert.Equal (4, tf.Size.Width);
-				Assert.Equal (1, tf.Size.Height);
-
-			tf.Size = new Size (1, 1);
-			if (autoSize && textAlignment != TextAlignment.Justified) {
-				Assert.Equal (4, tf.Size.Width);
-				Assert.Equal (1, tf.Size.Height);
-			} else {
-				Assert.Equal (1, tf.Size.Width);
-				Assert.Equal (1, tf.Size.Height);
-			}
+	[Theory]
+	[InlineData (TextAlignment.Left,      false)]
+	[InlineData (TextAlignment.Centered,  true)]
+	[InlineData (TextAlignment.Right,     false)]
+	[InlineData (TextAlignment.Justified, true)]
+	public void TestSize_SizeChange_AutoSize_True_Or_False_Horizontal (TextAlignment textAlignment, bool autoSize)
+	{
+		var tf = new TextFormatter { Direction = TextDirection.LeftRight_TopBottom, Text = "你你", Alignment = textAlignment, AutoSize = autoSize };
+		Assert.Equal (4, tf.Size.Width);
+		Assert.Equal (1, tf.Size.Height);
+
+		tf.Size = new Size (1, 1);
+		if (autoSize && textAlignment != TextAlignment.Justified) {
+			Assert.Equal (4, tf.Size.Width);
+			Assert.Equal (1, tf.Size.Height);
+		} else {
+			Assert.Equal (1, tf.Size.Width);
+			Assert.Equal (1, tf.Size.Height);
 		}
+	}
 
-		[Theory]
-		[InlineData (VerticalTextAlignment.Top, false)]
-		[InlineData (VerticalTextAlignment.Middle, true)]
-		[InlineData (VerticalTextAlignment.Bottom, false)]
-		[InlineData (VerticalTextAlignment.Justified, true)]
-		public void TestSize_SizeChange_AutoSize_True_Or_False_Vertical (VerticalTextAlignment textAlignment, bool autoSize)
-		{
-			var tf = new TextFormatter () { Direction = TextDirection.TopBottom_LeftRight, Text = "你你", VerticalAlignment = textAlignment, AutoSize = autoSize };
+	[Theory]
+	[InlineData (VerticalTextAlignment.Top,       false)]
+	[InlineData (VerticalTextAlignment.Middle,    true)]
+	[InlineData (VerticalTextAlignment.Bottom,    false)]
+	[InlineData (VerticalTextAlignment.Justified, true)]
+	public void TestSize_SizeChange_AutoSize_True_Or_False_Vertical (VerticalTextAlignment textAlignment, bool autoSize)
+	{
+		var tf = new TextFormatter { Direction = TextDirection.TopBottom_LeftRight, Text = "你你", VerticalAlignment = textAlignment, AutoSize = autoSize };
+		Assert.Equal (2, tf.Size.Width);
+		Assert.Equal (2, tf.Size.Height);
+
+		tf.Size = new Size (1, 1);
+		if (autoSize && textAlignment != VerticalTextAlignment.Justified) {
 			Assert.Equal (2, tf.Size.Width);
 			Assert.Equal (2, tf.Size.Height);
-
-			tf.Size = new Size (1, 1);
-			if (autoSize && textAlignment != VerticalTextAlignment.Justified) {
-				Assert.Equal (2, tf.Size.Width);
-				Assert.Equal (2, tf.Size.Height);
-			} else {
-				Assert.Equal (1, tf.Size.Width);
-				Assert.Equal (1, tf.Size.Height);
-			}
+		} else {
+			Assert.Equal (1, tf.Size.Width);
+			Assert.Equal (1, tf.Size.Height);
 		}
+	}
 
-		[Fact]
-		public void NeedsFormat_Sets ()
-		{
-			var testText = "test";
-			var testBounds = new Rect (0, 0, 100, 1);
-			var tf = new TextFormatter ();
-
-			tf.Text = "test";
-			Assert.True (tf.NeedsFormat); // get_Lines causes a Format
-			Assert.NotEmpty (tf.Lines);
-			Assert.False (tf.NeedsFormat); // get_Lines causes a Format
-			Assert.Equal (testText, tf.Text);
-			tf.Draw (testBounds, new Attribute (), new Attribute ());
-			Assert.False (tf.NeedsFormat);
-
-			tf.Size = new Size (1, 1);
-			Assert.True (tf.NeedsFormat);
-			Assert.NotEmpty (tf.Lines);
-			Assert.False (tf.NeedsFormat); // get_Lines causes a Format
-
-			tf.Alignment = TextAlignment.Centered;
-			Assert.True (tf.NeedsFormat);
-			Assert.NotEmpty (tf.Lines);
-			Assert.False (tf.NeedsFormat); // get_Lines causes a Format
+	[Theory]
+	[InlineData (TextAlignment.Left,      false)]
+	[InlineData (TextAlignment.Centered,  true)]
+	[InlineData (TextAlignment.Right,     false)]
+	[InlineData (TextAlignment.Justified, true)]
+	public void TestSize_DirectionChange_AutoSize_True_Or_False_Horizontal (TextAlignment textAlignment, bool autoSize)
+	{
+		var tf = new TextFormatter { Direction = TextDirection.LeftRight_TopBottom, Text = "你你", Alignment = textAlignment, AutoSize = autoSize };
+		Assert.Equal (4, tf.Size.Width);
+		Assert.Equal (1, tf.Size.Height);
+
+		tf.Direction = TextDirection.TopBottom_LeftRight;
+		if (autoSize && textAlignment != TextAlignment.Justified) {
+			Assert.Equal (2, tf.Size.Width);
+			Assert.Equal (2, tf.Size.Height);
+		} else {
+			Assert.Equal (4, tf.Size.Width);
+			Assert.Equal (1, tf.Size.Height);
 		}
+	}
 
-		[Theory]
-		[InlineData (null)]
-		[InlineData ("")]
-		[InlineData ("no hotkey")]
-		[InlineData ("No hotkey, Upper Case")]
-		[InlineData ("Non-english: Сохранить")]
-		public void FindHotKey_Invalid_ReturnsFalse (string text)
-		{
-			Rune hotKeySpecifier = (Rune)'_';
-			bool supportFirstUpperCase = false;
-			int hotPos = 0;
-			Key hotKey = KeyCode.Null;
-			bool result = false;
-
-			result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out hotPos, out hotKey);
-			Assert.False (result);
-			Assert.Equal (-1, hotPos);
-			Assert.Equal (KeyCode.Null, hotKey);
+	[Theory]
+	[InlineData (VerticalTextAlignment.Top,       false)]
+	[InlineData (VerticalTextAlignment.Middle,    true)]
+	[InlineData (VerticalTextAlignment.Bottom,    false)]
+	[InlineData (VerticalTextAlignment.Justified, true)]
+	public void TestSize_DirectionChange_AutoSize_True_Or_False_Vertical (VerticalTextAlignment textAlignment, bool autoSize)
+	{
+		var tf = new TextFormatter { Direction = TextDirection.TopBottom_LeftRight, Text = "你你", VerticalAlignment = textAlignment, AutoSize = autoSize };
+		Assert.Equal (2, tf.Size.Width);
+		Assert.Equal (2, tf.Size.Height);
+
+		tf.Direction = TextDirection.LeftRight_TopBottom;
+		if (autoSize && textAlignment != VerticalTextAlignment.Justified) {
+			Assert.Equal (4, tf.Size.Width);
+			Assert.Equal (1, tf.Size.Height);
+		} else {
+			Assert.Equal (2, tf.Size.Width);
+			Assert.Equal (2, tf.Size.Height);
 		}
+	}
 
-		[Theory]
-		[InlineData ("_K Before", true, 0, (KeyCode)'K')]
-		[InlineData ("a_K Second", true, 1, (KeyCode)'K')]
-		[InlineData ("Last _K", true, 5, (KeyCode)'K')]
-		[InlineData ("After K_", false, -1, KeyCode.Null)]
-		[InlineData ("Multiple _K and _R", true, 9, (KeyCode)'K')]
-		[InlineData ("Non-english: _Кдать", true, 13, (KeyCode)'К')] // Cryllic K (К)
-		[InlineData ("_K Before", true, 0, (KeyCode)'K', true)] // Turn on FirstUpperCase and verify same results
-		[InlineData ("a_K Second", true, 1, (KeyCode)'K', true)]
-		[InlineData ("Last _K", true, 5, (KeyCode)'K', true)]
-		[InlineData ("After K_", false, -1, KeyCode.Null, true)]
-		[InlineData ("Multiple _K and _R", true, 9, (KeyCode)'K', true)]
-		[InlineData ("Non-english: _Кдать", true, 13, (KeyCode)'К', true)] // Cryllic K (К)
-		public void FindHotKey_AlphaUpperCase_Succeeds (string text, bool expectedResult, int expectedHotPos, KeyCode expectedKey, bool supportFirstUpperCase = false)
-		{
-			Rune hotKeySpecifier = (Rune)'_';
-
-			var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out int hotPos, out var hotKey);
-			if (expectedResult) {
-				Assert.True (result);
-			} else {
-				Assert.False (result);
-			}
-			Assert.Equal (expectedResult, result);
-			Assert.Equal (expectedHotPos, hotPos);
-			Assert.Equal ((Key)expectedKey, hotKey);
-		}
+	[Fact]
+	public void NeedsFormat_Sets ()
+	{
+		var testText = "test";
+		var testBounds = new Rect (0, 0, 100, 1);
+		var tf = new TextFormatter ();
+
+		tf.Text = "test";
+		Assert.True (tf.NeedsFormat); // get_Lines causes a Format
+		Assert.NotEmpty (tf.Lines);
+		Assert.False (tf.NeedsFormat); // get_Lines causes a Format
+		Assert.Equal (testText, tf.Text);
+		tf.Draw (testBounds, new Attribute (), new Attribute ());
+		Assert.False (tf.NeedsFormat);
+
+		tf.Size = new Size (1, 1);
+		Assert.True (tf.NeedsFormat);
+		Assert.NotEmpty (tf.Lines);
+		Assert.False (tf.NeedsFormat); // get_Lines causes a Format
+
+		tf.Alignment = TextAlignment.Centered;
+		Assert.True (tf.NeedsFormat);
+		Assert.NotEmpty (tf.Lines);
+		Assert.False (tf.NeedsFormat); // get_Lines causes a Format
+	}
 
-		[Theory]
-		[InlineData ("_k Before", true, 0, (KeyCode)'K')] // lower case should return uppercase Hotkey
-		[InlineData ("a_k Second", true, 1, (KeyCode)'K')]
-		[InlineData ("Last _k", true, 5, (KeyCode)'K')]
-		[InlineData ("After k_", false, -1, KeyCode.Null)]
-		[InlineData ("Multiple _k and _R", true, 9, (KeyCode)'K')]
-		[InlineData ("Non-english: _кдать", true, 13, (KeyCode)'к')] // Lower case Cryllic K (к)
-		[InlineData ("_k Before", true, 0, (KeyCode)'K', true)] // Turn on FirstUpperCase and verify same results
-		[InlineData ("a_k Second", true, 1, (KeyCode)'K', true)]
-		[InlineData ("Last _k", true, 5, (KeyCode)'K', true)]
-		[InlineData ("After k_", false, -1, KeyCode.Null, true)]
-		[InlineData ("Multiple _k and _r", true, 9, (KeyCode)'K', true)]
-		[InlineData ("Non-english: _кдать", true, 13, (KeyCode)'к', true)] // Cryllic K (К)
-		public void FindHotKey_AlphaLowerCase_Succeeds (string text, bool expectedResult, int expectedHotPos, KeyCode expectedKey, bool supportFirstUpperCase = false)
-		{
-			Rune hotKeySpecifier = (Rune)'_';
-
-			var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out int hotPos, out var hotKey);
-			if (expectedResult) {
-				Assert.True (result);
-			} else {
-				Assert.False (result);
-			}
-			Assert.Equal (expectedResult, result);
-			Assert.Equal (expectedHotPos, hotPos);
-			Assert.Equal ((Key)expectedKey, hotKey);
-		}
+	[Theory]
+	[InlineData (null)]
+	[InlineData ("")]
+	[InlineData ("no hotkey")]
+	[InlineData ("No hotkey, Upper Case")]
+	[InlineData ("Non-english: Сохранить")]
+	public void FindHotKey_Invalid_ReturnsFalse (string text)
+	{
+		var hotKeySpecifier = (Rune)'_';
+		var supportFirstUpperCase = false;
+		var hotPos = 0;
+		Key hotKey = KeyCode.Null;
+		var result = false;
+
+		result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out hotPos, out hotKey);
+		Assert.False (result);
+		Assert.Equal (-1,           hotPos);
+		Assert.Equal (KeyCode.Null, hotKey);
+	}
 
-		[Theory]
-		[InlineData ("_1 Before", true, 0, (KeyCode)'1')] // Digits 
-		[InlineData ("a_1 Second", true, 1, (KeyCode)'1')]
-		[InlineData ("Last _1", true, 5, (KeyCode)'1')]
-		[InlineData ("After 1_", false, -1, KeyCode.Null)]
-		[InlineData ("Multiple _1 and _2", true, 9, (KeyCode)'1')]
-		[InlineData ("_1 Before", true, 0, (KeyCode)'1', true)] // Turn on FirstUpperCase and verify same results
-		[InlineData ("a_1 Second", true, 1, (KeyCode)'1', true)]
-		[InlineData ("Last _1", true, 5, (KeyCode)'1', true)]
-		[InlineData ("After 1_", false, -1, KeyCode.Null, true)]
-		[InlineData ("Multiple _1 and _2", true, 9, (KeyCode)'1', true)]
-		public void FindHotKey_Numeric_Succeeds (string text, bool expectedResult, int expectedHotPos, KeyCode expectedKey, bool supportFirstUpperCase = false)
-		{
-			Rune hotKeySpecifier = (Rune)'_';
-
-			var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out int hotPos, out var hotKey);
-			if (expectedResult) {
-				Assert.True (result);
-			} else {
-				Assert.False (result);
-			}
-			Assert.Equal (expectedResult, result);
-			Assert.Equal (expectedHotPos, hotPos);
-			Assert.Equal ((Key)expectedKey, hotKey);
+	[Theory]
+	[InlineData ("_K Before",           true,  0,  (KeyCode)'K')]
+	[InlineData ("a_K Second",          true,  1,  (KeyCode)'K')]
+	[InlineData ("Last _K",             true,  5,  (KeyCode)'K')]
+	[InlineData ("After K_",            false, -1, KeyCode.Null)]
+	[InlineData ("Multiple _K and _R",  true,  9,  (KeyCode)'K')]
+	[InlineData ("Non-english: _Кдать", true,  13, (KeyCode)'К')]       // Cryllic K (К)
+	[InlineData ("_K Before",           true,  0,  (KeyCode)'K', true)] // Turn on FirstUpperCase and verify same results
+	[InlineData ("a_K Second",          true,  1,  (KeyCode)'K', true)]
+	[InlineData ("Last _K",             true,  5,  (KeyCode)'K', true)]
+	[InlineData ("After K_",            false, -1, KeyCode.Null, true)]
+	[InlineData ("Multiple _K and _R",  true,  9,  (KeyCode)'K', true)]
+	[InlineData ("Non-english: _Кдать", true,  13, (KeyCode)'К', true)] // Cryllic K (К)
+	public void FindHotKey_AlphaUpperCase_Succeeds (string text, bool expectedResult, int expectedHotPos, KeyCode expectedKey, bool supportFirstUpperCase = false)
+	{
+		var hotKeySpecifier = (Rune)'_';
+
+		var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out var hotPos, out var hotKey);
+		if (expectedResult) {
+			Assert.True (result);
+		} else {
+			Assert.False (result);
 		}
+		Assert.Equal (expectedResult, result);
+		Assert.Equal (expectedHotPos, hotPos);
+		Assert.Equal (expectedKey,    hotKey);
+	}
 
-		[Theory]
-		[InlineData ("K Before", true, 0, (KeyCode)'K')]
-		[InlineData ("aK Second", true, 1, (KeyCode)'K')]
-		[InlineData ("last K", true, 5, (KeyCode)'K')]
-		[InlineData ("multiple K and R", true, 9, (KeyCode)'K')]
-		[InlineData ("non-english: Кдать", true, 13, (KeyCode)'К')] // Cryllic K (К)
-		public void FindHotKey_Legacy_FirstUpperCase_Succeeds (string text, bool expectedResult, int expectedHotPos, KeyCode expectedKey)
-		{
-			var supportFirstUpperCase = true;
-
-			Rune hotKeySpecifier = (Rune)0;
-
-			var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out int hotPos, out var hotKey);
-			if (expectedResult) {
-				Assert.True (result);
-			} else {
-				Assert.False (result);
-			}
-			Assert.Equal (expectedResult, result);
-			Assert.Equal (expectedHotPos, hotPos);
-			Assert.Equal ((Key)expectedKey, hotKey);
-		}
-		
-		[Theory]
-		[InlineData ("_\"k before", true, (KeyCode)'"')] // BUGBUG: Not sure why this fails. " is a normal char
-		[InlineData ("\"_k before", true, KeyCode.K)]
-		[InlineData ("_`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?", true, (KeyCode)'`')]
-		[InlineData ("`_~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?", true, (KeyCode)'~')]
-		[InlineData ("`~!@#$%^&*()-__=+[{]}\\|;:'\",<.>/?", true, (KeyCode)'=')] // BUGBUG: Not sure why this fails. Ignore the first and consider the second
-		[InlineData ("_ ~  s  gui.cs   master ↑10", true, (KeyCode)'')] // ~IsLetterOrDigit + Unicode
-		[InlineData (" ~  s  gui.cs  _ master ↑10", true, (KeyCode)'')] // ~IsLetterOrDigit + Unicode
-		[InlineData ("non-english: _кдать", true, (KeyCode)'к')] // Lower case Cryllic K (к)
-		public void FindHotKey_Symbols_Returns_Symbol (string text, bool found, KeyCode expected)
-		{
-			var hotKeySpecifier = (Rune)'_';
-
-			var result = TextFormatter.FindHotKey (text, hotKeySpecifier, false, out int _, out var hotKey);
-			Assert.Equal (found, result);
-			Assert.Equal ((Key)expected, hotKey);
+	[Theory]
+	[InlineData ("_k Before",           true,  0,  (KeyCode)'K')] // lower case should return uppercase Hotkey
+	[InlineData ("a_k Second",          true,  1,  (KeyCode)'K')]
+	[InlineData ("Last _k",             true,  5,  (KeyCode)'K')]
+	[InlineData ("After k_",            false, -1, KeyCode.Null)]
+	[InlineData ("Multiple _k and _R",  true,  9,  (KeyCode)'K')]
+	[InlineData ("Non-english: _кдать", true,  13, (KeyCode)'к')]       // Lower case Cryllic K (к)
+	[InlineData ("_k Before",           true,  0,  (KeyCode)'K', true)] // Turn on FirstUpperCase and verify same results
+	[InlineData ("a_k Second",          true,  1,  (KeyCode)'K', true)]
+	[InlineData ("Last _k",             true,  5,  (KeyCode)'K', true)]
+	[InlineData ("After k_",            false, -1, KeyCode.Null, true)]
+	[InlineData ("Multiple _k and _r",  true,  9,  (KeyCode)'K', true)]
+	[InlineData ("Non-english: _кдать", true,  13, (KeyCode)'к', true)] // Cryllic K (К)
+	public void FindHotKey_AlphaLowerCase_Succeeds (string text, bool expectedResult, int expectedHotPos, KeyCode expectedKey, bool supportFirstUpperCase = false)
+	{
+		var hotKeySpecifier = (Rune)'_';
+
+		var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out var hotPos, out var hotKey);
+		if (expectedResult) {
+			Assert.True (result);
+		} else {
+			Assert.False (result);
 		}
+		Assert.Equal (expectedResult, result);
+		Assert.Equal (expectedHotPos, hotPos);
+		Assert.Equal (expectedKey,    hotKey);
+	}
 
-		[Theory]
-		[InlineData ("\"k before")]
-		[InlineData ("ak second")]
-		[InlineData ("last k")]
-		[InlineData ("multiple k and r")]
-		[InlineData ("12345")]
-		[InlineData ("`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?")] // punctuation
-		[InlineData (" ~  s  gui.cs   master ↑10")] // ~IsLetterOrDigit + Unicode
-		[InlineData ("non-english: кдать")] // Lower case Cryllic K (к)
-		public void FindHotKey_Legacy_FirstUpperCase_NotFound_Returns_False (string text)
-		{
-			bool supportFirstUpperCase = true;
-
-			var hotKeySpecifier = (Rune)0;
-
-			var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out int hotPos, out var hotKey);
+	[Theory]
+	[InlineData ("_1 Before",          true,  0,  (KeyCode)'1')] // Digits 
+	[InlineData ("a_1 Second",         true,  1,  (KeyCode)'1')]
+	[InlineData ("Last _1",            true,  5,  (KeyCode)'1')]
+	[InlineData ("After 1_",           false, -1, KeyCode.Null)]
+	[InlineData ("Multiple _1 and _2", true,  9,  (KeyCode)'1')]
+	[InlineData ("_1 Before",          true,  0,  (KeyCode)'1', true)] // Turn on FirstUpperCase and verify same results
+	[InlineData ("a_1 Second",         true,  1,  (KeyCode)'1', true)]
+	[InlineData ("Last _1",            true,  5,  (KeyCode)'1', true)]
+	[InlineData ("After 1_",           false, -1, KeyCode.Null, true)]
+	[InlineData ("Multiple _1 and _2", true,  9,  (KeyCode)'1', true)]
+	public void FindHotKey_Numeric_Succeeds (string text, bool expectedResult, int expectedHotPos, KeyCode expectedKey, bool supportFirstUpperCase = false)
+	{
+		var hotKeySpecifier = (Rune)'_';
+
+		var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out var hotPos, out var hotKey);
+		if (expectedResult) {
+			Assert.True (result);
+		} else {
 			Assert.False (result);
-			Assert.Equal (-1, hotPos);
-			Assert.Equal (KeyCode.Null, hotKey);
 		}
+		Assert.Equal (expectedResult, result);
+		Assert.Equal (expectedHotPos, hotPos);
+		Assert.Equal (expectedKey,    hotKey);
+	}
 
-		[Theory]
-		[InlineData (null)]
-		[InlineData ("")]
-		[InlineData ("a")]
-		public void RemoveHotKeySpecifier_InValid_ReturnsOriginal (string text)
-		{
-			Rune hotKeySpecifier = (Rune)'_';
-
-			if (text == null) {
-				Assert.Null (TextFormatter.RemoveHotKeySpecifier (text, 0, hotKeySpecifier));
-				Assert.Null (TextFormatter.RemoveHotKeySpecifier (text, -1, hotKeySpecifier));
-				Assert.Null (TextFormatter.RemoveHotKeySpecifier (text, 100, hotKeySpecifier));
-			} else {
-				Assert.Equal (text, TextFormatter.RemoveHotKeySpecifier (text, 0, hotKeySpecifier));
-				Assert.Equal (text, TextFormatter.RemoveHotKeySpecifier (text, -1, hotKeySpecifier));
-				Assert.Equal (text, TextFormatter.RemoveHotKeySpecifier (text, 100, hotKeySpecifier));
-			}
+	[Theory]
+	[InlineData ("K Before",           true, 0,  (KeyCode)'K')]
+	[InlineData ("aK Second",          true, 1,  (KeyCode)'K')]
+	[InlineData ("last K",             true, 5,  (KeyCode)'K')]
+	[InlineData ("multiple K and R",   true, 9,  (KeyCode)'K')]
+	[InlineData ("non-english: Кдать", true, 13, (KeyCode)'К')] // Cryllic K (К)
+	public void FindHotKey_Legacy_FirstUpperCase_Succeeds (string text, bool expectedResult, int expectedHotPos, KeyCode expectedKey)
+	{
+		var supportFirstUpperCase = true;
+
+		var hotKeySpecifier = (Rune)0;
+
+		var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out var hotPos, out var hotKey);
+		if (expectedResult) {
+			Assert.True (result);
+		} else {
+			Assert.False (result);
 		}
+		Assert.Equal (expectedResult, result);
+		Assert.Equal (expectedHotPos, hotPos);
+		Assert.Equal (expectedKey,    hotKey);
+	}
 
-		[Theory]
-		[InlineData ("_K Before", 0, "K Before")]
-		[InlineData ("a_K Second", 1, "aK Second")]
-		[InlineData ("Last _K", 5, "Last K")]
-		[InlineData ("After K_", 7, "After K")]
-		[InlineData ("Multiple _K and _R", 9, "Multiple K and _R")]
-		[InlineData ("Non-english: _Кдать", 13, "Non-english: Кдать")]
-		public void RemoveHotKeySpecifier_Valid_ReturnsStripped (string text, int hotPos, string expectedText)
-		{
-			Rune hotKeySpecifier = (Rune)'_';
-
-			Assert.Equal (expectedText, TextFormatter.RemoveHotKeySpecifier (text, hotPos, hotKeySpecifier));
-		}
+	[Theory]
+	[InlineData ("_\"k before",                         true, (KeyCode)'"')] // BUGBUG: Not sure why this fails. " is a normal char
+	[InlineData ("\"_k before",                         true, KeyCode.K)]
+	[InlineData ("_`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?", true, (KeyCode)'`')]
+	[InlineData ("`_~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?", true, (KeyCode)'~')]
+	[InlineData ("`~!@#$%^&*()-__=+[{]}\\|;:'\",<.>/?", true, (KeyCode)'=')] // BUGBUG: Not sure why this fails. Ignore the first and consider the second
+	[InlineData ("_ ~  s  gui.cs   master ↑10",    true, (KeyCode)'')] // ~IsLetterOrDigit + Unicode
+	[InlineData (" ~  s  gui.cs  _ master ↑10",    true, (KeyCode)'')] // ~IsLetterOrDigit + Unicode
+	[InlineData ("non-english: _кдать",                 true, (KeyCode)'к')] // Lower case Cryllic K (к)
+	public void FindHotKey_Symbols_Returns_Symbol (string text, bool found, KeyCode expected)
+	{
+		var hotKeySpecifier = (Rune)'_';
+
+		var result = TextFormatter.FindHotKey (text, hotKeySpecifier, false, out var _, out var hotKey);
+		Assert.Equal (found,    result);
+		Assert.Equal (expected, hotKey);
+	}
 
-		[Theory]
-		[InlineData ("all lower case", 0)]
-		[InlineData ("K Before", 0)]
-		[InlineData ("aK Second", 1)]
-		[InlineData ("Last K", 5)]
-		[InlineData ("fter K", 7)]
-		[InlineData ("Multiple K and R", 9)]
-		[InlineData ("Non-english: Кдать", 13)]
-		public void RemoveHotKeySpecifier_Valid_Legacy_ReturnsOriginal (string text, int hotPos)
-		{
-			Rune hotKeySpecifier = (Rune)'_';
-
-			Assert.Equal (text, TextFormatter.RemoveHotKeySpecifier (text, hotPos, hotKeySpecifier));
-		}
+	[Theory]
+	[InlineData ("\"k before")]
+	[InlineData ("ak second")]
+	[InlineData ("last k")]
+	[InlineData ("multiple k and r")]
+	[InlineData ("12345")]
+	[InlineData ("`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?")] // punctuation
+	[InlineData (" ~  s  gui.cs   master ↑10")]    // ~IsLetterOrDigit + Unicode
+	[InlineData ("non-english: кдать")]                 // Lower case Cryllic K (к)
+	public void FindHotKey_Legacy_FirstUpperCase_NotFound_Returns_False (string text)
+	{
+		var supportFirstUpperCase = true;
+
+		var hotKeySpecifier = (Rune)0;
+
+		var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out var hotPos, out var hotKey);
+		Assert.False (result);
+		Assert.Equal (-1,           hotPos);
+		Assert.Equal (KeyCode.Null, hotKey);
+	}
 
-		[Theory]
-		[InlineData (null)]
-		[InlineData ("")]
-		public void CalcRect_Invalid_Returns_Empty (string text)
-		{
-			Assert.Equal (Rect.Empty, TextFormatter.CalcRect (0, 0, text));
-			Assert.Equal (new Rect (new Point (1, 2), Size.Empty), TextFormatter.CalcRect (1, 2, text));
-			Assert.Equal (new Rect (new Point (-1, -2), Size.Empty), TextFormatter.CalcRect (-1, -2, text));
+	[Theory]
+	[InlineData (null)]
+	[InlineData ("")]
+	[InlineData ("a")]
+	public void RemoveHotKeySpecifier_InValid_ReturnsOriginal (string text)
+	{
+		var hotKeySpecifier = (Rune)'_';
+
+		if (text == null) {
+			Assert.Null (TextFormatter.RemoveHotKeySpecifier (text, 0,   hotKeySpecifier));
+			Assert.Null (TextFormatter.RemoveHotKeySpecifier (text, -1,  hotKeySpecifier));
+			Assert.Null (TextFormatter.RemoveHotKeySpecifier (text, 100, hotKeySpecifier));
+		} else {
+			Assert.Equal (text, TextFormatter.RemoveHotKeySpecifier (text, 0,   hotKeySpecifier));
+			Assert.Equal (text, TextFormatter.RemoveHotKeySpecifier (text, -1,  hotKeySpecifier));
+			Assert.Equal (text, TextFormatter.RemoveHotKeySpecifier (text, 100, hotKeySpecifier));
 		}
+	}
 
-		[Theory]
-		[InlineData ("test")]
-		[InlineData (" ~  s  gui.cs   master ↑10")]
-		public void CalcRect_SingleLine_Returns_1High (string text)
-		{
-			Assert.Equal (new Rect (0, 0, text.GetRuneCount (), 1), TextFormatter.CalcRect (0, 0, text));
-			Assert.Equal (new Rect (0, 0, text.GetColumns (), 1), TextFormatter.CalcRect (0, 0, text));
-		}
+	[Theory]
+	[InlineData ("_K Before",           0,  "K Before")]
+	[InlineData ("a_K Second",          1,  "aK Second")]
+	[InlineData ("Last _K",             5,  "Last K")]
+	[InlineData ("After K_",            7,  "After K")]
+	[InlineData ("Multiple _K and _R",  9,  "Multiple K and _R")]
+	[InlineData ("Non-english: _Кдать", 13, "Non-english: Кдать")]
+	public void RemoveHotKeySpecifier_Valid_ReturnsStripped (string text, int hotPos, string expectedText)
+	{
+		var hotKeySpecifier = (Rune)'_';
+
+		Assert.Equal (expectedText, TextFormatter.RemoveHotKeySpecifier (text, hotPos, hotKeySpecifier));
+	}
 
-		[Theory]
-		[InlineData ("line1\nline2", 5, 2)]
-		[InlineData ("\nline2", 5, 2)]
-		[InlineData ("\n\n", 0, 3)]
-		[InlineData ("\n\n\n", 0, 4)]
-		[InlineData ("line1\nline2\nline3long!", 10, 3)]
-		[InlineData ("line1\nline2\n\n", 5, 4)]
-		[InlineData ("line1\r\nline2", 5, 2)]
-		[InlineData (" ~  s  gui.cs   master ↑10\n", 31, 2)]
-		[InlineData ("\n ~  s  gui.cs   master ↑10", 31, 2)]
-		[InlineData (" ~  s  gui.cs   master\n↑10", 27, 2)]
-		public void CalcRect_MultiLine_Returns_nHigh (string text, int expectedWidth, int expectedLines)
-		{
-			Assert.Equal (new Rect (0, 0, expectedWidth, expectedLines), TextFormatter.CalcRect (0, 0, text));
-			var lines = text.Split (text.Contains (Environment.NewLine) ? Environment.NewLine : "\n");
-			var maxWidth = lines.Max (s => s.GetColumns ());
-			var lineWider = 0;
-			for (int i = 0; i < lines.Length; i++) {
-				var w = lines [i].GetColumns ();
-				if (w == maxWidth) {
-					lineWider = i;
-				}
-			}
-			Assert.Equal (new Rect (0, 0, maxWidth, expectedLines), TextFormatter.CalcRect (0, 0, text));
-			Assert.Equal (new Rect (0, 0, lines [lineWider].ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 0)), expectedLines), TextFormatter.CalcRect (0, 0, text));
-		}
+	[Theory]
+	[InlineData ("all lower case",     0)]
+	[InlineData ("K Before",           0)]
+	[InlineData ("aK Second",          1)]
+	[InlineData ("Last K",             5)]
+	[InlineData ("fter K",             7)]
+	[InlineData ("Multiple K and R",   9)]
+	[InlineData ("Non-english: Кдать", 13)]
+	public void RemoveHotKeySpecifier_Valid_Legacy_ReturnsOriginal (string text, int hotPos)
+	{
+		var hotKeySpecifier = (Rune)'_';
+
+		Assert.Equal (text, TextFormatter.RemoveHotKeySpecifier (text, hotPos, hotKeySpecifier));
+	}
 
-		[Theory]
-		[InlineData ("")]
-		[InlineData (null)]
-		[InlineData ("test")]
-		public void ClipAndJustify_Invalid_Returns_Original (string text)
-		{
-			var expected = string.IsNullOrEmpty (text) ? text : "";
-			Assert.Equal (expected, TextFormatter.ClipAndJustify (text, 0, TextAlignment.Left));
-			Assert.Equal (expected, TextFormatter.ClipAndJustify (text, 0, TextAlignment.Left));
-			Assert.Throws<ArgumentOutOfRangeException> (() => TextFormatter.ClipAndJustify (text, -1, TextAlignment.Left));
-		}
+	[Theory]
+	[InlineData (null)]
+	[InlineData ("")]
+	public void CalcRect_Invalid_Returns_Empty (string text)
+	{
+		Assert.Equal (Rect.Empty,                                TextFormatter.CalcRect (0,  0,  text));
+		Assert.Equal (new Rect (new Point (1,  2),  Size.Empty), TextFormatter.CalcRect (1,  2,  text));
+		Assert.Equal (new Rect (new Point (-1, -2), Size.Empty), TextFormatter.CalcRect (-1, -2, text));
+	}
 
-		[Theory]
-		[InlineData ("test", "", 0)]
-		[InlineData ("test", "te", 2)]
-		[InlineData ("test", "test", int.MaxValue)]
-		[InlineData ("A sentence has words.", "A sentence has words.", 22)] // should fit
-		[InlineData ("A sentence has words.", "A sentence has words.", 21)] // should fit
-		[InlineData ("A sentence has words.", "A sentence has words.", int.MaxValue)] // should fit
-		[InlineData ("A sentence has words.", "A sentence has words", 20)] // Should not fit
-		[InlineData ("A sentence has words.", "A sentence", 10)] // Should not fit
-		[InlineData ("A\tsentence\thas\twords.", "A sentence has words.", int.MaxValue)]
-		[InlineData ("A\tsentence\thas\twords.", "A sentence", 10)]
-		[InlineData ("line1\nline2\nline3long!", "line1\nline2\nline3long!", int.MaxValue)]
-		[InlineData ("line1\nline2\nline3long!", "line1\nline", 10)]
-		[InlineData (" ~  s  gui.cs   master ↑10", " ~  s  ", 10)] // Unicode
-		[InlineData ("Ð ÑÐ", "Ð ÑÐ", 5)] // should fit
-		[InlineData ("Ð ÑÐ", "Ð ÑÐ", 4)] // should fit
-		[InlineData ("Ð ÑÐ", "Ð Ñ", 3)] // Should not fit
-		public void ClipAndJustify_Valid_Left (string text, string justifiedText, int maxWidth)
-		{
-			var align = TextAlignment.Left;
-			var textDirection = TextDirection.LeftRight_BottomTop;
-			var tabWidth = 1;
-
-			Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth));
-			var expectedClippedWidth = Math.Min (justifiedText.GetRuneCount (), maxWidth);
-			Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth));
-			Assert.True (justifiedText.GetRuneCount () <= maxWidth);
-			Assert.True (justifiedText.GetColumns () <= maxWidth);
-			Assert.Equal (expectedClippedWidth, justifiedText.GetRuneCount ());
-			Assert.Equal (expectedClippedWidth, justifiedText.ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 1)));
-			Assert.True (expectedClippedWidth <= maxWidth);
-			Assert.Equal (StringExtensions.ToString (justifiedText.ToRunes () [0..expectedClippedWidth]), justifiedText);
-		}
+	[Theory]
+	[InlineData ("test")]
+	[InlineData (" ~  s  gui.cs   master ↑10")]
+	public void CalcRect_SingleLine_Returns_1High (string text)
+	{
+		Assert.Equal (new Rect (0, 0, text.GetRuneCount (), 1), TextFormatter.CalcRect (0, 0, text));
+		Assert.Equal (new Rect (0, 0, text.GetColumns (),   1), TextFormatter.CalcRect (0, 0, text));
+	}
 
-		[Theory]
-		[InlineData ("test", "", 0)]
-		[InlineData ("test", "te", 2)]
-		[InlineData ("test", "test", int.MaxValue)]
-		[InlineData ("A sentence has words.", "A sentence has words.", 22)] // should fit
-		[InlineData ("A sentence has words.", "A sentence has words.", 21)] // should fit
-		[InlineData ("A sentence has words.", "A sentence has words.", int.MaxValue)] // should fit
-		[InlineData ("A sentence has words.", "A sentence has words", 20)] // Should not fit
-		[InlineData ("A sentence has words.", "A sentence", 10)] // Should not fit
-		[InlineData ("A\tsentence\thas\twords.", "A sentence has words.", int.MaxValue)]
-		[InlineData ("A\tsentence\thas\twords.", "A sentence", 10)]
-		[InlineData ("line1\nline2\nline3long!", "line1\nline2\nline3long!", int.MaxValue)]
-		[InlineData ("line1\nline2\nline3long!", "line1\nline", 10)]
-		[InlineData (" ~  s  gui.cs   master ↑10", " ~  s  ", 10)] // Unicode
-		[InlineData ("Ð ÑÐ", "Ð ÑÐ", 5)] // should fit
-		[InlineData ("Ð ÑÐ", "Ð ÑÐ", 4)] // should fit
-		[InlineData ("Ð ÑÐ", "Ð Ñ", 3)] // Should not fit
-		public void ClipAndJustify_Valid_Right (string text, string justifiedText, int maxWidth)
-		{
-			var align = TextAlignment.Right;
-			var textDirection = TextDirection.LeftRight_BottomTop;
-			var tabWidth = 1;
-
-			Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth));
-			var expectedClippedWidth = Math.Min (justifiedText.GetRuneCount (), maxWidth);
-			Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth));
-			Assert.True (justifiedText.GetRuneCount () <= maxWidth);
-			Assert.True (justifiedText.GetColumns () <= maxWidth);
-			Assert.Equal (expectedClippedWidth, justifiedText.GetRuneCount ());
-			Assert.Equal (expectedClippedWidth, justifiedText.ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 1)));
-			Assert.True (expectedClippedWidth <= maxWidth);
-			Assert.Equal (StringExtensions.ToString (justifiedText.ToRunes () [0..expectedClippedWidth]), justifiedText);
+	[Theory]
+	[InlineData ("line1\nline2",                      5,  2)]
+	[InlineData ("\nline2",                           5,  2)]
+	[InlineData ("\n\n",                              0,  3)]
+	[InlineData ("\n\n\n",                            0,  4)]
+	[InlineData ("line1\nline2\nline3long!",          10, 3)]
+	[InlineData ("line1\nline2\n\n",                  5,  4)]
+	[InlineData ("line1\r\nline2",                    5,  2)]
+	[InlineData (" ~  s  gui.cs   master ↑10\n", 31, 2)]
+	[InlineData ("\n ~  s  gui.cs   master ↑10", 31, 2)]
+	[InlineData (" ~  s  gui.cs   master\n↑10",  27, 2)]
+	public void CalcRect_MultiLine_Returns_nHigh (string text, int expectedWidth, int expectedLines)
+	{
+		Assert.Equal (new Rect (0, 0, expectedWidth, expectedLines), TextFormatter.CalcRect (0, 0, text));
+		var lines = text.Split (text.Contains (Environment.NewLine) ? Environment.NewLine : "\n");
+		var maxWidth = lines.Max (s => s.GetColumns ());
+		var lineWider = 0;
+		for (var i = 0; i < lines.Length; i++) {
+			var w = lines [i].GetColumns ();
+			if (w == maxWidth) {
+				lineWider = i;
+			}
 		}
+		Assert.Equal (new Rect (0, 0, maxWidth,                                                                 expectedLines), TextFormatter.CalcRect (0, 0, text));
+		Assert.Equal (new Rect (0, 0, lines [lineWider].ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 0)), expectedLines), TextFormatter.CalcRect (0, 0, text));
+	}
 
-		[Theory]
-		[InlineData ("test", "", 0)]
-		[InlineData ("test", "te", 2)]
-		[InlineData ("test", "test", int.MaxValue)]
-		[InlineData ("A sentence has words.", "A sentence has words.", 22)] // should fit
-		[InlineData ("A sentence has words.", "A sentence has words.", 21)] // should fit
-		[InlineData ("A sentence has words.", "A sentence has words.", int.MaxValue)] // should fit
-		[InlineData ("A sentence has words.", "A sentence has words", 20)] // Should not fit
-		[InlineData ("A sentence has words.", "A sentence", 10)] // Should not fit
-		[InlineData ("A\tsentence\thas\twords.", "A sentence has words.", int.MaxValue)]
-		[InlineData ("A\tsentence\thas\twords.", "A sentence", 10)]
-		[InlineData ("line1\nline2\nline3long!", "line1\nline2\nline3long!", int.MaxValue)]
-		[InlineData ("line1\nline2\nline3long!", "line1\nline", 10)]
-		[InlineData (" ~  s  gui.cs   master ↑10", " ~  s  ", 10)] // Unicode
-		[InlineData ("Ð ÑÐ", "Ð ÑÐ", 5)] // should fit
-		[InlineData ("Ð ÑÐ", "Ð ÑÐ", 4)] // should fit
-		[InlineData ("Ð ÑÐ", "Ð Ñ", 3)] // Should not fit
-		public void ClipAndJustify_Valid_Centered (string text, string justifiedText, int maxWidth)
-		{
-			var align = TextAlignment.Centered;
-			var textDirection = TextDirection.LeftRight_TopBottom;
-			var tabWidth = 1;
-
-			Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth));
-			var expectedClippedWidth = Math.Min (justifiedText.GetRuneCount (), maxWidth);
-			Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth));
-			Assert.True (justifiedText.GetRuneCount () <= maxWidth);
-			Assert.True (justifiedText.GetColumns () <= maxWidth);
-			Assert.Equal (expectedClippedWidth, justifiedText.GetRuneCount ());
-			Assert.Equal (expectedClippedWidth, justifiedText.ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 1)));
-			Assert.True (expectedClippedWidth <= maxWidth);
-			Assert.Equal (StringExtensions.ToString (justifiedText.ToRunes () [0..expectedClippedWidth]), justifiedText);
-		}
+	[Theory]
+	[InlineData ("")]
+	[InlineData (null)]
+	[InlineData ("test")]
+	public void ClipAndJustify_Invalid_Returns_Original (string text)
+	{
+		var expected = string.IsNullOrEmpty (text) ? text : "";
+		Assert.Equal (expected, TextFormatter.ClipAndJustify (text, 0, TextAlignment.Left));
+		Assert.Equal (expected, TextFormatter.ClipAndJustify (text, 0, TextAlignment.Left));
+		Assert.Throws<ArgumentOutOfRangeException> (() => TextFormatter.ClipAndJustify (text, -1, TextAlignment.Left));
+	}
 
-		[Theory]
-		[InlineData ("test", "", 0)]
-		[InlineData ("test", "te", 2)]
-		[InlineData ("test", "test", int.MaxValue)] // This doesn't throw because it only create a word with length 1
-		[InlineData ("A sentence has words.", "A  sentence has words.", 22)] // should fit
-		[InlineData ("A sentence has words.", "A sentence has words.", 21)] // should fit
-		[InlineData ("A sentence has words.", "A                                                                                                                                                                 sentence                                                                                                                                                                 has                                                                                                                                                                words.", 500)] // should fit
-		[InlineData ("A sentence has words.", "A sentence has words", 20)] // Should not fit
-		[InlineData ("A sentence has words.", "A sentence", 10)] // Should not fit
-									 // Now throw System.OutOfMemoryException. See https://stackoverflow.com/questions/20672920/maxcapacity-of-stringbuilder
-									 //[InlineData ("A\tsentence\thas\twords.", "A sentence has words.", int.MaxValue)]
-		[InlineData ("A\tsentence\thas\twords.", "A sentence", 10)]
-		[InlineData ("line1\nline2\nline3long!", "line1\nline2\nline3long!", int.MaxValue)] // This doesn't throw because it only create a line with length 1
-		[InlineData ("line1\nline2\nline3long!", "line1\nline", 10)]
-		[InlineData (" ~  s  gui.cs   master ↑10", " ~  s  ", 10)] // Unicode
-		[InlineData ("Ð ÑÐ", "Ð  ÑÐ", 5)] // should fit
-		[InlineData ("Ð ÑÐ", "Ð ÑÐ", 4)] // should fit
-		[InlineData ("Ð ÑÐ", "Ð Ñ", 3)] // Should not fit
-		public void ClipAndJustify_Valid_Justified (string text, string justifiedText, int maxWidth)
-		{
-			var align = TextAlignment.Justified;
-			var textDirection = TextDirection.LeftRight_TopBottom;
-			var tabWidth = 1;
-
-			Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth));
-			var expectedClippedWidth = Math.Min (justifiedText.GetRuneCount (), maxWidth);
-			Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth));
-			Assert.True (justifiedText.GetRuneCount () <= maxWidth);
-			Assert.True (justifiedText.GetColumns () <= maxWidth);
-			Assert.Equal (expectedClippedWidth, justifiedText.GetRuneCount ());
-			Assert.Equal (expectedClippedWidth, justifiedText.ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 1)));
-			Assert.True (expectedClippedWidth <= maxWidth);
-			Assert.Equal (StringExtensions.ToString (justifiedText.ToRunes () [0..expectedClippedWidth]), justifiedText);
-
-			// see Justify_ tests below
-		}
+	[Theory]
+	[InlineData ("test",                            "",                         0)]
+	[InlineData ("test",                            "te",                       2)]
+	[InlineData ("test",                            "test",                     int.MaxValue)]
+	[InlineData ("A sentence has words.",           "A sentence has words.",    22)]           // should fit
+	[InlineData ("A sentence has words.",           "A sentence has words.",    21)]           // should fit
+	[InlineData ("A sentence has words.",           "A sentence has words.",    int.MaxValue)] // should fit
+	[InlineData ("A sentence has words.",           "A sentence has words",     20)]           // Should not fit
+	[InlineData ("A sentence has words.",           "A sentence",               10)]           // Should not fit
+	[InlineData ("A\tsentence\thas\twords.",        "A sentence has words.",    int.MaxValue)]
+	[InlineData ("A\tsentence\thas\twords.",        "A sentence",               10)]
+	[InlineData ("line1\nline2\nline3long!",        "line1\nline2\nline3long!", int.MaxValue)]
+	[InlineData ("line1\nline2\nline3long!",        "line1\nline",              10)]
+	[InlineData (" ~  s  gui.cs   master ↑10", " ~  s  ",               10)] // Unicode
+	[InlineData ("Ð ÑÐ",                            "Ð ÑÐ",                     5)]  // should fit
+	[InlineData ("Ð ÑÐ",                            "Ð ÑÐ",                     4)]  // should fit
+	[InlineData ("Ð ÑÐ",                            "Ð Ñ",                      3)]  // Should not fit
+	public void ClipAndJustify_Valid_Left (string text, string justifiedText, int maxWidth)
+	{
+		var align = TextAlignment.Left;
+		var textDirection = TextDirection.LeftRight_BottomTop;
+		var tabWidth = 1;
+
+		Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth));
+		var expectedClippedWidth = Math.Min (justifiedText.GetRuneCount (), maxWidth);
+		Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth));
+		Assert.True (justifiedText.GetRuneCount () <= maxWidth);
+		Assert.True (justifiedText.GetColumns () <= maxWidth);
+		Assert.Equal (expectedClippedWidth, justifiedText.GetRuneCount ());
+		Assert.Equal (expectedClippedWidth, justifiedText.ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 1)));
+		Assert.True (expectedClippedWidth <= maxWidth);
+		Assert.Equal (StringExtensions.ToString (justifiedText.ToRunes () [..expectedClippedWidth]), justifiedText);
+	}
 
-		[Theory]
-		[InlineData ("")]
-		[InlineData (null)]
-		[InlineData ("test")]
-		public void Justify_Invalid (string text)
-		{
-			Assert.Equal (text, TextFormatter.Justify (text, 0));
-			Assert.Equal (text, TextFormatter.Justify (text, 0));
-			Assert.Throws<ArgumentOutOfRangeException> (() => TextFormatter.Justify (text, -1));
-		}
+	[Theory]
+	[InlineData ("test",                            "",                         0)]
+	[InlineData ("test",                            "te",                       2)]
+	[InlineData ("test",                            "test",                     int.MaxValue)]
+	[InlineData ("A sentence has words.",           "A sentence has words.",    22)]           // should fit
+	[InlineData ("A sentence has words.",           "A sentence has words.",    21)]           // should fit
+	[InlineData ("A sentence has words.",           "A sentence has words.",    int.MaxValue)] // should fit
+	[InlineData ("A sentence has words.",           "A sentence has words",     20)]           // Should not fit
+	[InlineData ("A sentence has words.",           "A sentence",               10)]           // Should not fit
+	[InlineData ("A\tsentence\thas\twords.",        "A sentence has words.",    int.MaxValue)]
+	[InlineData ("A\tsentence\thas\twords.",        "A sentence",               10)]
+	[InlineData ("line1\nline2\nline3long!",        "line1\nline2\nline3long!", int.MaxValue)]
+	[InlineData ("line1\nline2\nline3long!",        "line1\nline",              10)]
+	[InlineData (" ~  s  gui.cs   master ↑10", " ~  s  ",               10)] // Unicode
+	[InlineData ("Ð ÑÐ",                            "Ð ÑÐ",                     5)]  // should fit
+	[InlineData ("Ð ÑÐ",                            "Ð ÑÐ",                     4)]  // should fit
+	[InlineData ("Ð ÑÐ",                            "Ð Ñ",                      3)]  // Should not fit
+	public void ClipAndJustify_Valid_Right (string text, string justifiedText, int maxWidth)
+	{
+		var align = TextAlignment.Right;
+		var textDirection = TextDirection.LeftRight_BottomTop;
+		var tabWidth = 1;
+
+		Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth));
+		var expectedClippedWidth = Math.Min (justifiedText.GetRuneCount (), maxWidth);
+		Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth));
+		Assert.True (justifiedText.GetRuneCount () <= maxWidth);
+		Assert.True (justifiedText.GetColumns () <= maxWidth);
+		Assert.Equal (expectedClippedWidth, justifiedText.GetRuneCount ());
+		Assert.Equal (expectedClippedWidth, justifiedText.ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 1)));
+		Assert.True (expectedClippedWidth <= maxWidth);
+		Assert.Equal (StringExtensions.ToString (justifiedText.ToRunes () [..expectedClippedWidth]), justifiedText);
+	}
 
-		[Theory]
-		[InlineData ("word")] // Even # of chars
-		[InlineData ("word.")] // Odd # of chars
-		[InlineData ("пÑивеÑ")] // Unicode (even #)
-		[InlineData ("пÑивеÑ.")] // Unicode (odd # of chars)
-		public void Justify_SingleWord (string text)
-		{
-			var justifiedText = text;
-			char fillChar = '+';
-
-			int width = text.GetRuneCount ();
-			Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar));
-			width = text.GetRuneCount () + 1;
-			Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar));
-			width = text.GetRuneCount () + 2;
-			Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar));
-			width = text.GetRuneCount () + 10;
-			Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar));
-			width = text.GetRuneCount () + 11;
-			Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar));
-		}
+	[Theory]
+	[InlineData ("test",                            "",                         0)]
+	[InlineData ("test",                            "te",                       2)]
+	[InlineData ("test",                            "test",                     int.MaxValue)]
+	[InlineData ("A sentence has words.",           "A sentence has words.",    22)]           // should fit
+	[InlineData ("A sentence has words.",           "A sentence has words.",    21)]           // should fit
+	[InlineData ("A sentence has words.",           "A sentence has words.",    int.MaxValue)] // should fit
+	[InlineData ("A sentence has words.",           "A sentence has words",     20)]           // Should not fit
+	[InlineData ("A sentence has words.",           "A sentence",               10)]           // Should not fit
+	[InlineData ("A\tsentence\thas\twords.",        "A sentence has words.",    int.MaxValue)]
+	[InlineData ("A\tsentence\thas\twords.",        "A sentence",               10)]
+	[InlineData ("line1\nline2\nline3long!",        "line1\nline2\nline3long!", int.MaxValue)]
+	[InlineData ("line1\nline2\nline3long!",        "line1\nline",              10)]
+	[InlineData (" ~  s  gui.cs   master ↑10", " ~  s  ",               10)] // Unicode
+	[InlineData ("Ð ÑÐ",                            "Ð ÑÐ",                     5)]  // should fit
+	[InlineData ("Ð ÑÐ",                            "Ð ÑÐ",                     4)]  // should fit
+	[InlineData ("Ð ÑÐ",                            "Ð Ñ",                      3)]  // Should not fit
+	public void ClipAndJustify_Valid_Centered (string text, string justifiedText, int maxWidth)
+	{
+		var align = TextAlignment.Centered;
+		var textDirection = TextDirection.LeftRight_TopBottom;
+		var tabWidth = 1;
+
+		Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth));
+		var expectedClippedWidth = Math.Min (justifiedText.GetRuneCount (), maxWidth);
+		Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth));
+		Assert.True (justifiedText.GetRuneCount () <= maxWidth);
+		Assert.True (justifiedText.GetColumns () <= maxWidth);
+		Assert.Equal (expectedClippedWidth, justifiedText.GetRuneCount ());
+		Assert.Equal (expectedClippedWidth, justifiedText.ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 1)));
+		Assert.True (expectedClippedWidth <= maxWidth);
+		Assert.Equal (StringExtensions.ToString (justifiedText.ToRunes () [..expectedClippedWidth]), justifiedText);
+	}
 
-		[Theory]
-		// Even # of spaces
-		//            0123456789
-		[InlineData ("012 456 89", "012 456 89", 10, 0, "+", true)]
-		[InlineData ("012 456 89", "012++456+89", 11, 1)]
-		[InlineData ("012 456 89", "012 456 89", 12, 2, "++", true)]
-		[InlineData ("012 456 89", "012+++456++89", 13, 3)]
-		[InlineData ("012 456 89", "012 456 89", 14, 4, "+++", true)]
-		[InlineData ("012 456 89", "012++++456+++89", 15, 5)]
-		[InlineData ("012 456 89", "012 456 89", 16, 6, "++++", true)]
-		[InlineData ("012 456 89", "012 456 89", 30, 20, "+++++++++++", true)]
-		[InlineData ("012 456 89", "012+++++++++++++456++++++++++++89", 33, 23)]
-		// Odd # of spaces
-		//            01234567890123
-		[InlineData ("012 456 89 end", "012 456 89 end", 14, 0, "+", true)]
-		[InlineData ("012 456 89 end", "012++456+89+end", 15, 1)]
-		[InlineData ("012 456 89 end", "012++456++89+end", 16, 2)]
-		[InlineData ("012 456 89 end", "012 456 89 end", 17, 3, "++", true)]
-		[InlineData ("012 456 89 end", "012+++456++89++end", 18, 4)]
-		[InlineData ("012 456 89 end", "012+++456+++89++end", 19, 5)]
-		[InlineData ("012 456 89 end", "012 456 89 end", 20, 6, "+++", true)]
-		[InlineData ("012 456 89 end", "012++++++++456++++++++89+++++++end", 34, 20)]
-		[InlineData ("012 456 89 end", "012+++++++++456+++++++++89++++++++end", 37, 23)]
-		// Unicode
-		// Even # of chars
-		//            0123456789
-		[InlineData ("пÑРвРÑ", "пÑРвРÑ", 10, 0, "+", true)]
-		[InlineData ("пÑРвРÑ", "пÑÐ++вÐ+Ñ", 11, 1)]
-		[InlineData ("пÑРвРÑ", "пÑРвРÑ", 12, 2, "++", true)]
-		[InlineData ("пÑРвРÑ", "пÑÐ+++вÐ++Ñ", 13, 3)]
-		[InlineData ("пÑРвРÑ", "пÑРвРÑ", 14, 4, "+++", true)]
-		[InlineData ("пÑРвРÑ", "пÑÐ++++вÐ+++Ñ", 15, 5)]
-		[InlineData ("пÑРвРÑ", "пÑРвРÑ", 16, 6, "++++", true)]
-		[InlineData ("пÑРвРÑ", "пÑРвРÑ", 30, 20, "+++++++++++", true)]
-		[InlineData ("пÑРвРÑ", "пÑÐ+++++++++++++вÐ++++++++++++Ñ", 33, 23)]
-		// Unicode
-		// Odd # of chars
-		//            0123456789
-		[InlineData ("Ð ÑРвРÑ", "Ð ÑРвРÑ", 10, 0, "+", true)]
-		[InlineData ("Ð ÑРвРÑ", "Ð++ÑÐ+вÐ+Ñ", 11, 1)]
-		[InlineData ("Ð ÑРвРÑ", "Ð++ÑÐ++вÐ+Ñ", 12, 2)]
-		[InlineData ("Ð ÑРвРÑ", "Ð ÑРвРÑ", 13, 3, "++", true)]
-		[InlineData ("Ð ÑРвРÑ", "Ð+++ÑÐ++вÐ++Ñ", 14, 4)]
-		[InlineData ("Ð ÑРвРÑ", "Ð+++ÑÐ+++вÐ++Ñ", 15, 5)]
-		[InlineData ("Ð ÑРвРÑ", "Ð ÑРвРÑ", 16, 6, "+++", true)]
-		[InlineData ("Ð ÑРвРÑ", "Ð++++++++ÑÐ++++++++вÐ+++++++Ñ", 30, 20)]
-		[InlineData ("Ð ÑРвРÑ", "Ð+++++++++ÑÐ+++++++++вÐ++++++++Ñ", 33, 23)]
-		public void Justify_Sentence (string text, string justifiedText, int forceToWidth, int widthOffset, string replaceWith = null, bool replace = false)
-		{
-			char fillChar = '+';
-
-			Assert.Equal (forceToWidth, text.GetRuneCount () + widthOffset);
-			if (replace) {
-				justifiedText = text.Replace (" ", replaceWith);
-			}
-			Assert.Equal (justifiedText, TextFormatter.Justify (text, forceToWidth, fillChar));
-			Assert.True (Math.Abs (forceToWidth - justifiedText.GetRuneCount ()) < text.Count (s => s == ' '));
-			Assert.True (Math.Abs (forceToWidth - justifiedText.GetColumns ()) < text.Count (s => s == ' '));
-		}
+	[Theory]
+	[InlineData ("test",                  "",                       0)]
+	[InlineData ("test",                  "te",                     2)]
+	[InlineData ("test",                  "test",                   int.MaxValue)] // This doesn't throw because it only create a word with length 1
+	[InlineData ("A sentence has words.", "A  sentence has words.", 22)]           // should fit
+	[InlineData ("A sentence has words.", "A sentence has words.",  21)]           // should fit
+	[InlineData ("A sentence has words.",
+		"A                                                                                                                                                                 sentence                                                                                                                                                                 has                                                                                                                                                                words.",
+		500)]                                                      // should fit
+	[InlineData ("A sentence has words.", "A sentence has words", 20)] // Should not fit
+	[InlineData ("A sentence has words.", "A sentence",           10)] // Should not fit
+	// Now throw System.OutOfMemoryException. See https://stackoverflow.com/questions/20672920/maxcapacity-of-stringbuilder
+	//[InlineData ("A\tsentence\thas\twords.", "A sentence has words.", int.MaxValue)]
+	[InlineData ("A\tsentence\thas\twords.",        "A sentence",               10)]
+	[InlineData ("line1\nline2\nline3long!",        "line1\nline2\nline3long!", int.MaxValue)] // This doesn't throw because it only create a line with length 1
+	[InlineData ("line1\nline2\nline3long!",        "line1\nline",              10)]
+	[InlineData (" ~  s  gui.cs   master ↑10", " ~  s  ",               10)] // Unicode
+	[InlineData ("Ð ÑÐ",                            "Ð  ÑÐ",                    5)]  // should fit
+	[InlineData ("Ð ÑÐ",                            "Ð ÑÐ",                     4)]  // should fit
+	[InlineData ("Ð ÑÐ",                            "Ð Ñ",                      3)]  // Should not fit
+	public void ClipAndJustify_Valid_Justified (string text, string justifiedText, int maxWidth)
+	{
+		var align = TextAlignment.Justified;
+		var textDirection = TextDirection.LeftRight_TopBottom;
+		var tabWidth = 1;
+
+		Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth));
+		var expectedClippedWidth = Math.Min (justifiedText.GetRuneCount (), maxWidth);
+		Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth));
+		Assert.True (justifiedText.GetRuneCount () <= maxWidth);
+		Assert.True (justifiedText.GetColumns () <= maxWidth);
+		Assert.Equal (expectedClippedWidth, justifiedText.GetRuneCount ());
+		Assert.Equal (expectedClippedWidth, justifiedText.ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 1)));
+		Assert.True (expectedClippedWidth <= maxWidth);
+		Assert.Equal (StringExtensions.ToString (justifiedText.ToRunes () [..expectedClippedWidth]), justifiedText);
+
+		// see Justify_ tests below
+	}
 
-		[Fact]
-		public void WordWrap_Invalid ()
-		{
-			var text = string.Empty;
-			int width = 0;
+	[Theory]
+	[InlineData ("")]
+	[InlineData (null)]
+	[InlineData ("test")]
+	public void Justify_Invalid (string text)
+	{
+		Assert.Equal (text, TextFormatter.Justify (text, 0));
+		Assert.Equal (text, TextFormatter.Justify (text, 0));
+		Assert.Throws<ArgumentOutOfRangeException> (() => TextFormatter.Justify (text, -1));
+	}
 
-			Assert.Empty (TextFormatter.WordWrapText (null, width));
-			Assert.Empty (TextFormatter.WordWrapText (text, width));
-			Assert.Throws<ArgumentOutOfRangeException> (() => TextFormatter.WordWrapText (text, -1));
-		}
+	[Theory]
+	[InlineData ("word")]        // Even # of chars
+	[InlineData ("word.")]       // Odd # of chars
+	[InlineData ("пÑивеÑ")]  // Unicode (even #)
+	[InlineData ("пÑивеÑ.")] // Unicode (odd # of chars)
+	public void Justify_SingleWord (string text)
+	{
+		var justifiedText = text;
+		var fillChar = '+';
+
+		var width = text.GetRuneCount ();
+		Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar));
+		width = text.GetRuneCount () + 1;
+		Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar));
+		width = text.GetRuneCount () + 2;
+		Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar));
+		width = text.GetRuneCount () + 10;
+		Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar));
+		width = text.GetRuneCount () + 11;
+		Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar));
+	}
 
-		[Fact]
-		public void WordWrap_BigWidth ()
-		{
-			List<string> wrappedLines;
+	[Theory]
+	// Even # of spaces
+	//            0123456789
+	[InlineData ("012 456 89", "012 456 89",                        10, 0, "+", true)]
+	[InlineData ("012 456 89", "012++456+89",                       11, 1)]
+	[InlineData ("012 456 89", "012 456 89",                        12, 2, "++", true)]
+	[InlineData ("012 456 89", "012+++456++89",                     13, 3)]
+	[InlineData ("012 456 89", "012 456 89",                        14, 4, "+++", true)]
+	[InlineData ("012 456 89", "012++++456+++89",                   15, 5)]
+	[InlineData ("012 456 89", "012 456 89",                        16, 6,  "++++",        true)]
+	[InlineData ("012 456 89", "012 456 89",                        30, 20, "+++++++++++", true)]
+	[InlineData ("012 456 89", "012+++++++++++++456++++++++++++89", 33, 23)]
+	// Odd # of spaces
+	//            01234567890123
+	[InlineData ("012 456 89 end", "012 456 89 end",                        14, 0, "+", true)]
+	[InlineData ("012 456 89 end", "012++456+89+end",                       15, 1)]
+	[InlineData ("012 456 89 end", "012++456++89+end",                      16, 2)]
+	[InlineData ("012 456 89 end", "012 456 89 end",                        17, 3, "++", true)]
+	[InlineData ("012 456 89 end", "012+++456++89++end",                    18, 4)]
+	[InlineData ("012 456 89 end", "012+++456+++89++end",                   19, 5)]
+	[InlineData ("012 456 89 end", "012 456 89 end",                        20, 6, "+++", true)]
+	[InlineData ("012 456 89 end", "012++++++++456++++++++89+++++++end",    34, 20)]
+	[InlineData ("012 456 89 end", "012+++++++++456+++++++++89++++++++end", 37, 23)]
+	// Unicode
+	// Even # of chars
+	//            0123456789
+	[InlineData ("пÑРвРÑ", "пÑРвРÑ",                        10, 0, "+", true)]
+	[InlineData ("пÑРвРÑ", "пÑÐ++вÐ+Ñ",                       11, 1)]
+	[InlineData ("пÑРвРÑ", "пÑРвРÑ",                        12, 2, "++", true)]
+	[InlineData ("пÑРвРÑ", "пÑÐ+++вÐ++Ñ",                     13, 3)]
+	[InlineData ("пÑРвРÑ", "пÑРвРÑ",                        14, 4, "+++", true)]
+	[InlineData ("пÑРвРÑ", "пÑÐ++++вÐ+++Ñ",                   15, 5)]
+	[InlineData ("пÑРвРÑ", "пÑРвРÑ",                        16, 6,  "++++",        true)]
+	[InlineData ("пÑРвРÑ", "пÑРвРÑ",                        30, 20, "+++++++++++", true)]
+	[InlineData ("пÑРвРÑ", "пÑÐ+++++++++++++вÐ++++++++++++Ñ", 33, 23)]
+	// Unicode
+	// Odd # of chars
+	//            0123456789
+	[InlineData ("Ð ÑРвРÑ", "Ð ÑРвРÑ",                        10, 0, "+", true)]
+	[InlineData ("Ð ÑРвРÑ", "Ð++ÑÐ+вÐ+Ñ",                       11, 1)]
+	[InlineData ("Ð ÑРвРÑ", "Ð++ÑÐ++вÐ+Ñ",                      12, 2)]
+	[InlineData ("Ð ÑРвРÑ", "Ð ÑРвРÑ",                        13, 3, "++", true)]
+	[InlineData ("Ð ÑРвРÑ", "Ð+++ÑÐ++вÐ++Ñ",                    14, 4)]
+	[InlineData ("Ð ÑРвРÑ", "Ð+++ÑÐ+++вÐ++Ñ",                   15, 5)]
+	[InlineData ("Ð ÑРвРÑ", "Ð ÑРвРÑ",                        16, 6, "+++", true)]
+	[InlineData ("Ð ÑРвРÑ", "Ð++++++++ÑÐ++++++++вÐ+++++++Ñ",    30, 20)]
+	[InlineData ("Ð ÑРвРÑ", "Ð+++++++++ÑÐ+++++++++вÐ++++++++Ñ", 33, 23)]
+	public void Justify_Sentence (string text, string justifiedText, int forceToWidth, int widthOffset, string replaceWith = null, bool replace = false)
+	{
+		var fillChar = '+';
+
+		Assert.Equal (forceToWidth, text.GetRuneCount () + widthOffset);
+		if (replace) {
+			justifiedText = text.Replace (" ", replaceWith);
+		}
+		Assert.Equal (justifiedText, TextFormatter.Justify (text, forceToWidth, fillChar));
+		Assert.True (Math.Abs (forceToWidth - justifiedText.GetRuneCount ()) < text.Count (s => s == ' '));
+		Assert.True (Math.Abs (forceToWidth - justifiedText.GetColumns ()) < text.Count (s => s == ' '));
+	}
 
-			var text = "Constantinople";
-			wrappedLines = TextFormatter.WordWrapText (text, 100);
-			Assert.True (wrappedLines.Count == 1);
-			Assert.Equal ("Constantinople", wrappedLines [0]);
-		}
+	[Fact]
+	public void WordWrap_Invalid ()
+	{
+		var text = string.Empty;
+		var width = 0;
 
-		[Theory]
-		[InlineData ("Constantinople", 14, 0, new string [] { "Constantinople" })]
-		[InlineData ("Constantinople", 12, -2, new string [] { "Constantinop", "le" })]
-		[InlineData ("Constantinople", 9, -5, new string [] { "Constanti", "nople" })]
-		[InlineData ("Constantinople", 7, -7, new string [] { "Constan", "tinople" })]
-		[InlineData ("Constantinople", 5, -9, new string [] { "Const", "antin", "ople" })]
-		[InlineData ("Constantinople", 4, -10, new string [] { "Cons", "tant", "inop", "le" })]
-		[InlineData ("Constantinople", 1, -13, new string [] { "C", "o", "n", "s", "t", "a", "n", "t", "i", "n", "o", "p", "l", "e" })]
-		public void WordWrap_SingleWordLine (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
-		{
-			List<string> wrappedLines;
-
-			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
-			var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
-			wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
-			Assert.Equal (wrappedLines.Count, resultLines.Count ());
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
-			Assert.Equal (resultLines, wrappedLines);
-		}
+		Assert.Empty (TextFormatter.WordWrapText (null, width));
+		Assert.Empty (TextFormatter.WordWrapText (text, width));
+		Assert.Throws<ArgumentOutOfRangeException> (() => TextFormatter.WordWrapText (text, -1));
+	}
 
-		[Theory]
-		[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 51, 0, new string [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ" })]
-		[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 50, -1, new string [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ" })]
-		[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 46, -5, new string [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮ", "ฯะัาำ" })]
-		[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 26, -25, new string [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบ", "ปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ" })]
-		[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 17, -34, new string [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑ", "ฒณดตถทธนบปผฝพฟภมย", "รฤลฦวศษสหฬอฮฯะัาำ" })]
-		[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 13, -38, new string [] { "กขฃคฅฆงจฉชซฌญ", "ฎฏฐฑฒณดตถทธนบ", "ปผฝพฟภมยรฤลฦว", "ศษสหฬอฮฯะัาำ" })]
-		[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 1, -50, new string [] { "ก", "ข", "ฃ", "ค", "ฅ", "ฆ", "ง", "จ", "ฉ", "ช", "ซ", "ฌ", "ญ", "ฎ", "ฏ", "ฐ", "ฑ", "ฒ", "ณ", "ด", "ต", "ถ", "ท", "ธ", "น", "บ", "ป", "ผ", "ฝ", "พ", "ฟ", "ภ", "ม", "ย", "ร", "ฤ", "ล", "ฦ", "ว", "ศ", "ษ", "ส", "ห", "ฬ", "อ", "ฮ", "ฯ", "ะั", "า", "ำ" })]
-		public void WordWrap_Unicode_SingleWordLine (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
-		{
-			List<string> wrappedLines;
-
-			var zeroWidth = text.EnumerateRunes ().Where (r => r.GetColumns () == 0);
-			Assert.Single (zeroWidth);
-			Assert.Equal ('ั', zeroWidth.ElementAt (0).Value);
-			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
-			var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
-			wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
-			Assert.Equal (wrappedLines.Count, resultLines.Count ());
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount () + zeroWidth.Count () - 1 + widthOffset) : 0));
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
-			Assert.Equal (resultLines, wrappedLines);
-		}
+	[Fact]
+	public void WordWrap_BigWidth ()
+	{
+		List<string> wrappedLines;
 
-		[Theory]
-		[InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 19, 0, new string [] { "This\u00A0is\u00A0a\u00A0sentence." })]
-		[InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 18, -1, new string [] { "This\u00A0is\u00A0a\u00A0sentence", "." })]
-		[InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 17, -2, new string [] { "This\u00A0is\u00A0a\u00A0sentenc", "e." })]
-		[InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 14, -5, new string [] { "This\u00A0is\u00A0a\u00A0sent", "ence." })]
-		[InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 10, -9, new string [] { "This\u00A0is\u00A0a\u00A0", "sentence." })]
-		[InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 7, -12, new string [] { "This\u00A0is", "\u00A0a\u00A0sent", "ence." })]
-		[InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 5, -14, new string [] { "This\u00A0", "is\u00A0a\u00A0", "sente", "nce." })]
-		[InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 1, -18, new string [] { "T", "h", "i", "s", "\u00A0", "i", "s", "\u00A0", "a", "\u00A0", "s", "e", "n", "t", "e", "n", "c", "e", "." })]
-		public void WordWrap_Unicode_LineWithNonBreakingSpace (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
-		{
-			List<string> wrappedLines;
-
-			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
-			var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
-			wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
-			Assert.Equal (wrappedLines.Count, resultLines.Count ());
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
-			Assert.Equal (resultLines, wrappedLines);
-		}
+		var text = "Constantinople";
+		wrappedLines = TextFormatter.WordWrapText (text, 100);
+		Assert.True (wrappedLines.Count == 1);
+		Assert.Equal ("Constantinople", wrappedLines [0]);
+	}
 
-		[Theory]
-		[InlineData ("This\u00A0is\n\u00A0a\u00A0sentence.", 20, 0, new string [] { "This\u00A0is\u00A0a\u00A0sentence." })]
-		[InlineData ("This\u00A0is\n\u00A0a\u00A0sentence.", 19, -1, new string [] { "This\u00A0is\u00A0a\u00A0sentence." })]
-		[InlineData ("\u00A0\u00A0\u00A0\u00A0\u00A0test\u00A0sentence.", 19, 0, new string [] { "\u00A0\u00A0\u00A0\u00A0\u00A0test\u00A0sentence." })]
-		public void WordWrap_Unicode_2LinesWithNonBreakingSpace (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
-		{
-			List<string> wrappedLines;
-
-			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
-			var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
-			wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
-			Assert.Equal (wrappedLines.Count, resultLines.Count ());
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
-			Assert.Equal (resultLines, wrappedLines);
-		}
+	[Theory]
+	[InlineData ("Constantinople", 14, 0,   new [] { "Constantinople" })]
+	[InlineData ("Constantinople", 12, -2,  new [] { "Constantinop", "le" })]
+	[InlineData ("Constantinople", 9,  -5,  new [] { "Constanti", "nople" })]
+	[InlineData ("Constantinople", 7,  -7,  new [] { "Constan", "tinople" })]
+	[InlineData ("Constantinople", 5,  -9,  new [] { "Const", "antin", "ople" })]
+	[InlineData ("Constantinople", 4,  -10, new [] { "Cons", "tant", "inop", "le" })]
+	[InlineData ("Constantinople", 1,  -13, new [] { "C", "o", "n", "s", "t", "a", "n", "t", "i", "n", "o", "p", "l", "e" })]
+	public void WordWrap_SingleWordLine (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
+	{
+		List<string> wrappedLines;
+
+		Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
+		var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
+		wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
+		Assert.Equal (wrappedLines.Count, resultLines.Count ());
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
+		Assert.Equal (resultLines, wrappedLines);
+	}
 
-		[Theory]
-		[InlineData ("A sentence has words.", 21, 0, new string [] { "A sentence has words." })]
-		[InlineData ("A sentence has words.", 20, -1, new string [] { "A sentence has", "words." })]
-		[InlineData ("A sentence has words.", 15, -6, new string [] { "A sentence has", "words." })]
-		[InlineData ("A sentence has words.", 14, -7, new string [] { "A sentence has", "words." })]
-		[InlineData ("A sentence has words.", 13, -8, new string [] { "A sentence", "has words." })]
-		// Unicode 
-		[InlineData ("A Unicode sentence (пÑивеÑ) has words.", 42, 0, new string [] { "A Unicode sentence (пÑивеÑ) has words." })]
-		[InlineData ("A Unicode sentence (пÑивеÑ) has words.", 41, -1, new string [] { "A Unicode sentence (пÑивеÑ) has", "words." })]
-		[InlineData ("A Unicode sentence (пÑивеÑ) has words.", 36, -6, new string [] { "A Unicode sentence (пÑивеÑ) has", "words." })]
-		[InlineData ("A Unicode sentence (пÑивеÑ) has words.", 35, -7, new string [] { "A Unicode sentence (пÑивеÑ) has", "words." })]
-		[InlineData ("A Unicode sentence (пÑивеÑ) has words.", 34, -8, new string [] { "A Unicode sentence (пÑивеÑ)", "has words." })]
-		[InlineData ("A Unicode sentence (пÑивеÑ) has words.", 25, -17, new string [] { "A Unicode sentence", "(пÑивеÑ) has words." })]
-		public void WordWrap_NoNewLines_Default (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
-		{
-			// Calls WordWrapText (text, width) and thus preserveTrailingSpaces defaults to false
-			List<string> wrappedLines;
-
-			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
-			var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
-			wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
-			Assert.Equal (wrappedLines.Count, resultLines.Count ());
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
-			Assert.Equal (resultLines, wrappedLines);
-		}
+	[Theory]
+	[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 51, 0,   new [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ" })]
+	[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 50, -1,  new [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ" })]
+	[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 46, -5,  new [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮ", "ฯะัาำ" })]
+	[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 26, -25, new [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบ", "ปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ" })]
+	[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 17, -34, new [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑ", "ฒณดตถทธนบปผฝพฟภมย", "รฤลฦวศษสหฬอฮฯะัาำ" })]
+	[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 13, -38, new [] { "กขฃคฅฆงจฉชซฌญ", "ฎฏฐฑฒณดตถทธนบ", "ปผฝพฟภมยรฤลฦว", "ศษสหฬอฮฯะัาำ" })]
+	[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 1,  -50,
+		new [] {
+			"ก", "ข", "ฃ", "ค", "ฅ", "ฆ", "ง", "จ", "ฉ", "ช", "ซ", "ฌ", "ญ", "ฎ", "ฏ", "ฐ", "ฑ", "ฒ", "ณ", "ด", "ต", "ถ", "ท", "ธ", "น", "บ", "ป", "ผ", "ฝ", "พ", "ฟ", "ภ", "ม", "ย", "ร",
+			"ฤ", "ล", "ฦ", "ว", "ศ", "ษ", "ส", "ห", "ฬ", "อ", "ฮ", "ฯ", "ะั", "า", "ำ"
+		})]
+	public void WordWrap_Unicode_SingleWordLine (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
+	{
+		List<string> wrappedLines;
+
+		var zeroWidth = text.EnumerateRunes ().Where (r => r.GetColumns () == 0);
+		Assert.Single (zeroWidth);
+		Assert.Equal ('ั',      zeroWidth.ElementAt (0).Value);
+		Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
+		var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
+		wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
+		Assert.Equal (wrappedLines.Count, resultLines.Count ());
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount () + zeroWidth.Count () - 1 + widthOffset) : 0));
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
+		Assert.Equal (resultLines, wrappedLines);
+	}
 
-		/// <summary>
-		/// WordWrap strips CRLF
-		/// </summary>
-		[Theory]
-		[InlineData ("A sentence has words.\nA paragraph has lines.", 44, 0, new string [] { "A sentence has words.A paragraph has lines." })]
-		[InlineData ("A sentence has words.\nA paragraph has lines.", 43, -1, new string [] { "A sentence has words.A paragraph has lines." })]
-		[InlineData ("A sentence has words.\nA paragraph has lines.", 38, -6, new string [] { "A sentence has words.A paragraph has", "lines." })]
-		[InlineData ("A sentence has words.\nA paragraph has lines.", 34, -10, new string [] { "A sentence has words.A paragraph", "has lines." })]
-		[InlineData ("A sentence has words.\nA paragraph has lines.", 27, -17, new string [] { "A sentence has words.A", "paragraph has lines." })]
-		// Unicode 
-		[InlineData ("A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", 69, 0, new string [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode Пункт has Линии." })]
-		[InlineData ("A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", 68, -1, new string [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode Пункт has Линии." })]
-		[InlineData ("A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", 63, -6, new string [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode Пункт has", "Линии." })]
-		[InlineData ("A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", 59, -10, new string [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode Пункт", "has Линии." })]
-		[InlineData ("A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", 52, -17, new string [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode", "Пункт has Линии." })]
-		public void WordWrap_WithNewLines (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
-		{
-			List<string> wrappedLines;
-
-			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
-			var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
-			wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
-			Assert.Equal (wrappedLines.Count, resultLines.Count ());
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
-			Assert.Equal (resultLines, wrappedLines);
-		}
+	[Theory]
+	[InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 19, 0,   new [] { "This\u00A0is\u00A0a\u00A0sentence." })]
+	[InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 18, -1,  new [] { "This\u00A0is\u00A0a\u00A0sentence", "." })]
+	[InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 17, -2,  new [] { "This\u00A0is\u00A0a\u00A0sentenc", "e." })]
+	[InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 14, -5,  new [] { "This\u00A0is\u00A0a\u00A0sent", "ence." })]
+	[InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 10, -9,  new [] { "This\u00A0is\u00A0a\u00A0", "sentence." })]
+	[InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 7,  -12, new [] { "This\u00A0is", "\u00A0a\u00A0sent", "ence." })]
+	[InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 5,  -14, new [] { "This\u00A0", "is\u00A0a\u00A0", "sente", "nce." })]
+	[InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 1,  -18, new [] { "T", "h", "i", "s", "\u00A0", "i", "s", "\u00A0", "a", "\u00A0", "s", "e", "n", "t", "e", "n", "c", "e", "." })]
+	public void WordWrap_Unicode_LineWithNonBreakingSpace (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
+	{
+		List<string> wrappedLines;
+
+		Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
+		var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
+		wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
+		Assert.Equal (wrappedLines.Count, resultLines.Count ());
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
+		Assert.Equal (resultLines, wrappedLines);
+	}
 
-		[Theory]
-		[InlineData ("A sentence has words.", 3, -18, new string [] { "A", "sen", "ten", "ce", "has", "wor", "ds." })]
-		[InlineData ("A sentence has words.", 2, -19, new string [] { "A", "se", "nt", "en", "ce", "ha", "s", "wo", "rd", "s." })]
-		[InlineData ("A sentence has words.", 1, -20, new string [] { "A", "s", "e", "n", "t", "e", "n", "c", "e", "h", "a", "s", "w", "o", "r", "d", "s", "." })]
-		public void WordWrap_Narrow_Default (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
-		{
-			// Calls WordWrapText (text, width) and thus preserveTrailingSpaces defaults to false
-			List<string> wrappedLines;
-
-			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
-			var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
-			wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
-			Assert.Equal (wrappedLines.Count, resultLines.Count ());
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
-			Assert.Equal (resultLines, wrappedLines);
-		}
+	[Theory]
+	[InlineData ("This\u00A0is\n\u00A0a\u00A0sentence.",              20, 0,  new [] { "This\u00A0is\u00A0a\u00A0sentence." })]
+	[InlineData ("This\u00A0is\n\u00A0a\u00A0sentence.",              19, -1, new [] { "This\u00A0is\u00A0a\u00A0sentence." })]
+	[InlineData ("\u00A0\u00A0\u00A0\u00A0\u00A0test\u00A0sentence.", 19, 0,  new [] { "\u00A0\u00A0\u00A0\u00A0\u00A0test\u00A0sentence." })]
+	public void WordWrap_Unicode_2LinesWithNonBreakingSpace (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
+	{
+		List<string> wrappedLines;
+
+		Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
+		var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
+		wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
+		Assert.Equal (wrappedLines.Count, resultLines.Count ());
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
+		Assert.Equal (resultLines, wrappedLines);
+	}
 
-		[Theory]
-		[InlineData ("A sentence has words.", 14, -7, new string [] { "A sentence ", "has words." })]
-		[InlineData ("A sentence has words.", 8, -13, new string [] { "A ", "sentence", " has ", "words." })]
-		[InlineData ("A sentence has words.", 6, -15, new string [] { "A ", "senten", "ce ", "has ", "words." })]
-		[InlineData ("A sentence has words.", 3, -18, new string [] { "A ", "sen", "ten", "ce ", "has", " ", "wor", "ds." })]
-		[InlineData ("A sentence has words.", 2, -19, new string [] { "A ", "se", "nt", "en", "ce", " ", "ha", "s ", "wo", "rd", "s." })]
-		[InlineData ("A sentence has words.", 1, -20, new string [] { "A", " ", "s", "e", "n", "t", "e", "n", "c", "e", " ", "h", "a", "s", " ", "w", "o", "r", "d", "s", "." })]
-		public void WordWrap_PreserveTrailingSpaces_True (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
-		{
-			List<string> wrappedLines;
-
-			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
-			var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
-			wrappedLines = TextFormatter.WordWrapText (text, maxWidth, preserveTrailingSpaces: true);
-			Assert.Equal (wrappedLines.Count, resultLines.Count ());
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
-			Assert.Equal (resultLines, wrappedLines);
-		}
+	[Theory]
+	[InlineData ("A sentence has words.", 21, 0,  new [] { "A sentence has words." })]
+	[InlineData ("A sentence has words.", 20, -1, new [] { "A sentence has", "words." })]
+	[InlineData ("A sentence has words.", 15, -6, new [] { "A sentence has", "words." })]
+	[InlineData ("A sentence has words.", 14, -7, new [] { "A sentence has", "words." })]
+	[InlineData ("A sentence has words.", 13, -8, new [] { "A sentence", "has words." })]
+	// Unicode 
+	[InlineData ("A Unicode sentence (пÑивеÑ) has words.", 42, 0,   new [] { "A Unicode sentence (пÑивеÑ) has words." })]
+	[InlineData ("A Unicode sentence (пÑивеÑ) has words.", 41, -1,  new [] { "A Unicode sentence (пÑивеÑ) has", "words." })]
+	[InlineData ("A Unicode sentence (пÑивеÑ) has words.", 36, -6,  new [] { "A Unicode sentence (пÑивеÑ) has", "words." })]
+	[InlineData ("A Unicode sentence (пÑивеÑ) has words.", 35, -7,  new [] { "A Unicode sentence (пÑивеÑ) has", "words." })]
+	[InlineData ("A Unicode sentence (пÑивеÑ) has words.", 34, -8,  new [] { "A Unicode sentence (пÑивеÑ)", "has words." })]
+	[InlineData ("A Unicode sentence (пÑивеÑ) has words.", 25, -17, new [] { "A Unicode sentence", "(пÑивеÑ) has words." })]
+	public void WordWrap_NoNewLines_Default (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
+	{
+		// Calls WordWrapText (text, width) and thus preserveTrailingSpaces defaults to false
+		List<string> wrappedLines;
+
+		Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
+		var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
+		wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
+		Assert.Equal (wrappedLines.Count, resultLines.Count ());
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
+		Assert.Equal (resultLines, wrappedLines);
+	}
 
-		[Theory]
-		[InlineData ("文に は言葉 があり ます。", 14, 0, new string [] { "文に は言葉 ", "があり ます。" })]
-		[InlineData ("文に は言葉 があり ます。", 3, -11, new string [] { "文", "に ", "は", "言", "葉 ", "が", "あ", "り ", "ま", "す", "。" })]
-		[InlineData ("文に は言葉 があり ます。", 2, -12, new string [] { "文", "に", " ", "は", "言", "葉", " ", "が", "あ", "り", " ", "ま", "す", "。" })]
-		[InlineData ("文に は言葉 があり ます。", 1, -13, new string [] { })]
-		public void WordWrap_PreserveTrailingSpaces_True_Wide_Runes (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
-		{
-			List<string> wrappedLines;
-
-			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
-			var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
-			wrappedLines = TextFormatter.WordWrapText (text, maxWidth, preserveTrailingSpaces: true);
-			Assert.Equal (wrappedLines.Count, resultLines.Count ());
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
-			Assert.Equal (resultLines, wrappedLines);
-		}
+	/// <summary>
+	/// WordWrap strips CRLF
+	/// </summary>
+	[Theory]
+	[InlineData ("A sentence has words.\nA paragraph has lines.", 44, 0,   new [] { "A sentence has words.A paragraph has lines." })]
+	[InlineData ("A sentence has words.\nA paragraph has lines.", 43, -1,  new [] { "A sentence has words.A paragraph has lines." })]
+	[InlineData ("A sentence has words.\nA paragraph has lines.", 38, -6,  new [] { "A sentence has words.A paragraph has", "lines." })]
+	[InlineData ("A sentence has words.\nA paragraph has lines.", 34, -10, new [] { "A sentence has words.A paragraph", "has lines." })]
+	[InlineData ("A sentence has words.\nA paragraph has lines.", 27, -17, new [] { "A sentence has words.A", "paragraph has lines." })]
+	// Unicode 
+	[InlineData ("A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", 69, 0,   new [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode Пункт has Линии." })]
+	[InlineData ("A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", 68, -1,  new [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode Пункт has Линии." })]
+	[InlineData ("A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", 63, -6,  new [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode Пункт has", "Линии." })]
+	[InlineData ("A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", 59, -10, new [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode Пункт", "has Линии." })]
+	[InlineData ("A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", 52, -17, new [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode", "Пункт has Линии." })]
+	public void WordWrap_WithNewLines (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
+	{
+		List<string> wrappedLines;
+
+		Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
+		var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
+		wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
+		Assert.Equal (wrappedLines.Count, resultLines.Count ());
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
+		Assert.Equal (resultLines, wrappedLines);
+	}
 
-		[Theory]
-		[InlineData ("文に は言葉 があり ます。", 14, 0, new string [] { "文に は言葉", "があり ます。" })]
-		[InlineData ("文に は言葉 があり ます。", 3, -11, new string [] { "文", "に", "は", "言", "葉", "が", "あ", "り", "ま", "す", "。" })]
-		[InlineData ("文に は言葉 があり ます。", 2, -12, new string [] { "文", "に", "は", "言", "葉", "が", "あ", "り", "ま", "す", "。" })]
-		[InlineData ("文に は言葉 があり ます。", 1, -13, new string [] { " ", " ", " " })] // Just Spaces; should result in a single space for each line
-		public void WordWrap_PreserveTrailingSpaces_False_Wide_Runes (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
-		{
-			List<string> wrappedLines;
-
-			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
-			var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
-			wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
-			Assert.Equal (wrappedLines.Count, resultLines.Count ());
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
-			Assert.Equal (resultLines, wrappedLines);
-		}
+	[Theory]
+	[InlineData ("A sentence has words.", 3, -18, new [] { "A", "sen", "ten", "ce", "has", "wor", "ds." })]
+	[InlineData ("A sentence has words.", 2, -19, new [] { "A", "se", "nt", "en", "ce", "ha", "s", "wo", "rd", "s." })]
+	[InlineData ("A sentence has words.", 1, -20, new [] { "A", "s", "e", "n", "t", "e", "n", "c", "e", "h", "a", "s", "w", "o", "r", "d", "s", "." })]
+	public void WordWrap_Narrow_Default (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
+	{
+		// Calls WordWrapText (text, width) and thus preserveTrailingSpaces defaults to false
+		List<string> wrappedLines;
+
+		Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
+		var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
+		wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
+		Assert.Equal (wrappedLines.Count, resultLines.Count ());
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
+		Assert.Equal (resultLines, wrappedLines);
+	}
 
-		[Theory]
-		[InlineData ("A sentence has words. ", 3, new string [] { "A ", "sen", "ten", "ce ", "has", " ", "wor", "ds.", " " })]
-		[InlineData ("A   sentence          has  words.  ", 3, new string [] { "A  ", " ", "sen", "ten", "ce ", "   ", "   ", "   ", "has", "  ", "wor", "ds.", "  " })]
-		public void WordWrap_PreserveTrailingSpaces_True_With_Simple_Runes_Width_3 (string text, int width, IEnumerable<string> resultLines)
-		{
-			var wrappedLines = TextFormatter.WordWrapText (text, width, preserveTrailingSpaces: true);
-			Assert.Equal (wrappedLines.Count, resultLines.Count ());
-			Assert.Equal (resultLines, wrappedLines);
-			var breakLines = "";
-			foreach (var line in wrappedLines) {
-				breakLines += $"{line}{Environment.NewLine}";
-			}
-			var expected = string.Empty;
-			foreach (var line in resultLines) {
-				expected += $"{line}{Environment.NewLine}";
-			}
-			Assert.Equal (expected, breakLines);
-
-			// Double space Complex example - this is how VS 2022 does it
-			//text = "A  sentence      has words.  ";
-			//breakLines = "";
-			//wrappedLines = TextFormatter.WordWrapText (text, width, preserveTrailingSpaces: true);
-			//foreach (var line in wrappedLines) {
-			//	breakLines += $"{line}{Environment.NewLine}";
-			//}
-			//expected = "A  " + Environment.NewLine +
-			//	" se" + Environment.NewLine +
-			//	" nt" + Environment.NewLine +
-			//	" en" + Environment.NewLine +
-			//	" ce" + Environment.NewLine +
-			//	"  " + Environment.NewLine +
-			//	"  " + Environment.NewLine +
-			//	"  " + Environment.NewLine +
-			//	" ha" + Environment.NewLine +
-			//	" s " + Environment.NewLine +
-			//	" wo" + Environment.NewLine +
-			//	" rd" + Environment.NewLine +
-			//	" s." + Environment.NewLine;
-			//Assert.Equal (expected, breakLines);
-		}
+	[Theory]
+	[InlineData ("A sentence has words.", 14, -7,  new [] { "A sentence ", "has words." })]
+	[InlineData ("A sentence has words.", 8,  -13, new [] { "A ", "sentence", " has ", "words." })]
+	[InlineData ("A sentence has words.", 6,  -15, new [] { "A ", "senten", "ce ", "has ", "words." })]
+	[InlineData ("A sentence has words.", 3,  -18, new [] { "A ", "sen", "ten", "ce ", "has", " ", "wor", "ds." })]
+	[InlineData ("A sentence has words.", 2,  -19, new [] { "A ", "se", "nt", "en", "ce", " ", "ha", "s ", "wo", "rd", "s." })]
+	[InlineData ("A sentence has words.", 1,  -20, new [] { "A", " ", "s", "e", "n", "t", "e", "n", "c", "e", " ", "h", "a", "s", " ", "w", "o", "r", "d", "s", "." })]
+	public void WordWrap_PreserveTrailingSpaces_True (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
+	{
+		List<string> wrappedLines;
+
+		Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
+		var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
+		wrappedLines = TextFormatter.WordWrapText (text, maxWidth, true);
+		Assert.Equal (wrappedLines.Count, resultLines.Count ());
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
+		Assert.Equal (resultLines, wrappedLines);
+	}
 
-		[Theory]
-		[InlineData (null, 1, new string [] { })] // null input
-		[InlineData ("", 1, new string [] { })] // Empty input
-		[InlineData ("1 34", 1, new string [] { "1", "3", "4" })] // Single Spaces
-		[InlineData ("1", 1, new string [] { "1" })] // Short input
-		[InlineData ("12", 1, new string [] { "1", "2" })]
-		[InlineData ("123", 1, new string [] { "1", "2", "3" })]
-		[InlineData ("123456", 1, new string [] { "1", "2", "3", "4", "5", "6" })] // No spaces
-		[InlineData (" ", 1, new string [] { " " })] // Just Spaces; should result in a single space
-		[InlineData ("  ", 1, new string [] { " " })]
-		[InlineData ("   ", 1, new string [] { " ", " " })]
-		[InlineData ("    ", 1, new string [] { " ", " " })]
-		[InlineData ("12 456", 1, new string [] { "1", "2", "4", "5", "6" })] // Single Spaces
-		[InlineData (" 2 456", 1, new string [] { " ", "2", "4", "5", "6" })] // Leading spaces should be preserved.
-		[InlineData (" 2 456 8", 1, new string [] { " ", "2", "4", "5", "6", "8" })]
-		[InlineData ("A sentence has words. ", 1, new string [] { "A", "s", "e", "n", "t", "e", "n", "c", "e", "h", "a", "s", "w", "o", "r", "d", "s", "." })] // Complex example
-		[InlineData ("12  567", 1, new string [] { "1", "2", " ", "5", "6", "7" })] // Double Spaces
-		[InlineData ("  3 567", 1, new string [] { " ", "3", "5", "6", "7" })] // Double Leading spaces should be preserved.
-		[InlineData ("  3  678  1", 1, new string [] { " ", "3", " ", "6", "7", "8", " ", "1" })]
-		[InlineData ("1  456", 1, new string [] { "1", " ", "4", "5", "6" })]
-		[InlineData ("A  sentence   has words.  ", 1, new string [] { "A", " ", "s", "e", "n", "t", "e", "n", "c", "e", " ", "h", "a", "s", "w", "o", "r", "d", "s", ".", " " })] // Double space Complex example
-		public void WordWrap_PreserveTrailingSpaces_False_With_Simple_Runes_Width_1 (string text, int width, IEnumerable<string> resultLines)
-		{
-			var wrappedLines = TextFormatter.WordWrapText (text, width, preserveTrailingSpaces: false);
-			Assert.Equal (wrappedLines.Count, resultLines.Count ());
-			Assert.Equal (resultLines, wrappedLines);
-			var breakLines = "";
-			foreach (var line in wrappedLines) {
-				breakLines += $"{line}{Environment.NewLine}";
-			}
-			var expected = string.Empty;
-			foreach (var line in resultLines) {
-				expected += $"{line}{Environment.NewLine}";
-			}
-			Assert.Equal (expected, breakLines);
-		}
+	[Theory]
+	[InlineData ("文に は言葉 があり ます。", 14, 0,   new [] { "文に は言葉 ", "があり ます。" })]
+	[InlineData ("文に は言葉 があり ます。", 3,  -11, new [] { "文", "に ", "は", "言", "葉 ", "が", "あ", "り ", "ま", "す", "。" })]
+	[InlineData ("文に は言葉 があり ます。", 2,  -12, new [] { "文", "に", " ", "は", "言", "葉", " ", "が", "あ", "り", " ", "ま", "す", "。" })]
+	[InlineData ("文に は言葉 があり ます。", 1,  -13, new string [] { })]
+	public void WordWrap_PreserveTrailingSpaces_True_Wide_Runes (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
+	{
+		List<string> wrappedLines;
+
+		Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
+		var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
+		wrappedLines = TextFormatter.WordWrapText (text, maxWidth, true);
+		Assert.Equal (wrappedLines.Count, resultLines.Count ());
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
+		Assert.Equal (resultLines, wrappedLines);
+	}
 
-		[Theory]
-		[InlineData (null, 3, new string [] { })] // null input
-		[InlineData ("", 3, new string [] { })] // Empty input
-		[InlineData ("1", 3, new string [] { "1" })] // Short input
-		[InlineData ("12", 3, new string [] { "12" })]
-		[InlineData ("123", 3, new string [] { "123" })]
-		[InlineData ("123456", 3, new string [] { "123", "456" })] // No spaces
-		[InlineData ("1234567", 3, new string [] { "123", "456", "7" })] // No spaces
-		[InlineData (" ", 3, new string [] { " " })] // Just Spaces; should result in a single space
-		[InlineData ("  ", 3, new string [] { "  " })]
-		[InlineData ("   ", 3, new string [] { "   " })]
-		[InlineData ("    ", 3, new string [] { "   " })]
-		[InlineData ("12 456", 3, new string [] { "12", "456" })] // Single Spaces
-		[InlineData (" 2 456", 3, new string [] { " 2", "456" })] // Leading spaces should be preserved.
-		[InlineData (" 2 456 8", 3, new string [] { " 2", "456", "8" })]
-		[InlineData ("A sentence has words. ", 3, new string [] { "A", "sen", "ten", "ce", "has", "wor", "ds." })] // Complex example
-		[InlineData ("12  567", 3, new string [] { "12 ", "567" })] // Double Spaces
-		[InlineData ("  3 567", 3, new string [] { "  3", "567" })] // Double Leading spaces should be preserved.
-		[InlineData ("  3  678  1", 3, new string [] { "  3", " 67", "8 ", "1" })]
-		[InlineData ("1  456", 3, new string [] { "1 ", "456" })]
-		[InlineData ("A  sentence      has words.  ", 3, new string [] { "A ", "sen", "ten", "ce ", "   ", "has", "wor", "ds.", " " })] // Double space Complex example
-		public void WordWrap_PreserveTrailingSpaces_False_With_Simple_Runes_Width_3 (string text, int width, IEnumerable<string> resultLines)
-		{
-			var wrappedLines = TextFormatter.WordWrapText (text, width, preserveTrailingSpaces: false);
-			Assert.Equal (wrappedLines.Count, resultLines.Count ());
-			Assert.Equal (resultLines, wrappedLines);
-			var breakLines = "";
-			foreach (var line in wrappedLines) {
-				breakLines += $"{line}{Environment.NewLine}";
-			}
-			var expected = string.Empty;
-			foreach (var line in resultLines) {
-				expected += $"{line}{Environment.NewLine}";
-			}
-			Assert.Equal (expected, breakLines);
-		}
+	[Theory]
+	[InlineData ("文に は言葉 があり ます。", 14, 0,   new [] { "文に は言葉", "があり ます。" })]
+	[InlineData ("文に は言葉 があり ます。", 3,  -11, new [] { "文", "に", "は", "言", "葉", "が", "あ", "り", "ま", "す", "。" })]
+	[InlineData ("文に は言葉 があり ます。", 2,  -12, new [] { "文", "に", "は", "言", "葉", "が", "あ", "り", "ま", "す", "。" })]
+	[InlineData ("文に は言葉 があり ます。", 1,  -13, new [] { " ", " ", " " })] // Just Spaces; should result in a single space for each line
+	public void WordWrap_PreserveTrailingSpaces_False_Wide_Runes (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
+	{
+		List<string> wrappedLines;
+
+		Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
+		var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
+		wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
+		Assert.Equal (wrappedLines.Count, resultLines.Count ());
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
+		Assert.Equal (resultLines, wrappedLines);
+	}
 
-		[Theory]
-		[InlineData (null, 50, new string [] { })] // null input
-		[InlineData ("", 50, new string [] { })] // Empty input
-		[InlineData ("1", 50, new string [] { "1" })] // Short input
-		[InlineData ("12", 50, new string [] { "12" })]
-		[InlineData ("123", 50, new string [] { "123" })]
-		[InlineData ("123456", 50, new string [] { "123456" })] // No spaces
-		[InlineData ("1234567", 50, new string [] { "1234567" })] // No spaces
-		[InlineData (" ", 50, new string [] { " " })] // Just Spaces; should result in a single space
-		[InlineData ("  ", 50, new string [] { "  " })]
-		[InlineData ("   ", 50, new string [] { "   " })]
-		[InlineData ("12 456", 50, new string [] { "12 456" })] // Single Spaces
-		[InlineData (" 2 456", 50, new string [] { " 2 456" })] // Leading spaces should be preserved.
-		[InlineData (" 2 456 8", 50, new string [] { " 2 456 8" })]
-		[InlineData ("A sentence has words. ", 50, new string [] { "A sentence has words. " })] // Complex example
-		[InlineData ("12  567", 50, new string [] { "12  567" })] // Double Spaces
-		[InlineData ("  3 567", 50, new string [] { "  3 567" })] // Double Leading spaces should be preserved.
-		[InlineData ("  3  678  1", 50, new string [] { "  3  678  1" })]
-		[InlineData ("1  456", 50, new string [] { "1  456" })]
-		[InlineData ("A  sentence      has words.  ", 50, new string [] { "A  sentence      has words.  " })] // Double space Complex example
-		public void WordWrap_PreserveTrailingSpaces_False_With_Simple_Runes_Width_50 (string text, int width, IEnumerable<string> resultLines)
-		{
-			var wrappedLines = TextFormatter.WordWrapText (text, width, preserveTrailingSpaces: false);
-			Assert.Equal (wrappedLines.Count, resultLines.Count ());
-			Assert.Equal (resultLines, wrappedLines);
-			var breakLines = "";
-			foreach (var line in wrappedLines) {
-				breakLines += $"{line}{Environment.NewLine}";
-			}
-			var expected = string.Empty;
-			foreach (var line in resultLines) {
-				expected += $"{line}{Environment.NewLine}";
-			}
-			Assert.Equal (expected, breakLines);
-		}
+	[Theory]
+	[InlineData ("A sentence has words. ",              3, new [] { "A ", "sen", "ten", "ce ", "has", " ", "wor", "ds.", " " })]
+	[InlineData ("A   sentence          has  words.  ", 3, new [] { "A  ", " ", "sen", "ten", "ce ", "   ", "   ", "   ", "has", "  ", "wor", "ds.", "  " })]
+	public void WordWrap_PreserveTrailingSpaces_True_With_Simple_Runes_Width_3 (string text, int width, IEnumerable<string> resultLines)
+	{
+		var wrappedLines = TextFormatter.WordWrapText (text, width, true);
+		Assert.Equal (wrappedLines.Count, resultLines.Count ());
+		Assert.Equal (resultLines,        wrappedLines);
+		var breakLines = "";
+		foreach (var line in wrappedLines) {
+			breakLines += $"{line}{Environment.NewLine}";
+		}
+		var expected = string.Empty;
+		foreach (var line in resultLines) {
+			expected += $"{line}{Environment.NewLine}";
+		}
+		Assert.Equal (expected, breakLines);
+
+		// Double space Complex example - this is how VS 2022 does it
+		//text = "A  sentence      has words.  ";
+		//breakLines = "";
+		//wrappedLines = TextFormatter.WordWrapText (text, width, preserveTrailingSpaces: true);
+		//foreach (var line in wrappedLines) {
+		//	breakLines += $"{line}{Environment.NewLine}";
+		//}
+		//expected = "A  " + Environment.NewLine +
+		//	" se" + Environment.NewLine +
+		//	" nt" + Environment.NewLine +
+		//	" en" + Environment.NewLine +
+		//	" ce" + Environment.NewLine +
+		//	"  " + Environment.NewLine +
+		//	"  " + Environment.NewLine +
+		//	"  " + Environment.NewLine +
+		//	" ha" + Environment.NewLine +
+		//	" s " + Environment.NewLine +
+		//	" wo" + Environment.NewLine +
+		//	" rd" + Environment.NewLine +
+		//	" s." + Environment.NewLine;
+		//Assert.Equal (expected, breakLines);
+	}
 
-		[Theory]
-		[InlineData ("A sentence\t\t\t has words.", 14, -10, new string [] { "A sentence\t", "\t\t has ", "words." })]
-		[InlineData ("A sentence\t\t\t has words.", 8, -16, new string [] { "A ", "sentence", "\t\t", "\t ", "has ", "words." })]
-		[InlineData ("A sentence\t\t\t has words.", 3, -21, new string [] { "A ", "sen", "ten", "ce", "\t", "\t", "\t", " ", "has", " ", "wor", "ds." })]
-		[InlineData ("A sentence\t\t\t has words.", 2, -22, new string [] { "A ", "se", "nt", "en", "ce", "\t", "\t", "\t", " ", "ha", "s ", "wo", "rd", "s." })]
-		[InlineData ("A sentence\t\t\t has words.", 1, -23, new string [] { "A", " ", "s", "e", "n", "t", "e", "n", "c", "e", "\t", "\t", "\t", " ", "h", "a", "s", " ", "w", "o", "r", "d", "s", "." })]
-		public void WordWrap_PreserveTrailingSpaces_True_With_Tab (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines, int tabWidth = 4)
-		{
-			List<string> wrappedLines;
-
-			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
-			var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
-			wrappedLines = TextFormatter.WordWrapText (text, maxWidth, preserveTrailingSpaces: true, tabWidth: tabWidth);
-			Assert.Equal (wrappedLines.Count, resultLines.Count ());
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
-			Assert.Equal (resultLines, wrappedLines);
-		}
+	[Theory]
+	[InlineData (null,                         1, new string [] { })] // null input
+	[InlineData ("",                           1, new string [] { })] // Empty input
+	[InlineData ("1 34",                       1, new [] { "1", "3", "4" })] // Single Spaces
+	[InlineData ("1",                          1, new [] { "1" })] // Short input
+	[InlineData ("12",                         1, new [] { "1", "2" })]
+	[InlineData ("123",                        1, new [] { "1", "2", "3" })]
+	[InlineData ("123456",                     1, new [] { "1", "2", "3", "4", "5", "6" })] // No spaces
+	[InlineData (" ",                          1, new [] { " " })] // Just Spaces; should result in a single space
+	[InlineData ("  ",                         1, new [] { " " })]
+	[InlineData ("   ",                        1, new [] { " ", " " })]
+	[InlineData ("    ",                       1, new [] { " ", " " })]
+	[InlineData ("12 456",                     1, new [] { "1", "2", "4", "5", "6" })] // Single Spaces
+	[InlineData (" 2 456",                     1, new [] { " ", "2", "4", "5", "6" })] // Leading spaces should be preserved.
+	[InlineData (" 2 456 8",                   1, new [] { " ", "2", "4", "5", "6", "8" })]
+	[InlineData ("A sentence has words. ",     1, new [] { "A", "s", "e", "n", "t", "e", "n", "c", "e", "h", "a", "s", "w", "o", "r", "d", "s", "." })] // Complex example
+	[InlineData ("12  567",                    1, new [] { "1", "2", " ", "5", "6", "7" })] // Double Spaces
+	[InlineData ("  3 567",                    1, new [] { " ", "3", "5", "6", "7" })] // Double Leading spaces should be preserved.
+	[InlineData ("  3  678  1",                1, new [] { " ", "3", " ", "6", "7", "8", " ", "1" })]
+	[InlineData ("1  456",                     1, new [] { "1", " ", "4", "5", "6" })]
+	[InlineData ("A  sentence   has words.  ", 1,
+		new [] { "A", " ", "s", "e", "n", "t", "e", "n", "c", "e", " ", "h", "a", "s", "w", "o", "r", "d", "s", ".", " " })] // Double space Complex example
+	public void WordWrap_PreserveTrailingSpaces_False_With_Simple_Runes_Width_1 (string text, int width, IEnumerable<string> resultLines)
+	{
+		var wrappedLines = TextFormatter.WordWrapText (text, width);
+		Assert.Equal (wrappedLines.Count, resultLines.Count ());
+		Assert.Equal (resultLines,        wrappedLines);
+		var breakLines = "";
+		foreach (var line in wrappedLines) {
+			breakLines += $"{line}{Environment.NewLine}";
+		}
+		var expected = string.Empty;
+		foreach (var line in resultLines) {
+			expected += $"{line}{Environment.NewLine}";
+		}
+		Assert.Equal (expected, breakLines);
+	}
 
-		[Theory]
-		[InlineData ("これが最初の行です。 こんにちは世界。 これが2行目です。", 29, 0, new string [] { "これが最初の行です。", "こんにちは世界。", "これが2行目です。" })]
-		public void WordWrap_PreserveTrailingSpaces_False_Unicode_Wide_Runes (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
-		{
-			List<string> wrappedLines;
-
-			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
-			var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
-			wrappedLines = TextFormatter.WordWrapText (text, maxWidth, preserveTrailingSpaces: false);
-			Assert.Equal (wrappedLines.Count, resultLines.Count ());
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
-			Assert.Equal (resultLines, wrappedLines);
-		}
+	[Theory]
+	[InlineData (null,                            3, new string [] { })] // null input
+	[InlineData ("",                              3, new string [] { })] // Empty input
+	[InlineData ("1",                             3, new [] { "1" })]    // Short input
+	[InlineData ("12",                            3, new [] { "12" })]
+	[InlineData ("123",                           3, new [] { "123" })]
+	[InlineData ("123456",                        3, new [] { "123", "456" })]      // No spaces
+	[InlineData ("1234567",                       3, new [] { "123", "456", "7" })] // No spaces
+	[InlineData (" ",                             3, new [] { " " })]               // Just Spaces; should result in a single space
+	[InlineData ("  ",                            3, new [] { "  " })]
+	[InlineData ("   ",                           3, new [] { "   " })]
+	[InlineData ("    ",                          3, new [] { "   " })]
+	[InlineData ("12 456",                        3, new [] { "12", "456" })] // Single Spaces
+	[InlineData (" 2 456",                        3, new [] { " 2", "456" })] // Leading spaces should be preserved.
+	[InlineData (" 2 456 8",                      3, new [] { " 2", "456", "8" })]
+	[InlineData ("A sentence has words. ",        3, new [] { "A", "sen", "ten", "ce", "has", "wor", "ds." })] // Complex example
+	[InlineData ("12  567",                       3, new [] { "12 ", "567" })]                                 // Double Spaces
+	[InlineData ("  3 567",                       3, new [] { "  3", "567" })]                                 // Double Leading spaces should be preserved.
+	[InlineData ("  3  678  1",                   3, new [] { "  3", " 67", "8 ", "1" })]
+	[InlineData ("1  456",                        3, new [] { "1 ", "456" })]
+	[InlineData ("A  sentence      has words.  ", 3, new [] { "A ", "sen", "ten", "ce ", "   ", "has", "wor", "ds.", " " })] // Double space Complex example
+	public void WordWrap_PreserveTrailingSpaces_False_With_Simple_Runes_Width_3 (string text, int width, IEnumerable<string> resultLines)
+	{
+		var wrappedLines = TextFormatter.WordWrapText (text, width);
+		Assert.Equal (wrappedLines.Count, resultLines.Count ());
+		Assert.Equal (resultLines,        wrappedLines);
+		var breakLines = "";
+		foreach (var line in wrappedLines) {
+			breakLines += $"{line}{Environment.NewLine}";
+		}
+		var expected = string.Empty;
+		foreach (var line in resultLines) {
+			expected += $"{line}{Environment.NewLine}";
+		}
+		Assert.Equal (expected, breakLines);
+	}
 
-		[Theory]
-		[InlineData ("test", 0, 't', "test")]
-		[InlineData ("test", 1, 'e', "test")]
-		[InlineData ("Ok", 0, 'O', "Ok")]
-		[InlineData ("[◦ Ok ◦]", 3, 'O', "[◦ Ok ◦]")]
-		[InlineData ("^k", 0, '^', "^k")]
-		public void ReplaceHotKeyWithTag (string text, int hotPos, uint tag, string expected)
-		{
-			var tf = new TextFormatter ();
-			var runes = text.ToRuneList ();
-			Rune rune;
-			if (Rune.TryGetRuneAt (text, hotPos, out rune)) {
-				Assert.Equal (rune, (Rune)tag);
+	[Theory]
+	[InlineData (null,                            50, new string [] { })] // null input
+	[InlineData ("",                              50, new string [] { })] // Empty input
+	[InlineData ("1",                             50, new [] { "1" })]    // Short input
+	[InlineData ("12",                            50, new [] { "12" })]
+	[InlineData ("123",                           50, new [] { "123" })]
+	[InlineData ("123456",                        50, new [] { "123456" })]  // No spaces
+	[InlineData ("1234567",                       50, new [] { "1234567" })] // No spaces
+	[InlineData (" ",                             50, new [] { " " })]       // Just Spaces; should result in a single space
+	[InlineData ("  ",                            50, new [] { "  " })]
+	[InlineData ("   ",                           50, new [] { "   " })]
+	[InlineData ("12 456",                        50, new [] { "12 456" })] // Single Spaces
+	[InlineData (" 2 456",                        50, new [] { " 2 456" })] // Leading spaces should be preserved.
+	[InlineData (" 2 456 8",                      50, new [] { " 2 456 8" })]
+	[InlineData ("A sentence has words. ",        50, new [] { "A sentence has words. " })] // Complex example
+	[InlineData ("12  567",                       50, new [] { "12  567" })]                // Double Spaces
+	[InlineData ("  3 567",                       50, new [] { "  3 567" })]                // Double Leading spaces should be preserved.
+	[InlineData ("  3  678  1",                   50, new [] { "  3  678  1" })]
+	[InlineData ("1  456",                        50, new [] { "1  456" })]
+	[InlineData ("A  sentence      has words.  ", 50, new [] { "A  sentence      has words.  " })] // Double space Complex example
+	public void WordWrap_PreserveTrailingSpaces_False_With_Simple_Runes_Width_50 (string text, int width, IEnumerable<string> resultLines)
+	{
+		var wrappedLines = TextFormatter.WordWrapText (text, width);
+		Assert.Equal (wrappedLines.Count, resultLines.Count ());
+		Assert.Equal (resultLines,        wrappedLines);
+		var breakLines = "";
+		foreach (var line in wrappedLines) {
+			breakLines += $"{line}{Environment.NewLine}";
+		}
+		var expected = string.Empty;
+		foreach (var line in resultLines) {
+			expected += $"{line}{Environment.NewLine}";
+		}
+		Assert.Equal (expected, breakLines);
+	}
 
-			}
-			var result = tf.ReplaceHotKeyWithTag (text, hotPos);
-			Assert.Equal (result, expected);
-			Assert.Equal ((Rune)tag, result.ToRunes () [hotPos]);
-			Assert.Equal (text.GetRuneCount (), runes.Count);
-			Assert.Equal (text, StringExtensions.ToString (runes));
-		}
+	[Theory]
+	[InlineData ("A sentence\t\t\t has words.", 14, -10, new [] { "A sentence\t", "\t\t has ", "words." })]
+	[InlineData ("A sentence\t\t\t has words.", 8,  -16, new [] { "A ", "sentence", "\t\t", "\t ", "has ", "words." })]
+	[InlineData ("A sentence\t\t\t has words.", 3,  -21, new [] { "A ", "sen", "ten", "ce", "\t", "\t", "\t", " ", "has", " ", "wor", "ds." })]
+	[InlineData ("A sentence\t\t\t has words.", 2,  -22, new [] { "A ", "se", "nt", "en", "ce", "\t", "\t", "\t", " ", "ha", "s ", "wo", "rd", "s." })]
+	[InlineData ("A sentence\t\t\t has words.", 1,  -23, new [] { "A", " ", "s", "e", "n", "t", "e", "n", "c", "e", "\t", "\t", "\t", " ", "h", "a", "s", " ", "w", "o", "r", "d", "s", "." })]
+	public void WordWrap_PreserveTrailingSpaces_True_With_Tab (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines, int tabWidth = 4)
+	{
+		List<string> wrappedLines;
+
+		Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
+		var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
+		wrappedLines = TextFormatter.WordWrapText (text, maxWidth, true, tabWidth);
+		Assert.Equal (wrappedLines.Count, resultLines.Count ());
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
+		Assert.Equal (resultLines, wrappedLines);
+	}
 
-		[Theory]
-		[InlineData ("", -1, TextAlignment.Left, false, 0)]
-		[InlineData (null, 0, TextAlignment.Left, false, 1)]
-		[InlineData (null, 0, TextAlignment.Left, true, 1)]
-		[InlineData ("", 0, TextAlignment.Left, false, 1)]
-		[InlineData ("", 0, TextAlignment.Left, true, 1)]
-		public void Reformat_Invalid (string text, int maxWidth, TextAlignment textAlignment, bool wrap, int linesCount)
-		{
-			if (maxWidth < 0) {
-				Assert.Throws<ArgumentOutOfRangeException> (() => TextFormatter.Format (text, maxWidth, textAlignment, wrap));
-			} else {
-				var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap);
-				Assert.NotEmpty (list);
-				Assert.True (list.Count == linesCount);
-				Assert.Equal (string.Empty, list [0]);
-			}
-		}
+	[Theory]
+	[InlineData ("これが最初の行です。 こんにちは世界。 これが2行目です。", 29, 0, new [] { "これが最初の行です。", "こんにちは世界。", "これが2行目です。" })]
+	public void WordWrap_PreserveTrailingSpaces_False_Unicode_Wide_Runes (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
+	{
+		List<string> wrappedLines;
+
+		Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
+		var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
+		wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
+		Assert.Equal (wrappedLines.Count, resultLines.Count ());
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
+		Assert.Equal (resultLines, wrappedLines);
+	}
 
-		[Theory]
-		[InlineData ("", 0, 0, TextAlignment.Left, false, 1, true)]
-		[InlineData ("", 1, 1, TextAlignment.Left, false, 1, true)]
-		[InlineData ("A sentence has words.", 0, -21, TextAlignment.Left, false, 1, true)]
-		[InlineData ("A sentence has words.", 1, -20, TextAlignment.Left, false, 1, false)]
-		[InlineData ("A sentence has words.", 5, -16, TextAlignment.Left, false, 1, false)]
-		[InlineData ("A sentence has words.", 20, -1, TextAlignment.Left, false, 1, false)]
-		// no clip
-		[InlineData ("A sentence has words.", 21, 0, TextAlignment.Left, false, 1, false)]
-		[InlineData ("A sentence has words.", 22, 1, TextAlignment.Left, false, 1, false)]
-		public void Reformat_NoWordrap_SingleLine (string text, int maxWidth, int widthOffset, TextAlignment textAlignment, bool wrap, int linesCount, bool stringEmpty)
-		{
-			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
-			var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
-			var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap);
-			Assert.NotEmpty (list);
-			Assert.True (list.Count == linesCount);
-			if (stringEmpty) {
-				Assert.Equal (string.Empty, list [0]);
-			} else {
-				Assert.NotEqual (string.Empty, list [0]);
-			}
-			Assert.Equal (StringExtensions.ToString (text.ToRunes () [0..expectedClippedWidth]), list [0]);
-		}
+	[Theory]
+	[InlineData ("test",     0, 't', "test")]
+	[InlineData ("test",     1, 'e', "test")]
+	[InlineData ("Ok",       0, 'O', "Ok")]
+	[InlineData ("[◦ Ok ◦]", 3, 'O', "[◦ Ok ◦]")]
+	[InlineData ("^k",       0, '^', "^k")]
+	public void ReplaceHotKeyWithTag (string text, int hotPos, uint tag, string expected)
+	{
+		var tf = new TextFormatter ();
+		var runes = text.ToRuneList ();
+		Rune rune;
+		if (Rune.TryGetRuneAt (text, hotPos, out rune)) {
+			Assert.Equal (rune, (Rune)tag);
+
+		}
+		var result = tf.ReplaceHotKeyWithTag (text, hotPos);
+		Assert.Equal (result,               expected);
+		Assert.Equal ((Rune)tag,            result.ToRunes () [hotPos]);
+		Assert.Equal (text.GetRuneCount (), runes.Count);
+		Assert.Equal (text,                 StringExtensions.ToString (runes));
+	}
 
-		[Theory]
-		[InlineData ("A sentence has words.\nLine 2.", 0, -29, TextAlignment.Left, false, 1, true)]
-		[InlineData ("A sentence has words.\nLine 2.", 1, -28, TextAlignment.Left, false, 1, false)]
-		[InlineData ("A sentence has words.\nLine 2.", 5, -24, TextAlignment.Left, false, 1, false)]
-		[InlineData ("A sentence has words.\nLine 2.", 28, -1, TextAlignment.Left, false, 1, false)]
-		// no clip
-		[InlineData ("A sentence has words.\nLine 2.", 29, 0, TextAlignment.Left, false, 1, false)]
-		[InlineData ("A sentence has words.\nLine 2.", 30, 1, TextAlignment.Left, false, 1, false)]
-		[InlineData ("A sentence has words.\r\nLine 2.", 0, -30, TextAlignment.Left, false, 1, true)]
-		[InlineData ("A sentence has words.\r\nLine 2.", 1, -29, TextAlignment.Left, false, 1, false)]
-		[InlineData ("A sentence has words.\r\nLine 2.", 5, -25, TextAlignment.Left, false, 1, false)]
-		[InlineData ("A sentence has words.\r\nLine 2.", 29, -1, TextAlignment.Left, false, 1, false, 1)]
-		[InlineData ("A sentence has words.\r\nLine 2.", 30, 0, TextAlignment.Left, false, 1, false)]
-		[InlineData ("A sentence has words.\r\nLine 2.", 31, 1, TextAlignment.Left, false, 1, false)]
-		public void Reformat_NoWordrap_NewLines_MultiLine_False (string text, int maxWidth, int widthOffset, TextAlignment textAlignment, bool wrap, int linesCount, bool stringEmpty, int clipWidthOffset = 0)
-		{
-			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
-			var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth) + clipWidthOffset;
+	[Theory]
+	[InlineData ("",   -1, TextAlignment.Left, false, 0)]
+	[InlineData (null, 0,  TextAlignment.Left, false, 1)]
+	[InlineData (null, 0,  TextAlignment.Left, true,  1)]
+	[InlineData ("",   0,  TextAlignment.Left, false, 1)]
+	[InlineData ("",   0,  TextAlignment.Left, true,  1)]
+	public void Reformat_Invalid (string text, int maxWidth, TextAlignment textAlignment, bool wrap, int linesCount)
+	{
+		if (maxWidth < 0) {
+			Assert.Throws<ArgumentOutOfRangeException> (() => TextFormatter.Format (text, maxWidth, textAlignment, wrap));
+		} else {
 			var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap);
 			Assert.NotEmpty (list);
 			Assert.True (list.Count == linesCount);
-			if (stringEmpty) {
-				Assert.Equal (string.Empty, list [0]);
-			} else {
-				Assert.NotEqual (string.Empty, list [0]);
-			}
-			if (text.Contains ("\r\n") && maxWidth > 0) {
-				Assert.Equal (StringExtensions.ToString (text.ToRunes () [0..expectedClippedWidth]).Replace ("\r\n", " "), list [0]);
-			} else if (text.Contains ('\n') && maxWidth > 0) {
-				Assert.Equal (StringExtensions.ToString (text.ToRunes () [0..expectedClippedWidth]).Replace ("\n", " "), list [0]);
-			} else {
-				Assert.Equal (StringExtensions.ToString (text.ToRunes () [0..expectedClippedWidth]), list [0]);
-			}
+			Assert.Equal (string.Empty, list [0]);
 		}
+	}
 
-		[Theory]
-		[InlineData ("A sentence has words.\nLine 2.", 0, -29, TextAlignment.Left, false, 1, true, new string [] { "" })]
-		[InlineData ("A sentence has words.\nLine 2.", 1, -28, TextAlignment.Left, false, 2, false, new string [] { "A", "L" })]
-		[InlineData ("A sentence has words.\nLine 2.", 5, -24, TextAlignment.Left, false, 2, false, new string [] { "A sen", "Line " })]
-		[InlineData ("A sentence has words.\nLine 2.", 28, -1, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })]
-		//// no clip
-		[InlineData ("A sentence has words.\nLine 2.", 29, 0, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })]
-		[InlineData ("A sentence has words.\nLine 2.", 30, 1, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })]
-		[InlineData ("A sentence has words.\r\nLine 2.", 0, -30, TextAlignment.Left, false, 1, true, new string [] { "" })]
-		[InlineData ("A sentence has words.\r\nLine 2.", 1, -29, TextAlignment.Left, false, 2, false, new string [] { "A", "L" })]
-		[InlineData ("A sentence has words.\r\nLine 2.", 5, -25, TextAlignment.Left, false, 2, false, new string [] { "A sen", "Line " })]
-		[InlineData ("A sentence has words.\r\nLine 2.", 29, -1, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })]
-		[InlineData ("A sentence has words.\r\nLine 2.", 30, 0, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })]
-		[InlineData ("A sentence has words.\r\nLine 2.", 31, 1, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })]
-		public void Reformat_NoWordrap_NewLines_MultiLine_True (string text, int maxWidth, int widthOffset, TextAlignment textAlignment, bool wrap, int linesCount, bool stringEmpty, IEnumerable<string> resultLines)
-		{
-			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
-			var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap, false, 0, TextDirection.LeftRight_TopBottom, true);
-			Assert.NotEmpty (list);
-			Assert.True (list.Count == linesCount);
-			if (stringEmpty) {
-				Assert.Equal (string.Empty, list [0]);
-			} else {
-				Assert.NotEqual (string.Empty, list [0]);
-			}
+	[Theory]
+	[InlineData ("",                      0,  0,   TextAlignment.Left, false, 1, true)]
+	[InlineData ("",                      1,  1,   TextAlignment.Left, false, 1, true)]
+	[InlineData ("A sentence has words.", 0,  -21, TextAlignment.Left, false, 1, true)]
+	[InlineData ("A sentence has words.", 1,  -20, TextAlignment.Left, false, 1, false)]
+	[InlineData ("A sentence has words.", 5,  -16, TextAlignment.Left, false, 1, false)]
+	[InlineData ("A sentence has words.", 20, -1,  TextAlignment.Left, false, 1, false)]
+	// no clip
+	[InlineData ("A sentence has words.", 21, 0, TextAlignment.Left, false, 1, false)]
+	[InlineData ("A sentence has words.", 22, 1, TextAlignment.Left, false, 1, false)]
+	public void Reformat_NoWordrap_SingleLine (string text, int maxWidth, int widthOffset, TextAlignment textAlignment, bool wrap, int linesCount, bool stringEmpty)
+	{
+		Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
+		var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
+		var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap);
+		Assert.NotEmpty (list);
+		Assert.True (list.Count == linesCount);
+		if (stringEmpty) {
+			Assert.Equal (string.Empty, list [0]);
+		} else {
+			Assert.NotEqual (string.Empty, list [0]);
+		}
+		Assert.Equal (StringExtensions.ToString (text.ToRunes () [..expectedClippedWidth]), list [0]);
+	}
 
-			Assert.Equal (list, resultLines);
+	[Theory]
+	[InlineData ("A sentence has words.\nLine 2.", 0,  -29, TextAlignment.Left, false, 1, true)]
+	[InlineData ("A sentence has words.\nLine 2.", 1,  -28, TextAlignment.Left, false, 1, false)]
+	[InlineData ("A sentence has words.\nLine 2.", 5,  -24, TextAlignment.Left, false, 1, false)]
+	[InlineData ("A sentence has words.\nLine 2.", 28, -1,  TextAlignment.Left, false, 1, false)]
+	// no clip
+	[InlineData ("A sentence has words.\nLine 2.",   29, 0,   TextAlignment.Left, false, 1, false)]
+	[InlineData ("A sentence has words.\nLine 2.",   30, 1,   TextAlignment.Left, false, 1, false)]
+	[InlineData ("A sentence has words.\r\nLine 2.", 0,  -30, TextAlignment.Left, false, 1, true)]
+	[InlineData ("A sentence has words.\r\nLine 2.", 1,  -29, TextAlignment.Left, false, 1, false)]
+	[InlineData ("A sentence has words.\r\nLine 2.", 5,  -25, TextAlignment.Left, false, 1, false)]
+	[InlineData ("A sentence has words.\r\nLine 2.", 29, -1,  TextAlignment.Left, false, 1, false, 1)]
+	[InlineData ("A sentence has words.\r\nLine 2.", 30, 0,   TextAlignment.Left, false, 1, false)]
+	[InlineData ("A sentence has words.\r\nLine 2.", 31, 1,   TextAlignment.Left, false, 1, false)]
+	public void Reformat_NoWordrap_NewLines_MultiLine_False (string text,
+								 int maxWidth,
+								 int widthOffset,
+								 TextAlignment textAlignment,
+								 bool wrap,
+								 int linesCount,
+								 bool stringEmpty,
+								 int clipWidthOffset = 0)
+	{
+		Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
+		var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth) + clipWidthOffset;
+		var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap);
+		Assert.NotEmpty (list);
+		Assert.True (list.Count == linesCount);
+		if (stringEmpty) {
+			Assert.Equal (string.Empty, list [0]);
+		} else {
+			Assert.NotEqual (string.Empty, list [0]);
+		}
+		if (text.Contains ("\r\n") && maxWidth > 0) {
+			Assert.Equal (StringExtensions.ToString (text.ToRunes () [..expectedClippedWidth]).Replace ("\r\n", " "), list [0]);
+		} else if (text.Contains ('\n') && maxWidth > 0) {
+			Assert.Equal (StringExtensions.ToString (text.ToRunes () [..expectedClippedWidth]).Replace ("\n", " "), list [0]);
+		} else {
+			Assert.Equal (StringExtensions.ToString (text.ToRunes () [..expectedClippedWidth]), list [0]);
 		}
+	}
 
-		[Theory]
-		[InlineData ("A sentence has words.\nLine 2.", 0, -29, TextAlignment.Left, false, 1, true, new string [] { "" })]
-		[InlineData ("A sentence has words.\nLine 2.", 1, -28, TextAlignment.Left, false, 2, false, new string [] { "A", "L" })]
-		[InlineData ("A sentence has words.\nLine 2.", 5, -24, TextAlignment.Left, false, 2, false, new string [] { "A sen", "Line " })]
-		[InlineData ("A sentence has words.\nLine 2.", 28, -1, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })]
-		//// no clip
-		[InlineData ("A sentence has words.\nLine 2.", 29, 0, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })]
-		[InlineData ("A sentence has words.\nLine 2.", 30, 1, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })]
-		[InlineData ("A sentence has words.\r\nLine 2.", 0, -30, TextAlignment.Left, false, 1, true, new string [] { "" })]
-		[InlineData ("A sentence has words.\r\nLine 2.", 1, -29, TextAlignment.Left, false, 2, false, new string [] { "A", "L" })]
-		[InlineData ("A sentence has words.\r\nLine 2.", 5, -25, TextAlignment.Left, false, 2, false, new string [] { "A sen", "Line " })]
-		[InlineData ("A sentence has words.\r\nLine 2.", 29, -1, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })]
-		[InlineData ("A sentence has words.\r\nLine 2.", 30, 0, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })]
-		[InlineData ("A sentence has words.\r\nLine 2.", 31, 1, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })]
-		public void Reformat_NoWordrap_NewLines_MultiLine_True_Vertical (string text, int maxWidth, int widthOffset, TextAlignment textAlignment, bool wrap, int linesCount, bool stringEmpty, IEnumerable<string> resultLines)
-		{
-			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
-			var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap, false, 0, TextDirection.TopBottom_LeftRight, true);
-			Assert.NotEmpty (list);
-			Assert.True (list.Count == linesCount);
-			if (stringEmpty) {
-				Assert.Equal (string.Empty, list [0]);
-			} else {
-				Assert.NotEqual (string.Empty, list [0]);
-			}
+	[Theory]
+	[InlineData ("A sentence has words.\nLine 2.", 0,  -29, TextAlignment.Left, false, 1, true,  new [] { "" })]
+	[InlineData ("A sentence has words.\nLine 2.", 1,  -28, TextAlignment.Left, false, 2, false, new [] { "A", "L" })]
+	[InlineData ("A sentence has words.\nLine 2.", 5,  -24, TextAlignment.Left, false, 2, false, new [] { "A sen", "Line " })]
+	[InlineData ("A sentence has words.\nLine 2.", 28, -1,  TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })]
+	//// no clip
+	[InlineData ("A sentence has words.\nLine 2.",   29, 0,   TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })]
+	[InlineData ("A sentence has words.\nLine 2.",   30, 1,   TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })]
+	[InlineData ("A sentence has words.\r\nLine 2.", 0,  -30, TextAlignment.Left, false, 1, true,  new [] { "" })]
+	[InlineData ("A sentence has words.\r\nLine 2.", 1,  -29, TextAlignment.Left, false, 2, false, new [] { "A", "L" })]
+	[InlineData ("A sentence has words.\r\nLine 2.", 5,  -25, TextAlignment.Left, false, 2, false, new [] { "A sen", "Line " })]
+	[InlineData ("A sentence has words.\r\nLine 2.", 29, -1,  TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })]
+	[InlineData ("A sentence has words.\r\nLine 2.", 30, 0,   TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })]
+	[InlineData ("A sentence has words.\r\nLine 2.", 31, 1,   TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })]
+	public void Reformat_NoWordrap_NewLines_MultiLine_True (string text,
+								int maxWidth,
+								int widthOffset,
+								TextAlignment textAlignment,
+								bool wrap,
+								int linesCount,
+								bool stringEmpty,
+								IEnumerable<string> resultLines)
+	{
+		Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
+		var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap, false, 0, TextDirection.LeftRight_TopBottom, true);
+		Assert.NotEmpty (list);
+		Assert.True (list.Count == linesCount);
+		if (stringEmpty) {
+			Assert.Equal (string.Empty, list [0]);
+		} else {
+			Assert.NotEqual (string.Empty, list [0]);
+		}
+
+		Assert.Equal (list, resultLines);
+	}
 
-			Assert.Equal (list, resultLines);
-		}
+	[Theory]
+	[InlineData ("A sentence has words.\nLine 2.", 0,  -29, TextAlignment.Left, false, 1, true,  new [] { "" })]
+	[InlineData ("A sentence has words.\nLine 2.", 1,  -28, TextAlignment.Left, false, 2, false, new [] { "A", "L" })]
+	[InlineData ("A sentence has words.\nLine 2.", 5,  -24, TextAlignment.Left, false, 2, false, new [] { "A sen", "Line " })]
+	[InlineData ("A sentence has words.\nLine 2.", 28, -1,  TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })]
+	//// no clip
+	[InlineData ("A sentence has words.\nLine 2.",   29, 0,   TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })]
+	[InlineData ("A sentence has words.\nLine 2.",   30, 1,   TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })]
+	[InlineData ("A sentence has words.\r\nLine 2.", 0,  -30, TextAlignment.Left, false, 1, true,  new [] { "" })]
+	[InlineData ("A sentence has words.\r\nLine 2.", 1,  -29, TextAlignment.Left, false, 2, false, new [] { "A", "L" })]
+	[InlineData ("A sentence has words.\r\nLine 2.", 5,  -25, TextAlignment.Left, false, 2, false, new [] { "A sen", "Line " })]
+	[InlineData ("A sentence has words.\r\nLine 2.", 29, -1,  TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })]
+	[InlineData ("A sentence has words.\r\nLine 2.", 30, 0,   TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })]
+	[InlineData ("A sentence has words.\r\nLine 2.", 31, 1,   TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })]
+	public void Reformat_NoWordrap_NewLines_MultiLine_True_Vertical (string text,
+									 int maxWidth,
+									 int widthOffset,
+									 TextAlignment textAlignment,
+									 bool wrap,
+									 int linesCount,
+									 bool stringEmpty,
+									 IEnumerable<string> resultLines)
+	{
+		Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
+		var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap, false, 0, TextDirection.TopBottom_LeftRight, true);
+		Assert.NotEmpty (list);
+		Assert.True (list.Count == linesCount);
+		if (stringEmpty) {
+			Assert.Equal (string.Empty, list [0]);
+		} else {
+			Assert.NotEqual (string.Empty, list [0]);
+		}
+
+		Assert.Equal (list, resultLines);
+	}
 
-		[Theory]
-		// Even # of spaces
-		//            0123456789
-		[InlineData ("012 456 89", 0, -10, TextAlignment.Left, true, true, true, new string [] { "" })]
-		[InlineData ("012 456 89", 1, -9, TextAlignment.Left, true, true, false, new string [] { "0", "1", "2", " ", "4", "5", "6", " ", "8", "9" }, "01245689")]
-		[InlineData ("012 456 89", 5, -5, TextAlignment.Left, true, true, false, new string [] { "012 ", "456 ", "89" })]
-		[InlineData ("012 456 89", 9, -1, TextAlignment.Left, true, true, false, new string [] { "012 456 ", "89" })]
-		// no clip
-		[InlineData ("012 456 89", 10, 0, TextAlignment.Left, true, true, false, new string [] { "012 456 89" })]
-		[InlineData ("012 456 89", 11, 1, TextAlignment.Left, true, true, false, new string [] { "012 456 89" })]
-		// Odd # of spaces
-		//            01234567890123
-		[InlineData ("012 456 89 end", 13, -1, TextAlignment.Left, true, true, false, new string [] { "012 456 89 ", "end" })]
-		// no clip
-		[InlineData ("012 456 89 end", 14, 0, TextAlignment.Left, true, true, false, new string [] { "012 456 89 end" })]
-		[InlineData ("012 456 89 end", 15, 1, TextAlignment.Left, true, true, false, new string [] { "012 456 89 end" })]
-		public void Reformat_Wrap_Spaces_No_NewLines (string text, int maxWidth, int widthOffset, TextAlignment textAlignment, bool wrap, bool preserveTrailingSpaces, bool stringEmpty, IEnumerable<string> resultLines, string noSpaceText = "")
-		{
-			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
-			var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
-			var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap, preserveTrailingSpaces);
-			Assert.NotEmpty (list);
-			Assert.True (list.Count == resultLines.Count ());
-			if (stringEmpty) {
-				Assert.Equal (string.Empty, list [0]);
+	[Theory]
+	// Even # of spaces
+	//            0123456789
+	[InlineData ("012 456 89", 0, -10, TextAlignment.Left, true, true, true,  new [] { "" })]
+	[InlineData ("012 456 89", 1, -9,  TextAlignment.Left, true, true, false, new [] { "0", "1", "2", " ", "4", "5", "6", " ", "8", "9" }, "01245689")]
+	[InlineData ("012 456 89", 5, -5,  TextAlignment.Left, true, true, false, new [] { "012 ", "456 ", "89" })]
+	[InlineData ("012 456 89", 9, -1,  TextAlignment.Left, true, true, false, new [] { "012 456 ", "89" })]
+	// no clip
+	[InlineData ("012 456 89", 10, 0, TextAlignment.Left, true, true, false, new [] { "012 456 89" })]
+	[InlineData ("012 456 89", 11, 1, TextAlignment.Left, true, true, false, new [] { "012 456 89" })]
+	// Odd # of spaces
+	//            01234567890123
+	[InlineData ("012 456 89 end", 13, -1, TextAlignment.Left, true, true, false, new [] { "012 456 89 ", "end" })]
+	// no clip
+	[InlineData ("012 456 89 end", 14, 0, TextAlignment.Left, true, true, false, new [] { "012 456 89 end" })]
+	[InlineData ("012 456 89 end", 15, 1, TextAlignment.Left, true, true, false, new [] { "012 456 89 end" })]
+	public void Reformat_Wrap_Spaces_No_NewLines (string text,
+						      int maxWidth,
+						      int widthOffset,
+						      TextAlignment textAlignment,
+						      bool wrap,
+						      bool preserveTrailingSpaces,
+						      bool stringEmpty,
+						      IEnumerable<string> resultLines,
+						      string noSpaceText = "")
+	{
+		Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
+		var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
+		var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap, preserveTrailingSpaces);
+		Assert.NotEmpty (list);
+		Assert.True (list.Count == resultLines.Count ());
+		if (stringEmpty) {
+			Assert.Equal (string.Empty, list [0]);
+		} else {
+			Assert.NotEqual (string.Empty, list [0]);
+		}
+		Assert.Equal (resultLines, list);
+
+		if (maxWidth > 0) {
+			// remove whitespace chars
+			if (maxWidth < 5) {
+				expectedClippedWidth = text.GetRuneCount () - text.Sum (r => r == ' ' ? 1 : 0);
 			} else {
-				Assert.NotEqual (string.Empty, list [0]);
+				expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth - text.Sum (r => r == ' ' ? 1 : 0));
 			}
-			Assert.Equal (resultLines, list);
-
-			if (maxWidth > 0) {
-				// remove whitespace chars
-				if (maxWidth < 5) {
-					expectedClippedWidth = text.GetRuneCount () - text.Sum (r => r == ' ' ? 1 : 0);
-				} else {
-					expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth - text.Sum (r => r == ' ' ? 1 : 0));
-				}
-				list = TextFormatter.Format (text, maxWidth, TextAlignment.Left, wrap, preserveTrailingSpaces: false);
-				if (maxWidth == 1) {
-					Assert.Equal (expectedClippedWidth, list.Count);
-					Assert.Equal (noSpaceText, string.Concat (list.ToArray ()));
-				}
-				if (maxWidth > 1 && maxWidth < 10) {
-					Assert.Equal (StringExtensions.ToString (text.ToRunes () [0..expectedClippedWidth]), list [0]);
-				}
+			list = TextFormatter.Format (text, maxWidth, TextAlignment.Left, wrap);
+			if (maxWidth == 1) {
+				Assert.Equal (expectedClippedWidth, list.Count);
+				Assert.Equal (noSpaceText,          string.Concat (list.ToArray ()));
 			}
-		}
-
-		[Theory]
-		// Unicode
-		// Even # of chars
-		//       0123456789
-		[InlineData ("\u2660пÑРвРÑ", 10, -1, TextAlignment.Left, true, false, new string [] { "\u2660пÑРвÐ", "Ñ" })]
-		// no clip
-		[InlineData ("\u2660пÑРвРÑ", 11, 0, TextAlignment.Left, true, false, new string [] { "\u2660пÑРвРÑ" })]
-		[InlineData ("\u2660пÑРвРÑ", 12, 1, TextAlignment.Left, true, false, new string [] { "\u2660пÑРвРÑ" })]
-		// Unicode
-		// Odd # of chars
-		//            0123456789
-		[InlineData ("\u2660 ÑРвРÑ", 9, -1, TextAlignment.Left, true, false, new string [] { "\u2660 ÑРвÐ", "Ñ" })]
-		// no clip
-		[InlineData ("\u2660 ÑРвРÑ", 10, 0, TextAlignment.Left, true, false, new string [] { "\u2660 ÑРвРÑ" })]
-		[InlineData ("\u2660 ÑРвРÑ", 11, 1, TextAlignment.Left, true, false, new string [] { "\u2660 ÑРвРÑ" })]
-		public void Reformat_Unicode_Wrap_Spaces_No_NewLines (string text, int maxWidth, int widthOffset, TextAlignment textAlignment, bool wrap, bool preserveTrailingSpaces, IEnumerable<string> resultLines)
-		{
-			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
-			var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap, preserveTrailingSpaces);
-			Assert.Equal (list.Count, resultLines.Count ());
-			Assert.Equal (resultLines, list);
-		}
-
-		[Theory]
-		// Unicode
-		[InlineData ("\u2460\u2461\u2462\n\u2460\u2461\u2462\u2463\u2464", 8, -1, TextAlignment.Left, true, false, new string [] { "\u2460\u2461\u2462", "\u2460\u2461\u2462\u2463\u2464" })]
-		// no clip
-		[InlineData ("\u2460\u2461\u2462\n\u2460\u2461\u2462\u2463\u2464", 9, 0, TextAlignment.Left, true, false, new string [] { "\u2460\u2461\u2462", "\u2460\u2461\u2462\u2463\u2464" })]
-		[InlineData ("\u2460\u2461\u2462\n\u2460\u2461\u2462\u2463\u2464", 10, 1, TextAlignment.Left, true, false, new string [] { "\u2460\u2461\u2462", "\u2460\u2461\u2462\u2463\u2464" })]
-		public void Reformat_Unicode_Wrap_Spaces_NewLines (string text, int maxWidth, int widthOffset, TextAlignment textAlignment, bool wrap, bool preserveTrailingSpaces, IEnumerable<string> resultLines)
-		{
-			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
-			var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap, preserveTrailingSpaces);
-			Assert.Equal (list.Count, resultLines.Count ());
-			Assert.Equal (resultLines, list);
-		}
-
-		[Theory]
-		[InlineData (" A sentence has words. \n This is the second Line - 2. ", 4, -50, TextAlignment.Left, true, false, new string [] { " A", "sent", "ence", "has", "word", "s. ", " Thi", "s is", "the", "seco", "nd", "Line", "- 2." }, " Asentencehaswords.  This isthesecondLine- 2.")]
-		[InlineData (" A sentence has words. \n This is the second Line - 2. ", 4, -50, TextAlignment.Left, true, true, new string [] { " A ", "sent", "ence", " ", "has ", "word", "s. ", " ", "This", " is ", "the ", "seco", "nd ", "Line", " - ", "2. " }, " A sentence has words.  This is the second Line - 2. ")]
-		public void Format_WordWrap_PreserveTrailingSpaces (string text, int maxWidth, int widthOffset, TextAlignment textAlignment, bool wrap, bool preserveTrailingSpaces, IEnumerable<string> resultLines, string expectedWrappedText)
-		{
-			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
-			var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap, preserveTrailingSpaces);
-			Assert.Equal (list.Count, resultLines.Count ());
-			Assert.Equal (resultLines, list);
-			string wrappedText = string.Empty;
-			foreach (var txt in list) wrappedText += txt;
-			Assert.Equal (expectedWrappedText, wrappedText);
-		}
-
-		[Fact]
-		public void Format_Dont_Throw_ArgumentException_With_WordWrap_As_False_And_Keep_End_Spaces_As_True ()
-		{
-			var exception = Record.Exception (() => TextFormatter.Format ("Some text", 4, TextAlignment.Left, false, true));
-			Assert.Null (exception);
-		}
-
-		[Theory]
-		[InlineData ("Hello world, how are you today? Pretty neat!", 44, 80, "Hello      world,      how      are      you      today?      Pretty      neat!")]
-		public void Format_Justified_Always_Returns_Text_Width_Equal_To_Passed_Width_Horizontal (string text, int runeCount, int maxWidth, string justifiedText)
-		{
-			Assert.Equal (runeCount, text.GetRuneCount ());
-
-			var fmtText = string.Empty;
-			for (int i = text.GetRuneCount (); i < maxWidth; i++) {
-				fmtText = TextFormatter.Format (text, i, TextAlignment.Justified, false, true) [0];
-				Assert.Equal (i, fmtText.GetRuneCount ());
-				var c = fmtText [^1];
-				Assert.True (text.EndsWith (c));
+			if (maxWidth > 1 && maxWidth < 10) {
+				Assert.Equal (StringExtensions.ToString (text.ToRunes () [..expectedClippedWidth]), list [0]);
 			}
-			Assert.Equal (justifiedText, fmtText);
 		}
+	}
 
-		[Theory]
-		[InlineData ("Hello world, how are you today? Pretty neat!", 44, 80, "Hello      world,      how      are      you      today?      Pretty      neat!")]
-		public void Format_Justified_Always_Returns_Text_Width_Equal_To_Passed_Width_Vertical (string text, int runeCount, int maxWidth, string justifiedText)
-		{
-			Assert.Equal (runeCount, text.GetRuneCount ());
-
-			var fmtText = string.Empty;
-			for (int i = text.GetRuneCount (); i < maxWidth; i++) {
-				fmtText = TextFormatter.Format (text, i, TextAlignment.Justified, false, true, 0, TextDirection.TopBottom_LeftRight) [0];
-				Assert.Equal (i, fmtText.GetRuneCount ());
-				var c = fmtText [^1];
-				Assert.True (text.EndsWith (c));
-			}
-			Assert.Equal (justifiedText, fmtText);
-		}
+	[Theory]
+	// Unicode
+	// Even # of chars
+	//       0123456789
+	[InlineData ("\u2660пÑРвРÑ", 10, -1, TextAlignment.Left, true, false, new [] { "\u2660пÑРвÐ", "Ñ" })]
+	// no clip
+	[InlineData ("\u2660пÑРвРÑ", 11, 0, TextAlignment.Left, true, false, new [] { "\u2660пÑРвРÑ" })]
+	[InlineData ("\u2660пÑРвРÑ", 12, 1, TextAlignment.Left, true, false, new [] { "\u2660пÑРвРÑ" })]
+	// Unicode
+	// Odd # of chars
+	//            0123456789
+	[InlineData ("\u2660 ÑРвРÑ", 9, -1, TextAlignment.Left, true, false, new [] { "\u2660 ÑРвÐ", "Ñ" })]
+	// no clip
+	[InlineData ("\u2660 ÑРвРÑ", 10, 0, TextAlignment.Left, true, false, new [] { "\u2660 ÑРвРÑ" })]
+	[InlineData ("\u2660 ÑРвРÑ", 11, 1, TextAlignment.Left, true, false, new [] { "\u2660 ÑРвРÑ" })]
+	public void Reformat_Unicode_Wrap_Spaces_No_NewLines (string text,
+							      int maxWidth,
+							      int widthOffset,
+							      TextAlignment textAlignment,
+							      bool wrap,
+							      bool preserveTrailingSpaces,
+							      IEnumerable<string> resultLines)
+	{
+		Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
+		var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap, preserveTrailingSpaces);
+		Assert.Equal (list.Count,  resultLines.Count ());
+		Assert.Equal (resultLines, list);
+	}
 
-		[Theory]
-		[InlineData ("fff", 6, "fff   ")]
-		[InlineData ("Hello World", 16, "Hello World     ")]
-		public void TestClipOrPad_ShortWord (string text, int fillPad, string expectedText)
-		{
-			// word is short but we want it to fill # so it should be padded
-			Assert.Equal (expectedText, TextFormatter.ClipOrPad (text, fillPad));
-		}
+	[Theory]
+	// Unicode
+	[InlineData ("\u2460\u2461\u2462\n\u2460\u2461\u2462\u2463\u2464", 8, -1, TextAlignment.Left, true, false, new [] { "\u2460\u2461\u2462", "\u2460\u2461\u2462\u2463\u2464" })]
+	// no clip
+	[InlineData ("\u2460\u2461\u2462\n\u2460\u2461\u2462\u2463\u2464", 9,  0, TextAlignment.Left, true, false, new [] { "\u2460\u2461\u2462", "\u2460\u2461\u2462\u2463\u2464" })]
+	[InlineData ("\u2460\u2461\u2462\n\u2460\u2461\u2462\u2463\u2464", 10, 1, TextAlignment.Left, true, false, new [] { "\u2460\u2461\u2462", "\u2460\u2461\u2462\u2463\u2464" })]
+	public void Reformat_Unicode_Wrap_Spaces_NewLines (string text,
+							   int maxWidth,
+							   int widthOffset,
+							   TextAlignment textAlignment,
+							   bool wrap,
+							   bool preserveTrailingSpaces,
+							   IEnumerable<string> resultLines)
+	{
+		Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
+		var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap, preserveTrailingSpaces);
+		Assert.Equal (list.Count,  resultLines.Count ());
+		Assert.Equal (resultLines, list);
+	}
 
-		[Theory]
-		[InlineData ("123456789", 3, "123")]
-		[InlineData ("Hello World", 8, "Hello Wo")]
-		public void TestClipOrPad_LongWord (string text, int fillPad, string expectedText)
-		{
-			// word is long but we want it to fill # space only
-			Assert.Equal (expectedText, TextFormatter.ClipOrPad (text, fillPad));
-		}
+	[Theory]
+	[InlineData (" A sentence has words. \n This is the second Line - 2. ", 4, -50, TextAlignment.Left, true, false,
+		new [] { " A", "sent", "ence", "has", "word", "s. ", " Thi", "s is", "the", "seco", "nd", "Line", "- 2." }, " Asentencehaswords.  This isthesecondLine- 2.")]
+	[InlineData (" A sentence has words. \n This is the second Line - 2. ", 4, -50, TextAlignment.Left, true, true,
+		new [] { " A ", "sent", "ence", " ", "has ", "word", "s. ", " ", "This", " is ", "the ", "seco", "nd ", "Line", " - ", "2. " },
+		" A sentence has words.  This is the second Line - 2. ")]
+	public void Format_WordWrap_PreserveTrailingSpaces (string text,
+							    int maxWidth,
+							    int widthOffset,
+							    TextAlignment textAlignment,
+							    bool wrap,
+							    bool preserveTrailingSpaces,
+							    IEnumerable<string> resultLines,
+							    string expectedWrappedText)
+	{
+		Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
+		var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap, preserveTrailingSpaces);
+		Assert.Equal (list.Count,  resultLines.Count ());
+		Assert.Equal (resultLines, list);
+		var wrappedText = string.Empty;
+		foreach (var txt in list) {
+			wrappedText += txt;
+		}
+		Assert.Equal (expectedWrappedText, wrappedText);
+	}
 
-		[Fact]
-		public void Internal_Tests ()
-		{
-			var tf = new TextFormatter ();
-			Assert.Equal (KeyCode.Null, tf.HotKey);
-			tf.HotKey = KeyCode.CtrlMask | KeyCode.Q;
-			Assert.Equal (KeyCode.CtrlMask | KeyCode.Q, tf.HotKey);
-		}
+	[Fact]
+	public void Format_Dont_Throw_ArgumentException_With_WordWrap_As_False_And_Keep_End_Spaces_As_True ()
+	{
+		var exception = Record.Exception (() => TextFormatter.Format ("Some text", 4, TextAlignment.Left, false, true));
+		Assert.Null (exception);
+	}
 
-		[Theory]
-		[InlineData ("Hello World", 11)]
-		[InlineData ("こんにちは世界", 14)]
-		public void GetColumns_Simple_And_Wide_Runes (string text, int width)
-		{
-			Assert.Equal (width, text.GetColumns ());
-		}
+	[Theory]
+	[InlineData ("Hello world, how are you today? Pretty neat!", 44, 80, "Hello      world,      how      are      you      today?      Pretty      neat!")]
+	public void Format_Justified_Always_Returns_Text_Width_Equal_To_Passed_Width_Horizontal (string text, int runeCount, int maxWidth, string justifiedText)
+	{
+		Assert.Equal (runeCount, text.GetRuneCount ());
 
-		[Theory]
-		[InlineData ("Hello World", 11, 6, 1, 1)]
-		[InlineData ("こんにちは 世界", 15, 6, 1, 2)]
-		public void GetSumMaxCharWidth_Simple_And_Wide_Runes (string text, int width, int index, int length, int indexWidth)
-		{
-			Assert.Equal (width, TextFormatter.GetSumMaxCharWidth (text));
-			Assert.Equal (indexWidth, TextFormatter.GetSumMaxCharWidth (text, index, length));
-		}
-
-		[Theory]
-		[InlineData (new string [] { "Hello", "World" }, 2, 1, 1, 1)]
-		[InlineData (new string [] { "こんにちは", "世界" }, 4, 1, 1, 2)]
-		public void GetSumMaxCharWidth_List_Simple_And_Wide_Runes (IEnumerable<string> text, int width, int index, int length, int indexWidth)
-		{
-			Assert.Equal (width, TextFormatter.GetSumMaxCharWidth (text.ToList ()));
-			Assert.Equal (indexWidth, TextFormatter.GetSumMaxCharWidth (text.ToList (), index, length));
+		var fmtText = string.Empty;
+		for (var i = text.GetRuneCount (); i < maxWidth; i++) {
+			fmtText = TextFormatter.Format (text, i, TextAlignment.Justified, false, true) [0];
+			Assert.Equal (i, fmtText.GetRuneCount ());
+			var c = fmtText [^1];
+			Assert.True (text.EndsWith (c));
 		}
+		Assert.Equal (justifiedText, fmtText);
+	}
 
-		[Theory]
-		[InlineData ("test", 3, 3)]
-		[InlineData ("test", 4, 4)]
-		[InlineData ("test", 10, 4)]
-		public void GetLengthThatFits_Runelist (string text, int columns, int expectedLength)
-		{
-			var runes = text.ToRuneList ();
+	[Theory]
+	[InlineData ("Hello world, how are you today? Pretty neat!", 44, 80, "Hello      world,      how      are      you      today?      Pretty      neat!")]
+	public void Format_Justified_Always_Returns_Text_Width_Equal_To_Passed_Width_Vertical (string text, int runeCount, int maxWidth, string justifiedText)
+	{
+		Assert.Equal (runeCount, text.GetRuneCount ());
 
-			Assert.Equal (expectedLength, TextFormatter.GetLengthThatFits (runes, columns));
+		var fmtText = string.Empty;
+		for (var i = text.GetRuneCount (); i < maxWidth; i++) {
+			fmtText = TextFormatter.Format (text, i, TextAlignment.Justified, false, true, 0, TextDirection.TopBottom_LeftRight) [0];
+			Assert.Equal (i, fmtText.GetRuneCount ());
+			var c = fmtText [^1];
+			Assert.True (text.EndsWith (c));
 		}
+		Assert.Equal (justifiedText, fmtText);
+	}
 
-		[Theory]
-		[InlineData ("test", 3, 3)]
-		[InlineData ("test", 4, 4)]
-		[InlineData ("test", 10, 4)]
-		[InlineData ("test", 1, 1)]
-		[InlineData ("test", 0, 0)]
-		[InlineData ("test", -1, 0)]
-		[InlineData (null, -1, 0)]
-		[InlineData ("", -1, 0)]
-		public void GetLengthThatFits_String (string text, int columns, int expectedLength)
-		{
-			Assert.Equal (expectedLength, TextFormatter.GetLengthThatFits (text, columns));
-		}
+	[Theory]
+	[InlineData ("fff",         6,  "fff   ")]
+	[InlineData ("Hello World", 16, "Hello World     ")]
+	public void TestClipOrPad_ShortWord (string text, int fillPad, string expectedText) =>
+		// word is short but we want it to fill # so it should be padded
+		Assert.Equal (expectedText, TextFormatter.ClipOrPad (text, fillPad));
+
+	[Theory]
+	[InlineData ("123456789",   3, "123")]
+	[InlineData ("Hello World", 8, "Hello Wo")]
+	public void TestClipOrPad_LongWord (string text, int fillPad, string expectedText) =>
+		// word is long but we want it to fill # space only
+		Assert.Equal (expectedText, TextFormatter.ClipOrPad (text, fillPad));
+
+	[Fact]
+	public void Internal_Tests ()
+	{
+		var tf = new TextFormatter ();
+		Assert.Equal (KeyCode.Null, tf.HotKey);
+		tf.HotKey = KeyCode.CtrlMask | KeyCode.Q;
+		Assert.Equal (KeyCode.CtrlMask | KeyCode.Q, tf.HotKey);
+	}
 
-		[Theory]
-		[InlineData ("Hello World", 6, 6)]
-		[InlineData ("こんにちは 世界", 6, 3)]
-		public void GetLengthThatFits_Simple_And_Wide_Runes (string text, int columns, int expectedLength)
-		{
-			Assert.Equal (expectedLength, TextFormatter.GetLengthThatFits (text, columns));
-		}
+	[Theory]
+	[InlineData ("Hello World", 11)]
+	[InlineData ("こんにちは世界",     14)]
+	public void GetColumns_Simple_And_Wide_Runes (string text, int width) => Assert.Equal (width, text.GetColumns ());
+
+	[Theory]
+	[InlineData ("Hello World", 11, 6, 1, 1)]
+	[InlineData ("こんにちは 世界",    15, 6, 1, 2)]
+	public void GetSumMaxCharWidth_Simple_And_Wide_Runes (string text, int width, int index, int length, int indexWidth)
+	{
+		Assert.Equal (width,      TextFormatter.GetSumMaxCharWidth (text));
+		Assert.Equal (indexWidth, TextFormatter.GetSumMaxCharWidth (text, index, length));
+	}
 
-		[Theory]
-		[InlineData ("Hello World", 6, 6)]
-		[InlineData ("こんにちは 世界", 6, 3)]
-		[MemberData (nameof (CMGlyphs))]
-		public void GetLengthThatFits_List_Simple_And_Wide_Runes (string text, int columns, int expectedLength)
-		{
-			var runes = text.ToRuneList ();
-			Assert.Equal (expectedLength, TextFormatter.GetLengthThatFits (runes, columns));
-		}
+	[Theory]
+	[InlineData (new [] { "Hello", "World" }, 2, 1, 1, 1)]
+	[InlineData (new [] { "こんにちは", "世界" },    4, 1, 1, 2)]
+	public void GetSumMaxCharWidth_List_Simple_And_Wide_Runes (IEnumerable<string> text, int width, int index, int length, int indexWidth)
+	{
+		Assert.Equal (width,      TextFormatter.GetSumMaxCharWidth (text.ToList ()));
+		Assert.Equal (indexWidth, TextFormatter.GetSumMaxCharWidth (text.ToList (), index, length));
+	}
 
-		public static IEnumerable<object []> CMGlyphs =>
-			new List<object []>
-			{
-			    new object[] { $"{CM.Glyphs.LeftBracket} Say Hello 你 {CM.Glyphs.RightBracket}", 16, 15 }
-			};
-
-		[Theory]
-		[InlineData ("Truncate", 3, "Tru")]
-		[InlineData ("デモエムポンズ", 3, "デ")]
-		public void Format_Truncate_Simple_And_Wide_Runes (string text, int width, string expected)
-		{
-			var list = TextFormatter.Format (text, width, false, false);
-			Assert.Equal (expected, list [^1]);
-		}
+	[Theory]
+	[InlineData ("test", 3,  3)]
+	[InlineData ("test", 4,  4)]
+	[InlineData ("test", 10, 4)]
+	public void GetLengthThatFits_Runelist (string text, int columns, int expectedLength)
+	{
+		var runes = text.ToRuneList ();
 
-		[Theory]
-		[MemberData (nameof (FormatEnvironmentNewLine))]
-		public void Format_With_PreserveTrailingSpaces_And_Without_PreserveTrailingSpaces (string text, int width, IEnumerable<string> expected)
-		{
-			var preserveTrailingSpaces = false;
-			var formated = TextFormatter.Format (text, width, false, true, preserveTrailingSpaces);
-			Assert.Equal (expected, formated);
-
-			preserveTrailingSpaces = true;
-			formated = TextFormatter.Format (text, width, false, true, preserveTrailingSpaces);
-			Assert.Equal (expected, formated);
-		}
+		Assert.Equal (expectedLength, TextFormatter.GetLengthThatFits (runes, columns));
+	}
 
-		public static IEnumerable<object []> FormatEnvironmentNewLine =>
-			new List<object []>
-			{
-				new object[] { $"Line1{Environment.NewLine}Line2{Environment.NewLine}Line3{Environment.NewLine}", 60, new string [] { "Line1", "Line2", "Line3" } }
-			};
-
-		[Theory]
-		[MemberData (nameof (SplitEnvironmentNewLine))]
-		public void SplitNewLine_Ending__With_Or_Without_NewLine_Probably_CRLF (string text, IEnumerable<string> expected)
-		{
-			var splited = TextFormatter.SplitNewLine (text);
-			Assert.Equal (expected, splited);
-		}
+	[Theory]
+	[InlineData ("test", 3,  3)]
+	[InlineData ("test", 4,  4)]
+	[InlineData ("test", 10, 4)]
+	[InlineData ("test", 1,  1)]
+	[InlineData ("test", 0,  0)]
+	[InlineData ("test", -1, 0)]
+	[InlineData (null,   -1, 0)]
+	[InlineData ("",     -1, 0)]
+	public void GetLengthThatFits_String (string text, int columns, int expectedLength) => Assert.Equal (expectedLength, TextFormatter.GetLengthThatFits (text, columns));
+
+	[Theory]
+	[InlineData ("Hello World", 6, 6)]
+	[InlineData ("こんにちは 世界",    6, 3)]
+	public void GetLengthThatFits_Simple_And_Wide_Runes (string text, int columns, int expectedLength) => Assert.Equal (expectedLength, TextFormatter.GetLengthThatFits (text, columns));
+
+	[Theory]
+	[InlineData ("Hello World", 6, 6)]
+	[InlineData ("こんにちは 世界",    6, 3)]
+	[MemberData (nameof (CMGlyphs))]
+	public void GetLengthThatFits_List_Simple_And_Wide_Runes (string text, int columns, int expectedLength)
+	{
+		var runes = text.ToRuneList ();
+		Assert.Equal (expectedLength, TextFormatter.GetLengthThatFits (runes, columns));
+	}
 
-		public static IEnumerable<object []> SplitEnvironmentNewLine =>
-		new List<object []>
-		{
-			new object[] { $"First Line 界{Environment.NewLine}Second Line 界{Environment.NewLine}Third Line 界", new string [] { "First Line 界", "Second Line 界", "Third Line 界" } },
-			new object[] { $"First Line 界{Environment.NewLine}Second Line 界{Environment.NewLine}Third Line 界{Environment.NewLine}", new string [] { "First Line 界", "Second Line 界", "Third Line 界", "" } }
-		};
+	[Theory]
+	[InlineData ("Truncate", 3, "Tru")]
+	[InlineData ("デモエムポンズ",  3, "デ")]
+	public void Format_Truncate_Simple_And_Wide_Runes (string text, int width, string expected)
+	{
+		var list = TextFormatter.Format (text, width, false, false);
+		Assert.Equal (expected, list [^1]);
+	}
 
-		[Theory]
-		[InlineData ($"First Line 界\nSecond Line 界\nThird Line 界", new string [] { "First Line 界", "Second Line 界", "Third Line 界" })]
-		public void SplitNewLine_Ending_Without_NewLine_Only_LF (string text, IEnumerable<string> expected)
-		{
-			var splited = TextFormatter.SplitNewLine (text);
-			Assert.Equal (expected, splited);
-		}
+	[Theory]
+	[MemberData (nameof (FormatEnvironmentNewLine))]
+	public void Format_With_PreserveTrailingSpaces_And_Without_PreserveTrailingSpaces (string text, int width, IEnumerable<string> expected)
+	{
+		var preserveTrailingSpaces = false;
+		var formated = TextFormatter.Format (text, width, false, true, preserveTrailingSpaces);
+		Assert.Equal (expected, formated);
+
+		preserveTrailingSpaces = true;
+		formated = TextFormatter.Format (text, width, false, true, preserveTrailingSpaces);
+		Assert.Equal (expected, formated);
+	}
 
-		[Theory]
-		[InlineData ($"First Line 界\nSecond Line 界\nThird Line 界\n", new string [] { "First Line 界", "Second Line 界", "Third Line 界", "" })]
-		public void SplitNewLine_Ending_With_NewLine_Only_LF (string text, IEnumerable<string> expected)
-		{
-			var splited = TextFormatter.SplitNewLine (text);
-			Assert.Equal (expected, splited);
-		}
+	[Theory]
+	[MemberData (nameof (SplitEnvironmentNewLine))]
+	public void SplitNewLine_Ending__With_Or_Without_NewLine_Probably_CRLF (string text, IEnumerable<string> expected)
+	{
+		var splited = TextFormatter.SplitNewLine (text);
+		Assert.Equal (expected, splited);
+	}
 
-		[Theory]
-		[InlineData ("Single Line 界", 14)]
-		[InlineData ($"First Line 界\nSecond Line 界\nThird Line 界\n", 14)]
-		public void MaxWidthLine_With_And_Without_Newlines (string text, int expected)
-		{
-			Assert.Equal (expected, TextFormatter.MaxWidthLine (text));
-		}
+	[Theory]
+	[InlineData ("First Line 界\nSecond Line 界\nThird Line 界", new [] { "First Line 界", "Second Line 界", "Third Line 界" })]
+	public void SplitNewLine_Ending_Without_NewLine_Only_LF (string text, IEnumerable<string> expected)
+	{
+		var splited = TextFormatter.SplitNewLine (text);
+		Assert.Equal (expected, splited);
+	}
 
-		[Theory]
-		[InlineData ("New Test 你", 10, 10, 20320, 20320, 9, "你")]
-		[InlineData ("New Test \U0001d539", 10, 11, 120121, 55349, 9, "𝔹")]
-		public void String_Array_Is_Not_Always_Equal_ToRunes_Array (string text, int runesLength, int stringLength, int runeValue, int stringValue, int index, string expected)
-		{
-			var usToRunes = text.ToRunes ();
-			Assert.Equal (runesLength, usToRunes.Length);
-			Assert.Equal (stringLength, text.Length);
-			Assert.Equal (runeValue, usToRunes [index].Value);
-			Assert.Equal (stringValue, text [index]);
-			Assert.Equal (expected, usToRunes [index].ToString ());
-			if (char.IsHighSurrogate (text [index])) {
-				// Rune array length isn't equal to string array
-				Assert.Equal (expected, new string (new char [] { text [index], text [index + 1] }));
-			} else {
-				// Rune array length is equal to string array
-				Assert.Equal (expected, text [index].ToString ());
-			}
-		}
+	[Theory]
+	[InlineData ("First Line 界\nSecond Line 界\nThird Line 界\n", new [] { "First Line 界", "Second Line 界", "Third Line 界", "" })]
+	public void SplitNewLine_Ending_With_NewLine_Only_LF (string text, IEnumerable<string> expected)
+	{
+		var splited = TextFormatter.SplitNewLine (text);
+		Assert.Equal (expected, splited);
+	}
 
-		[Fact]
-		public void GetLengthThatFits_With_Combining_Runes ()
-		{
-			var text = "Les Mise\u0328\u0301rables";
-			Assert.Equal (16, TextFormatter.GetLengthThatFits (text, 14));
+	[Theory]
+	[InlineData ("Single Line 界",                               14)]
+	[InlineData ("First Line 界\nSecond Line 界\nThird Line 界\n", 14)]
+	public void MaxWidthLine_With_And_Without_Newlines (string text, int expected) => Assert.Equal (expected, TextFormatter.MaxWidthLine (text));
+
+	[Theory]
+	[InlineData ("New Test 你",          10, 10, 20320,  20320, 9, "你")]
+	[InlineData ("New Test \U0001d539", 10, 11, 120121, 55349, 9, "𝔹")]
+	public void String_Array_Is_Not_Always_Equal_ToRunes_Array (string text, int runesLength, int stringLength, int runeValue, int stringValue, int index, string expected)
+	{
+		var usToRunes = text.ToRunes ();
+		Assert.Equal (runesLength,  usToRunes.Length);
+		Assert.Equal (stringLength, text.Length);
+		Assert.Equal (runeValue,    usToRunes [index].Value);
+		Assert.Equal (stringValue,  text [index]);
+		Assert.Equal (expected,     usToRunes [index].ToString ());
+		if (char.IsHighSurrogate (text [index])) {
+			// Rune array length isn't equal to string array
+			Assert.Equal (expected, new string (new [] { text [index], text [index + 1] }));
+		} else {
+			// Rune array length is equal to string array
+			Assert.Equal (expected, text [index].ToString ());
 		}
+	}
 
-		[Fact]
-		public void GetMaxColsForWidth_With_Combining_Runes ()
-		{
-			var text = new List<string> () { "Les Mis", "e\u0328\u0301", "rables" };
-			Assert.Equal (1, TextFormatter.GetMaxColsForWidth (text, 1));
-		}
+	[Fact]
+	public void GetLengthThatFits_With_Combining_Runes ()
+	{
+		var text = "Les Mise\u0328\u0301rables";
+		Assert.Equal (16, TextFormatter.GetLengthThatFits (text, 14));
+	}
 
-		[Fact]
-		public void GetSumMaxCharWidth_With_Combining_Runes ()
-		{
-			var text = "Les Mise\u0328\u0301rables";
-			Assert.Equal (1, TextFormatter.GetSumMaxCharWidth (text, 1, 1));
-		}
+	[Fact]
+	public void GetMaxColsForWidth_With_Combining_Runes ()
+	{
+		var text = new List<string> { "Les Mis", "e\u0328\u0301", "rables" };
+		Assert.Equal (1, TextFormatter.GetMaxColsForWidth (text, 1));
+	}
 
-		[Fact]
-		public void GetSumMaxCharWidth_List_With_Combining_Runes ()
-		{
-			var text = new List<string> () { "Les Mis", "e\u0328\u0301", "rables" };
-			Assert.Equal (1, TextFormatter.GetSumMaxCharWidth (text, 1, 1));
-		}
+	[Fact]
+	public void GetSumMaxCharWidth_With_Combining_Runes ()
+	{
+		var text = "Les Mise\u0328\u0301rables";
+		Assert.Equal (1, TextFormatter.GetSumMaxCharWidth (text, 1, 1));
+	}
 
-		[Theory]
-		[InlineData (14, 1, TextDirection.LeftRight_TopBottom)]
-		[InlineData (1, 14, TextDirection.TopBottom_LeftRight)]
-		public void CalcRect_With_Combining_Runes (int width, int height, TextDirection textDirection)
-		{
-			var text = "Les Mise\u0328\u0301rables";
-			Assert.Equal (new Rect (0, 0, width, height), TextFormatter.CalcRect (0, 0, text, textDirection));
-		}
+	[Fact]
+	public void GetSumMaxCharWidth_List_With_Combining_Runes ()
+	{
+		var text = new List<string> { "Les Mis", "e\u0328\u0301", "rables" };
+		Assert.Equal (1, TextFormatter.GetSumMaxCharWidth (text, 1, 1));
+	}
+
+	[Theory]
+	[InlineData (14, 1,  TextDirection.LeftRight_TopBottom)]
+	[InlineData (1,  14, TextDirection.TopBottom_LeftRight)]
+	public void CalcRect_With_Combining_Runes (int width, int height, TextDirection textDirection)
+	{
+		var text = "Les Mise\u0328\u0301rables";
+		Assert.Equal (new Rect (0, 0, width, height), TextFormatter.CalcRect (0, 0, text, textDirection));
+	}
 
-		[Theory]
-		[InlineData (14, 1, TextDirection.LeftRight_TopBottom, "Les Misęrables")]
-		[InlineData (1, 14, TextDirection.TopBottom_LeftRight, "L\ne\ns\n \nM\ni\ns\nę\nr\na\nb\nl\ne\ns")]
-		[InlineData (4, 4, TextDirection.TopBottom_LeftRight, @"
+	[Theory]
+	[InlineData (14, 1,  TextDirection.LeftRight_TopBottom, "Les Misęrables")]
+	[InlineData (1,  14, TextDirection.TopBottom_LeftRight, "L\ne\ns\n \nM\ni\ns\nę\nr\na\nb\nl\ne\ns")]
+	[InlineData (4,  4,  TextDirection.TopBottom_LeftRight, @"
 LMre
 eias
 ssb 
  ęl ")]
-		public void Draw_With_Combining_Runes (int width, int height, TextDirection textDirection, string expected)
-		{
-			var driver = new FakeDriver ();
-			driver.Init ();
+	public void Draw_With_Combining_Runes (int width, int height, TextDirection textDirection, string expected)
+	{
+		var driver = new FakeDriver ();
+		driver.Init ();
 
-			var text = "Les Mise\u0328\u0301rables";
+		var text = "Les Mise\u0328\u0301rables";
 
-			var tf = new TextFormatter ();
-			tf.Direction = textDirection;
-			tf.Text = text;
-
-			Assert.True (tf.WordWrap);
-			if (textDirection == TextDirection.LeftRight_TopBottom) {
-				Assert.Equal (new Size (width, height), tf.Size);
-			} else {
-				Assert.Equal (new Size (1, text.GetColumns ()), tf.Size);
-				tf.Size = new Size (width, height);
-			}
-			tf.Draw (new Rect (0, 0, width, height), new Attribute (ColorName.White, ColorName.Black), new Attribute (ColorName.Blue, ColorName.Black), default, true, driver);
-			TestHelpers.AssertDriverContentsWithFrameAre (expected, output, driver);
-
-			driver.End ();
-		}
+		var tf = new TextFormatter ();
+		tf.Direction = textDirection;
+		tf.Text = text;
 
-		[Theory]
-		[InlineData (17, 1, TextDirection.LeftRight_TopBottom, 4, "This is a     Tab")]
-		[InlineData (1, 17, TextDirection.TopBottom_LeftRight, 4, "T\nh\ni\ns\n \ni\ns\n \na\n \n \n \n \n \nT\na\nb")]
-		[InlineData (13, 1, TextDirection.LeftRight_TopBottom, 0, "This is a Tab")]
-		[InlineData (1, 13, TextDirection.TopBottom_LeftRight, 0, "T\nh\ni\ns\n \ni\ns\n \na\n \nT\na\nb")]
-		public void TabWith_PreserveTrailingSpaces_False (int width, int height, TextDirection textDirection, int tabWidth, string expected)
-		{
-			var driver = new FakeDriver ();
-			driver.Init ();
-
-			var text = "This is a \tTab";
-			var tf = new TextFormatter ();
-			tf.Direction = textDirection;
-			tf.TabWidth = tabWidth;
-			tf.Text = text;
-
-			Assert.True (tf.WordWrap);
-			Assert.False (tf.PreserveTrailingSpaces);
+		Assert.True (tf.WordWrap);
+		if (textDirection == TextDirection.LeftRight_TopBottom) {
 			Assert.Equal (new Size (width, height), tf.Size);
-			tf.Draw (new Rect (0, 0, width, height), new Attribute (ColorName.White, ColorName.Black), new Attribute (ColorName.Blue, ColorName.Black), default, true, driver);
-			TestHelpers.AssertDriverContentsWithFrameAre (expected, output, driver);
-
-			driver.End ();
+		} else {
+			Assert.Equal (new Size (1, text.GetColumns ()), tf.Size);
+			tf.Size = new Size (width, height);
 		}
+		tf.Draw (new Rect (0, 0, width, height), new Attribute (ColorName.White, ColorName.Black), new Attribute (ColorName.Blue, ColorName.Black), default, true, driver);
+		TestHelpers.AssertDriverContentsWithFrameAre (expected, output, driver);
 
-		[Theory]
-		[InlineData (17, 1, TextDirection.LeftRight_TopBottom, 4, "This is a     Tab")]
-		[InlineData (1, 17, TextDirection.TopBottom_LeftRight, 4, "T\nh\ni\ns\n \ni\ns\n \na\n \n \n \n \n \nT\na\nb")]
-		[InlineData (13, 1, TextDirection.LeftRight_TopBottom, 0, "This is a Tab")]
-		[InlineData (1, 13, TextDirection.TopBottom_LeftRight, 0, "T\nh\ni\ns\n \ni\ns\n \na\n \nT\na\nb")]
-		public void TabWith_PreserveTrailingSpaces_True (int width, int height, TextDirection textDirection, int tabWidth, string expected)
-		{
-			var driver = new FakeDriver ();
-			driver.Init ();
-
-			var text = "This is a \tTab";
-			var tf = new TextFormatter ();
-			tf.Direction = textDirection;
-			tf.TabWidth = tabWidth;
-			tf.PreserveTrailingSpaces = true;
-			tf.Text = text;
-
-			Assert.True (tf.WordWrap);
-			Assert.Equal (new Size (width, height), tf.Size);
-			tf.Draw (new Rect (0, 0, width, height), new Attribute (ColorName.White, ColorName.Black), new Attribute (ColorName.Blue, ColorName.Black), default, true, driver);
-			TestHelpers.AssertDriverContentsWithFrameAre (expected, output, driver);
+		driver.End ();
+	}
 
-			driver.End ();
-		}
+	[Theory]
+	[InlineData (17, 1,  TextDirection.LeftRight_TopBottom, 4, "This is a     Tab")]
+	[InlineData (1,  17, TextDirection.TopBottom_LeftRight, 4, "T\nh\ni\ns\n \ni\ns\n \na\n \n \n \n \n \nT\na\nb")]
+	[InlineData (13, 1,  TextDirection.LeftRight_TopBottom, 0, "This is a Tab")]
+	[InlineData (1,  13, TextDirection.TopBottom_LeftRight, 0, "T\nh\ni\ns\n \ni\ns\n \na\n \nT\na\nb")]
+	public void TabWith_PreserveTrailingSpaces_False (int width, int height, TextDirection textDirection, int tabWidth, string expected)
+	{
+		var driver = new FakeDriver ();
+		driver.Init ();
+
+		var text = "This is a \tTab";
+		var tf = new TextFormatter ();
+		tf.Direction = textDirection;
+		tf.TabWidth = tabWidth;
+		tf.Text = text;
+
+		Assert.True (tf.WordWrap);
+		Assert.False (tf.PreserveTrailingSpaces);
+		Assert.Equal (new Size (width, height), tf.Size);
+		tf.Draw (new Rect (0, 0, width, height), new Attribute (ColorName.White, ColorName.Black), new Attribute (ColorName.Blue, ColorName.Black), default, true, driver);
+		TestHelpers.AssertDriverContentsWithFrameAre (expected, output, driver);
+
+		driver.End ();
+	}
 
-		[Theory]
-		[InlineData (17, 1, TextDirection.LeftRight_TopBottom, 4, "This is a     Tab")]
-		[InlineData (1, 17, TextDirection.TopBottom_LeftRight, 4, "T\nh\ni\ns\n \ni\ns\n \na\n \n \n \n \n \nT\na\nb")]
-		[InlineData (13, 1, TextDirection.LeftRight_TopBottom, 0, "This is a Tab")]
-		[InlineData (1, 13, TextDirection.TopBottom_LeftRight, 0, "T\nh\ni\ns\n \ni\ns\n \na\n \nT\na\nb")]
-		public void TabWith_WordWrap_True (int width, int height, TextDirection textDirection, int tabWidth, string expected)
-		{
-			var driver = new FakeDriver ();
-			driver.Init ();
-
-			var text = "This is a \tTab";
-			var tf = new TextFormatter ();
-			tf.Direction = textDirection;
-			tf.TabWidth = tabWidth;
-			tf.WordWrap = true;
-			tf.Text = text;
-
-			Assert.False (tf.PreserveTrailingSpaces);
-			Assert.Equal (new Size (width, height), tf.Size);
-			tf.Draw (new Rect (0, 0, width, height), new Attribute (ColorName.White, ColorName.Black), new Attribute (ColorName.Blue, ColorName.Black), default, true, driver);
-			TestHelpers.AssertDriverContentsWithFrameAre (expected, output, driver);
+	[Theory]
+	[InlineData (17, 1,  TextDirection.LeftRight_TopBottom, 4, "This is a     Tab")]
+	[InlineData (1,  17, TextDirection.TopBottom_LeftRight, 4, "T\nh\ni\ns\n \ni\ns\n \na\n \n \n \n \n \nT\na\nb")]
+	[InlineData (13, 1,  TextDirection.LeftRight_TopBottom, 0, "This is a Tab")]
+	[InlineData (1,  13, TextDirection.TopBottom_LeftRight, 0, "T\nh\ni\ns\n \ni\ns\n \na\n \nT\na\nb")]
+	public void TabWith_PreserveTrailingSpaces_True (int width, int height, TextDirection textDirection, int tabWidth, string expected)
+	{
+		var driver = new FakeDriver ();
+		driver.Init ();
+
+		var text = "This is a \tTab";
+		var tf = new TextFormatter ();
+		tf.Direction = textDirection;
+		tf.TabWidth = tabWidth;
+		tf.PreserveTrailingSpaces = true;
+		tf.Text = text;
+
+		Assert.True (tf.WordWrap);
+		Assert.Equal (new Size (width, height), tf.Size);
+		tf.Draw (new Rect (0, 0, width, height), new Attribute (ColorName.White, ColorName.Black), new Attribute (ColorName.Blue, ColorName.Black), default, true, driver);
+		TestHelpers.AssertDriverContentsWithFrameAre (expected, output, driver);
+
+		driver.End ();
+	}
 
-			driver.End ();
-		}
+	[Theory]
+	[InlineData (17, 1,  TextDirection.LeftRight_TopBottom, 4, "This is a     Tab")]
+	[InlineData (1,  17, TextDirection.TopBottom_LeftRight, 4, "T\nh\ni\ns\n \ni\ns\n \na\n \n \n \n \n \nT\na\nb")]
+	[InlineData (13, 1,  TextDirection.LeftRight_TopBottom, 0, "This is a Tab")]
+	[InlineData (1,  13, TextDirection.TopBottom_LeftRight, 0, "T\nh\ni\ns\n \ni\ns\n \na\n \nT\na\nb")]
+	public void TabWith_WordWrap_True (int width, int height, TextDirection textDirection, int tabWidth, string expected)
+	{
+		var driver = new FakeDriver ();
+		driver.Init ();
+
+		var text = "This is a \tTab";
+		var tf = new TextFormatter ();
+		tf.Direction = textDirection;
+		tf.TabWidth = tabWidth;
+		tf.WordWrap = true;
+		tf.Text = text;
+
+		Assert.False (tf.PreserveTrailingSpaces);
+		Assert.Equal (new Size (width, height), tf.Size);
+		tf.Draw (new Rect (0, 0, width, height), new Attribute (ColorName.White, ColorName.Black), new Attribute (ColorName.Blue, ColorName.Black), default, true, driver);
+		TestHelpers.AssertDriverContentsWithFrameAre (expected, output, driver);
+
+		driver.End ();
 	}
 }

+ 10 - 16
UnitTests/View/Layout/DimTests.cs

@@ -559,8 +559,6 @@ public class DimTests {
 		t.BeginInit ();
 		t.EndInit ();
 
-		// BUGBUG: v2 - f references t here; t is f's super-superview. This is supported!
-		// BUGBUG: v2 - f references v2 here; v2 is f's subview. This is not supported!
 		f.Width = Dim.Width (t) - Dim.Width (v2);    // 80 - 74 = 6
 		f.Height = Dim.Height (t) - Dim.Height (v2); // 25 - 19 = 6
 
@@ -569,13 +567,12 @@ public class DimTests {
 		Assert.Equal (25, t.Frame.Height);
 		Assert.Equal (78, w.Frame.Width);
 		Assert.Equal (23, w.Frame.Height);
-		// BUGBUG: v2 - this no longer works - see above
-		//Assert.Equal (6, f.Frame.Width);
-		//Assert.Equal (6, f.Frame.Height);
-		//Assert.Equal (76, v1.Frame.Width);
-		//Assert.Equal (21, v1.Frame.Height);
-		//Assert.Equal (74, v2.Frame.Width);
-		//Assert.Equal (19, v2.Frame.Height);
+		Assert.Equal (6, f.Frame.Width);
+		Assert.Equal (6, f.Frame.Height);
+		Assert.Equal (76, v1.Frame.Width);
+		Assert.Equal (21, v1.Frame.Height);
+		Assert.Equal (74, v2.Frame.Width);
+		Assert.Equal (19, v2.Frame.Height);
 		t.Dispose ();
 	}
 
@@ -632,7 +629,6 @@ public class DimTests {
 	{
 		var t = new View { Width = 80, Height = 50 };
 
-		// BUGBUG: v2 - super should not reference it's superview (t)
 		var super = new View {
 			Width = Dim.Width (t) - 2,
 			Height = Dim.Height (t) - 2
@@ -720,19 +716,17 @@ public class DimTests {
 		var count = 20;
 		var listLabels = new List<Label> ();
 
-		for (var i = 0; i < count; i++) {
+		for (int i = 0; i < count; i++) {
 			field.Text = $"Label {i}";
 			var label = new Label (field.Text) { X = 0, Y = view.Bounds.Height, Width = 20 };
 			view.Add (label);
 			Assert.Equal ($"Label {i}", label.Text);
-			// BUGBUG: Bogus test; views have not been initialized yet
-			//Assert.Equal ($"Absolute({i})", label.Y.ToString ());
+			Assert.Equal ($"Absolute({i})", label.Y.ToString ());
 			listLabels.Add (label);
 
-			// BUGBUG: Bogus test; views have not been initialized yet
-			//Assert.Equal ($"Absolute({i})", view.Height.ToString ());
+			Assert.Equal ($"Absolute({i})", view.Height.ToString ());
 			view.Height += 1;
-			//Assert.Equal ($"Absolute({i + 1})", view.Height.ToString ());
+			Assert.Equal ($"Absolute({i + 1})", view.Height.ToString ());
 		}
 
 		field.KeyDown += (s, k) => {

+ 60 - 117
UnitTests/View/Layout/LayoutTests.cs

@@ -190,7 +190,8 @@ public class LayoutTests {
 				      };
 
 		var v = new View () {
-					    Width = Dim.Fill ()
+					    Width          = Dim.Fill (),
+					    ValidatePosDim = true
 				    };
 		top.Add (v);
 		top.BeginInit ();
@@ -198,19 +199,23 @@ public class LayoutTests {
 		top.LayoutSubviews ();
 
 		Assert.False (v.AutoSize);
-		Assert.True (v.TrySetWidth (0, out _));
+		Assert.False (v.TrySetWidth (0, out _));
+		Assert.True (v.Width is Dim.DimFill);
 		Assert.Equal (80, v.Frame.Width);
 
 		v.Width = Dim.Fill (1);
 		top.LayoutSubviews ();
 
-		Assert.True (v.TrySetWidth (0, out _));
+		Assert.False (v.TrySetWidth (0, out _));
+		Assert.True (v.Width is Dim.DimFill);
 		Assert.Equal (79, v.Frame.Width);
 
 		v.AutoSize = true;
 		top.LayoutSubviews ();
 
 		Assert.True (v.TrySetWidth (0, out _));
+		Assert.True (v.Width is Dim.DimAbsolute);
+		Assert.Equal (79, v.Frame.Width);
 		top.Dispose ();
 	}
 
@@ -224,7 +229,8 @@ public class LayoutTests {
 				      };
 
 		var v = new View () {
-					    Height = Dim.Fill ()
+					    Height         = Dim.Fill (),
+					    ValidatePosDim = true
 				    };
 		top.Add (v);
 		top.BeginInit ();
@@ -232,19 +238,22 @@ public class LayoutTests {
 		top.LayoutSubviews ();
 
 		Assert.False (v.AutoSize);
-		Assert.True (v.TrySetHeight (0, out _));
+		Assert.False (v.TrySetHeight (0, out _));
 		Assert.Equal (20, v.Frame.Height);
 
 		v.Height = Dim.Fill (1);
 		top.LayoutSubviews ();
 
-		Assert.True (v.TrySetHeight (0, out _));
+		Assert.False (v.TrySetHeight (0, out _));
+		Assert.True (v.Height is Dim.DimFill);
 		Assert.Equal (19, v.Frame.Height);
 
 		v.AutoSize = true;
 		top.LayoutSubviews ();
 
 		Assert.True (v.TrySetHeight (0, out _));
+		Assert.True (v.Height is Dim.DimAbsolute);
+		Assert.Equal (19, v.Frame.Height);
 		top.Dispose ();
 	}
 
@@ -267,117 +276,6 @@ public class LayoutTests {
 		Assert.Equal (5,  view.Frame.Height);
 	}
 
-	[Fact] [AutoInitShutdown]
-	public void Width_Height_SetMinWidthHeight_Narrow_Wide_Runes ()
-	{
-		string text = $"First line{Environment.NewLine}Second line";
-		var horizontalView = new View () {
-							 Width  = 20,
-							 Height = 1,
-							 Text   = text
-						 };
-		var verticalView = new View () {
-						       Y             = 3,
-						       Height        = 20,
-						       Width         = 1,
-						       Text          = text,
-						       TextDirection = TextDirection.TopBottom_LeftRight
-					       };
-		var win = new Window () {
-						Width  = Dim.Fill (),
-						Height = Dim.Fill (),
-						Text   = "Window"
-					};
-		win.Add (horizontalView, verticalView);
-		Application.Top.Add (win);
-		var rs = Application.Begin (Application.Top);
-		((FakeDriver)Application.Driver).SetBufferSize (32, 32);
-
-		Assert.False (horizontalView.AutoSize);
-		Assert.False (verticalView.AutoSize);
-		Assert.Equal (new Rect (0, 0, 20, 1),  horizontalView.Frame);
-		Assert.Equal (new Rect (0, 3, 1,  20), verticalView.Frame);
-		string expected = @"
-┌──────────────────────────────┐
-│First line Second li          │
-│                              │
-│                              │
-│F                             │
-│i                             │
-│r                             │
-│s                             │
-│t                             │
-│                              │
-│l                             │
-│i                             │
-│n                             │
-│e                             │
-│                              │
-│S                             │
-│e                             │
-│c                             │
-│o                             │
-│n                             │
-│d                             │
-│                              │
-│l                             │
-│i                             │
-│                              │
-│                              │
-│                              │
-│                              │
-│                              │
-│                              │
-│                              │
-└──────────────────────────────┘
-";
-
-		var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
-		Assert.Equal (new Rect (0, 0, 32, 32), pos);
-
-		verticalView.Text = $"最初の行{Environment.NewLine}二行目";
-		Application.Top.Draw ();
-		Assert.Equal (new Rect (0, 3, 2, 20), verticalView.Frame);
-		expected = @"
-┌──────────────────────────────┐
-│First line Second li          │
-│                              │
-│                              │
-│最                            │
-│初                            │
-│の                            │
-│行                            │
-│                              │
-│二                            │
-│行                            │
-│目                            │
-│                              │
-│                              │
-│                              │
-│                              │
-│                              │
-│                              │
-│                              │
-│                              │
-│                              │
-│                              │
-│                              │
-│                              │
-│                              │
-│                              │
-│                              │
-│                              │
-│                              │
-│                              │
-│                              │
-└──────────────────────────────┘
-";
-
-		pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
-		Assert.Equal (new Rect (0, 0, 32, 32), pos);
-		Application.End (rs);
-	}
-
 
 	// Tested in AbsoluteLayoutTests.cs
 	// public void Pos_Dim_Are_Null_If_Not_Initialized_On_Constructor_IsAdded_False ()
@@ -777,6 +675,51 @@ public class LayoutTests {
 		Application.Shutdown ();
 	}
 
+	[Fact]
+	public void PosCombine_Refs_SuperView_Throws ()
+	{
+		Application.Init (new FakeDriver ());
+
+		var w = new Window () {
+					      X = Pos.Left (Application.Top) + 2,
+					      Y = Pos.Top (Application.Top) + 2
+				      };
+		var f = new FrameView ();
+		var v1 = new View () {
+					     X = Pos.Left (w) + 2,
+					     Y = Pos.Top (w) + 2
+				     };
+		var v2 = new View () {
+					     X = Pos.Left (v1) + 2,
+					     Y = Pos.Top (v1) + 2
+				     };
+
+		f.Add (v1, v2);
+		w.Add (f);
+		Application.Top.Add (w);
+
+		f.X = Pos.X (Application.Top) + Pos.X (v2) - Pos.X (v1);
+		f.Y = Pos.Y (Application.Top) + Pos.Y (v2) - Pos.Y (v1);
+
+		Application.Top.LayoutComplete += (s, e) => {
+			Assert.Equal (0, Application.Top.Frame.X);
+			Assert.Equal (0, Application.Top.Frame.Y);
+			Assert.Equal (2, w.Frame.X);
+			Assert.Equal (2, w.Frame.Y);
+			Assert.Equal (2, f.Frame.X);
+			Assert.Equal (2, f.Frame.Y);
+			Assert.Equal (4, v1.Frame.X);
+			Assert.Equal (4, v1.Frame.Y);
+			Assert.Equal (6, v2.Frame.X);
+			Assert.Equal (6, v2.Frame.Y);
+		};
+
+		Application.Iteration += (s, a) => Application.RequestStop ();
+
+		Assert.Throws<InvalidOperationException> (() => Application.Run ());
+		Application.Shutdown ();
+	}
+	
 	// Was named AutoSize_Pos_Validation_Do_Not_Throws_If_NewValue_Is_PosAbsolute_And_OldValue_Is_Another_Type_After_Sets_To_LayoutStyle_Absolute ()
 	// but doesn't actually have anything to do with AutoSize.
 	[Fact]

文件差異過大導致無法顯示
+ 257 - 263
UnitTests/View/Text/AutoSizeTextTests.cs


+ 112 - 1
UnitTests/View/Text/TextTests.cs

@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
 using Xunit;
 using Xunit.Abstractions;
 
@@ -538,4 +539,114 @@ public class TextTests {
 		Application.End (rs);
 	}
 
+	[Fact]
+	[AutoInitShutdown]
+	public void AutoSize_False_Width_Height_SetMinWidthHeight_Narrow_Wide_Runes ()
+	{
+		string text = $"First line{Environment.NewLine}Second line";
+		var horizontalView = new View () {
+			Width = 20,
+			Height = 1,
+			Text = text
+		};
+		var verticalView = new View () {
+			Y = 3,
+			Height = 20,
+			Width = 1,
+			Text = text,
+			TextDirection = TextDirection.TopBottom_LeftRight
+		};
+		var win = new Window () {
+			Width = Dim.Fill (),
+			Height = Dim.Fill (),
+			Text = "Window"
+		};
+		win.Add (horizontalView, verticalView);
+		Application.Top.Add (win);
+		var rs = Application.Begin (Application.Top);
+		((FakeDriver)Application.Driver).SetBufferSize (32, 32);
+
+		Assert.False (horizontalView.AutoSize);
+		Assert.False (verticalView.AutoSize);
+		Assert.Equal (new Rect (0, 0, 20, 1), horizontalView.Frame);
+		Assert.Equal (new Rect (0, 3, 1, 20), verticalView.Frame);
+		string expected = @"
+┌──────────────────────────────┐
+│First line Second li          │
+│                              │
+│                              │
+│F                             │
+│i                             │
+│r                             │
+│s                             │
+│t                             │
+│                              │
+│l                             │
+│i                             │
+│n                             │
+│e                             │
+│                              │
+│S                             │
+│e                             │
+│c                             │
+│o                             │
+│n                             │
+│d                             │
+│                              │
+│l                             │
+│i                             │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+└──────────────────────────────┘
+";
+
+		var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+
+		verticalView.Text = $"最初の行{Environment.NewLine}二行目";
+		Application.Top.Draw ();
+		// BUGBUG: #3127 - If AutoSize == false, setting text should NOT change the size of the view.
+		Assert.Equal (new Rect (0, 3, 2, 20), verticalView.Frame);
+		expected = @"
+┌──────────────────────────────┐
+│First line Second li          │
+│                              │
+│                              │
+│最                            │
+│初                            │
+│の                            │
+│行                            │
+│                              │
+│二                            │
+│行                            │
+│目                            │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+└──────────────────────────────┘
+";
+
+		pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+		Application.End (rs);
+	}
 }

文件差異過大導致無法顯示
+ 623 - 626
UnitTests/View/ViewTests.cs


+ 17 - 19
UnitTests/Views/AppendAutocompleteTests.cs

@@ -1,12 +1,10 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
 using Xunit;
 using Xunit.Abstractions;
 
-namespace Terminal.Gui.TextTests; 
+namespace Terminal.Gui.TextTests;
 
 public class AppendAutocompleteTests {
 	readonly ITestOutputHelper output;
@@ -26,7 +24,7 @@ public class AppendAutocompleteTests {
 		tf.PositionCursor ();
 		TestHelpers.AssertDriverContentsAre ("", output);
 
-		tf.NewKeyDownEvent (new ('f'));
+		tf.NewKeyDownEvent (new Key ('f'));
 
 		tf.Draw ();
 		tf.PositionCursor ();
@@ -93,7 +91,7 @@ public class AppendAutocompleteTests {
 		// When cancelling autocomplete
 		Application.Driver.SendKeys ('e', ConsoleKey.Escape, false, false, false);
 
-		// Suggestion should disapear
+		// Suggestion should disappear
 		tf.Draw ();
 		TestHelpers.AssertDriverContentsAre ("f", output);
 		Assert.Equal ("f", tf.Text);
@@ -107,7 +105,7 @@ public class AppendAutocompleteTests {
 	}
 
 	[Fact] [AutoInitShutdown]
-	public void TestAutoAppend_AfterCloseKey_ReapearsOnLetter ()
+	public void TestAutoAppend_AfterCloseKey_ReappearsOnLetter ()
 	{
 		var tf = GetTextFieldsInViewSuggesting ("fish");
 
@@ -119,25 +117,25 @@ public class AppendAutocompleteTests {
 		Assert.Equal ("f", tf.Text);
 
 		// When cancelling autocomplete
-		Application.Driver.SendKeys ('e', ConsoleKey.Escape, false, false, false);
+		Application.Driver.SendKeys ('\0', ConsoleKey.Escape, false, false, false);
 
-		// Suggestion should disapear
+		// Suggestion should disappear
 		tf.Draw ();
 		TestHelpers.AssertDriverContentsAre ("f", output);
 		Assert.Equal ("f", tf.Text);
 
-		// Should reapear when you press next letter
+		// Should reappear when you press next letter
 		Application.Driver.SendKeys ('i', ConsoleKey.I, false, false, false);
 		tf.Draw ();
-		// BUGBUG: v2 - I broke this test and don't have time to figure out why. @tznind - help!
-		//TestHelpers.AssertDriverContentsAre ("fish", output);
+		tf.PositionCursor ();
+		TestHelpers.AssertDriverContentsAre ("fish", output);
 		Assert.Equal ("fi", tf.Text);
 	}
 
 	[Theory] [AutoInitShutdown]
 	[InlineData ("ffffffffffffffffffffffffff", "ffffffffff")]
-	[InlineData ("f234567890", "f234567890")]
-	[InlineData ("fisérables", "fisérables")]
+	[InlineData ("f234567890",                 "f234567890")]
+	[InlineData ("fisérables",                 "fisérables")]
 	public void TestAutoAppendRendering_ShouldNotOverspill (string overspillUsing, string expectRender)
 	{
 		var tf = GetTextFieldsInViewSuggesting (overspillUsing);
@@ -212,7 +210,7 @@ public class AppendAutocompleteTests {
 		Assert.Equal ("f", tf.Text);
 
 		// add a space then go back 1
-		Application.Driver.SendKeys (' ', ConsoleKey.Spacebar, false, false, false);
+		Application.Driver.SendKeys (' ', ConsoleKey.Spacebar,  false, false, false);
 		Application.Driver.SendKeys ('<', ConsoleKey.LeftArrow, false, false, false);
 
 		tf.Draw ();
@@ -239,12 +237,12 @@ public class AppendAutocompleteTests {
 	TextField GetTextFieldsInView ()
 	{
 		var tf = new TextField {
-			Width = 10
-		};
+					       Width = 10
+				       };
 		var tf2 = new TextField {
-			Y = 1,
-			Width = 10
-		};
+						Y     = 1,
+						Width = 10
+					};
 
 		var top = Application.Top;
 		top.Add (tf);

+ 44 - 45
UnitTests/Views/CheckBoxTests.cs

@@ -139,53 +139,52 @@ namespace Terminal.Gui.ViewsTests {
 			checkBox.Checked = true;
 			Assert.Equal ($"{CM.Glyphs.Checked} Check this out 你", checkBox.TextFormatter.Text);
 
-			//checkBox.AutoSize = false;
+			checkBox.AutoSize = false;
+			checkBox.AutoSize = false;
 			// It isn't auto-size so the height is guaranteed by the SetMinWidthHeight
-			//checkBox.Text = "Check this out 你 changed";
-			//Application.RunMainLoopIteration (ref runstate, ref first);
+			checkBox.Text = "Check this out 你 changed";
+			var firstIteration = false;
+			Application.RunIteration (ref runstate, ref firstIteration);
 			// BUGBUG - v2 - Autosize is busted; disabling tests for now
-//			Assert.Equal (new Rect (1, 1, 19, 1), checkBox.Frame);
-//			expected = @"
-//┌┤Test Demo 你├──────────────┐
-//│                            │
-//│ √ Check this out 你        │
-//│                            │
-//└────────────────────────────┘
-//";
-
-//			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
-//			Assert.Equal (new Rect (0, 0, 30, 5), pos);
-
-//			checkBox.Width = 19;
-//			// It isn't auto-size so the height is guaranteed by the SetMinWidthHeight
-//			checkBox.Text = "Check this out 你 changed";
-//			Application.RunMainLoopIteration (ref runstate, ref first);
-//			Assert.False (checkBox.AutoSize);
-//			Assert.Equal (new Rect (1, 1, 19, 1), checkBox.Frame);
-//			expected = @"
-//┌┤Test Demo 你├──────────────┐
-//│                            │
-//│ √ Check this out 你        │
-//│                            │
-//└────────────────────────────┘
-//";
-
-//			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
-//			Assert.Equal (new Rect (0, 0, 30, 5), pos);
-
-//			checkBox.AutoSize = true;
-//			Application.RunMainLoopIteration (ref runstate, ref first);
-//			Assert.Equal (new Rect (1, 1, 27, 1), checkBox.Frame);
-//			expected = @"
-//┌┤Test Demo 你├──────────────┐
-//│                            │
-//│ √ Check this out 你 changed│
-//│                            │
-//└────────────────────────────┘
-//";
-
-//			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
-//			Assert.Equal (new Rect (0, 0, 30, 5), pos);
+			Assert.Equal (new Rect (1, 1, 19, 1), checkBox.Frame);
+			var expected = @"
+┌┤Test Demo 你├──────────────┐
+│                            │
+│ ☑ Check this out 你        │
+│                            │
+└────────────────────────────┘";
+
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 30, 5), pos);
+
+			checkBox.Width = 19;
+			// It isn't auto-size so the height is guaranteed by the SetMinWidthHeight
+			checkBox.Text = "Check this out 你 changed";
+			Application.RunIteration (ref runstate, ref firstIteration);
+			Assert.False (checkBox.AutoSize);
+			Assert.Equal (new Rect (1, 1, 19, 1), checkBox.Frame);
+			expected = @"
+┌┤Test Demo 你├──────────────┐
+│                            │
+│ ☑ Check this out 你        │
+│                            │
+└────────────────────────────┘";
+
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 30, 5), pos);
+
+			checkBox.AutoSize = true;
+			Application.RunIteration (ref runstate, ref firstIteration);
+			Assert.Equal (new Rect (1, 1, 27, 1), checkBox.Frame);
+			expected = @"
+┌┤Test Demo 你├──────────────┐
+│                            │
+│ ☑ Check this out 你 changed│
+│                            │
+└────────────────────────────┘";
+
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 30, 5), pos);
 		}
 
 		[Fact, AutoInitShutdown]

文件差異過大導致無法顯示
+ 440 - 435
UnitTests/Views/LabelTests.cs


文件差異過大導致無法顯示
+ 661 - 717
UnitTests/Views/ScrollBarViewTests.cs


+ 217 - 218
UnitTests/Views/ScrollViewTests.cs

@@ -370,236 +370,235 @@ namespace Terminal.Gui.ViewsTests {
 ", output);
 		}
 
-		// BUGBUG: v2 - I can't figure out what this test is trying to test and it fails in weird ways
-		// Disabling for now
-		//[Fact, AutoInitShutdown]
-		//public void Frame_And_Labels_Does_Not_Overspill_ScrollView ()
-		//{
-		//	var sv = new ScrollView {
-		//		X = 3,
-		//		Y = 3,
-		//		Width = 10,
-		//		Height = 10,
-		//		ContentSize = new Size (50, 50)
-		//	};
-		//	for (int i = 0; i < 8; i++) {
-		//		sv.Add (new CustomButton ("█", $"Button {i}", 20, 3) { Y = i * 3 });
-		//	}
-		//	Application.Top.Add (sv);
-		//	Application.Begin (Application.Top);
-
-		//	TestHelpers.AssertDriverContentsWithFrameAre (@"
-		// █████████▲
-		// ██████But┬
-		// █████████┴
-		// ┌────────░
-		// │     But░
-		// └────────░
-		// ┌────────░
-		// │     But░
-		// └────────▼
-		// ◄├┤░░░░░► ", output);
-
-		//	sv.ContentOffset = new Point (5, 5);
-		//	sv.LayoutSubviews ();
-		//	Application.Refresh ();
-		//	TestHelpers.AssertDriverContentsWithFrameAre (@"
-		// ─────────▲
-		// ─────────┬
-		//  Button 2│
-		// ─────────┴
-		// ─────────░
-		//  Button 3░
-		// ─────────░
-		// ─────────░
-		//  Button 4▼
-		// ◄├─┤░░░░► ", output);
-		//}
-
-		//private class CustomButton : FrameView {
-		//	private Label labelFill;
-		//	private Label labelText;
-
-		//	public CustomButton (string fill, string text, int width, int height) : base ()
-		//	{
-		//		Width = width;
-		//		Height = height;
-		//		labelFill = new Label () { AutoSize = false, X = Pos.Center (), Y = Pos.Center (), Width = Dim.Fill (), Height = Dim.Fill (), Visible = false };
-		//		labelFill.LayoutComplete += (s, e) => {
-		//			var fillText = new System.Text.StringBuilder ();
-		//			for (int i = 0; i < labelFill.Bounds.Height; i++) {
-		//				if (i > 0) {
-		//					fillText.AppendLine ("");
-		//				}
-		//				for (int j = 0; j < labelFill.Bounds.Width; j++) {
-		//					fillText.Append (fill);
-		//				}
-		//			}
-		//			labelFill.Text = fillText;
-		//		};
-
-		//		labelText = new Label (text) { X = Pos.Center (), Y = Pos.Center () };
-		//		Add (labelFill, labelText);
-		//		CanFocus = true;
-		//	}
-
-		//	public override bool OnEnter (View view)
-		//	{
-		//		Border.BorderStyle = BorderStyle.None;
-		//		Border.DrawMarginFrame = false;
-		//		labelFill.Visible = true;
-		//		view = this;
-		//		return base.OnEnter (view);
-		//	}
-
-		//	public override bool OnLeave (View view)
-		//	{
-		//		Border.BorderStyle = BorderStyle.Single;
-		//		Border.DrawMarginFrame = true;
-		//		labelFill.Visible = false;
-		//		if (view == null)
-		//			view = this;
-		//		return base.OnLeave (view);
-		//	}
-		//}
-
-		// BUGBUG: Broke this test with #2483 - @bdisp I need your help figuring out why
-//		[Fact, AutoInitShutdown]
-//		public void Clear_Window_Inside_ScrollView ()
-//		{
-//			var topLabel = new Label ("At 15,0") { X = 15 };
-//			var sv = new ScrollView {
-//				X = 3,
-//				Y = 3,
-//				Width = 10,
-//				Height = 10,
-//				ContentSize = new Size (23, 23),
-//				KeepContentAlwaysInViewport = false
-//			};
-//			var bottomLabel = new Label ("At 15,15") { X = 15, Y = 15 };
-//			Application.Top.Add (topLabel, sv, bottomLabel);
-//			Application.Begin (Application.Top);
-
-//			TestHelpers.AssertDriverContentsWithFrameAre (@"
-//               At 15,0 
+		// There still have an issue with lower right corner of the scroll view
+		[Fact, AutoInitShutdown]
+		public void Frame_And_Labels_Does_Not_Overspill_ScrollView ()
+		{
+			var sv = new ScrollView {
+				X = 3,
+				Y = 3,
+				Width = 10,
+				Height = 10,
+				ContentSize = new Size (50, 50)
+			};
+			for (int i = 0; i < 8; i++) {
+				sv.Add (new CustomButton ("█", $"Button {i}", 20, 3) { Y = i * 3 });
+			}
+			Application.Top.Add (sv);
+			Application.Begin (Application.Top);
+
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+   █████████▲
+   ██████But┬
+   █████████┴
+   ┌────────░
+   │     But░
+   └────────░
+   ┌────────░
+   │     But░
+   └────────▼
+   ◄├┤░░░░░►─", output);
+
+			sv.ContentOffset = new Point (5, 5);
+			sv.LayoutSubviews ();
+			Application.Refresh ();
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+   ─────────▲
+   ─────────┬
+    Button 2│
+   ─────────┴
+   ─────────░
+    Button 3░
+   ─────────░
+   ─────────░
+    Button 4▼
+   ◄├─┤░░░░►─", output);
+		}
+
+		private class CustomButton : FrameView {
+			private Label labelFill;
+			private Label labelText;
+
+			public CustomButton (string fill, string text, int width, int height) : base ()
+			{
+				Width = width;
+				Height = height;
+				//labelFill = new Label () { AutoSize = false, X = Pos.Center (), Y = Pos.Center (), Width = Dim.Fill (), Height = Dim.Fill (), Visible = false };
+				labelFill = new Label () { AutoSize = false, Width = Dim.Fill (), Height = Dim.Fill (), Visible = false };
+				labelFill.LayoutComplete += (s, e) => {
+					var fillText = new System.Text.StringBuilder ();
+					for (int i = 0; i < labelFill.Bounds.Height; i++) {
+						if (i > 0) {
+							fillText.AppendLine ("");
+						}
+						for (int j = 0; j < labelFill.Bounds.Width; j++) {
+							fillText.Append (fill);
+						}
+					}
+					labelFill.Text = fillText.ToString ();
+				};
+
+				labelText = new Label (text) { X = Pos.Center (), Y = Pos.Center () };
+				Add (labelFill, labelText);
+				CanFocus = true;
+			}
+
+			public override bool OnEnter (View view)
+			{
+				Border.BorderStyle = LineStyle.None;
+				Border.Thickness = new Thickness (0);
+				labelFill.Visible = true;
+				view = this;
+				return base.OnEnter (view);
+			}
+
+			public override bool OnLeave (View view)
+			{
+				Border.BorderStyle = LineStyle.Single;
+				Border.Thickness = new Thickness (1);
+				labelFill.Visible = false;
+				if (view == null)
+					view = this;
+				return base.OnLeave (view);
+			}
+		}
+		// There are still issue with the lower right corner of the scroll view
+		[Fact, AutoInitShutdown]
+		public void Clear_Window_Inside_ScrollView ()
+		{
+			var topLabel = new Label ("At 15,0") { X = 15 };
+			var sv = new ScrollView {
+				X = 3,
+				Y = 3,
+				Width = 10,
+				Height = 10,
+				ContentSize = new Size (23, 23),
+				KeepContentAlwaysInViewport = false
+			};
+			var bottomLabel = new Label ("At 15,15") { X = 15, Y = 15 };
+			Application.Top.Add (topLabel, sv, bottomLabel);
+			Application.Begin (Application.Top);
+
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+               At 15,0 
                        
                        
-//
-//
-//
-//
-//
-//
-//
-//
-//
-//   ◄├┤░░░░░►           
+            ▲          
+            ┬          
+            ┴          
+            ░          
+            ░          
+            ░          
+            ░          
+            ░          
+            ▼          
+   ◄├┤░░░░░►           
                        
                        
-//               At 15,15", output);
-
-//			var attributes = new Attribute [] {
-//				Colors.TopLevel.Normal,
-//				Colors.TopLevel.Focus,
-//				Colors.Base.Normal
-//			};
-
-//			TestHelpers.AssertDriverColorsAre (@"
-//00000000000000000000000
-//00000000000000000000000
-//00000000000000000000000
-//00000000000010000000000
-//00000000000010000000000
-//00000000000010000000000
-//00000000000010000000000
-//00000000000010000000000
-//00000000000010000000000
-//00000000000010000000000
-//00000000000010000000000
-//00000000000010000000000
-//00011111111110000000000
-//00000000000000000000000
-//00000000000000000000000
-//00000000000000000000000", attributes);
-
-//			sv.Add (new Window { X = 3, Y = 3, Width = 20, Height = 20 });
-
-//			Application.Refresh ();
-//			TestHelpers.AssertDriverContentsWithFrameAre (@"
-//               At 15,0 
+               At 15,15", output);
+
+			var attributes = new Attribute [] {
+						Colors.TopLevel.Normal,
+						Colors.TopLevel.Focus,
+						Colors.Base.Normal
+					};
+
+			TestHelpers.AssertDriverColorsAre (@"
+00000000000000000000000
+00000000000000000000000
+00000000000000000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00011111111100000000000
+00000000000000000000000
+00000000000000000000000
+00000000000000000000000", null, attributes);
+
+			sv.Add (new Window { X = 3, Y = 3, Width = 20, Height = 20 });
+
+			Application.Refresh ();
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+               At 15,0 
                        
                        
-//
-//
-//
-//      ┌─────░          
-//      │     ░          
-//      │     ░          
-//      │     ░          
-//      │     ░          
-//      │     ▼          
-//   ◄├┤░░░░░►           
+            ▲          
+            ┬          
+            ┴          
+      ┌─────░          
+      │     ░          
+      │     ░          
+      │     ░          
+      │     ░          
+      │     ▼          
+   ◄├┤░░░░░►           
                        
                        
-//               At 15,15", output);
-
-//			TestHelpers.AssertDriverColorsAre (@"
-//00000000000000000000000
-//00000000000000000000000
-//00000000000000000000000
-//00000000000010000000000
-//00000000000010000000000
-//00000000000010000000000
-//00000022222210000000000
-//00000022222210000000000
-//00000022222210000000000
-//00000022222210000000000
-//00000022222210000000000
-//00000022222210000000000
-//00011111111110000000000
-//00000000000000000000000
-//00000000000000000000000
-//00000000000000000000000", attributes);
-
-//			sv.ContentOffset = new Point (20, 20);
-//			Application.Refresh ();
-//			TestHelpers.AssertDriverContentsWithFrameAre (@"
-//               At 15,0 
+               At 15,15", output);
+
+			TestHelpers.AssertDriverColorsAre (@"
+00000000000000000000000
+00000000000000000000000
+00000000000000000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00000022222210000000000
+00000022222210000000000
+00000022222210000000000
+00000022222210000000000
+00000022222210000000000
+00000022222210000000000
+00011111111120000000000
+00000000000000000000000
+00000000000000000000000
+00000000000000000000000", null, attributes);
+
+			sv.ContentOffset = new Point (20, 20);
+			Application.Refresh ();
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+               At 15,0 
                        
                        
-//     │      ▲          
-//     │      ░          
-//   ──┘      ░          
-//
-//
-//
-//
-//
-//
-//   ◄░░░░├─┤►           
+     │      ▲          
+     │      ░          
+   ──┘      ░          
+            ░          
+            ░          
+            ┬          
+            │          
+            ┴          
+            ▼          
+   ◄░░░░├─┤►           
                        
                        
-//               At 15,15", output);
-
-//			TestHelpers.AssertDriverColorsAre (@"
-//00000000000000000000000
-//00000000000000000000000
-//00000000000000000000000
-//00022200000010000000000
-//00022200000010000000000
-//00022200000010000000000
-//00000000000010000000000
-//00000000000010000000000
-//00000000000010000000000
-//00000000000010000000000
-//00000000000010000000000
-//00000000000010000000000
-//00011111111110000000000
-//00000000000000000000000
-//00000000000000000000000
-//00000000000000000000000", attributes);
-//		}
-
+               At 15,15", output);
+
+			TestHelpers.AssertDriverColorsAre (@"
+00000000000000000000000
+00000000000000000000000
+00000000000000000000000
+00022200000010000000000
+00022200000010000000000
+00022200000010000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00011111111100000000000
+00000000000000000000000
+00000000000000000000000
+00000000000000000000000", null, attributes);
+		}
+		
 		[Fact, AutoInitShutdown]
 		public void DrawTextFormatter_Respects_The_Clip_Bounds ()
 		{

文件差異過大導致無法顯示
+ 433 - 444
UnitTests/Views/TabViewTests.cs


文件差異過大導致無法顯示
+ 434 - 436
UnitTests/Views/TileViewTests.cs


部分文件因文件數量過多而無法顯示