소스 검색

Merge branch 'v2_develop' of tig:tig/Terminal.Gui into v2_develop

Tig 3 달 전
부모
커밋
5863d383f1

+ 0 - 5
.editorconfig

@@ -1881,8 +1881,3 @@ tab_width = 4
 indent_style = space
 indent_size = 2
 tab_width = 2
-
-[*.{appxmanifest,axaml,axml,build,config,cs,csproj,dbml,discomap,dtd,jsproj,lsproj,njsproj,nuspec,paml,proj,props,resw,resx,StyleCop,targets,tasks,vb,vbproj,xaml,xamlx,xml,xoml,xsd}]
-indent_style = space
-indent_size = 4
-tab_width = 4

+ 22 - 0
Examples/UICatalog/Scenarios/Editor.cs

@@ -201,6 +201,8 @@ public class Editor : Scenario
                          CreateAutocomplete (),
                          CreateAllowsTabChecked (),
                          CreateReadOnlyChecked (),
+                         CreateUseSameRuneTypeForWords (),
+                         CreateSelectWordOnlyOnDoubleClick (),
                          new MenuItem (
                                        "Colors",
                                        "",
@@ -776,6 +778,26 @@ public class Editor : Scenario
         return new [] { item };
     }
 
+    private MenuItem CreateSelectWordOnlyOnDoubleClick ()
+    {
+        var item = new MenuItem { Title = "SelectWordOnlyOnDoubleClick" };
+        item.CheckType |= MenuItemCheckStyle.Checked;
+        item.Checked = _textView.SelectWordOnlyOnDoubleClick;
+        item.Action += () => _textView.SelectWordOnlyOnDoubleClick = (bool)(item.Checked = !item.Checked);
+
+        return item;
+    }
+
+    private MenuItem CreateUseSameRuneTypeForWords ()
+    {
+        var item = new MenuItem { Title = "UseSameRuneTypeForWords" };
+        item.CheckType |= MenuItemCheckStyle.Checked;
+        item.Checked = _textView.UseSameRuneTypeForWords;
+        item.Action += () => _textView.UseSameRuneTypeForWords = (bool)(item.Checked = !item.Checked);
+
+        return item;
+    }
+
     private MenuItem CreateReadOnlyChecked ()
     {
         var item = new MenuItem { Title = "Read Only" };

+ 20 - 1
Terminal.Gui/App/IPopover.cs

@@ -26,7 +26,26 @@ namespace Terminal.Gui.App;
 ///         </list>
 ///     </para>
 ///     <para>
-///         To implement a custom popover, inherit from <see cref="PopoverBaseImpl"/> or implement this interface directly.
+///         <b>Focus and Input:</b><br/>
+///         When visible, a popover receives focus and input events. If the user clicks outside the popover (and not on a
+///         subview),
+///         presses <see cref="Application.QuitKey"/>, or another popover is shown, the popover will be hidden
+///         automatically.
+///     </para>
+///     <para>
+///         <b>Layout:</b><br/>
+///         When the popover becomes visible, it is automatically laid out to fill the screen by default. You can override
+///         this behavior
+///         by setting <see cref="View.Width"/> and <see cref="View.Height"/> in your derived class.
+///     </para>
+///     <para>
+///         <b>Mouse:</b><br/>
+///         Popovers are transparent to mouse events (see <see cref="ViewportSettingsFlags.TransparentMouse"/>),
+///         meaning mouse events in a popover that are not also within a subview of the popover will not be captured.
+///     </para>
+///     <para>
+///         <b>Custom Popovers:</b><br/>
+///         To create a custom popover, inherit from <see cref="PopoverBaseImpl"/> and add your own content and logic.
 ///     </para>
 /// </remarks>
 public interface IPopover

+ 5 - 0
Terminal.Gui/App/PopoverBaseImpl.cs

@@ -26,6 +26,11 @@ namespace Terminal.Gui.App;
 ///         by setting <see cref="View.Width"/> and <see cref="View.Height"/> in your derived class.
 ///     </para>
 ///     <para>
+///         <b>Mouse:</b><br/>
+///         Popovers are transparent to mouse events (see <see cref="ViewportSettingsFlags.TransparentMouse"/>),
+///         meaning mouse events in a popover that are not also within a subview of the popover will not be captured.
+///     </para>
+///     <para>
 ///         <b>Custom Popovers:</b><br/>
 ///         To create a custom popover, inherit from <see cref="PopoverBaseImpl"/> and add your own content and logic.
 ///     </para>

+ 2 - 3
Terminal.Gui/Drivers/NetDriver/NetDriver.cs

@@ -3,9 +3,6 @@
 // NetDriver.cs: The System.Console-based .NET driver, works on Windows and Unix, but is not particularly efficient.
 //
 
-using System.Collections.Concurrent;
-using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
 using System.Runtime.InteropServices;
 using static Terminal.Gui.Drivers.NetEvents;
 
@@ -232,6 +229,8 @@ internal class NetDriver : ConsoleDriver
     /// <inheritdoc />
     public override MainLoop Init ()
     {
+        Console.OutputEncoding = Encoding.UTF8;
+
         PlatformID p = Environment.OSVersion.Platform;
 
         if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows)

+ 2 - 0
Terminal.Gui/Drivers/V2/NetOutput.cs

@@ -22,6 +22,8 @@ public class NetOutput : IConsoleOutput
     {
         Logging.Logger.LogInformation ($"Creating {nameof (NetOutput)}");
 
+        Console.OutputEncoding = Encoding.UTF8;
+
         PlatformID p = Environment.OSVersion.Platform;
 
         if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows)

+ 6 - 6
Terminal.Gui/Resources/config.json

@@ -511,8 +511,8 @@
                 "Style": "None"
               },
               "Editable": {
-                "Foreground": "Goldenrod",
-                "Background": "WhiteSmoke",
+                "Foreground": "Black",
+                "Background": "LemonChiffon",
                 "Style": "None"
               },
               "ReadOnly": {
@@ -565,8 +565,8 @@
                 "Style": "None"
               },
               "Editable": {
-                "Foreground": "Goldenrod",
-                "Background": "WhiteSmoke",
+                "Foreground": "Black",
+                "Background": "LemonChiffon",
                 "Style": "None"
               },
               "ReadOnly": {
@@ -669,7 +669,7 @@
               },
               "Highlight": {
                 "Foreground": "Black",
-                "Background": "LightGray",
+                "Background": "LemonChiffon",
                 "Style": "None"
               },
               "Editable": {
@@ -727,7 +727,7 @@
                 "Style": "None"
               },
               "Editable": {
-                "Foreground": "Goldenrod",
+                "Foreground": "Black",
                 "Background": "WhiteSmoke",
                 "Style": "None"
               },

+ 1 - 1
Terminal.Gui/ViewBase/View.Layout.cs

@@ -1166,7 +1166,7 @@ public partial class View // Layout APIs
 
             result.AddRange (GetViewsUnderLocation (visiblePopover, screenLocation, excludeViewportSettingsFlags));
 
-            if (result.Count > 1)
+            if (result.Count > 0)
             {
                 return result;
             }

+ 2 - 4
Terminal.Gui/ViewBase/ViewportSettingsFlags.cs

@@ -153,11 +153,9 @@ public enum ViewportSettingsFlags
     /// </summary>
     Transparent = 0b_0001_0000_0000,
 
-    // BUGBUG: The API docs here are wrong: If a TransparentMouse View has subviews, those subviews WILL get mouse events. 
-    // BUGBUG: That's an important feature that enables Popovers to work.
     /// <summary>
-    ///     If set the View will be transparent to mouse events: Any mouse event that occurs over the View (and it's SubViews) will be passed to the
-    ///     Views below it.
+    ///     If set the View will be transparent to mouse events: Specifically, any mouse event that occurs over the View that is NOT occupied by a SubView
+    ///     will not be captured by the View.
     ///     <para>
     ///         Combine this with <see cref="Transparent"/> to get a view that is both visually transparent and transparent to the mouse.
     ///     </para>

+ 16 - 10
Terminal.Gui/Views/Autocomplete/PopupAutocomplete.cs

@@ -11,8 +11,8 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
     private bool _closed;
     private Scheme _scheme;
     private View _hostControl;
-    private View _top;  // The _hostControl's SuperView
-    private View _popup;
+    private View _top; // The _hostControl's SuperView
+    internal View _popup;
     private int _toRenderLength;
 
     /// <summary>Creates a new instance of the <see cref="PopupAutocomplete"/> class.</summary>
@@ -70,6 +70,7 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
                 {
                     _top.Initialized += _top_Initialized;
                 }
+
                 _top.Removed += _top_Removed;
             }
         }
@@ -268,7 +269,11 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
         else if (!Visible || HostControl?.HasFocus == false || Suggestions.Count == 0)
         {
             LastPopupPos = null;
-            Visible = false;
+
+            if (Visible)
+            {
+                Close ();
+            }
 
             if (Suggestions.Count == 0)
             {
@@ -372,16 +377,16 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
         if (PopupInsideContainer)
         {
             _popup.Frame = new (
-                               new (HostControl.Frame.X + renderAt.X, HostControl.Frame.Y + renderAt.Y),
-                               new (width, height)
-                              );
+                                new (HostControl.Frame.X + renderAt.X, HostControl.Frame.Y + renderAt.Y),
+                                new (width, height)
+                               );
         }
         else
         {
             _popup.Frame = new (
-                               renderAt with { X = HostControl.Frame.X + renderAt.X },
-                               new (width, height)
-                              );
+                                renderAt with { X = HostControl.Frame.X + renderAt.X },
+                                new (width, height)
+                               );
         }
 
         _popup.Move (0, 0);
@@ -419,6 +424,7 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
         ClearSuggestions ();
         Visible = false;
         _closed = true;
+
         //RemovePopupFromTop ();
         _popup.Visible = false;
         HostControl?.SetNeedsDraw ();
@@ -561,7 +567,6 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
             _top?.Remove (_popup);
             _popup.Dispose ();
             _popup = null;
-
         }
     }
 
@@ -571,6 +576,7 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
         {
             _top = sender as View;
         }
+
         AddPopupToTop ();
     }
 

+ 23 - 38
Terminal.Gui/Views/TextInput/TextField.cs

@@ -570,6 +570,19 @@ public class TextField : View, IDesignable
     /// </summary>
     public bool Used { get; set; }
 
+    /// <summary>
+    ///     Gets or sets whether the word forward and word backward navigation should use the same or equivalent rune type.
+    ///     Default is <c>false</c> meaning using equivalent rune type.
+    /// </summary>
+    public bool UseSameRuneTypeForWords { get; set; }
+
+    /// <summary>
+    ///     Gets or sets whether the word navigation should select only the word itself without spaces around it or with the
+    ///     spaces at right.
+    ///     Default is <c>false</c> meaning that the spaces at right are included in the selection.
+    /// </summary>
+    public bool SelectWordOnlyOnDoubleClick { get; set; }
+
     /// <summary>Clear the selected text.</summary>
     public void ClearAllSelection ()
     {
@@ -754,7 +767,7 @@ public class TextField : View, IDesignable
     public virtual void KillWordBackwards ()
     {
         ClearAllSelection ();
-        (int col, int row)? newPos = GetModel ().WordBackward (_cursorPosition, 0);
+        (int col, int row)? newPos = GetModel ().WordBackward (_cursorPosition, 0, UseSameRuneTypeForWords);
 
         if (newPos is null)
         {
@@ -777,7 +790,7 @@ public class TextField : View, IDesignable
     public virtual void KillWordForwards ()
     {
         ClearAllSelection ();
-        (int col, int row)? newPos = GetModel ().WordForward (_cursorPosition, 0);
+        (int col, int row)? newPos = GetModel ().WordForward (_cursorPosition, 0, UseSameRuneTypeForWords);
 
         if (newPos is null)
         {
@@ -857,43 +870,15 @@ public class TextField : View, IDesignable
         {
             EnsureHasFocus ();
             int x = PositionCursor (ev);
-            int sbw = x;
+            (int startCol, int col, int row)? newPos = GetModel ().ProcessDoubleClickSelection (x, x, 0, UseSameRuneTypeForWords, SelectWordOnlyOnDoubleClick);
 
-            if (x == _text.Count
-                || (x > 0 && (char)_text [x - 1].Value != ' ')
-                || (x > 0 && (char)_text [x].Value == ' '))
-            {
-                (int col, int row)? newPosBw = GetModel ().WordBackward (x, 0);
-
-                if (newPosBw is null)
-                {
-                    return true;
-                }
-
-                sbw = newPosBw.Value.col;
-            }
-
-            if (sbw != -1)
-            {
-                x = sbw;
-                PositionCursor (x);
-            }
-
-            (int col, int row)? newPosFw = GetModel ().WordForward (x, 0);
-
-            if (newPosFw is null)
+            if (newPos is null)
             {
                 return true;
             }
 
-            ClearAllSelection ();
-
-            if (newPosFw.Value.col != -1 && sbw != -1)
-            {
-                _cursorPosition = newPosFw.Value.col;
-            }
-
-            PrepareSelection (sbw, newPosFw.Value.col - sbw);
+            SelectedStart = newPos.Value.startCol;
+            CursorPosition = newPos.Value.col;
         }
         else if (ev.Flags == MouseFlags.Button1TripleClicked)
         {
@@ -1502,7 +1487,7 @@ public class TextField : View, IDesignable
     private void MoveWordLeft ()
     {
         ClearAllSelection ();
-        (int col, int row)? newPos = GetModel ().WordBackward (_cursorPosition, 0);
+        (int col, int row)? newPos = GetModel ().WordBackward (_cursorPosition, 0, UseSameRuneTypeForWords);
 
         if (newPos is null)
         {
@@ -1528,7 +1513,7 @@ public class TextField : View, IDesignable
 
             if (x > 0)
             {
-                (int col, int row)? newPos = GetModel ().WordBackward (x, 0);
+                (int col, int row)? newPos = GetModel ().WordBackward (x, 0, UseSameRuneTypeForWords);
 
                 if (newPos is null)
                 {
@@ -1548,7 +1533,7 @@ public class TextField : View, IDesignable
     private void MoveWordRight ()
     {
         ClearAllSelection ();
-        (int col, int row)? newPos = GetModel ().WordForward (_cursorPosition, 0);
+        (int col, int row)? newPos = GetModel ().WordForward (_cursorPosition, 0, UseSameRuneTypeForWords);
 
         if (newPos is null)
         {
@@ -1568,7 +1553,7 @@ public class TextField : View, IDesignable
         if (_cursorPosition < _text.Count)
         {
             int x = _start > -1 && _start > _cursorPosition ? _start : _cursorPosition;
-            (int col, int row)? newPos = GetModel ().WordForward (x, 0);
+            (int col, int row)? newPos = GetModel ().WordForward (x, 0, UseSameRuneTypeForWords);
 
             if (newPos is null)
             {

+ 152 - 107
Terminal.Gui/Views/TextInput/TextModel.cs

@@ -206,7 +206,7 @@ internal class TextModel
         return sb.ToString ();
     }
 
-    public (int col, int row)? WordBackward (int fromCol, int fromRow)
+    public (int col, int row)? WordBackward (int fromCol, int fromRow, bool useSameRuneType)
     {
         if (fromRow == 0 && fromCol == 0)
         {
@@ -245,7 +245,7 @@ internal class TextModel
 
             RuneType runeType = GetRuneType (rune);
 
-            int lastValidCol = IsSameRuneType (rune, runeType) && (Rune.IsLetterOrDigit (rune) || Rune.IsPunctuation (rune) || Rune.IsSymbol (rune))
+            int lastValidCol = IsSameRuneType (rune, runeType, useSameRuneType) && (Rune.IsLetterOrDigit (rune) || Rune.IsPunctuation (rune) || Rune.IsSymbol (rune))
                                    ? col
                                    : -1;
 
@@ -253,61 +253,49 @@ internal class TextModel
             {
                 if (Rune.IsWhiteSpace (nRune))
                 {
-                    while (MovePrev (ref nCol, ref nRow, out nRune))
+                    while (MovePrev (ref nCol, ref nRow, out nRune, useSameRuneType))
                     {
+                        lastValidCol = nCol;
+
                         if (Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune))
                         {
-                            lastValidCol = nCol;
-
-                            if (runeType == RuneType.IsWhiteSpace || runeType == RuneType.IsUnknown)
-                            {
-                                runeType = GetRuneType (nRune);
-                            }
-
-                            break;
+                            rune = nRune;
+                            runeType = GetRuneType (nRune);
                         }
                     }
 
-                    if (nRow != fromRow && (Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune)))
+                    if (lastValidCol > -1)
                     {
-                        List<Cell> line = GetLine (nRow);
-
-                        if (lastValidCol > -1)
-                        {
-                            nCol = lastValidCol + Math.Max (lastValidCol, line.Count);
-                        }
+                        nCol = lastValidCol;
+                        nRow = fromRow;
+                    }
 
+                    if ((!Rune.IsWhiteSpace (nRune) && Rune.IsWhiteSpace (rune))
+                        || (Rune.IsWhiteSpace (nRune) && !Rune.IsWhiteSpace (rune)))
+                    {
                         return;
                     }
 
-                    while (MovePrev (ref nCol, ref nRow, out nRune))
+                    if (nRow != fromRow && (Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune)))
                     {
-                        if (!Rune.IsLetterOrDigit (nRune) && !Rune.IsPunctuation (nRune) && !Rune.IsSymbol (nRune))
-                        {
-                            break;
-                        }
+                        List<Cell> line = GetLine (nRow);
 
-                        if (nRow != fromRow)
+                        if (lastValidCol > -1)
                         {
-                            break;
+                            nCol = lastValidCol + Math.Max (lastValidCol, line.Count);
                         }
-
-                        lastValidCol =
-                            (IsSameRuneType (nRune, runeType) && Rune.IsLetterOrDigit (nRune)) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune)
-                                ? nCol
-                                : lastValidCol;
-                    }
-
-                    if (lastValidCol > -1)
-                    {
-                        nCol = lastValidCol;
-                        nRow = fromRow;
                     }
                 }
                 else
                 {
-                    if (!MovePrev (ref nCol, ref nRow, out nRune))
+                    if (!MovePrev (ref nCol, ref nRow, out nRune, useSameRuneType))
                     {
+                        if (lastValidCol > -1)
+                        {
+                            nCol = lastValidCol;
+                            nRow = fromRow;
+                        }
+
                         return;
                     }
 
@@ -321,7 +309,7 @@ internal class TextModel
                     }
 
                     lastValidCol =
-                        (IsSameRuneType (nRune, runeType) && Rune.IsLetterOrDigit (nRune)) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune)
+                        (IsSameRuneType (nRune, runeType, useSameRuneType) && Rune.IsLetterOrDigit (nRune)) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune)
                             ? nCol
                             : lastValidCol;
 
@@ -350,6 +338,15 @@ internal class TextModel
                 return (col, row);
             }
 
+            if (fromCol == col && fromRow == row && row > 0)
+            {
+                row--;
+                List<Cell> line = GetLine (row);
+                col = line.Count;
+
+                return (col, row);
+            }
+
             return null;
         }
         catch (Exception)
@@ -358,7 +355,7 @@ internal class TextModel
         }
     }
 
-    public (int col, int row)? WordForward (int fromCol, int fromRow)
+    public (int col, int row)? WordForward (int fromCol, int fromRow, bool useSameRuneType)
     {
         if (fromRow == _lines.Count - 1 && fromCol == GetLine (_lines.Count - 1).Count)
         {
@@ -370,10 +367,10 @@ internal class TextModel
 
         try
         {
-            Rune rune = RuneAt (col, row)!.Value.Rune;
+            Rune rune = _lines [row].Count > 0 ? RuneAt (col, row)!.Value.Rune : default (Rune);
             RuneType runeType = GetRuneType (rune);
 
-            int lastValidCol = IsSameRuneType (rune, runeType) && (Rune.IsLetterOrDigit (rune) || Rune.IsPunctuation (rune) || Rune.IsSymbol (rune))
+            int lastValidCol = IsSameRuneType (rune, runeType, useSameRuneType) && (Rune.IsLetterOrDigit (rune) || Rune.IsPunctuation (rune) || Rune.IsSymbol (rune))
                                    ? col
                                    : -1;
 
@@ -381,42 +378,31 @@ internal class TextModel
             {
                 if (Rune.IsWhiteSpace (nRune))
                 {
-                    while (MoveNext (ref nCol, ref nRow, out nRune))
+                    while (MoveNext (ref nCol, ref nRow, out nRune, useSameRuneType))
                     {
+                        lastValidCol = nCol;
+
                         if (Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune))
                         {
-                            lastValidCol = nCol;
-
                             return;
                         }
                     }
 
-                    if (nRow != fromRow && (Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune)))
-                    {
-                        if (lastValidCol > -1)
-                        {
-                            nCol = lastValidCol;
-                        }
+                    lastValidCol = nCol;
 
+                    if (!Rune.IsWhiteSpace (nRune) && Rune.IsWhiteSpace (rune))
+                    {
                         return;
                     }
 
-                    while (MoveNext (ref nCol, ref nRow, out nRune))
+                    if (nRow != fromRow && (Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune)))
                     {
-                        if (!Rune.IsLetterOrDigit (nRune) && !Rune.IsPunctuation (nRune) && !Rune.IsSymbol (nRune))
-                        {
-                            break;
-                        }
-
-                        if (nRow != fromRow)
+                        if (lastValidCol > -1)
                         {
-                            break;
+                            nCol = lastValidCol;
                         }
 
-                        lastValidCol =
-                            (IsSameRuneType (nRune, runeType) && Rune.IsLetterOrDigit (nRune)) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune)
-                                ? nCol
-                                : lastValidCol;
+                        return;
                     }
 
                     if (lastValidCol > -1)
@@ -427,12 +413,14 @@ internal class TextModel
                 }
                 else
                 {
-                    if (!MoveNext (ref nCol, ref nRow, out nRune))
+                    if (!MoveNext (ref nCol, ref nRow, out nRune, useSameRuneType))
                     {
                         return;
                     }
 
-                    if (!IsSameRuneType (nRune, runeType) && !Rune.IsWhiteSpace (nRune))
+                    lastValidCol = nCol;
+
+                    if (!IsSameRuneType (nRune, runeType, useSameRuneType) && !Rune.IsWhiteSpace (nRune))
                     {
                         return;
                     }
@@ -446,11 +434,6 @@ internal class TextModel
                         return;
                     }
 
-                    lastValidCol =
-                        (IsSameRuneType (nRune, runeType) && Rune.IsLetterOrDigit (nRune)) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune)
-                            ? nCol
-                            : lastValidCol;
-
                     if (fromRow != nRow)
                     {
                         nCol = 0;
@@ -477,6 +460,64 @@ internal class TextModel
         }
     }
 
+    public (int startCol, int col, int row)? ProcessDoubleClickSelection (int fromStartCol, int fromCol, int fromRow, bool useSameRuneType, bool selectWordOnly)
+    {
+        List<Cell> line = GetLine (fromRow);
+
+        int startCol = fromStartCol;
+        int col = fromCol;
+        int row = fromRow;
+
+        (int col, int row)? newPos = WordForward (col, row, useSameRuneType);
+
+        if (newPos.HasValue)
+        {
+            col = row == newPos.Value.row ? newPos.Value.col : 0;
+        }
+
+        if (startCol > 0
+            && StringExtensions.ToString (line.GetRange (startCol, col - startCol).Select (c => c.Rune).ToList ()).Trim () == ""
+            && (col - startCol > 1 || (col - startCol > 0 && line [startCol - 1].Rune == (Rune)' ')))
+        {
+            while (startCol > 0 && line [startCol - 1].Rune == (Rune)' ')
+            {
+                startCol--;
+            }
+        }
+        else
+        {
+            newPos = WordBackward (col, row, useSameRuneType);
+
+            if (newPos is { })
+            {
+                startCol = row == newPos.Value.row ? newPos.Value.col : line.Count;
+            }
+        }
+
+        if (selectWordOnly)
+        {
+            List<Rune> selRunes = line.GetRange (startCol, col - startCol).Select (c => c.Rune).ToList ();
+
+            if (StringExtensions.ToString (selRunes).Trim () != "")
+            {
+                for (int i = selRunes.Count - 1; i > -1; i--)
+                {
+                    if (selRunes [i] == (Rune)' ')
+                    {
+                        col--;
+                    }
+                }
+            }
+        }
+
+        if (fromStartCol != startCol || fromCol != col || fromRow != row)
+        {
+            return (startCol, col, row);
+        }
+
+        return null;
+    }
+
     internal static int CalculateLeftColumn (List<Cell> t, int start, int end, int width, int tabWidth = 0)
     {
         List<Rune> runes = new ();
@@ -966,11 +1007,27 @@ internal class TextModel
         return RuneType.IsUnknown;
     }
 
-    private bool IsSameRuneType (Rune newRune, RuneType runeType)
+    private bool IsSameRuneType (Rune newRune, RuneType runeType, bool useSameRuneType)
     {
         RuneType rt = GetRuneType (newRune);
 
-        return rt == runeType;
+        if (useSameRuneType)
+        {
+            return rt == runeType;
+        }
+
+        switch (runeType)
+        {
+            case RuneType.IsSymbol:
+            case RuneType.IsPunctuation:
+                return rt is RuneType.IsSymbol or RuneType.IsPunctuation;
+            case RuneType.IsWhiteSpace:
+            case RuneType.IsLetterOrDigit:
+            case RuneType.IsUnknown:
+                return rt == runeType;
+            default:
+                throw new ArgumentOutOfRangeException (nameof (runeType), runeType, null);
+        }
     }
 
     private bool MatchWholeWord (string source, string matchText, int index = 0)
@@ -992,7 +1049,7 @@ internal class TextModel
         return false;
     }
 
-    private bool MoveNext (ref int col, ref int row, out Rune rune)
+    private bool MoveNext (ref int col, ref int row, out Rune rune, bool useSameRuneType)
     {
         List<Cell> line = GetLine (row);
 
@@ -1001,39 +1058,40 @@ internal class TextModel
             col++;
             rune = line [col].Rune;
 
-            if (col + 1 == line.Count && !Rune.IsLetterOrDigit (rune) && !Rune.IsWhiteSpace (line [col - 1].Rune))
+            if (col + 1 == line.Count
+                && !Rune.IsLetterOrDigit (rune)
+                && !Rune.IsWhiteSpace (line [col - 1].Rune)
+                && IsSameRuneType (line [col - 1].Rune, GetRuneType (rune), useSameRuneType))
             {
                 col++;
             }
 
+            if (!Rune.IsWhiteSpace (rune)
+                && (Rune.IsWhiteSpace (line [col - 1].Rune) || !IsSameRuneType (line [col - 1].Rune, GetRuneType (rune), useSameRuneType)))
+            {
+                return false;
+            }
+
             return true;
         }
 
         if (col + 1 == line.Count)
         {
             col++;
-        }
-
-        while (row + 1 < Count)
-        {
-            col = 0;
-            row++;
-            line = GetLine (row);
-
-            if (line.Count > 0)
-            {
-                rune = line [0].Rune;
+            rune = default (Rune);
 
-                return true;
-            }
+            return false;
         }
 
+        // End of line
+        col = 0;
+        row++;
         rune = default (Rune);
 
         return false;
     }
 
-    private bool MovePrev (ref int col, ref int row, out Rune rune)
+    private bool MovePrev (ref int col, ref int row, out Rune rune, bool useSameRuneType)
     {
         List<Cell> line = GetLine (row);
 
@@ -1042,28 +1100,15 @@ internal class TextModel
             col--;
             rune = line [col].Rune;
 
-            return true;
-        }
-
-        if (row == 0)
-        {
-            rune = default (Rune);
-
-            return false;
-        }
-
-        while (row > 0)
-        {
-            row--;
-            line = GetLine (row);
-            col = line.Count - 1;
-
-            if (col >= 0)
+            if ((!Rune.IsWhiteSpace (rune)
+                 && !Rune.IsWhiteSpace (line [col + 1].Rune)
+                 && !IsSameRuneType (line [col + 1].Rune, GetRuneType (rune), useSameRuneType))
+                || (Rune.IsWhiteSpace (rune) && !Rune.IsWhiteSpace (line [col + 1].Rune)))
             {
-                rune = line [col].Rune;
-
-                return true;
+                return false;
             }
+
+            return true;
         }
 
         rune = default (Rune);

+ 25 - 21
Terminal.Gui/Views/TextInput/TextView.cs

@@ -3,6 +3,7 @@
 // TextView.cs: multi-line text editing
 using System.Globalization;
 using System.Runtime.CompilerServices;
+using static Unix.Terminal.Delegates;
 
 namespace Terminal.Gui.Views;
 
@@ -1014,6 +1015,19 @@ public class TextView : View, IDesignable
         }
     }
 
+    /// <summary>
+    ///     Gets or sets whether the word forward and word backward navigation should use the same or equivalent rune type.
+    ///     Default is <c>false</c> meaning using equivalent rune type.
+    /// </summary>
+    public bool UseSameRuneTypeForWords { get; set; }
+
+    /// <summary>
+    ///     Gets or sets whether the word navigation should select only the word itself without spaces around it or with the
+    ///     spaces at right.
+    ///     Default is <c>false</c> meaning that the spaces at right are included in the selection.
+    /// </summary>
+    public bool SelectWordOnlyOnDoubleClick { get; set; }
+
     /// <summary>Allows clearing the <see cref="HistoryTextItemEventArgs"/> items updating the original text.</summary>
     public void ClearHistoryChanges () { _historyText?.Clear (_model.GetAllLines ()); }
 
@@ -1688,29 +1702,19 @@ public class TextView : View, IDesignable
             }
 
             ProcessMouseClick (ev, out List<Cell> line);
-            (int col, int row)? newPos;
-
-            if (CurrentColumn == line.Count
-                || (CurrentColumn > 0 && (line [CurrentColumn - 1].Rune.Value != ' ' || line [CurrentColumn].Rune.Value == ' ')))
-            {
-                newPos = _model.WordBackward (CurrentColumn, CurrentRow);
-
-                if (newPos.HasValue)
-                {
-                    CurrentColumn = CurrentRow == newPos.Value.row ? newPos.Value.col : 0;
-                }
-            }
 
             if (!IsSelecting)
             {
                 StartSelecting ();
             }
 
-            newPos = _model.WordForward (CurrentColumn, CurrentRow);
+            (int startCol, int col, int row)? newPos = _model.ProcessDoubleClickSelection (SelectionStartColumn, CurrentColumn, CurrentRow, UseSameRuneTypeForWords, SelectWordOnlyOnDoubleClick);
 
-            if (newPos is { } && newPos.HasValue)
+            if (newPos.HasValue)
             {
-                CurrentColumn = CurrentRow == newPos.Value.row ? newPos.Value.col : line.Count;
+                SelectionStartColumn = newPos.Value.startCol;
+                CurrentColumn = newPos.Value.col;
+                CurrentRow = newPos.Value.row;
             }
 
             PositionCursor ();
@@ -1868,6 +1872,7 @@ public class TextView : View, IDesignable
 
             if (col < right)
             {
+                SetAttributeForRole (ReadOnly ? VisualRole.ReadOnly : VisualRole.Editable);
                 ClearRegion (col, row, right, row + 1);
             }
 
@@ -1876,11 +1881,10 @@ public class TextView : View, IDesignable
 
         if (row < bottom)
         {
+            SetAttributeForRole (ReadOnly ? VisualRole.ReadOnly : VisualRole.Editable);
             ClearRegion (Viewport.Left, row, right, bottom);
         }
 
-        //PositionCursor ();
-
         _isDrawing = false;
 
         return false;
@@ -3379,7 +3383,7 @@ public class TextView : View, IDesignable
             return;
         }
 
-        (int col, int row)? newPos = _model.WordBackward (CurrentColumn, CurrentRow);
+        (int col, int row)? newPos = _model.WordBackward (CurrentColumn, CurrentRow, UseSameRuneTypeForWords);
 
         if (newPos.HasValue && CurrentRow == newPos.Value.row)
         {
@@ -3463,7 +3467,7 @@ public class TextView : View, IDesignable
             return;
         }
 
-        (int col, int row)? newPos = _model.WordForward (CurrentColumn, CurrentRow);
+        (int col, int row)? newPos = _model.WordForward (CurrentColumn, CurrentRow, UseSameRuneTypeForWords);
         var restCount = 0;
 
         if (newPos.HasValue && CurrentRow == newPos.Value.row)
@@ -3753,7 +3757,7 @@ public class TextView : View, IDesignable
 
     private void MoveWordBackward ()
     {
-        (int col, int row)? newPos = _model.WordBackward (CurrentColumn, CurrentRow);
+        (int col, int row)? newPos = _model.WordBackward (CurrentColumn, CurrentRow, UseSameRuneTypeForWords);
 
         if (newPos.HasValue)
         {
@@ -3766,7 +3770,7 @@ public class TextView : View, IDesignable
 
     private void MoveWordForward ()
     {
-        (int col, int row)? newPos = _model.WordForward (CurrentColumn, CurrentRow);
+        (int col, int row)? newPos = _model.WordForward (CurrentColumn, CurrentRow, UseSameRuneTypeForWords);
 
         if (newPos.HasValue)
         {

+ 65 - 3
Tests/UnitTests/Application/ApplicationPopoverTests.cs

@@ -2,9 +2,6 @@
 
 public class ApplicationPopoverTests
 {
-
-
-
     [Fact]
     public void Application_Init_Initializes_PopoverManager ()
     {
@@ -203,6 +200,71 @@ public class ApplicationPopoverTests
         Application.ResetState (true);
     }
 
+    // See: https://github.com/gui-cs/Terminal.Gui/issues/4122
+    [Theory]
+    [InlineData (0, 0, new [] { "top" })]
+    [InlineData (10, 10, new string [] { })]
+    [InlineData (1, 1, new [] { "top", "view" })]
+    [InlineData (5, 5, new [] { "top" })]
+    [InlineData (6, 6, new [] { "popoverSubView" })]
+    [InlineData (7, 7, new [] { "top" })]
+    [InlineData (3, 3, new [] { "top" })]
+    public void GetViewsUnderMouse_Supports_ActivePopover (int mouseX, int mouseY, string [] viewIdStrings)
+    {
+        Application.ResetState (true);
+        // Arrange
+        Assert.Null (Application.Popover);
+        Application.Init (new FakeDriver ());
+        Application.Top = new ()
+        {
+            Frame = new (0, 0, 10, 10),
+            Id = "top"
+        };
+
+        View view = new ()
+        {
+            Id = "view",
+            X = 1,
+            Y = 1,
+            Width = 2,
+            Height = 2,
+        }; // at 1,1 to 3,2 (screen)
+
+        Application.Top.Add (view);
+
+        PopoverTestClass popover = new ()
+        {
+            Id = "popover",
+            X = 5,
+            Y = 5,
+            Width = 3,
+            Height = 3,
+        }; // at 5,5 to 8,8 (screen)
+
+        View popoverSubView = new ()
+        {
+            Id = "popoverSubView",
+            X = 1,
+            Y = 1,
+            Width = 1,
+            Height = 1,
+        }; // at 6,6 to 7,7 (screen)
+
+        popover.Add (popoverSubView);
+
+        Application.Popover?.Show (popover);
+
+        List<View?> found = View.GetViewsUnderLocation (new (mouseX, mouseY), ViewportSettingsFlags.TransparentMouse);
+
+        string [] foundIds = found.Select (v => v!.Id).ToArray ();
+
+        Assert.Equal (viewIdStrings, foundIds);
+
+        popover.Dispose ();
+        Application.Top.Dispose ();
+        Application.ResetState (true);
+    }
+
     public class PopoverTestClass : PopoverBaseImpl
     {
         public List<Key> HandledKeys { get; } = [];

+ 3 - 0
Tests/UnitTests/Text/AutocompleteTests.cs

@@ -153,6 +153,9 @@ This an long line and against TextView.
 This an long line and against TextView.",
                                                        output
                                                       );
+        Assert.Empty (tv.Autocomplete.Suggestions);
+        Assert.False (((PopupAutocomplete)tv.Autocomplete)._popup.Visible);
+
         top.Dispose ();
     }
 

+ 23 - 0
Tests/UnitTests/Views/TextFieldTests.cs

@@ -607,6 +607,8 @@ public class TextFieldTests (ITestOutputHelper output)
         Assert.True (tf.NewKeyDownEvent (Key.CursorRight.WithShift.WithCtrl));
 #endif
         Assert.Equal ("is is a test.", tf.Text);
+        Assert.Equal ("is a test", tf.SelectedText);
+        Assert.True (tf.NewKeyDownEvent (Key.CursorRight.WithShift.WithCtrl));
         Assert.Equal ("is a test.", tf.SelectedText);
         Assert.Equal (13, tf.CursorPosition);
         Assert.True (tf.NewKeyDownEvent (Key.CursorLeft));
@@ -1340,6 +1342,13 @@ public class TextFieldTests (ITestOutputHelper output)
 
                     break;
                 case 5:
+                    Assert.Equal (31, _textField.CursorPosition);
+                    Assert.Equal (-1, _textField.SelectedStart);
+                    Assert.Equal (0, _textField.SelectedLength);
+                    Assert.Null (_textField.SelectedText);
+
+                    break;
+                case 6:
                     Assert.Equal (32, _textField.CursorPosition);
                     Assert.Equal (-1, _textField.SelectedStart);
                     Assert.Equal (0, _textField.SelectedLength);
@@ -1501,6 +1510,13 @@ public class TextFieldTests (ITestOutputHelper output)
 
                     break;
                 case 5:
+                    Assert.Equal (31, _textField.CursorPosition);
+                    Assert.Equal (0, _textField.SelectedStart);
+                    Assert.Equal (31, _textField.SelectedLength);
+                    Assert.Equal ("TAB to jump between text fields", _textField.SelectedText);
+
+                    break;
+                case 6:
                     Assert.Equal (32, _textField.CursorPosition);
                     Assert.Equal (0, _textField.SelectedStart);
                     Assert.Equal (32, _textField.SelectedLength);
@@ -1550,6 +1566,13 @@ public class TextFieldTests (ITestOutputHelper output)
 
                     break;
                 case 3:
+                    Assert.Equal (31, _textField.CursorPosition);
+                    Assert.Equal (10, _textField.SelectedStart);
+                    Assert.Equal (21, _textField.SelectedLength);
+                    Assert.Equal ("p between text fields", _textField.SelectedText);
+
+                    break;
+                case 4:
                     Assert.Equal (32, _textField.CursorPosition);
                     Assert.Equal (10, _textField.SelectedStart);
                     Assert.Equal (22, _textField.SelectedLength);

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 80 - 959
Tests/UnitTests/Views/TextViewTests.cs


+ 5 - 2
Tests/UnitTestsParallelizable/Views/TextFieldTests.cs

@@ -348,6 +348,9 @@ public class TextFieldTests
         tf.BeginInit ();
         tf.EndInit ();
 
+        Assert.False (tf.UseSameRuneTypeForWords);
+        Assert.Equal (22, tf.CursorPosition);
+
         tf.NewKeyDownEvent (Key.CursorLeft.WithCtrl);
         Assert.Equal (15, tf.CursorPosition);
         tf.NewKeyDownEvent (Key.CursorLeft.WithCtrl);
@@ -402,14 +405,14 @@ public class TextFieldTests
                                        new () { Position = new (idx, 1), Flags = MouseFlags.Button1DoubleClicked, View = tf }
                                       )
                     );
-        Assert.Equal ("movie.", tf.SelectedText);
+        Assert.Equal ("movie", tf.SelectedText);
 
         Assert.True (
                      tf.NewMouseEvent (
                                        new () { Position = new (idx + 1, 1), Flags = MouseFlags.Button1DoubleClicked, View = tf }
                                       )
                     );
-        Assert.Equal ("movie.", tf.SelectedText);
+        Assert.Equal ("movie", tf.SelectedText);
     }
 
     [Fact]

+ 2408 - 0
Tests/UnitTestsParallelizable/Views/TextViewTests.cs

@@ -0,0 +1,2408 @@
+using System.Text;
+
+namespace Terminal.Gui.ViewsTests;
+
+public class TextViewTests
+{
+    [Fact]
+    public void CloseFile_Throws_If_FilePath_Is_Null ()
+    {
+        var tv = new TextView ();
+        Assert.Throws<ArgumentNullException> (() => tv.CloseFile ());
+    }
+
+    [Fact]
+    public void ContentsChanged_Event_Fires_ClearHistoryChanges ()
+    {
+        var eventcount = 0;
+
+        var text = "This is the first line.\nThis is the second line.\nThis is the third line.";
+        var tv = new TextView { Width = 50, Height = 10, Text = text };
+        tv.ContentsChanged += (s, e) => { eventcount++; };
+
+        Assert.True (tv.NewKeyDownEvent (Key.Enter));
+
+        Assert.Equal (
+                      $"{Environment.NewLine}This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (4, tv.Lines);
+
+        var expectedEventCount = 1; // for ENTER key
+        Assert.Equal (expectedEventCount, eventcount);
+
+        tv.ClearHistoryChanges ();
+        expectedEventCount = 2;
+        Assert.Equal (expectedEventCount, eventcount);
+    }
+
+    [Fact]
+    public void ContentsChanged_Event_Fires_LoadStream_By_Calling_HistoryText_Clear ()
+    {
+        var eventcount = 0;
+
+        var tv = new TextView { Width = 50, Height = 10 };
+        tv.ContentsChanged += (s, e) => { eventcount++; };
+
+        var text = "This is the first line.\r\nThis is the second line.\r\n";
+        tv.Load (new MemoryStream (Encoding.ASCII.GetBytes (text)));
+
+        Assert.Equal (
+                      $"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}",
+                      tv.Text
+                     );
+
+        Assert.Equal (1, eventcount);
+    }
+
+    [Fact]
+    public void ContentsChanged_Event_Fires_On_LoadFile_By_Calling_HistoryText_Clear ()
+    {
+        var eventcount = 0;
+
+        var tv = new TextView { Width = 50, Height = 10 };
+        tv.BeginInit ();
+        tv.EndInit ();
+
+        tv.ContentsChanged += (s, e) => { eventcount++; };
+
+        var fileName = "textview.txt";
+        File.WriteAllText (fileName, "This is the first line.\r\nThis is the second line.\r\n");
+
+        tv.Load (fileName);
+        Assert.Equal (1, eventcount);
+
+        Assert.Equal (
+                      $"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}",
+                      tv.Text
+                     );
+    }
+
+    [Fact]
+    public void GetRegion_StringFromRunes_Environment_NewLine ()
+    {
+        var tv = new TextView { Text = $"1{Environment.NewLine}2" };
+
+        Assert.Equal ($"1{Environment.NewLine}2", tv.Text);
+        Assert.Equal ("", tv.SelectedText);
+
+        tv.SelectAll ();
+        Assert.Equal ($"1{Environment.NewLine}2", tv.Text);
+        Assert.Equal ($"1{Environment.NewLine}2", tv.SelectedText);
+    }
+
+    [Fact]
+    public void HistoryText_ClearHistoryChanges ()
+    {
+        var text = "This is the first line.\nThis is the second line.\nThis is the third line.";
+        var tv = new TextView { Text = text };
+
+        Assert.True (tv.NewKeyDownEvent (Key.Enter));
+
+        Assert.Equal (
+                      $"{Environment.NewLine}This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (4, tv.Lines);
+        Assert.Equal (new (0, 1), tv.CursorPosition);
+        Assert.True (tv.IsDirty);
+        Assert.True (tv.HasHistoryChanges);
+
+        tv.ClearHistoryChanges ();
+
+        Assert.Equal (
+                      $"{Environment.NewLine}This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (4, tv.Lines);
+        Assert.Equal (new (0, 1), tv.CursorPosition);
+        Assert.False (tv.IsDirty);
+        Assert.False (tv.HasHistoryChanges);
+    }
+
+    [Fact]
+    public void HistoryText_Exceptions ()
+    {
+        var ht = new HistoryText ();
+
+        foreach (object ls in Enum.GetValues (typeof (TextEditingLineStatus)))
+        {
+            if ((TextEditingLineStatus)ls != TextEditingLineStatus.Original)
+            {
+                Assert.Throws<ArgumentException> (
+                                                  () => ht.Add (
+                                                                new List<List<Cell>> (),
+                                                                Point.Empty,
+                                                                (TextEditingLineStatus)ls
+                                                               )
+                                                 );
+            }
+        }
+
+        Assert.Null (Record.Exception (() => ht.Add (new () { new () }, Point.Empty)));
+    }
+
+    [Fact]
+    public void HistoryText_IsDirty_HasHistoryChanges ()
+    {
+        var tv = new TextView ();
+
+        Assert.Equal ("", tv.Text);
+        Assert.Equal (1, tv.Lines);
+        Assert.Equal (Point.Empty, tv.CursorPosition);
+        Assert.False (tv.IsDirty);
+        Assert.False (tv.HasHistoryChanges);
+
+        Assert.True (tv.NewKeyDownEvent (Key.D1));
+        Assert.Equal ("1", tv.Text);
+        Assert.Equal (1, tv.Lines);
+        Assert.Equal (new (1, 0), tv.CursorPosition);
+        Assert.True (tv.IsDirty);
+        Assert.True (tv.HasHistoryChanges);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Enter));
+        Assert.Equal ($"1{Environment.NewLine}", tv.Text);
+        Assert.Equal (2, tv.Lines);
+        Assert.Equal (new (0, 1), tv.CursorPosition);
+        Assert.True (tv.IsDirty);
+        Assert.True (tv.HasHistoryChanges);
+
+        Assert.True (tv.NewKeyDownEvent (Key.D2));
+        Assert.Equal ($"1{Environment.NewLine}2", tv.Text);
+        Assert.Equal (2, tv.Lines);
+        Assert.Equal (new (1, 1), tv.CursorPosition);
+        Assert.True (tv.IsDirty);
+        Assert.True (tv.HasHistoryChanges);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Backspace));
+        Assert.Equal ($"1{Environment.NewLine}", tv.Text);
+        Assert.Equal (2, tv.Lines);
+        Assert.Equal (new (0, 1), tv.CursorPosition);
+        Assert.True (tv.IsDirty);
+        Assert.True (tv.HasHistoryChanges);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Backspace));
+        Assert.Equal ("1", tv.Text);
+        Assert.Equal (1, tv.Lines);
+        Assert.Equal (new (1, 0), tv.CursorPosition);
+        Assert.True (tv.IsDirty);
+        Assert.True (tv.HasHistoryChanges);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Backspace));
+        Assert.Equal ("", tv.Text);
+        Assert.Equal (1, tv.Lines);
+        Assert.Equal (Point.Empty, tv.CursorPosition);
+
+        // IsDirty cannot be based on HasHistoryChanges because HasHistoryChanges is greater than 0
+        // The only way is comparing from the original text
+        Assert.False (tv.IsDirty);
+
+        // Still true because HasHistoryChanges is greater than 0
+        Assert.True (tv.HasHistoryChanges);
+    }
+
+    [Fact]
+    public void HistoryText_Undo_Redo_Changing_On_Middle_Clear_History_Forwards ()
+    {
+        var tv = new TextView ();
+
+        Assert.True (tv.NewKeyDownEvent (Key.D1));
+        Assert.Equal ("1", tv.Text);
+        Assert.Equal (1, tv.Lines);
+        Assert.Equal (new (1, 0), tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.D2));
+        Assert.Equal ("12", tv.Text);
+        Assert.Equal (1, tv.Lines);
+        Assert.Equal (new (2, 0), tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.D3));
+        Assert.Equal ("123", tv.Text);
+        Assert.Equal (1, tv.Lines);
+        Assert.Equal (new (3, 0), tv.CursorPosition);
+
+        // Undo
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+        Assert.Equal ("12", tv.Text);
+        Assert.Equal (1, tv.Lines);
+        Assert.Equal (new (2, 0), tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.D4));
+        Assert.Equal ("124", tv.Text);
+        Assert.Equal (1, tv.Lines);
+        Assert.Equal (new (3, 0), tv.CursorPosition);
+
+        // Redo
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+        Assert.Equal ("124", tv.Text);
+        Assert.Equal (1, tv.Lines);
+        Assert.Equal (new (3, 0), tv.CursorPosition);
+    }
+
+    [Fact]
+    public void HistoryText_Undo_Redo_Disabled_On_WordWrap ()
+    {
+        var text = "This is the first line.\nThis is the second line.\nThis is the third line.\n";
+        var tv = new TextView { Width = 80, Height = 5, Text = text };
+
+        Assert.False (tv.WordWrap);
+        tv.WordWrap = true;
+
+        tv.SelectionStartColumn = 12;
+        tv.CursorPosition = new (12, 2);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Enter));
+        Assert.Equal ($"This is the {Environment.NewLine}third line.{Environment.NewLine}", tv.Text);
+        Assert.Equal (3, tv.Lines);
+        Assert.Equal (new (0, 1), tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.A));
+        Assert.Equal ($"This is the {Environment.NewLine}athird line.{Environment.NewLine}", tv.Text);
+        Assert.Equal (3, tv.Lines);
+        Assert.Equal (new (1, 1), tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+        Assert.Equal ($"This is the {Environment.NewLine}third line.{Environment.NewLine}", tv.Text);
+        Assert.Equal (3, tv.Lines);
+        Assert.Equal (new (0, 1), tv.CursorPosition);
+        Assert.True (tv.IsDirty);
+
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+        Assert.Equal ($"This is the {Environment.NewLine}athird line.{Environment.NewLine}", tv.Text);
+        Assert.Equal (3, tv.Lines);
+        Assert.Equal (new (1, 1), tv.CursorPosition);
+    }
+
+    [Fact]
+    public void HistoryText_Undo_Redo_Ending_With_Newline_Multi_Line_Selected_Almost_All_Return_And_InsertText ()
+    {
+        var text = "This is the first line.\nThis is the second line.\nThis is the third line.\n";
+        var tv = new TextView { Text = text };
+
+        tv.SelectionStartColumn = 12;
+        tv.CursorPosition = new (12, 2);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Enter));
+        Assert.Equal ($"This is the {Environment.NewLine}third line.{Environment.NewLine}", tv.Text);
+        Assert.Equal (3, tv.Lines);
+        Assert.Equal (new (0, 1), tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.A));
+        Assert.Equal ($"This is the {Environment.NewLine}athird line.{Environment.NewLine}", tv.Text);
+        Assert.Equal (3, tv.Lines);
+        Assert.Equal (new (1, 1), tv.CursorPosition);
+
+        // Undo
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+        Assert.Equal ($"This is the {Environment.NewLine}third line.{Environment.NewLine}", tv.Text);
+        Assert.Equal (3, tv.Lines);
+        Assert.Equal (new (0, 1), tv.CursorPosition);
+        Assert.True (tv.IsDirty);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+
+        Assert.Equal (
+                      $"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.{Environment.NewLine}",
+                      tv.Text
+                     );
+        Assert.Equal (4, tv.Lines);
+        Assert.Equal (new (12, 2), tv.CursorPosition);
+        Assert.False (tv.IsDirty);
+
+        // Redo
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+        Assert.Equal ($"This is the {Environment.NewLine}third line.{Environment.NewLine}", tv.Text);
+        Assert.Equal (3, tv.Lines);
+        Assert.Equal (new (0, 1), tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+        Assert.Equal ($"This is the {Environment.NewLine}athird line.{Environment.NewLine}", tv.Text);
+        Assert.Equal (3, tv.Lines);
+        Assert.Equal (new (1, 1), tv.CursorPosition);
+
+        // Undo
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+        Assert.Equal ($"This is the {Environment.NewLine}third line.{Environment.NewLine}", tv.Text);
+        Assert.Equal (3, tv.Lines);
+        Assert.Equal (new (0, 1), tv.CursorPosition);
+        Assert.True (tv.IsDirty);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+
+        Assert.Equal (
+                      $"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.{Environment.NewLine}",
+                      tv.Text
+                     );
+        Assert.Equal (4, tv.Lines);
+        Assert.Equal (new (12, 2), tv.CursorPosition);
+        Assert.False (tv.IsDirty);
+
+        // Redo
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+        Assert.Equal ($"This is the {Environment.NewLine}third line.{Environment.NewLine}", tv.Text);
+        Assert.Equal (3, tv.Lines);
+        Assert.Equal (new (0, 1), tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+        Assert.Equal ($"This is the {Environment.NewLine}athird line.{Environment.NewLine}", tv.Text);
+        Assert.Equal (3, tv.Lines);
+        Assert.Equal (new (1, 1), tv.CursorPosition);
+    }
+
+    [Fact]
+    public void HistoryText_Undo_Redo_First_Line_Selected_Return_And_InsertText ()
+    {
+        var text = "This is the first line.\nThis is the second line.\nThis is the third line.";
+        var tv = new TextView { Text = text };
+
+        tv.SelectionStartColumn = 12;
+        tv.CursorPosition = new (17, 0);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Enter));
+
+        Assert.Equal (
+                      $"This is the {Environment.NewLine} line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (4, tv.Lines);
+        Assert.Equal (new (0, 1), tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.A));
+
+        Assert.Equal (
+                      $"This is the {Environment.NewLine}a line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (4, tv.Lines);
+        Assert.Equal (new (1, 1), tv.CursorPosition);
+
+        // Undo
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+
+        Assert.Equal (
+                      $"This is the {Environment.NewLine} line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (4, tv.Lines);
+        Assert.Equal (new (0, 1), tv.CursorPosition);
+        Assert.True (tv.IsDirty);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+
+        Assert.Equal (
+                      $"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (3, tv.Lines);
+        Assert.Equal (new (17, 0), tv.CursorPosition);
+        Assert.False (tv.IsDirty);
+
+        // Redo
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+
+        Assert.Equal (
+                      $"This is the {Environment.NewLine} line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (4, tv.Lines);
+        Assert.Equal (new (0, 1), tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+
+        Assert.Equal (
+                      $"This is the {Environment.NewLine}a line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (4, tv.Lines);
+        Assert.Equal (new (1, 1), tv.CursorPosition);
+
+        // Undo
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+
+        Assert.Equal (
+                      $"This is the {Environment.NewLine} line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (4, tv.Lines);
+        Assert.Equal (new (0, 1), tv.CursorPosition);
+        Assert.True (tv.IsDirty);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+
+        Assert.Equal (
+                      $"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (3, tv.Lines);
+        Assert.Equal (new (17, 0), tv.CursorPosition);
+        Assert.False (tv.IsDirty);
+
+        // Redo
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+
+        Assert.Equal (
+                      $"This is the {Environment.NewLine} line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (4, tv.Lines);
+        Assert.Equal (new (0, 1), tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+
+        Assert.Equal (
+                      $"This is the {Environment.NewLine}a line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (4, tv.Lines);
+        Assert.Equal (new (1, 1), tv.CursorPosition);
+    }
+
+    [Fact]
+    public void HistoryText_Undo_Redo_KillWordBackward ()
+    {
+        var text = "First line.\nSecond line.";
+        var tv = new TextView { Text = text };
+
+        Assert.True (tv.NewKeyDownEvent (Key.End.WithCtrl));
+        Assert.Equal ($"First line.{Environment.NewLine}Second line.", tv.Text);
+        Assert.Equal ("", tv.SelectedText);
+        Assert.Equal (2, tv.Lines);
+        Assert.Equal (new (12, 1), tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Backspace.WithCtrl));
+        Assert.Equal ($"First line.{Environment.NewLine}Second line", tv.Text);
+        Assert.Equal ("", tv.SelectedText);
+        Assert.Equal (2, tv.Lines);
+        Assert.Equal (new (11, 1), tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Backspace.WithCtrl));
+        Assert.Equal ($"First line.{Environment.NewLine}Second ", tv.Text);
+        Assert.Equal ("", tv.SelectedText);
+        Assert.Equal (2, tv.Lines);
+        Assert.Equal (new (7, 1), tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Backspace.WithCtrl));
+        Assert.Equal ($"First line.{Environment.NewLine}", tv.Text);
+        Assert.Equal ("", tv.SelectedText);
+        Assert.Equal (2, tv.Lines);
+        Assert.Equal (new (0, 1), tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Backspace.WithCtrl));
+        Assert.Equal ("First line.", tv.Text);
+        Assert.Equal ("", tv.SelectedText);
+        Assert.Equal (1, tv.Lines);
+        Assert.Equal (new (11, 0), tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Backspace.WithCtrl));
+        Assert.Equal ("First line", tv.Text);
+        Assert.Equal ("", tv.SelectedText);
+        Assert.Equal (1, tv.Lines);
+        Assert.Equal (new (10, 0), tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Backspace.WithCtrl));
+        Assert.Equal ("First ", tv.Text);
+        Assert.Equal ("", tv.SelectedText);
+        Assert.Equal (1, tv.Lines);
+        Assert.Equal (new (6, 0), tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Backspace.WithCtrl));
+        Assert.Equal ("", tv.Text);
+        Assert.Equal ("", tv.SelectedText);
+        Assert.Equal (1, tv.Lines);
+        Assert.Equal (Point.Empty, tv.CursorPosition);
+
+        // Undo
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+        Assert.Equal ("First ", tv.Text);
+        Assert.Equal (1, tv.Lines);
+        Assert.Equal (new (6, 0), tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+        Assert.Equal ("First line", tv.Text);
+        Assert.Equal (1, tv.Lines);
+        Assert.Equal (new (10, 0), tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+        Assert.Equal ("First line.", tv.Text);
+        Assert.Equal (1, tv.Lines);
+        Assert.Equal (new (11, 0), tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+        Assert.Equal ($"First line.{Environment.NewLine}", tv.Text);
+        Assert.Equal (2, tv.Lines);
+        Assert.Equal (new (0, 1), tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+        Assert.Equal ($"First line.{Environment.NewLine}Second ", tv.Text);
+        Assert.Equal (2, tv.Lines);
+        Assert.Equal (new (7, 1), tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+        Assert.Equal ($"First line.{Environment.NewLine}Second line", tv.Text);
+        Assert.Equal (2, tv.Lines);
+        Assert.Equal (new (11, 1), tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+        Assert.Equal ($"First line.{Environment.NewLine}Second line.", tv.Text);
+        Assert.Equal (2, tv.Lines);
+        Assert.Equal (new (12, 1), tv.CursorPosition);
+
+        // Redo
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+        Assert.Equal ($"First line.{Environment.NewLine}Second line", tv.Text);
+        Assert.Equal (2, tv.Lines);
+        Assert.Equal (new (11, 1), tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+        Assert.Equal ($"First line.{Environment.NewLine}Second ", tv.Text);
+        Assert.Equal (2, tv.Lines);
+        Assert.Equal (new (7, 1), tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+        Assert.Equal ($"First line.{Environment.NewLine}", tv.Text);
+        Assert.Equal (2, tv.Lines);
+        Assert.Equal (new (0, 1), tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+        Assert.Equal ("First line.", tv.Text);
+        Assert.Equal (1, tv.Lines);
+        Assert.Equal (new (11, 0), tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+        Assert.Equal ("First line", tv.Text);
+        Assert.Equal (1, tv.Lines);
+        Assert.Equal (new (10, 0), tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+        Assert.Equal ("First ", tv.Text);
+        Assert.Equal (1, tv.Lines);
+        Assert.Equal (new (6, 0), tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+        Assert.Equal ("", tv.Text);
+        Assert.Equal (1, tv.Lines);
+        Assert.Equal (Point.Empty, tv.CursorPosition);
+    }
+
+    [Fact]
+    public void HistoryText_Undo_Redo_KillWordForward ()
+    {
+        var text = "First line.\nSecond line.";
+        var tv = new TextView { Text = text };
+
+        Assert.True (tv.NewKeyDownEvent (Key.Delete.WithCtrl));
+        Assert.Equal ($"line.{Environment.NewLine}Second line.", tv.Text);
+        Assert.Equal ("", tv.SelectedText);
+        Assert.Equal (2, tv.Lines);
+        Assert.Equal (Point.Empty, tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Delete.WithCtrl));
+        Assert.Equal ($".{Environment.NewLine}Second line.", tv.Text);
+        Assert.Equal (2, tv.Lines);
+        Assert.Equal (Point.Empty, tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Delete.WithCtrl));
+        Assert.Equal ($"{Environment.NewLine}Second line.", tv.Text);
+        Assert.Equal (2, tv.Lines);
+        Assert.Equal (Point.Empty, tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Delete.WithCtrl));
+        Assert.Equal ("Second line.", tv.Text);
+        Assert.Equal (1, tv.Lines);
+        Assert.Equal (Point.Empty, tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Delete.WithCtrl));
+        Assert.Equal ("line.", tv.Text);
+        Assert.Equal (1, tv.Lines);
+        Assert.Equal (Point.Empty, tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Delete.WithCtrl));
+        Assert.Equal (".", tv.Text);
+        Assert.Equal (1, tv.Lines);
+        Assert.Equal (Point.Empty, tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Delete.WithCtrl));
+        Assert.Equal ("", tv.Text);
+        Assert.Equal (1, tv.Lines);
+        Assert.Equal (Point.Empty, tv.CursorPosition);
+
+        // Undo
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+        Assert.Equal (".", tv.Text);
+        Assert.Equal (1, tv.Lines);
+        Assert.Equal (Point.Empty, tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+        Assert.Equal ("line.", tv.Text);
+        Assert.Equal (1, tv.Lines);
+        Assert.Equal (Point.Empty, tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+        Assert.Equal ("Second line.", tv.Text);
+        Assert.Equal (1, tv.Lines);
+        Assert.Equal (Point.Empty, tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+        Assert.Equal ($"{Environment.NewLine}Second line.", tv.Text);
+        Assert.Equal (2, tv.Lines);
+        Assert.Equal (Point.Empty, tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+        Assert.Equal ($".{Environment.NewLine}Second line.", tv.Text);
+        Assert.Equal (2, tv.Lines);
+        Assert.Equal (Point.Empty, tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+        Assert.Equal ($"line.{Environment.NewLine}Second line.", tv.Text);
+        Assert.Equal (2, tv.Lines);
+        Assert.Equal (Point.Empty, tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+        Assert.Equal ($"First line.{Environment.NewLine}Second line.", tv.Text);
+        Assert.Equal (2, tv.Lines);
+        Assert.Equal (Point.Empty, tv.CursorPosition);
+
+        // Redo
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+        Assert.Equal ($"line.{Environment.NewLine}Second line.", tv.Text);
+        Assert.Equal (2, tv.Lines);
+        Assert.Equal (Point.Empty, tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+        Assert.Equal ($".{Environment.NewLine}Second line.", tv.Text);
+        Assert.Equal (2, tv.Lines);
+        Assert.Equal (Point.Empty, tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+        Assert.Equal ($"{Environment.NewLine}Second line.", tv.Text);
+        Assert.Equal (2, tv.Lines);
+        Assert.Equal (Point.Empty, tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+        Assert.Equal ("Second line.", tv.Text);
+        Assert.Equal (1, tv.Lines);
+        Assert.Equal (Point.Empty, tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+        Assert.Equal ("line.", tv.Text);
+        Assert.Equal (1, tv.Lines);
+        Assert.Equal (Point.Empty, tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+        Assert.Equal (".", tv.Text);
+        Assert.Equal (1, tv.Lines);
+        Assert.Equal (Point.Empty, tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+        Assert.Equal ("", tv.Text);
+        Assert.Equal (1, tv.Lines);
+        Assert.Equal (Point.Empty, tv.CursorPosition);
+    }
+
+    [Fact]
+    public void HistoryText_Undo_Redo_Multi_Line_Selected_All_Return_And_InsertText ()
+    {
+        var text = "This is the first line.\nThis is the second line.\nThis is the third line.";
+        var tv = new TextView { Text = text };
+
+        Assert.True (tv.NewKeyDownEvent (Key.End.WithCtrl.WithShift));
+
+        Assert.Equal (
+                      $"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (3, tv.Lines);
+        Assert.Equal (new (23, 2), tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Enter));
+        Assert.Equal ($"{Environment.NewLine}", tv.Text);
+        Assert.Equal (2, tv.Lines);
+        Assert.Equal (new (0, 1), tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.A));
+        Assert.Equal ($"{Environment.NewLine}a", tv.Text);
+        Assert.Equal (2, tv.Lines);
+        Assert.Equal (new (1, 1), tv.CursorPosition);
+
+        // Undo
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+        Assert.Equal ($"{Environment.NewLine}", tv.Text);
+        Assert.Equal (2, tv.Lines);
+        Assert.Equal (new (0, 1), tv.CursorPosition);
+        Assert.True (tv.IsDirty);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+
+        Assert.Equal (
+                      $"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (3, tv.Lines);
+        Assert.Equal (new (23, 2), tv.CursorPosition);
+        Assert.False (tv.IsDirty);
+
+        // Redo
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+        Assert.Equal ($"{Environment.NewLine}", tv.Text);
+        Assert.Equal (2, tv.Lines);
+        Assert.Equal (new (0, 1), tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+        Assert.Equal ($"{Environment.NewLine}a", tv.Text);
+        Assert.Equal (2, tv.Lines);
+        Assert.Equal (new (1, 1), tv.CursorPosition);
+
+        // Undo
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+        Assert.Equal ($"{Environment.NewLine}", tv.Text);
+        Assert.Equal (2, tv.Lines);
+        Assert.Equal (new (0, 1), tv.CursorPosition);
+        Assert.True (tv.IsDirty);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+
+        Assert.Equal (
+                      $"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (3, tv.Lines);
+        Assert.Equal (new (23, 2), tv.CursorPosition);
+        Assert.False (tv.IsDirty);
+
+        // Redo
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+        Assert.Equal ($"{Environment.NewLine}", tv.Text);
+        Assert.Equal (2, tv.Lines);
+        Assert.Equal (new (0, 1), tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+        Assert.Equal ($"{Environment.NewLine}a", tv.Text);
+        Assert.Equal (2, tv.Lines);
+        Assert.Equal (new (1, 1), tv.CursorPosition);
+    }
+
+    [Fact]
+    public void HistoryText_Undo_Redo_Multi_Line_Selected_DeleteCharLeft_All ()
+    {
+        var text = "This is the first line.\nThis is the second line.\nThis is the third line.";
+        var tv = new TextView { Text = text };
+
+        Assert.Equal (
+                      $"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (3, tv.Lines);
+        Assert.Equal (Point.Empty, tv.CursorPosition);
+        Assert.False (tv.IsDirty);
+        Assert.False (tv.HasHistoryChanges);
+
+        Assert.True (tv.NewKeyDownEvent (Key.End.WithCtrl.WithShift));
+
+        Assert.Equal (
+                      $"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+
+        Assert.Equal (
+                      $"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.",
+                      tv.SelectedText
+                     );
+        Assert.Equal (3, tv.Lines);
+        Assert.Equal (new (23, 2), tv.CursorPosition);
+        Assert.Equal (70 + Environment.NewLine.Length * 2, tv.SelectedLength);
+        Assert.False (tv.IsDirty);
+        Assert.False (tv.HasHistoryChanges);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Backspace));
+        Assert.Equal ("", tv.Text);
+        Assert.Equal ("", tv.SelectedText);
+        Assert.Equal (1, tv.Lines);
+        Assert.Equal (Point.Empty, tv.CursorPosition);
+        Assert.Equal (0, tv.SelectedLength);
+        Assert.True (tv.IsDirty);
+        Assert.True (tv.HasHistoryChanges);
+
+        // Undo
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+
+        Assert.Equal (
+                      $"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal ("", tv.SelectedText);
+        Assert.Equal (3, tv.Lines);
+        Assert.Equal (new (23, 2), tv.CursorPosition);
+        Assert.Equal (0, tv.SelectedLength);
+        Assert.False (tv.IsDirty);
+        Assert.True (tv.HasHistoryChanges);
+
+        // Redo
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+        Assert.Equal ("", tv.Text);
+        Assert.Equal ("", tv.SelectedText);
+        Assert.Equal (1, tv.Lines);
+        Assert.Equal (Point.Empty, tv.CursorPosition);
+        Assert.Equal (0, tv.SelectedLength);
+        Assert.True (tv.IsDirty);
+        Assert.True (tv.HasHistoryChanges);
+    }
+
+    [Fact]
+    public void HistoryText_Undo_Redo_Multi_Line_Selected_DeleteCharRight_All ()
+    {
+        var text = "This is the first line.\nThis is the second line.\nThis is the third line.";
+        var tv = new TextView { Text = text };
+
+        Assert.Equal (
+                      $"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (3, tv.Lines);
+        Assert.Equal (Point.Empty, tv.CursorPosition);
+        Assert.False (tv.IsDirty);
+        Assert.False (tv.HasHistoryChanges);
+
+        Assert.True (tv.NewKeyDownEvent (Key.End.WithCtrl.WithShift));
+
+        Assert.Equal (
+                      $"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+
+        Assert.Equal (
+                      $"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.",
+                      tv.SelectedText
+                     );
+        Assert.Equal (3, tv.Lines);
+        Assert.Equal (new (23, 2), tv.CursorPosition);
+        Assert.Equal (70 + Environment.NewLine.Length * 2, tv.SelectedLength);
+        Assert.False (tv.IsDirty);
+        Assert.False (tv.HasHistoryChanges);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Delete));
+        Assert.Equal ("", tv.Text);
+        Assert.Equal ("", tv.SelectedText);
+        Assert.Equal (1, tv.Lines);
+        Assert.Equal (Point.Empty, tv.CursorPosition);
+        Assert.Equal (0, tv.SelectedLength);
+        Assert.True (tv.IsDirty);
+        Assert.True (tv.HasHistoryChanges);
+
+        // Undo
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+
+        Assert.Equal (
+                      $"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal ("", tv.SelectedText);
+        Assert.Equal (3, tv.Lines);
+        Assert.Equal (new (23, 2), tv.CursorPosition);
+        Assert.Equal (0, tv.SelectedLength);
+        Assert.False (tv.IsDirty);
+        Assert.True (tv.HasHistoryChanges);
+
+        // Redo
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+        Assert.Equal ("", tv.Text);
+        Assert.Equal ("", tv.SelectedText);
+        Assert.Equal (1, tv.Lines);
+        Assert.Equal (Point.Empty, tv.CursorPosition);
+        Assert.Equal (0, tv.SelectedLength);
+        Assert.True (tv.IsDirty);
+        Assert.True (tv.HasHistoryChanges);
+    }
+
+    [Fact]
+    public void HistoryText_Undo_Redo_Multiline_Selected_Tab_BackTab ()
+    {
+        var text = "First line.\nSecond line.\nThird line.";
+        var tv = new TextView { Width = 80, Height = 5, Text = text };
+
+        tv.SelectionStartColumn = 6;
+        tv.CursorPosition = new (6, 2);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Tab));
+        Assert.Equal ("First \tline.", tv.Text);
+        Assert.Equal (1, tv.Lines);
+        Assert.Equal (new (7, 0), tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Tab.WithShift));
+        Assert.Equal ("First line.", tv.Text);
+        Assert.Equal (1, tv.Lines);
+        Assert.Equal (new (6, 0), tv.CursorPosition);
+
+        // Undo
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+        Assert.Equal ("First \tline.", tv.Text);
+        Assert.Equal (1, tv.Lines);
+        Assert.Equal (new (7, 0), tv.CursorPosition);
+        Assert.True (tv.IsDirty);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+        Assert.Equal ($"First line.{Environment.NewLine}Second line.{Environment.NewLine}Third line.", tv.Text);
+        Assert.Equal (3, tv.Lines);
+        Assert.Equal (new (6, 2), tv.CursorPosition);
+        Assert.False (tv.IsDirty);
+
+        // Redo
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+        Assert.Equal ("First \tline.", tv.Text);
+        Assert.Equal (1, tv.Lines);
+        Assert.Equal (new (7, 0), tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+        Assert.Equal ("First line.", tv.Text);
+        Assert.Equal (1, tv.Lines);
+        Assert.Equal (new (6, 0), tv.CursorPosition);
+    }
+
+    [Fact]
+    public void HistoryText_Undo_Redo_Multiline_Simples_Tab_BackTab ()
+    {
+        var text = "First line.\nSecond line.\nThird line.";
+        var tv = new TextView { Width = 80, Height = 5, Text = text };
+
+        Assert.True (tv.NewKeyDownEvent (Key.Tab));
+
+        Assert.Equal (
+                      $"\tFirst line.{Environment.NewLine}Second line.{Environment.NewLine}Third line.",
+                      tv.Text
+                     );
+        Assert.Equal (3, tv.Lines);
+        Assert.Equal (new (1, 0), tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Tab.WithShift));
+        Assert.Equal ($"First line.{Environment.NewLine}Second line.{Environment.NewLine}Third line.", tv.Text);
+        Assert.Equal (3, tv.Lines);
+        Assert.Equal (Point.Empty, tv.CursorPosition);
+
+        // Undo
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+
+        Assert.Equal (
+                      $"\tFirst line.{Environment.NewLine}Second line.{Environment.NewLine}Third line.",
+                      tv.Text
+                     );
+        Assert.Equal (3, tv.Lines);
+        Assert.Equal (new (1, 0), tv.CursorPosition);
+        Assert.True (tv.IsDirty);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+        Assert.Equal ($"First line.{Environment.NewLine}Second line.{Environment.NewLine}Third line.", tv.Text);
+        Assert.Equal (3, tv.Lines);
+        Assert.Equal (Point.Empty, tv.CursorPosition);
+        Assert.False (tv.IsDirty);
+
+        // Redo
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+
+        Assert.Equal (
+                      $"\tFirst line.{Environment.NewLine}Second line.{Environment.NewLine}Third line.",
+                      tv.Text
+                     );
+        Assert.Equal (3, tv.Lines);
+        Assert.Equal (new (1, 0), tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+        Assert.Equal ($"First line.{Environment.NewLine}Second line.{Environment.NewLine}Third line.", tv.Text);
+        Assert.Equal (3, tv.Lines);
+        Assert.Equal (Point.Empty, tv.CursorPosition);
+    }
+
+    [Fact]
+    public void HistoryText_Undo_Redo_Single_Line_Selected_Return ()
+    {
+        var text = "This is the first line.\nThis is the second line.\nThis is the third line.";
+        var tv = new TextView { Text = text };
+
+        tv.SelectionStartColumn = 12;
+        tv.CursorPosition = new (17, 0);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Enter));
+
+        Assert.Equal (
+                      $"This is the {Environment.NewLine} line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (4, tv.Lines);
+        Assert.Equal (new (0, 1), tv.CursorPosition);
+
+        // Undo
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+
+        Assert.Equal (
+                      $"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (3, tv.Lines);
+        Assert.Equal (new (17, 0), tv.CursorPosition);
+        Assert.False (tv.IsDirty);
+
+        // Redo
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+
+        Assert.Equal (
+                      $"This is the {Environment.NewLine} line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (4, tv.Lines);
+        Assert.Equal (new (0, 1), tv.CursorPosition);
+
+        // Undo
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+
+        Assert.Equal (
+                      $"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (3, tv.Lines);
+        Assert.Equal (new (17, 0), tv.CursorPosition);
+        Assert.False (tv.IsDirty);
+
+        // Redo
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+
+        Assert.Equal (
+                      $"This is the {Environment.NewLine} line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (4, tv.Lines);
+        Assert.Equal (new (0, 1), tv.CursorPosition);
+    }
+
+    [Fact]
+    public void HistoryText_Undo_Redo_Single_Second_Line_Selected_Return ()
+    {
+        var text = "This is the first line.\nThis is the second line.\nThis is the third line.";
+        var tv = new TextView { Text = text };
+
+        tv.SelectionStartColumn = 12;
+        tv.SelectionStartRow = 1;
+        tv.CursorPosition = new (18, 1);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Enter));
+
+        Assert.Equal (
+                      $"This is the first line.{Environment.NewLine}This is the {Environment.NewLine} line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (4, tv.Lines);
+        Assert.Equal (new (0, 2), tv.CursorPosition);
+
+        // Undo
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+
+        Assert.Equal (
+                      $"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (3, tv.Lines);
+        Assert.Equal (new (18, 1), tv.CursorPosition);
+        Assert.False (tv.IsDirty);
+
+        // Redo
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+
+        Assert.Equal (
+                      $"This is the first line.{Environment.NewLine}This is the {Environment.NewLine} line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (4, tv.Lines);
+        Assert.Equal (new (0, 2), tv.CursorPosition);
+
+        // Undo
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+
+        Assert.Equal (
+                      $"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (3, tv.Lines);
+        Assert.Equal (new (18, 1), tv.CursorPosition);
+        Assert.False (tv.IsDirty);
+
+        // Redo
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+
+        Assert.Equal (
+                      $"This is the first line.{Environment.NewLine}This is the {Environment.NewLine} line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (4, tv.Lines);
+        Assert.Equal (new (0, 2), tv.CursorPosition);
+    }
+
+    [Fact]
+    public void HistoryText_Undo_Redo_Single_Second_Line_Selected_Return_And_InsertText ()
+    {
+        var text = "This is the first line.\nThis is the second line.\nThis is the third line.";
+        var tv = new TextView { Text = text };
+
+        tv.SelectionStartColumn = 12;
+        tv.SelectionStartRow = 1;
+        tv.CursorPosition = new (18, 1);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Enter));
+
+        Assert.Equal (
+                      $"This is the first line.{Environment.NewLine}This is the {Environment.NewLine} line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (4, tv.Lines);
+        Assert.Equal (new (0, 2), tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.A));
+
+        Assert.Equal (
+                      $"This is the first line.{Environment.NewLine}This is the {Environment.NewLine}a line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (4, tv.Lines);
+        Assert.Equal (new (1, 2), tv.CursorPosition);
+
+        // Undo
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+
+        Assert.Equal (
+                      $"This is the first line.{Environment.NewLine}This is the {Environment.NewLine} line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (4, tv.Lines);
+        Assert.Equal (new (0, 2), tv.CursorPosition);
+        Assert.True (tv.IsDirty);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+
+        Assert.Equal (
+                      $"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (3, tv.Lines);
+        Assert.Equal (new (18, 1), tv.CursorPosition);
+        Assert.False (tv.IsDirty);
+
+        // Redo
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+
+        Assert.Equal (
+                      $"This is the first line.{Environment.NewLine}This is the {Environment.NewLine} line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (4, tv.Lines);
+        Assert.Equal (new (0, 2), tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+
+        Assert.Equal (
+                      $"This is the first line.{Environment.NewLine}This is the {Environment.NewLine}a line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (4, tv.Lines);
+        Assert.Equal (new (1, 2), tv.CursorPosition);
+
+        // Undo
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+
+        Assert.Equal (
+                      $"This is the first line.{Environment.NewLine}This is the {Environment.NewLine} line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (4, tv.Lines);
+        Assert.Equal (new (0, 2), tv.CursorPosition);
+        Assert.True (tv.IsDirty);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+
+        Assert.Equal (
+                      $"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (3, tv.Lines);
+        Assert.Equal (new (18, 1), tv.CursorPosition);
+        Assert.False (tv.IsDirty);
+
+        // Redo
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+
+        Assert.Equal (
+                      $"This is the first line.{Environment.NewLine}This is the {Environment.NewLine} line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (4, tv.Lines);
+        Assert.Equal (new (0, 2), tv.CursorPosition);
+
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+
+        Assert.Equal (
+                      $"This is the first line.{Environment.NewLine}This is the {Environment.NewLine}a line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (4, tv.Lines);
+        Assert.Equal (new (1, 2), tv.CursorPosition);
+    }
+
+    [Fact]
+    public void HistoryText_Undo_Redo_Three_Line_Selected_Return ()
+    {
+        var text = "This is the first line.\nThis is the second line.\nThis is the third line.";
+        var tv = new TextView { Text = text };
+
+        tv.SelectionStartColumn = 12;
+        tv.CursorPosition = new (17, 2);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Enter));
+        Assert.Equal ($"This is the {Environment.NewLine} line.", tv.Text);
+        Assert.Equal (2, tv.Lines);
+        Assert.Equal (new (0, 1), tv.CursorPosition);
+
+        // Undo
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+
+        Assert.Equal (
+                      $"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (3, tv.Lines);
+        Assert.Equal (new (17, 2), tv.CursorPosition);
+        Assert.False (tv.IsDirty);
+
+        // Redo
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+        Assert.Equal ($"This is the {Environment.NewLine} line.", tv.Text);
+        Assert.Equal (2, tv.Lines);
+        Assert.Equal (new (0, 1), tv.CursorPosition);
+
+        // Undo
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+
+        Assert.Equal (
+                      $"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (3, tv.Lines);
+        Assert.Equal (new (17, 2), tv.CursorPosition);
+        Assert.False (tv.IsDirty);
+
+        // Redo
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+        Assert.Equal ($"This is the {Environment.NewLine} line.", tv.Text);
+        Assert.Equal (2, tv.Lines);
+        Assert.Equal (new (0, 1), tv.CursorPosition);
+    }
+
+    [Fact]
+    public void HistoryText_Undo_Redo_Two_Line_Selected_Return ()
+    {
+        var text = "This is the first line.\nThis is the second line.\nThis is the third line.";
+        var tv = new TextView { Text = text };
+
+        tv.SelectionStartColumn = 12;
+        tv.CursorPosition = new (18, 1);
+
+        Assert.True (tv.NewKeyDownEvent (Key.Enter));
+
+        Assert.Equal (
+                      $"This is the {Environment.NewLine} line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (3, tv.Lines);
+        Assert.Equal (new (0, 1), tv.CursorPosition);
+
+        // Undo
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+
+        Assert.Equal (
+                      $"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (3, tv.Lines);
+        Assert.Equal (new (18, 1), tv.CursorPosition);
+        Assert.False (tv.IsDirty);
+
+        // Redo
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+
+        Assert.Equal (
+                      $"This is the {Environment.NewLine} line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (3, tv.Lines);
+        Assert.Equal (new (0, 1), tv.CursorPosition);
+
+        // Undo
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+
+        Assert.Equal (
+                      $"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (3, tv.Lines);
+        Assert.Equal (new (18, 1), tv.CursorPosition);
+        Assert.False (tv.IsDirty);
+
+        // Redo
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+
+        Assert.Equal (
+                      $"This is the {Environment.NewLine} line.{Environment.NewLine}This is the third line.",
+                      tv.Text
+                     );
+        Assert.Equal (3, tv.Lines);
+        Assert.Equal (new (0, 1), tv.CursorPosition);
+    }
+
+    [Fact]
+    public void HistoryText_Undo_Redo_ApplyCellsAttribute ()
+    {
+        var text = "This is the first line.\nThis is the second line.\nThis is the third line.";
+        var tv = new TextView { Text = text };
+
+        tv.SelectionStartColumn = 12;
+        tv.CursorPosition = new (18, 1);
+
+        if (Environment.NewLine.Length == 2)
+        {
+            Assert.Equal (31, tv.SelectedLength);
+        }
+        else
+        {
+            Assert.Equal (30, tv.SelectedLength);
+        }
+
+        Assert.Equal ($"first line.{Environment.NewLine}This is the second", tv.SelectedText);
+        Assert.Equal ($"first line.{Environment.NewLine}This is the second", Cell.ToString (tv.SelectedCellsList));
+        Assert.Equal (new (18, 1), tv.CursorPosition);
+        Assert.False (tv.IsDirty);
+
+        AssertNullAttribute ();
+
+        tv.ApplyCellsAttribute (new (Color.Red, Color.Green));
+
+        AssertRedGreenAttribute ();
+
+        Assert.Equal (0, tv.SelectedLength);
+        Assert.Equal ("", tv.SelectedText);
+        Assert.Equal ($"first line.{Environment.NewLine}This is the second", Cell.ToString (tv.SelectedCellsList));
+        Assert.Equal (new (18, 1), tv.CursorPosition);
+        Assert.True (tv.IsDirty);
+
+        // Undo
+        Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
+
+        AssertNullAttribute ();
+
+        Assert.Equal (12, tv.SelectionStartColumn);
+        Assert.Equal (0, tv.SelectionStartRow);
+        Assert.Equal (0, tv.SelectedLength);
+        Assert.Equal ("", tv.SelectedText);
+        Assert.Empty (tv.SelectedCellsList);
+        Assert.Equal (new (12, 0), tv.CursorPosition);
+        Assert.False (tv.IsDirty);
+
+        // Redo
+        Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
+
+        AssertRedGreenAttribute ();
+
+        Assert.Equal (12, tv.SelectionStartColumn);
+        Assert.Equal (0, tv.SelectionStartRow);
+        Assert.Equal (0, tv.SelectedLength);
+        Assert.Equal ("", tv.SelectedText);
+        Assert.Empty (tv.SelectedCellsList);
+        Assert.Equal (new (12, 0), tv.CursorPosition);
+        Assert.True (tv.IsDirty);
+
+        void AssertNullAttribute ()
+        {
+            tv.GetRegion (out List<List<Cell>> region, 0, 12, 1, 18);
+
+            foreach (List<Cell> cells in region)
+            {
+                foreach (Cell cell in cells)
+                {
+                    Assert.Null (cell.Attribute);
+                }
+            }
+        }
+
+        void AssertRedGreenAttribute ()
+        {
+            tv.GetRegion (out List<List<Cell>> region, 0, 12, 1, 18);
+
+            foreach (List<Cell> cells in region)
+            {
+                foreach (Cell cell in cells)
+                {
+                    Assert.Equal ("[Red,Green,None]", cell.Attribute.ToString ());
+                }
+            }
+        }
+    }
+
+    [Fact]
+    public void Internal_Tests ()
+    {
+        var txt = "This is a text.";
+        List<Cell> txtRunes = Cell.StringToCells (txt);
+        Assert.Equal (txt.Length, txtRunes.Count);
+        Assert.Equal ('T', txtRunes [0].Rune.Value);
+        Assert.Equal ('h', txtRunes [1].Rune.Value);
+        Assert.Equal ('i', txtRunes [2].Rune.Value);
+        Assert.Equal ('s', txtRunes [3].Rune.Value);
+        Assert.Equal (' ', txtRunes [4].Rune.Value);
+        Assert.Equal ('i', txtRunes [5].Rune.Value);
+        Assert.Equal ('s', txtRunes [6].Rune.Value);
+        Assert.Equal (' ', txtRunes [7].Rune.Value);
+        Assert.Equal ('a', txtRunes [8].Rune.Value);
+        Assert.Equal (' ', txtRunes [9].Rune.Value);
+        Assert.Equal ('t', txtRunes [10].Rune.Value);
+        Assert.Equal ('e', txtRunes [11].Rune.Value);
+        Assert.Equal ('x', txtRunes [12].Rune.Value);
+        Assert.Equal ('t', txtRunes [13].Rune.Value);
+        Assert.Equal ('.', txtRunes [^1].Rune.Value);
+
+        var col = 0;
+        Assert.True (TextModel.SetCol (ref col, 80, 79));
+        Assert.False (TextModel.SetCol (ref col, 80, 80));
+        Assert.Equal (79, col);
+
+        var start = 0;
+        var x = 8;
+        Assert.Equal (8, TextModel.GetColFromX (txtRunes, start, x));
+        Assert.Equal ('a', txtRunes [start + x].Rune.Value);
+        start = 1;
+        x = 7;
+        Assert.Equal (7, TextModel.GetColFromX (txtRunes, start, x));
+        Assert.Equal ('a', txtRunes [start + x].Rune.Value);
+
+        Assert.Equal ((15, 15), TextModel.DisplaySize (txtRunes));
+        Assert.Equal ((6, 6), TextModel.DisplaySize (txtRunes, 1, 7));
+
+        Assert.Equal (0, TextModel.CalculateLeftColumn (txtRunes, 0, 7, 8));
+        Assert.Equal (1, TextModel.CalculateLeftColumn (txtRunes, 0, 8, 8));
+        Assert.Equal (2, TextModel.CalculateLeftColumn (txtRunes, 0, 9, 8));
+
+        var tm = new TextModel ();
+        tm.AddLine (0, Cell.StringToCells ("This is first line."));
+        tm.AddLine (1, Cell.StringToCells ("This is last line."));
+        Assert.Equal ((new Point (2, 0), true), tm.FindNextText ("is", out bool gaveFullTurn));
+        Assert.False (gaveFullTurn);
+        Assert.Equal ((new Point (5, 0), true), tm.FindNextText ("is", out gaveFullTurn));
+        Assert.False (gaveFullTurn);
+        Assert.Equal ((new Point (2, 1), true), tm.FindNextText ("is", out gaveFullTurn));
+        Assert.False (gaveFullTurn);
+        Assert.Equal ((new Point (5, 1), true), tm.FindNextText ("is", out gaveFullTurn));
+        Assert.False (gaveFullTurn);
+        Assert.Equal ((new Point (2, 0), true), tm.FindNextText ("is", out gaveFullTurn));
+        Assert.True (gaveFullTurn);
+        tm.ResetContinuousFind (Point.Empty);
+        Assert.Equal ((new Point (5, 1), true), tm.FindPreviousText ("is", out gaveFullTurn));
+        Assert.False (gaveFullTurn);
+        Assert.Equal ((new Point (2, 1), true), tm.FindPreviousText ("is", out gaveFullTurn));
+        Assert.False (gaveFullTurn);
+        Assert.Equal ((new Point (5, 0), true), tm.FindPreviousText ("is", out gaveFullTurn));
+        Assert.False (gaveFullTurn);
+        Assert.Equal ((new Point (2, 0), true), tm.FindPreviousText ("is", out gaveFullTurn));
+        Assert.False (gaveFullTurn);
+        Assert.Equal ((new Point (5, 1), true), tm.FindPreviousText ("is", out gaveFullTurn));
+        Assert.True (gaveFullTurn);
+
+        Assert.Equal ((new Point (9, 1), true), tm.ReplaceAllText ("is", false, false, "really"));
+        Assert.Equal (Cell.StringToCells ("Threally really first line."), tm.GetLine (0));
+        Assert.Equal (Cell.StringToCells ("Threally really last line."), tm.GetLine (1));
+        tm = new ();
+        tm.AddLine (0, Cell.StringToCells ("This is first line."));
+        tm.AddLine (1, Cell.StringToCells ("This is last line."));
+        Assert.Equal ((new Point (5, 1), true), tm.ReplaceAllText ("is", false, true, "really"));
+        Assert.Equal (Cell.StringToCells ("This really first line."), tm.GetLine (0));
+        Assert.Equal (Cell.StringToCells ("This really last line."), tm.GetLine (1));
+    }
+
+    [Fact]
+    public void LeftColumn_Add_One_If_Text_Length_Is_Equal_To_Width ()
+    {
+        var tv = new TextView { Width = 10, Text = "1234567890" };
+
+        Assert.Equal (Point.Empty, tv.CursorPosition);
+        Assert.Equal (0, tv.LeftColumn);
+
+        tv.CursorPosition = new (9, 0);
+        Assert.Equal (new (9, 0), tv.CursorPosition);
+        Assert.Equal (0, tv.LeftColumn);
+
+        Assert.True (tv.NewKeyDownEvent (Key.CursorRight));
+        tv.CursorPosition = new (10, 0);
+        Assert.Equal (new (10, 0), tv.CursorPosition);
+        Assert.Equal (1, tv.LeftColumn);
+    }
+
+    [Fact]
+    public void LoadFile_Throws_If_File_Is_Empty ()
+    {
+        var result = false;
+        var tv = new TextView ();
+        Assert.Throws<ArgumentException> (() => result = tv.Load (""));
+        Assert.False (result);
+    }
+
+    [Fact]
+    public void LoadFile_Throws_If_File_Is_Null ()
+    {
+        var result = false;
+        var tv = new TextView ();
+        Assert.Throws<ArgumentNullException> (() => result = tv.Load ((string)null));
+        Assert.False (result);
+    }
+
+    [Fact]
+    public void LoadFile_Throws_If_File_Not_Exist ()
+    {
+        var result = false;
+        var tv = new TextView ();
+        Assert.Throws<FileNotFoundException> (() => result = tv.Load ("blabla"));
+        Assert.False (result);
+    }
+
+    [Fact]
+    public void LoadStream_CRLF ()
+    {
+        var text = "This is the first line.\r\nThis is the second line.\r\n";
+        var tv = new TextView ();
+        tv.Load (new MemoryStream (Encoding.ASCII.GetBytes (text)));
+
+        Assert.Equal (
+                      $"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}",
+                      tv.Text
+                     );
+    }
+
+    [Fact]
+    public void LoadStream_IsDirty ()
+    {
+        var text = "Testing";
+
+        using (var stream = new MemoryStream ())
+        {
+            var writer = new StreamWriter (stream);
+            writer.Write (text);
+            writer.Flush ();
+            stream.Position = 0;
+
+            var tv = new TextView ();
+            tv.Load (stream);
+
+            Assert.Equal (7, text.Length);
+            Assert.Equal (text.Length, tv.Text.Length);
+            Assert.Equal (text, tv.Text);
+            Assert.False (tv.IsDirty);
+        }
+    }
+
+    [Fact]
+    public void LoadStream_IsDirty_With_Null_On_The_Text ()
+    {
+        var text = "Test\0ing";
+
+        using (var stream = new MemoryStream ())
+        {
+            var writer = new StreamWriter (stream);
+            writer.Write (text);
+            writer.Flush ();
+            stream.Position = 0;
+
+            var tv = new TextView ();
+            tv.Load (stream);
+
+            Assert.Equal (8, text.Length);
+            Assert.Equal (text.Length, tv.Text.Length);
+            Assert.Equal (8, text.Length);
+            Assert.Equal (8, tv.Text.Length);
+            Assert.Equal (text, tv.Text);
+            Assert.False (tv.IsDirty);
+            Assert.Equal ((Rune)'\u2400', ((Rune)tv.Text [4]).MakePrintable ());
+        }
+    }
+
+    [Fact]
+    public void LoadStream_LF ()
+    {
+        var text = "This is the first line.\nThis is the second line.\n";
+        var tv = new TextView ();
+        tv.Load (new MemoryStream (Encoding.ASCII.GetBytes (text)));
+
+        Assert.Equal (
+                      $"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}",
+                      tv.Text
+                     );
+    }
+
+    [Fact]
+    public void LoadStream_Stream_Is_Empty ()
+    {
+        var tv = new TextView ();
+        tv.Load (new MemoryStream ());
+        Assert.Equal ("", tv.Text);
+    }
+
+    [Fact]
+    public void LoadStream_Throws_If_Stream_Is_Null ()
+    {
+        var tv = new TextView ();
+        Assert.Throws<ArgumentNullException> (() => tv.Load ((Stream)null));
+    }
+
+    [Fact]
+    public void ReplaceAllText_Does_Not_Throw_Exception ()
+    {
+        var textToFind = "hello! hello!";
+        var textToReplace = "hello!";
+        var tv = new TextView { Width = 20, Height = 3, Text = textToFind };
+
+        Exception exception = Record.Exception (() => tv.ReplaceAllText (textToFind, false, false, textToReplace));
+        Assert.Null (exception);
+        Assert.Equal (textToReplace, tv.Text);
+    }
+
+    [Fact]
+    public void StringToRunes_Slipts_CRLF ()
+    {
+        var text = "This is the first line.\r\nThis is the second line.\r\n";
+        var tv = new TextView ();
+        tv.Text = text;
+
+        Assert.Equal (
+                      $"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}",
+                      tv.Text
+                     );
+    }
+
+    [Fact]
+    public void StringToRunes_Slipts_LF ()
+    {
+        var text = "This is the first line.\nThis is the second line.\n";
+        var tv = new TextView ();
+        tv.Text = text;
+
+        Assert.Equal (
+                      $"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}",
+                      tv.Text
+                     );
+    }
+
+    [Fact]
+    public void TextView_MultiLine_But_Without_Tabs ()
+    {
+        var view = new TextView ();
+
+        // the default for TextView
+        Assert.True (view.Multiline);
+
+        view.AllowsTab = false;
+        Assert.False (view.AllowsTab);
+
+        Assert.True (view.Multiline);
+    }
+
+    [Fact]
+    public void WordBackward_WordForward_Limits_Return_Null ()
+    {
+        var model = new TextModel ();
+        model.LoadString ("Test");
+        (int col, int row)? newPos = model.WordBackward (0, 0, false);
+        Assert.Null (newPos);
+        newPos = model.WordForward (4, 0, false);
+        Assert.Null (newPos);
+    }
+
+    [Fact]
+    public void WordWrap_Gets_Sets ()
+    {
+        var tv = new TextView { WordWrap = true };
+        Assert.True (tv.WordWrap);
+        tv.WordWrap = false;
+        Assert.False (tv.WordWrap);
+    }
+
+    [Fact]
+    public void WordWrap_True_Text_Always_Returns_Unwrapped ()
+    {
+        var text = "This is the first line.\nThis is the second line.\n";
+        var tv = new TextView { Width = 10 };
+        tv.Text = text;
+
+        Assert.Equal (
+                      $"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}",
+                      tv.Text
+                     );
+        tv.WordWrap = true;
+
+        Assert.Equal (
+                      $"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}",
+                      tv.Text
+                     );
+    }
+
+    [Fact]
+    public void HotKey_Command_SetsFocus ()
+    {
+        var view = new TextView ();
+
+        view.CanFocus = true;
+        Assert.False (view.HasFocus);
+        view.InvokeCommand (Command.HotKey);
+        Assert.True (view.HasFocus);
+    }
+
+    [Fact]
+    public void HotKey_Command_Does_Not_Accept ()
+    {
+        var view = new TextView ();
+        var accepted = false;
+        view.Accepting += OnAccept;
+        view.InvokeCommand (Command.HotKey);
+
+        Assert.False (accepted);
+
+        return;
+
+        void OnAccept (object sender, CommandEventArgs e) { accepted = true; }
+    }
+
+    [Theory]
+    [InlineData (false, 1)]
+    [InlineData (true, 1)]
+    public void Accepted_Command_Raises_Accepted_Regardles_Of_AllowsReturn (bool allowsReturn, int expectedAcceptEvents)
+    {
+        var view = new TextView
+        {
+            AllowsReturn = allowsReturn
+        };
+
+        var acceptedEvents = 0;
+        view.Accepting += Accept;
+        view.InvokeCommand (Command.Accept);
+        Assert.Equal (expectedAcceptEvents, acceptedEvents);
+
+        return;
+
+        void Accept (object sender, CommandEventArgs e) { acceptedEvents++; }
+    }
+
+    [Theory]
+    [InlineData (false, 1)]
+    [InlineData (true, 0)]
+    public void Enter_Key_Fires_Accepted_BasedOn_AllowsReturn (bool allowsReturn, int expectedAccepts)
+    {
+        var view = new TextView
+        {
+            Multiline = allowsReturn
+        };
+
+        var accepted = 0;
+        view.Accepting += Accept;
+        view.NewKeyDownEvent (Key.Enter);
+        Assert.Equal (expectedAccepts, accepted);
+
+        return;
+
+        void Accept (object sender, CommandEventArgs e) { accepted++; }
+    }
+
+    [Theory]
+    [InlineData (false, 1)]
+    [InlineData (true, 0)]
+    public void Enter_Key_Fires_Accepted_BasedOn_Multiline (bool multiline, int expectedAccepts)
+    {
+        var view = new TextView
+        {
+            Multiline = multiline
+        };
+
+        var accepted = 0;
+        view.Accepting += Accept;
+        view.NewKeyDownEvent (Key.Enter);
+        Assert.Equal (expectedAccepts, accepted);
+
+        return;
+
+        void Accept (object sender, CommandEventArgs e) { accepted++; }
+    }
+
+    [Fact]
+    public void Space_Key_Types_Space ()
+    {
+        var view = new TextView ();
+
+        view.NewKeyDownEvent (Key.Space);
+
+        Assert.Equal (" ", view.Text);
+    }
+
+    [Theory]
+    [InlineData (false, false, 1, 1)]
+    [InlineData (false, true, 1, 0)]
+    [InlineData (true, false, 0, 0)]
+    [InlineData (true, true, 0, 0)]
+    public void Accepted_Event_Handled_Prevents_Default_Button_Accept (bool multiline, bool handleAccept, int expectedAccepts, int expectedButtonAccepts)
+    {
+        var superView = new Window ();
+
+        var tv = new TextView
+        {
+            Multiline = multiline
+        };
+
+        var button = new Button
+        {
+            IsDefault = true
+        };
+
+        superView.Add (tv, button);
+
+        var buttonAccept = 0;
+        button.Accepting += ButtonAccept;
+
+        var textViewAccept = 0;
+        tv.Accepting += TextViewAccept;
+
+        tv.SetFocus ();
+        Assert.True (tv.HasFocus);
+
+        superView.NewKeyDownEvent (Key.Enter);
+        Assert.Equal (expectedAccepts, textViewAccept);
+        Assert.Equal (expectedButtonAccepts, buttonAccept);
+
+        button.SetFocus ();
+        superView.NewKeyDownEvent (Key.Enter);
+        Assert.Equal (expectedAccepts, textViewAccept);
+        Assert.Equal (expectedButtonAccepts + 1, buttonAccept);
+
+        return;
+
+        void TextViewAccept (object sender, CommandEventArgs e)
+        {
+            textViewAccept++;
+            e.Handled = handleAccept;
+        }
+
+        void ButtonAccept (object sender, CommandEventArgs e) { buttonAccept++; }
+    }
+
+    [Theory]
+    [InlineData (true, 0)]
+    [InlineData (false, 1)]
+    public void Accepted_No_Handler_Enables_Default_Button_Accept (bool multiline, int expectedButtonAccept)
+    {
+        var superView = new Window ();
+
+        var tv = new TextView
+        {
+            Multiline = multiline
+        };
+
+        var button = new Button
+        {
+            IsDefault = true
+        };
+
+        superView.Add (tv, button);
+
+        var buttonAccept = 0;
+        button.Accepting += ButtonAccept;
+
+        tv.SetFocus ();
+        Assert.True (tv.HasFocus);
+
+        superView.NewKeyDownEvent (Key.Enter);
+        Assert.Equal (expectedButtonAccept, buttonAccept);
+
+        button.SetFocus ();
+        superView.NewKeyDownEvent (Key.Enter);
+        Assert.Equal (expectedButtonAccept + 1, buttonAccept);
+
+        return;
+
+        void ButtonAccept (object sender, CommandEventArgs e) { buttonAccept++; }
+    }
+
+    [Fact]
+    public void Autocomplete_Popup_Added_To_SuperView_On_Init ()
+    {
+        View superView = new ()
+        {
+            CanFocus = true
+        };
+
+        TextView t = new ();
+
+        superView.Add (t);
+        Assert.Single (superView.SubViews);
+
+        superView.BeginInit ();
+        superView.EndInit ();
+
+        Assert.Equal (2, superView.SubViews.Count);
+    }
+
+    [Fact]
+    public void Autocomplete__Added_To_SuperView_On_Add ()
+    {
+        View superView = new ()
+        {
+            CanFocus = true,
+            Id = "superView"
+        };
+
+        superView.BeginInit ();
+        superView.EndInit ();
+        Assert.Empty (superView.SubViews);
+
+        TextView t = new ()
+        {
+            Id = "t"
+        };
+
+        superView.Add (t);
+
+        Assert.Equal (2, superView.SubViews.Count);
+    }
+
+    [Fact]
+    public void Autocomplete_Visible_False_By_Default ()
+    {
+        View superView = new ()
+        {
+            CanFocus = true
+        };
+
+        TextView t = new ();
+
+        superView.Add (t);
+        superView.BeginInit ();
+        superView.EndInit ();
+
+        Assert.Equal (2, superView.SubViews.Count);
+
+        Assert.True (t.Visible);
+        Assert.False (t.Autocomplete.Visible);
+    }
+
+    [Fact]
+    public void Right_CursorAtEnd_WithSelection_ShouldClearSelection ()
+    {
+        var tv = new TextView
+        {
+            Text = "Hello"
+        };
+        tv.SetFocus ();
+
+        tv.NewKeyDownEvent (Key.End.WithShift);
+        Assert.Equal (5, tv.CursorPosition.X);
+
+        // When there is selected text and the cursor is at the end of the text field
+        Assert.Equal ("Hello", tv.SelectedText);
+
+        // Pressing right should not move focus, instead it should clear selection
+        Assert.True (tv.NewKeyDownEvent (Key.CursorRight));
+        Assert.Empty (tv.SelectedText);
+
+        // Now that the selection is cleared another right keypress should move focus
+        Assert.False (tv.NewKeyDownEvent (Key.CursorRight));
+    }
+
+    [Fact]
+    public void Left_CursorAtStart_WithSelection_ShouldClearSelection ()
+    {
+        var tv = new TextView
+        {
+            Text = "Hello"
+        };
+        tv.SetFocus ();
+
+        tv.NewKeyDownEvent (Key.CursorRight);
+        tv.NewKeyDownEvent (Key.CursorRight);
+
+        Assert.Equal (2, tv.CursorPosition.X);
+
+        Assert.True (tv.NewKeyDownEvent (Key.CursorLeft.WithShift));
+        Assert.True (tv.NewKeyDownEvent (Key.CursorLeft.WithShift));
+
+        // When there is selected text and the cursor is at the start of the text field
+        Assert.Equal ("He", tv.SelectedText);
+
+        // Pressing left should not move focus, instead it should clear selection
+        Assert.True (tv.NewKeyDownEvent (Key.CursorLeft));
+        Assert.Empty (tv.SelectedText);
+
+        // When clearing selected text with left the cursor should be at the start of the selection
+        Assert.Equal (0, tv.CursorPosition.X);
+
+        // Now that the selection is cleared another left keypress should move focus
+        Assert.False (tv.NewKeyDownEvent (Key.CursorLeft));
+    }
+
+    [Fact]
+    public void Equals_True ()
+    {
+        var c1 = new Cell ();
+        var c2 = new Cell ();
+        Assert.True (c1.Equals (c2));
+        Assert.True (c2.Equals (c1));
+
+        c1.Rune = new ('a');
+        c1.Attribute = new ();
+        c2.Rune = new ('a');
+        c2.Attribute = new ();
+        Assert.True (c1.Equals (c2));
+        Assert.True (c2.Equals (c1));
+    }
+
+    [Fact]
+    public void Cell_LoadCells_Without_Scheme_Is_Never_Null ()
+    {
+        List<Cell> cells = new ()
+        {
+            new () { Rune = new ('T') },
+            new () { Rune = new ('e') },
+            new () { Rune = new ('s') },
+            new () { Rune = new ('t') }
+        };
+        TextView tv = CreateTextView ();
+        var top = new Toplevel ();
+        top.Add (tv);
+        tv.Load (cells);
+
+        for (var i = 0; i < tv.Lines; i++)
+        {
+            List<Cell> line = tv.GetLine (i);
+
+            foreach (Cell c in line)
+            {
+                Assert.NotNull (c.Attribute);
+            }
+        }
+    }
+
+    [Theory]
+    [InlineData ("", false, "")]
+    [InlineData ("", true, "")]
+    [InlineData (" ", false, "")]
+    [InlineData (" ", true, "")]
+    [InlineData ("  ", false, "")]
+    [InlineData ("  ", true, "")]
+    [InlineData ("a", false, "")]
+    [InlineData ("a", true, "")]
+    [InlineData ("a ", false, "")]
+    [InlineData ("a ", true, "")]
+    [InlineData (" a ", false, "a ", "")]
+    [InlineData (" a ", true, "a ", "")]
+    [InlineData ("  H1  ", false, "H1  ", "")]
+    [InlineData ("  H1  ", true, "H1  ", "")]
+    [InlineData ("a$", false, "$", "")]
+    [InlineData ("a$", true, "$", "")]
+    [InlineData ("a$#", false, "$#", "")]
+    [InlineData ("a$#", true, "$#", "#", "")]
+    [InlineData ("  a$#  ", false, "a$#  ", "$#  ", "")]
+    [InlineData ("  a$#  ", true, "a$#  ", "$#  ", "#  ", "")]
+    [InlineData ("\"$schema\"", false, "schema\"", "\"", "")]
+    [InlineData ("\"$schema\"", true, "$schema\"", "schema\"", "\"", "")]
+    [InlineData ("\": \"", false, "\"", "")]
+    [InlineData ("\": \"", true, "\"", "")]
+    [InlineData ("\"$schema\": \"", false, "schema\": \"", "\": \"", "\"", "")]
+    [InlineData ("\"$schema\": \"", true, "$schema\": \"", "schema\": \"", "\": \"", "\"", "")]
+    [InlineData ("1ºªA", false, "")]
+    [InlineData ("1ºªA", true, "")]
+    [InlineData (
+                    "ºª\\!\"#%&/()?'«»*;,:._-@{[]}]|$=+´`~^<>£€¨",
+                    false,
+                    "\\!\"#%&/()?'«»*;,:._-@{[]}]|$=+´`~^<>£€¨",
+                    "")]
+    [InlineData (
+                    "ºª\\!\"#%&/()?'«»*;,:._-@{[]}]|$=+´`~^<>£€¨",
+                    true,
+                    "\\!\"#%&/()?'«»*;,:._-@{[]}]|$=+´`~^<>£€¨",
+                    "|$=+´`~^<>£€¨",
+                    "")]
+    [InlineData (
+                    "{\r\n  \"$schema\": \"https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json\"\r\n}",
+                    false,
+                    "\r\n  \"$schema\": \"https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json\"\r\n}",
+                    "  \"$schema\": \"https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json\"\r\n}",
+                    "\"$schema\": \"https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json\"\r\n}",
+                    "schema\": \"https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json\"\r\n}",
+                    "\": \"https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json\"\r\n}",
+                    "\"https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json\"\r\n}",
+                    "https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json\"\r\n}",
+                    "://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json\"\r\n}",
+                    "gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json\"\r\n}",
+                    "-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json\"\r\n}",
+                    "cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json\"\r\n}",
+                    ".github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json\"\r\n}",
+                    "github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json\"\r\n}",
+                    ".io/Terminal.GuiV2Docs/schemas/tui-config-schema.json\"\r\n}",
+                    "io/Terminal.GuiV2Docs/schemas/tui-config-schema.json\"\r\n}",
+                    "/Terminal.GuiV2Docs/schemas/tui-config-schema.json\"\r\n}",
+                    "Terminal.GuiV2Docs/schemas/tui-config-schema.json\"\r\n}",
+                    ".GuiV2Docs/schemas/tui-config-schema.json\"\r\n}",
+                    "GuiV2Docs/schemas/tui-config-schema.json\"\r\n}",
+                    "/schemas/tui-config-schema.json\"\r\n}",
+                    "schemas/tui-config-schema.json\"\r\n}",
+                    "/tui-config-schema.json\"\r\n}",
+                    "tui-config-schema.json\"\r\n}",
+                    "-config-schema.json\"\r\n}",
+                    "config-schema.json\"\r\n}",
+                    "-schema.json\"\r\n}",
+                    "schema.json\"\r\n}",
+                    ".json\"\r\n}",
+                    "json\"\r\n}",
+                    "\"\r\n}",
+                    "\r\n}",
+                    "}",
+                    "")]
+    [InlineData (
+                    "{\r\n  \"$schema\": \"https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json\"\r\n}",
+                    true,
+                    "\r\n  \"$schema\": \"https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json\"\r\n}",
+                    "  \"$schema\": \"https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json\"\r\n}",
+                    "\"$schema\": \"https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json\"\r\n}",
+                    "$schema\": \"https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json\"\r\n}",
+                    "schema\": \"https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json\"\r\n}",
+                    "\": \"https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json\"\r\n}",
+                    "\"https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json\"\r\n}",
+                    "https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json\"\r\n}",
+                    "://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json\"\r\n}",
+                    "gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json\"\r\n}",
+                    "-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json\"\r\n}",
+                    "cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json\"\r\n}",
+                    ".github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json\"\r\n}",
+                    "github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json\"\r\n}",
+                    ".io/Terminal.GuiV2Docs/schemas/tui-config-schema.json\"\r\n}",
+                    "io/Terminal.GuiV2Docs/schemas/tui-config-schema.json\"\r\n}",
+                    "/Terminal.GuiV2Docs/schemas/tui-config-schema.json\"\r\n}",
+                    "Terminal.GuiV2Docs/schemas/tui-config-schema.json\"\r\n}",
+                    ".GuiV2Docs/schemas/tui-config-schema.json\"\r\n}",
+                    "GuiV2Docs/schemas/tui-config-schema.json\"\r\n}",
+                    "/schemas/tui-config-schema.json\"\r\n}",
+                    "schemas/tui-config-schema.json\"\r\n}",
+                    "/tui-config-schema.json\"\r\n}",
+                    "tui-config-schema.json\"\r\n}",
+                    "-config-schema.json\"\r\n}",
+                    "config-schema.json\"\r\n}",
+                    "-schema.json\"\r\n}",
+                    "schema.json\"\r\n}",
+                    ".json\"\r\n}",
+                    "json\"\r\n}",
+                    "\"\r\n}",
+                    "\r\n}",
+                    "}",
+                    "")]
+    public void WordForward_WordWrap_False_True (string text, bool useSameRuneType, params string [] expectedText)
+    {
+        TextView tv = CreateTextView ();
+        tv.UseSameRuneTypeForWords = useSameRuneType;
+
+        ProcessDeleteWithCtrl ();
+
+        tv.WordWrap = true;
+        ProcessDeleteWithCtrl ();
+
+        void ProcessDeleteWithCtrl ()
+        {
+            tv.Text = text;
+            var idx = 0;
+
+            while (!string.IsNullOrEmpty (tv.Text))
+            {
+                tv.NewKeyDownEvent (Key.Delete.WithCtrl);
+                Assert.Equal (expectedText [idx].Replace ("\r\n", Environment.NewLine), tv.Text);
+                idx++;
+            }
+        }
+    }
+
+    [Theory]
+    [InlineData ("", false, "")]
+    [InlineData ("", true, "")]
+    [InlineData (" ", false, "")]
+    [InlineData (" ", true, "")]
+    [InlineData ("  ", false, "")]
+    [InlineData ("  ", true, "")]
+    [InlineData ("a", false, "")]
+    [InlineData ("a", true, "")]
+    [InlineData ("a ", false, "")]
+    [InlineData ("a ", true, "")]
+    [InlineData (" a ", false, " ", "")]
+    [InlineData (" a ", true, " ", "")]
+    [InlineData ("  H1  ", false, "  ", "")]
+    [InlineData ("  H1  ", true, "  ", "")]
+    [InlineData ("a$", false, "a", "")]
+    [InlineData ("a$", true, "a", "")]
+    [InlineData ("a$#", false, "a", "")]
+    [InlineData ("a$#", true, "a$", "a", "")]
+    [InlineData ("  a$#  ", false, "  a", "  ", "")]
+    [InlineData ("  a$#  ", true, "  a$", "  a", "  ", "")]
+    [InlineData ("\"$schema\"", false, "\"$schema", "\"$", "")]
+    [InlineData ("\"$schema\"", true, "\"$schema", "\"$", "\"", "")]
+    [InlineData ("\"$schema\": \"", false, "\"$schema\": ", "\"$schema", "\"$", "")]
+    [InlineData ("\"$schema\": \"", true, "\"$schema\": ", "\"$schema", "\"$", "\"", "")]
+    [InlineData ("1ºªA", false, "")]
+    [InlineData ("1ºªA", true, "")]
+    [InlineData (
+                    "ºª\\!\"#%&/()?'«»*;,:._-@{[]}]|$=+´`~^<>£€¨",
+                    false,
+                    "ºª",
+                    "")]
+    [InlineData (
+                    "ºª\\!\"#%&/()?'«»*;,:._-@{[]}]|$=+´`~^<>£€¨",
+                    true,
+                    "ºª\\!\"#%&/()?'«»*;,:._-@{[]}]",
+                    "ºª",
+                    "")]
+    [InlineData (
+                    "{\r\n  \"$schema\": \"https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json\"\r\n}",
+                    false,
+                    "{\r\n  \"$schema\": \"https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json\"\r\n",
+                    "{\r\n  \"$schema\": \"https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json\"",
+                    "{\r\n  \"$schema\": \"https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json",
+                    "{\r\n  \"$schema\": \"https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.",
+                    "{\r\n  \"$schema\": \"https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema",
+                    "{\r\n  \"$schema\": \"https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-",
+                    "{\r\n  \"$schema\": \"https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config",
+                    "{\r\n  \"$schema\": \"https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-",
+                    "{\r\n  \"$schema\": \"https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui",
+                    "{\r\n  \"$schema\": \"https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/",
+                    "{\r\n  \"$schema\": \"https://gui-cs.github.io/Terminal.GuiV2Docs/schemas",
+                    "{\r\n  \"$schema\": \"https://gui-cs.github.io/Terminal.GuiV2Docs/",
+                    "{\r\n  \"$schema\": \"https://gui-cs.github.io/Terminal.GuiV2Docs",
+                    "{\r\n  \"$schema\": \"https://gui-cs.github.io/Terminal.",
+                    "{\r\n  \"$schema\": \"https://gui-cs.github.io/Terminal",
+                    "{\r\n  \"$schema\": \"https://gui-cs.github.io/",
+                    "{\r\n  \"$schema\": \"https://gui-cs.github.io",
+                    "{\r\n  \"$schema\": \"https://gui-cs.github.",
+                    "{\r\n  \"$schema\": \"https://gui-cs.github",
+                    "{\r\n  \"$schema\": \"https://gui-cs.",
+                    "{\r\n  \"$schema\": \"https://gui-cs",
+                    "{\r\n  \"$schema\": \"https://gui-",
+                    "{\r\n  \"$schema\": \"https://gui",
+                    "{\r\n  \"$schema\": \"https://",
+                    "{\r\n  \"$schema\": \"https",
+                    "{\r\n  \"$schema\": \"",
+                    "{\r\n  \"$schema\": ",
+                    "{\r\n  \"$schema",
+                    "{\r\n  \"$",
+                    "{\r\n  ",
+                    "{\r\n",
+                    "{",
+                    "")]
+    [InlineData (
+                    "{\r\n  \"$schema\": \"https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json\"\r\n}",
+                    true,
+                    "{\r\n  \"$schema\": \"https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json\"\r\n",
+                    "{\r\n  \"$schema\": \"https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json\"",
+                    "{\r\n  \"$schema\": \"https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json",
+                    "{\r\n  \"$schema\": \"https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.",
+                    "{\r\n  \"$schema\": \"https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema",
+                    "{\r\n  \"$schema\": \"https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-",
+                    "{\r\n  \"$schema\": \"https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config",
+                    "{\r\n  \"$schema\": \"https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-",
+                    "{\r\n  \"$schema\": \"https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui",
+                    "{\r\n  \"$schema\": \"https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/",
+                    "{\r\n  \"$schema\": \"https://gui-cs.github.io/Terminal.GuiV2Docs/schemas",
+                    "{\r\n  \"$schema\": \"https://gui-cs.github.io/Terminal.GuiV2Docs/",
+                    "{\r\n  \"$schema\": \"https://gui-cs.github.io/Terminal.GuiV2Docs",
+                    "{\r\n  \"$schema\": \"https://gui-cs.github.io/Terminal.",
+                    "{\r\n  \"$schema\": \"https://gui-cs.github.io/Terminal",
+                    "{\r\n  \"$schema\": \"https://gui-cs.github.io/",
+                    "{\r\n  \"$schema\": \"https://gui-cs.github.io",
+                    "{\r\n  \"$schema\": \"https://gui-cs.github.",
+                    "{\r\n  \"$schema\": \"https://gui-cs.github",
+                    "{\r\n  \"$schema\": \"https://gui-cs.",
+                    "{\r\n  \"$schema\": \"https://gui-cs",
+                    "{\r\n  \"$schema\": \"https://gui-",
+                    "{\r\n  \"$schema\": \"https://gui",
+                    "{\r\n  \"$schema\": \"https://",
+                    "{\r\n  \"$schema\": \"https",
+                    "{\r\n  \"$schema\": \"",
+                    "{\r\n  \"$schema\": ",
+                    "{\r\n  \"$schema",
+                    "{\r\n  \"$",
+                    "{\r\n  \"",
+                    "{\r\n  ",
+                    "{\r\n",
+                    "{",
+                    "")]
+    public void WordBackward_WordWrap_False_True (string text, bool useSameRuneType, params string [] expectedText)
+    {
+        TextView tv = CreateTextView ();
+        tv.UseSameRuneTypeForWords = useSameRuneType;
+
+        ProcessBackspaceWithCtrl ();
+
+        tv.WordWrap = true;
+        ProcessBackspaceWithCtrl ();
+
+        void ProcessBackspaceWithCtrl ()
+        {
+            tv.Text = text;
+            tv.MoveEnd ();
+            var idx = 0;
+
+            while (!string.IsNullOrEmpty (tv.Text))
+            {
+                tv.NewKeyDownEvent (Key.Backspace.WithCtrl);
+                Assert.Equal (expectedText [idx].Replace ("\r\n", Environment.NewLine), tv.Text);
+                idx++;
+            }
+        }
+    }
+
+    [Theory]
+    [InlineData ("", 0, false, "")]
+    [InlineData ("", 0, true, "")]
+    [InlineData ("a", 0, false, "a")]
+    [InlineData ("a", 0, true, "a")]
+    [InlineData ("a:", 0, false, "a")]
+    [InlineData ("a:", 0, true, "a")]
+    [InlineData ("a:", 1, false, ":")]
+    [InlineData ("a:", 1, true, ":")]
+    [InlineData ("a ", 0, false, "a ")]
+    [InlineData ("a ", 0, true, "a")]
+    [InlineData ("a ", 1, false, "a ")]
+    [InlineData ("a ", 1, true, "a")]
+    [InlineData ("a b", 0, false, "a ")]
+    [InlineData ("a b", 0, true, "a")]
+    [InlineData ("a b", 1, false, "a ")]
+    [InlineData ("a b", 1, true, "a")]
+    [InlineData ("a b ", 2, false, "b ")]
+    [InlineData ("a b ", 2, true, "b")]
+    [InlineData ("a b ", 3, false, "b ")]
+    [InlineData ("a b ", 3, true, "b")]
+    [InlineData (" a b ", 0, false, " ")]
+    [InlineData (" a b ", 0, true, " ")]
+    [InlineData (" a  b ", 2, false, "  ")]
+    [InlineData (" a  b ", 2, true, "  ")]
+    [InlineData (" a  b ", 3, false, "  ")]
+    [InlineData (" a  b ", 3, true, "  ")]
+    [InlineData (" H1$&#2you ", 2, false, "H1")]
+    [InlineData (" H1$&#2you ", 2, true, "H1")]
+    [InlineData (" H1$&#2you ", 3, false, "$&#")]
+    [InlineData (" H1$&#2you ", 3, true, "$&#")]
+    [InlineData (" H1$&#2you ", 4, false, "$&#")]
+    [InlineData (" H1$&#2you ", 4, true, "$&#")]
+    [InlineData (" H1$&#2you ", 5, false, "$&#")]
+    [InlineData (" H1$&#2you ", 5, true, "$&#")]
+    [InlineData (" H1$&#2you ", 6, false, "2you ")]
+    [InlineData (" H1$&#2you ", 6, true, "2you")]
+    public void ProcessDoubleClickSelection_False_True (string text, int col, bool selectWordOnly, string expectedText)
+    {
+        TextView tv = CreateTextView ();
+        tv.Text = text;
+        tv.SelectWordOnlyOnDoubleClick = selectWordOnly;
+
+        Assert.True (tv.NewMouseEvent (new () { Position = new (col, 0), Flags = MouseFlags.Button1DoubleClicked }));
+        Assert.Equal (expectedText, tv.SelectedText);
+    }
+
+    private TextView CreateTextView () { return new () { Width = 30, Height = 10 }; }
+}

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.