Selaa lähdekoodia

Reorganize TextView event handlers by logical functionality

Event handlers moved from TextView.EventHandlers.cs to their logical locations:

- Initialization handlers (TextView_Initialized, TextView_SuperViewChanged, TextView_ViewportChanged, TextView_LayoutComplete, Model_LinesLoaded) -> TextView.Core.cs

- History handler (HistoryText_ChangeText) -> TextView.Insert.cs

- Context menu handler (ContextMenu_KeyChanged) -> TextView.ContextMenu.cs

- Removed TextView.EventHandlers.cs

Build successful, all 163 tests pass
Tig 3 viikkoa sitten
vanhempi
sitoutus
4fe8df38cc

+ 0 - 0
Terminal.Gui/Views/TextInput/TextView/PARTIAL-SPLIT-PLAN.md


+ 151 - 0
Terminal.Gui/Views/TextInput/TextView/TextView.Clipboard.cs

@@ -0,0 +1,151 @@
+namespace Terminal.Gui.Views;
+
+public partial class TextView
+{
+    private void SetClipboard (string text)
+    {
+        if (text is { })
+        {
+            Clipboard.Contents = text;
+        }
+    }
+    /// <summary>Copy the selected text to the clipboard contents.</summary>
+    public void Copy ()
+    {
+        SetWrapModel ();
+
+        if (IsSelecting)
+        {
+            _copiedText = GetRegion (out _copiedCellsList);
+            SetClipboard (_copiedText);
+            _copyWithoutSelection = false;
+        }
+        else
+        {
+            List<Cell> currentLine = GetCurrentLine ();
+            _copiedCellsList.Add (currentLine);
+            _copiedText = Cell.ToString (currentLine);
+            SetClipboard (_copiedText);
+            _copyWithoutSelection = true;
+        }
+
+        UpdateWrapModel ();
+        DoNeededAction ();
+    }
+
+    /// <summary>Cut the selected text to the clipboard contents.</summary>
+    public void Cut ()
+    {
+        SetWrapModel ();
+        _copiedText = GetRegion (out _copiedCellsList);
+        SetClipboard (_copiedText);
+
+        if (!_isReadOnly)
+        {
+            ClearRegion ();
+
+            _historyText.Add (
+                              [new (GetCurrentLine ())],
+                              CursorPosition,
+                              TextEditingLineStatus.Replaced
+                             );
+        }
+
+        UpdateWrapModel ();
+        IsSelecting = false;
+        DoNeededAction ();
+        OnContentsChanged ();
+    }
+
+    /// <summary>Paste the clipboard contents into the current selected position.</summary>
+    public void Paste ()
+    {
+        if (_isReadOnly)
+        {
+            return;
+        }
+
+        SetWrapModel ();
+        string? contents = Clipboard.Contents;
+
+        if (_copyWithoutSelection && contents!.FirstOrDefault (x => x is '\n' or '\r') == 0)
+        {
+            List<Cell> runeList = contents is null ? [] : Cell.ToCellList (contents);
+            List<Cell> currentLine = GetCurrentLine ();
+
+            _historyText.Add ([new (currentLine)], CursorPosition);
+
+            List<List<Cell>> addedLine = [new (currentLine), runeList];
+
+            _historyText.Add (
+                              [.. addedLine],
+                              CursorPosition,
+                              TextEditingLineStatus.Added
+                             );
+
+            _model.AddLine (CurrentRow, runeList);
+            CurrentRow++;
+
+            _historyText.Add (
+                              [new (GetCurrentLine ())],
+                              CursorPosition,
+                              TextEditingLineStatus.Replaced
+                             );
+
+            SetNeedsDraw ();
+            OnContentsChanged ();
+        }
+        else
+        {
+            if (IsSelecting)
+            {
+                ClearRegion ();
+            }
+
+            _copyWithoutSelection = false;
+            InsertAllText (contents!, true);
+
+            if (IsSelecting)
+            {
+                _historyText.ReplaceLast (
+                                          [new (GetCurrentLine ())],
+                                          CursorPosition,
+                                          TextEditingLineStatus.Original
+                                         );
+            }
+
+            SetNeedsDraw ();
+        }
+
+        UpdateWrapModel ();
+        IsSelecting = false;
+        DoNeededAction ();
+    }
+
+    private void ProcessCopy ()
+    {
+        ResetColumnTrack ();
+        Copy ();
+    }
+
+    private void ProcessCut ()
+    {
+        ResetColumnTrack ();
+        Cut ();
+    }
+
+    private void ProcessPaste ()
+    {
+        ResetColumnTrack ();
+
+        if (_isReadOnly)
+        {
+            return;
+        }
+
+        Paste ();
+    }
+
+
+    private void AppendClipboard (string text) { Clipboard.Contents += text; }
+}

+ 46 - 0
Terminal.Gui/Views/TextInput/TextView/TextView.ContextMenu.cs

@@ -0,0 +1,46 @@
+using System.Globalization;
+
+namespace Terminal.Gui.Views;
+
+/// <summary>Context menu functionality</summary>
+public partial class TextView
+{
+    private PopoverMenu CreateContextMenu ()
+    {
+        PopoverMenu menu = new (
+                                new List<View>
+                                {
+                                    new MenuItem (this, Command.SelectAll, Strings.ctxSelectAll),
+                                    new MenuItem (this, Command.DeleteAll, Strings.ctxDeleteAll),
+                                    new MenuItem (this, Command.Copy, Strings.ctxCopy),
+                                    new MenuItem (this, Command.Cut, Strings.ctxCut),
+                                    new MenuItem (this, Command.Paste, Strings.ctxPaste),
+                                    new MenuItem (this, Command.Undo, Strings.ctxUndo),
+                                    new MenuItem (this, Command.Redo, Strings.ctxRedo)
+                                });
+
+        menu.KeyChanged += ContextMenu_KeyChanged;
+
+        return menu;
+    }
+
+    private void ShowContextMenu (Point? mousePosition)
+    {
+        if (!Equals (_currentCulture, Thread.CurrentThread.CurrentUICulture))
+        {
+            _currentCulture = Thread.CurrentThread.CurrentUICulture;
+        }
+
+        if (mousePosition is null)
+        {
+            mousePosition = ViewportToScreen (new Point (CursorPosition.X, CursorPosition.Y));
+        }
+
+        ContextMenu?.MakeVisible (mousePosition);
+    }
+
+    private void ContextMenu_KeyChanged (object? sender, KeyChangedEventArgs e) 
+    { 
+        KeyBindings.Replace (e.OldKey, e.NewKey); 
+    }
+}

+ 661 - 0
Terminal.Gui/Views/TextInput/TextView/TextView.Core.cs

@@ -0,0 +1,661 @@
+using System.Globalization;
+
+namespace Terminal.Gui.Views;
+
+/// <summary>Core functionality - Fields, Constructor, and fundamental properties</summary>
+public partial class TextView
+{
+    #region Fields
+
+    private readonly HistoryText _historyText = new ();
+    private bool _allowsReturn = true;
+    private bool _allowsTab = true;
+    private bool _clickWithSelecting;
+
+    // The column we are tracking, or -1 if we are not tracking any column
+    private int _columnTrack = -1;
+    private bool _continuousFind;
+    private bool _copyWithoutSelection;
+    private string? _currentCaller;
+    private CultureInfo? _currentCulture;
+    private bool _isButtonShift;
+    private bool _isButtonReleased;
+    private bool _isDrawing;
+    private bool _isReadOnly;
+    private bool _lastWasKill;
+    private int _leftColumn;
+    private TextModel _model = new ();
+    private bool _multiline = true;
+    private Dim? _savedHeight;
+    private int _selectionStartColumn, _selectionStartRow;
+    private bool _shiftSelecting;
+    private int _tabWidth = 4;
+    private int _topRow;
+    private bool _wordWrap;
+    private WordWrapManager? _wrapManager;
+    private bool _wrapNeeded;
+
+    private string? _copiedText;
+    private List<List<Cell>> _copiedCellsList = [];
+
+    #endregion
+
+    #region Constructor
+
+    /// <summary>
+    ///     Initializes a <see cref="TextView"/> on the specified area, with dimensions controlled with the X, Y, Width
+    ///     and Height properties.
+    /// </summary>
+    public TextView ()
+    {
+        CanFocus = true;
+        CursorVisibility = CursorVisibility.Default;
+        Used = true;
+
+        // By default, disable hotkeys (in case someone sets Title)
+        base.HotKeySpecifier = new ('\xffff');
+
+        _model.LinesLoaded += Model_LinesLoaded!;
+        _historyText.ChangeText += HistoryText_ChangeText!;
+
+        Initialized += TextView_Initialized!;
+
+        SuperViewChanged += TextView_SuperViewChanged!;
+
+        SubViewsLaidOut += TextView_LayoutComplete;
+
+        // Things this view knows how to do
+
+        // Note - NewLine is only bound to Enter if Multiline is true
+        AddCommand (Command.NewLine, ctx => ProcessEnterKey (ctx));
+
+        AddCommand (
+                    Command.PageDown,
+                    () =>
+                    {
+                        ProcessPageDown ();
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.PageDownExtend,
+                    () =>
+                    {
+                        ProcessPageDownExtend ();
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.PageUp,
+                    () =>
+                    {
+                        ProcessPageUp ();
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.PageUpExtend,
+                    () =>
+                    {
+                        ProcessPageUpExtend ();
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (Command.Down, () => ProcessMoveDown ());
+
+        AddCommand (
+                    Command.DownExtend,
+                    () =>
+                    {
+                        ProcessMoveDownExtend ();
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (Command.Up, () => ProcessMoveUp ());
+
+        AddCommand (
+                    Command.UpExtend,
+                    () =>
+                    {
+                        ProcessMoveUpExtend ();
+
+                        return true;
+                    }
+                   );
+        AddCommand (Command.Right, () => ProcessMoveRight ());
+
+        AddCommand (
+                    Command.RightExtend,
+                    () =>
+                    {
+                        ProcessMoveRightExtend ();
+
+                        return true;
+                    }
+                   );
+        AddCommand (Command.Left, () => ProcessMoveLeft ());
+
+        AddCommand (
+                    Command.LeftExtend,
+                    () =>
+                    {
+                        ProcessMoveLeftExtend ();
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.DeleteCharLeft,
+                    () =>
+                    {
+                        ProcessDeleteCharLeft ();
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.LeftStart,
+                    () =>
+                    {
+                        ProcessMoveLeftStart ();
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.LeftStartExtend,
+                    () =>
+                    {
+                        ProcessMoveLeftStartExtend ();
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.DeleteCharRight,
+                    () =>
+                    {
+                        ProcessDeleteCharRight ();
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.RightEnd,
+                    () =>
+                    {
+                        ProcessMoveEndOfLine ();
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.RightEndExtend,
+                    () =>
+                    {
+                        ProcessMoveRightEndExtend ();
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.CutToEndLine,
+                    () =>
+                    {
+                        KillToEndOfLine ();
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.CutToStartLine,
+                    () =>
+                    {
+                        KillToLeftStart ();
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.Paste,
+                    () =>
+                    {
+                        ProcessPaste ();
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.ToggleExtend,
+                    () =>
+                    {
+                        ToggleSelecting ();
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.Copy,
+                    () =>
+                    {
+                        ProcessCopy ();
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.Cut,
+                    () =>
+                    {
+                        ProcessCut ();
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.WordLeft,
+                    () =>
+                    {
+                        ProcessMoveWordBackward ();
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.WordLeftExtend,
+                    () =>
+                    {
+                        ProcessMoveWordBackwardExtend ();
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.WordRight,
+                    () =>
+                    {
+                        ProcessMoveWordForward ();
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.WordRightExtend,
+                    () =>
+                    {
+                        ProcessMoveWordForwardExtend ();
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.KillWordForwards,
+                    () =>
+                    {
+                        ProcessKillWordForward ();
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.KillWordBackwards,
+                    () =>
+                    {
+                        ProcessKillWordBackward ();
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.End,
+                    () =>
+                    {
+                        MoveBottomEnd ();
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.EndExtend,
+                    () =>
+                    {
+                        MoveBottomEndExtend ();
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.Start,
+                    () =>
+                    {
+                        MoveTopHome ();
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.StartExtend,
+                    () =>
+                    {
+                        MoveTopHomeExtend ();
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.SelectAll,
+                    () =>
+                    {
+                        ProcessSelectAll ();
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.ToggleOverwrite,
+                    () =>
+                    {
+                        ProcessSetOverwrite ();
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.EnableOverwrite,
+                    () =>
+                    {
+                        SetOverwrite (true);
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.DisableOverwrite,
+                    () =>
+                    {
+                        SetOverwrite (false);
+
+                        return true;
+                    }
+                   );
+        AddCommand (Command.Tab, () => ProcessTab ());
+        AddCommand (Command.BackTab, () => ProcessBackTab ());
+
+        AddCommand (
+                    Command.Undo,
+                    () =>
+                    {
+                        Undo ();
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.Redo,
+                    () =>
+                    {
+                        Redo ();
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.DeleteAll,
+                    () =>
+                    {
+                        DeleteAll ();
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.Context,
+                    () =>
+                    {
+                        ShowContextMenu (null);
+
+                        return true;
+                    }
+                   );
+
+        AddCommand (
+                    Command.Open,
+                    () =>
+                    {
+                        PromptForColors ();
+
+                        return true;
+                    });
+
+        // Default keybindings for this view
+        KeyBindings.Remove (Key.Space);
+
+        KeyBindings.Remove (Key.Enter);
+        KeyBindings.Add (Key.Enter, Multiline ? Command.NewLine : Command.Accept);
+
+        KeyBindings.Add (Key.PageDown, Command.PageDown);
+        KeyBindings.Add (Key.V.WithCtrl, Command.PageDown);
+
+        KeyBindings.Add (Key.PageDown.WithShift, Command.PageDownExtend);
+
+        KeyBindings.Add (Key.PageUp, Command.PageUp);
+
+        KeyBindings.Add (Key.PageUp.WithShift, Command.PageUpExtend);
+
+        KeyBindings.Add (Key.N.WithCtrl, Command.Down);
+        KeyBindings.Add (Key.CursorDown, Command.Down);
+
+        KeyBindings.Add (Key.CursorDown.WithShift, Command.DownExtend);
+
+        KeyBindings.Add (Key.P.WithCtrl, Command.Up);
+        KeyBindings.Add (Key.CursorUp, Command.Up);
+
+        KeyBindings.Add (Key.CursorUp.WithShift, Command.UpExtend);
+
+        KeyBindings.Add (Key.F.WithCtrl, Command.Right);
+        KeyBindings.Add (Key.CursorRight, Command.Right);
+
+        KeyBindings.Add (Key.CursorRight.WithShift, Command.RightExtend);
+
+        KeyBindings.Add (Key.B.WithCtrl, Command.Left);
+        KeyBindings.Add (Key.CursorLeft, Command.Left);
+
+        KeyBindings.Add (Key.CursorLeft.WithShift, Command.LeftExtend);
+
+        KeyBindings.Add (Key.Backspace, Command.DeleteCharLeft);
+
+        KeyBindings.Add (Key.Home, Command.LeftStart);
+
+        KeyBindings.Add (Key.Home.WithShift, Command.LeftStartExtend);
+
+        KeyBindings.Add (Key.Delete, Command.DeleteCharRight);
+        KeyBindings.Add (Key.D.WithCtrl, Command.DeleteCharRight);
+
+        KeyBindings.Add (Key.End, Command.RightEnd);
+        KeyBindings.Add (Key.E.WithCtrl, Command.RightEnd);
+
+        KeyBindings.Add (Key.End.WithShift, Command.RightEndExtend);
+
+        KeyBindings.Add (Key.K.WithCtrl, Command.CutToEndLine); // kill-to-end
+
+        KeyBindings.Add (Key.Delete.WithCtrl.WithShift, Command.CutToEndLine); // kill-to-end
+
+        KeyBindings.Add (Key.Backspace.WithCtrl.WithShift, Command.CutToStartLine); // kill-to-start
+
+        KeyBindings.Add (Key.Y.WithCtrl, Command.Paste); // Control-y, yank
+        KeyBindings.Add (Key.Space.WithCtrl, Command.ToggleExtend);
+
+        KeyBindings.Add (Key.C.WithCtrl, Command.Copy);
+
+        KeyBindings.Add (Key.W.WithCtrl, Command.Cut); // Move to Unix?
+        KeyBindings.Add (Key.X.WithCtrl, Command.Cut);
+
+        KeyBindings.Add (Key.CursorLeft.WithCtrl, Command.WordLeft);
+
+        KeyBindings.Add (Key.CursorLeft.WithCtrl.WithShift, Command.WordLeftExtend);
+
+        KeyBindings.Add (Key.CursorRight.WithCtrl, Command.WordRight);
+
+        KeyBindings.Add (Key.CursorRight.WithCtrl.WithShift, Command.WordRightExtend);
+        KeyBindings.Add (Key.Delete.WithCtrl, Command.KillWordForwards); // kill-word-forwards
+        KeyBindings.Add (Key.Backspace.WithCtrl, Command.KillWordBackwards); // kill-word-backwards
+
+        KeyBindings.Add (Key.End.WithCtrl, Command.End);
+        KeyBindings.Add (Key.End.WithCtrl.WithShift, Command.EndExtend);
+        KeyBindings.Add (Key.Home.WithCtrl, Command.Start);
+        KeyBindings.Add (Key.Home.WithCtrl.WithShift, Command.StartExtend);
+        KeyBindings.Add (Key.A.WithCtrl, Command.SelectAll);
+        KeyBindings.Add (Key.InsertChar, Command.ToggleOverwrite);
+        KeyBindings.Add (Key.Tab, Command.Tab);
+        KeyBindings.Add (Key.Tab.WithShift, Command.BackTab);
+
+        KeyBindings.Add (Key.Z.WithCtrl, Command.Undo);
+        KeyBindings.Add (Key.R.WithCtrl, Command.Redo);
+
+        KeyBindings.Add (Key.G.WithCtrl, Command.DeleteAll);
+        KeyBindings.Add (Key.D.WithCtrl.WithShift, Command.DeleteAll);
+
+        KeyBindings.Add (Key.L.WithCtrl, Command.Open);
+
+#if UNIX_KEY_BINDINGS
+        KeyBindings.Add (Key.C.WithAlt, Command.Copy);
+        KeyBindings.Add (Key.B.WithAlt, Command.WordLeft);
+        KeyBindings.Add (Key.W.WithAlt, Command.Cut);
+        KeyBindings.Add (Key.V.WithAlt, Command.PageUp);
+        KeyBindings.Add (Key.F.WithAlt, Command.WordRight);
+        KeyBindings.Add (Key.K.WithAlt, Command.CutToStartLine); // kill-to-start
+#endif
+
+        _currentCulture = Thread.CurrentThread.CurrentUICulture;
+    }
+
+    #endregion
+
+    #region Initialization and Configuration
+
+    /// <summary>
+    ///     Configures the ScrollBars to work with the modern View scrolling system.
+    /// </summary>
+    private void ConfigureScrollBars ()
+    {
+        // Subscribe to ViewportChanged to sync internal scroll fields
+        ViewportChanged += TextView_ViewportChanged;
+
+        // Vertical ScrollBar: AutoShow enabled by default as per requirements
+        VerticalScrollBar.AutoShow = true;
+
+        // Horizontal ScrollBar: AutoShow tracks WordWrap as per requirements
+        HorizontalScrollBar.AutoShow = !WordWrap;
+    }
+
+    private void TextView_Initialized (object sender, EventArgs e)
+    {
+        if (Autocomplete.HostControl is null)
+        {
+            Autocomplete.HostControl = this;
+        }
+
+        ContextMenu = CreateContextMenu ();
+        App?.Popover?.Register (ContextMenu);
+        KeyBindings.Add (ContextMenu.Key, Command.Context);
+
+        // Configure ScrollBars to use modern View scrolling infrastructure
+        ConfigureScrollBars ();
+
+        OnContentsChanged ();
+    }
+
+    private void TextView_SuperViewChanged (object sender, SuperViewChangedEventArgs e)
+    {
+        if (e.SuperView is { })
+        {
+            if (Autocomplete.HostControl is null)
+            {
+                Autocomplete.HostControl = this;
+            }
+        }
+        else
+        {
+            Autocomplete.HostControl = null;
+        }
+    }
+
+    private void TextView_ViewportChanged (object? sender, DrawEventArgs e)
+    {
+        // Sync internal scroll position fields with Viewport
+        // Only update if values actually changed to prevent infinite loops
+        if (_topRow != Viewport.Y)
+        {
+            _topRow = Viewport.Y;
+        }
+
+        if (_leftColumn != Viewport.X)
+        {
+            _leftColumn = Viewport.X;
+        }
+    }
+
+    private void TextView_LayoutComplete (object? sender, LayoutEventArgs e)
+    {
+        WrapTextModel ();
+        UpdateContentSize ();
+        Adjust ();
+    }
+
+    private void Model_LinesLoaded (object sender, EventArgs e)
+    {
+        // This call is not needed. Model_LinesLoaded gets invoked when
+        // model.LoadString (value) is called. LoadString is called from one place
+        // (Text.set) and historyText.Clear() is called immediately after.
+        // If this call happens, HistoryText_ChangeText will get called multiple times
+        // when Text is set, which is wrong.
+        //historyText.Clear (Text);
+
+        if (!_multiline && !IsInitialized)
+        {
+            CurrentColumn = Text.GetRuneCount ();
+            _leftColumn = CurrentColumn > Viewport.Width + 1 ? CurrentColumn - Viewport.Width + 1 : 0;
+        }
+    }
+
+    #endregion
+}

+ 641 - 0
Terminal.Gui/Views/TextInput/TextView/TextView.Delete.cs

@@ -0,0 +1,641 @@
+namespace Terminal.Gui.Views;
+
+public partial class TextView
+{
+    /// <summary>Deletes all text.</summary>
+    public void DeleteAll ()
+    {
+        if (Lines == 0)
+        {
+            return;
+        }
+
+        _selectionStartColumn = 0;
+        _selectionStartRow = 0;
+        MoveBottomEndExtend ();
+        DeleteCharLeft ();
+        SetNeedsDraw ();
+    }
+
+    /// <summary>Deletes all the selected or a single character at left from the position of the cursor.</summary>
+    public void DeleteCharLeft ()
+    {
+        if (_isReadOnly)
+        {
+            return;
+        }
+
+        SetWrapModel ();
+
+        if (IsSelecting)
+        {
+            _historyText.Add (new () { new (GetCurrentLine ()) }, CursorPosition);
+
+            ClearSelectedRegion ();
+
+            List<Cell> currentLine = GetCurrentLine ();
+
+            _historyText.Add (
+                              new () { new (currentLine) },
+                              CursorPosition,
+                              TextEditingLineStatus.Replaced
+                             );
+
+            UpdateWrapModel ();
+            OnContentsChanged ();
+
+            return;
+        }
+
+        if (DeleteTextBackwards ())
+        {
+            UpdateWrapModel ();
+            OnContentsChanged ();
+
+            return;
+        }
+
+        UpdateWrapModel ();
+
+        DoNeededAction ();
+        OnContentsChanged ();
+    }
+
+    /// <summary>Deletes all the selected or a single character at right from the position of the cursor.</summary>
+    public void DeleteCharRight ()
+    {
+        if (_isReadOnly)
+        {
+            return;
+        }
+
+        SetWrapModel ();
+
+        if (IsSelecting)
+        {
+            _historyText.Add (new () { new (GetCurrentLine ()) }, CursorPosition);
+
+            ClearSelectedRegion ();
+
+            List<Cell> currentLine = GetCurrentLine ();
+
+            _historyText.Add (
+                              new () { new (currentLine) },
+                              CursorPosition,
+                              TextEditingLineStatus.Replaced
+                             );
+
+            UpdateWrapModel ();
+            OnContentsChanged ();
+
+            return;
+        }
+
+        if (DeleteTextForwards ())
+        {
+            UpdateWrapModel ();
+            OnContentsChanged ();
+
+            return;
+        }
+
+        UpdateWrapModel ();
+
+        DoNeededAction ();
+        OnContentsChanged ();
+    }
+
+    private bool DeleteTextBackwards ()
+    {
+        SetWrapModel ();
+
+        if (CurrentColumn > 0)
+        {
+            // Delete backwards 
+            List<Cell> currentLine = GetCurrentLine ();
+
+            _historyText.Add (new () { new (currentLine) }, CursorPosition);
+
+            currentLine.RemoveAt (CurrentColumn - 1);
+
+            if (_wordWrap)
+            {
+                _wrapNeeded = true;
+            }
+
+            CurrentColumn--;
+
+            _historyText.Add (
+                              new () { new (currentLine) },
+                              CursorPosition,
+                              TextEditingLineStatus.Replaced
+                             );
+
+            if (CurrentColumn < _leftColumn)
+            {
+                _leftColumn--;
+                SetNeedsDraw ();
+            }
+            else
+            {
+                // BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method.
+                //SetNeedsDraw (new (0, currentRow - topRow, 1, Viewport.Width));
+                SetNeedsDraw ();
+            }
+        }
+        else
+        {
+            // Merges the current line with the previous one.
+            if (CurrentRow == 0)
+            {
+                return true;
+            }
+
+            int prowIdx = CurrentRow - 1;
+            List<Cell> prevRow = _model.GetLine (prowIdx);
+
+            _historyText.Add (new () { new (prevRow) }, CursorPosition);
+
+            List<List<Cell>> removedLines = new () { new (prevRow) };
+
+            removedLines.Add (new (GetCurrentLine ()));
+
+            _historyText.Add (
+                              removedLines,
+                              new (CurrentColumn, prowIdx),
+                              TextEditingLineStatus.Removed
+                             );
+
+            int prevCount = prevRow.Count;
+            _model.GetLine (prowIdx).AddRange (GetCurrentLine ());
+            _model.RemoveLine (CurrentRow);
+
+            if (_wordWrap)
+            {
+                _wrapNeeded = true;
+            }
+
+            CurrentRow--;
+
+            _historyText.Add (
+                              new () { GetCurrentLine () },
+                              new (CurrentColumn, prowIdx),
+                              TextEditingLineStatus.Replaced
+                             );
+
+            CurrentColumn = prevCount;
+            SetNeedsDraw ();
+        }
+
+        UpdateWrapModel ();
+
+        return false;
+    }
+
+    private bool DeleteTextForwards ()
+    {
+        SetWrapModel ();
+
+        List<Cell> currentLine = GetCurrentLine ();
+
+        if (CurrentColumn == currentLine.Count)
+        {
+            if (CurrentRow + 1 == _model.Count)
+            {
+                UpdateWrapModel ();
+
+                return true;
+            }
+
+            _historyText.Add (new () { new (currentLine) }, CursorPosition);
+
+            List<List<Cell>> removedLines = new () { new (currentLine) };
+
+            List<Cell> nextLine = _model.GetLine (CurrentRow + 1);
+
+            removedLines.Add (new (nextLine));
+
+            _historyText.Add (removedLines, CursorPosition, TextEditingLineStatus.Removed);
+
+            currentLine.AddRange (nextLine);
+            _model.RemoveLine (CurrentRow + 1);
+
+            _historyText.Add (
+                              new () { new (currentLine) },
+                              CursorPosition,
+                              TextEditingLineStatus.Replaced
+                             );
+
+            if (_wordWrap)
+            {
+                _wrapNeeded = true;
+            }
+
+            DoSetNeedsDraw (new (0, CurrentRow - _topRow, Viewport.Width, CurrentRow - _topRow + 1));
+        }
+        else
+        {
+            _historyText.Add ([ [.. currentLine]], CursorPosition);
+
+            currentLine.RemoveAt (CurrentColumn);
+
+            _historyText.Add (
+                              [ [.. currentLine]],
+                              CursorPosition,
+                              TextEditingLineStatus.Replaced
+                             );
+
+            if (_wordWrap)
+            {
+                _wrapNeeded = true;
+            }
+
+            DoSetNeedsDraw (
+                            new (
+                                 CurrentColumn - _leftColumn,
+                                 CurrentRow - _topRow,
+                                 Viewport.Width,
+                                 Math.Max (CurrentRow - _topRow + 1, 0)
+                                )
+                           );
+        }
+
+        UpdateWrapModel ();
+
+        return false;
+    }
+
+    private void ProcessKillWordForward ()
+    {
+        ResetColumnTrack ();
+        StopSelecting ();
+        KillWordForward ();
+    }
+
+    private void ProcessKillWordBackward ()
+    {
+        ResetColumnTrack ();
+        KillWordBackward ();
+    }
+
+    private void ProcessDeleteCharRight ()
+    {
+        ResetColumnTrack ();
+        DeleteCharRight ();
+    }
+
+    private void ProcessDeleteCharLeft ()
+    {
+        ResetColumnTrack ();
+        DeleteCharLeft ();
+    }
+
+    private void KillWordForward ()
+    {
+        if (_isReadOnly)
+        {
+            return;
+        }
+
+        SetWrapModel ();
+
+        List<Cell> currentLine = GetCurrentLine ();
+
+        _historyText.Add ([ [.. GetCurrentLine ()]], CursorPosition);
+
+        if (currentLine.Count == 0 || CurrentColumn == currentLine.Count)
+        {
+            DeleteTextForwards ();
+
+            _historyText.ReplaceLast (
+                                      [ [.. GetCurrentLine ()]],
+                                      CursorPosition,
+                                      TextEditingLineStatus.Replaced
+                                     );
+
+            UpdateWrapModel ();
+
+            return;
+        }
+
+        (int col, int row)? newPos = _model.WordForward (CurrentColumn, CurrentRow, UseSameRuneTypeForWords);
+        var restCount = 0;
+
+        if (newPos.HasValue && CurrentRow == newPos.Value.row)
+        {
+            restCount = newPos.Value.col - CurrentColumn;
+            currentLine.RemoveRange (CurrentColumn, restCount);
+        }
+        else if (newPos.HasValue)
+        {
+            restCount = currentLine.Count - CurrentColumn;
+            currentLine.RemoveRange (CurrentColumn, restCount);
+        }
+
+        if (_wordWrap)
+        {
+            _wrapNeeded = true;
+        }
+
+        _historyText.Add (
+                          [ [.. GetCurrentLine ()]],
+                          CursorPosition,
+                          TextEditingLineStatus.Replaced
+                         );
+
+        UpdateWrapModel ();
+
+        DoSetNeedsDraw (new (0, CurrentRow - _topRow, Viewport.Width, Viewport.Height));
+        DoNeededAction ();
+    }
+
+    private void KillWordBackward ()
+    {
+        if (_isReadOnly)
+        {
+            return;
+        }
+
+        SetWrapModel ();
+
+        List<Cell> currentLine = GetCurrentLine ();
+
+        _historyText.Add ([ [.. GetCurrentLine ()]], CursorPosition);
+
+        if (CurrentColumn == 0)
+        {
+            DeleteTextBackwards ();
+
+            _historyText.ReplaceLast (
+                                      [ [.. GetCurrentLine ()]],
+                                      CursorPosition,
+                                      TextEditingLineStatus.Replaced
+                                     );
+
+            UpdateWrapModel ();
+
+            return;
+        }
+
+        (int col, int row)? newPos = _model.WordBackward (CurrentColumn, CurrentRow, UseSameRuneTypeForWords);
+
+        if (newPos.HasValue && CurrentRow == newPos.Value.row)
+        {
+            int restCount = CurrentColumn - newPos.Value.col;
+            currentLine.RemoveRange (newPos.Value.col, restCount);
+
+            if (_wordWrap)
+            {
+                _wrapNeeded = true;
+            }
+
+            CurrentColumn = newPos.Value.col;
+        }
+        else if (newPos.HasValue)
+        {
+            int restCount;
+
+            if (newPos.Value.row == CurrentRow)
+            {
+                restCount = currentLine.Count - CurrentColumn;
+                currentLine.RemoveRange (CurrentColumn, restCount);
+            }
+            else
+            {
+                while (CurrentRow != newPos.Value.row)
+                {
+                    restCount = currentLine.Count;
+                    currentLine.RemoveRange (0, restCount);
+
+                    CurrentRow--;
+                    currentLine = GetCurrentLine ();
+                }
+            }
+
+            if (_wordWrap)
+            {
+                _wrapNeeded = true;
+            }
+
+            CurrentColumn = newPos.Value.col;
+            CurrentRow = newPos.Value.row;
+        }
+
+        _historyText.Add (
+                          [ [.. GetCurrentLine ()]],
+                          CursorPosition,
+                          TextEditingLineStatus.Replaced
+                         );
+
+        UpdateWrapModel ();
+
+        DoSetNeedsDraw (new (0, CurrentRow - _topRow, Viewport.Width, Viewport.Height));
+        DoNeededAction ();
+    }
+
+    private void KillToLeftStart ()
+    {
+        if (_isReadOnly)
+        {
+            return;
+        }
+
+        if (_model.Count == 1 && GetCurrentLine ().Count == 0)
+        {
+            // Prevents from adding line feeds if there is no more lines.
+            return;
+        }
+
+        SetWrapModel ();
+
+        List<Cell> currentLine = GetCurrentLine ();
+        var setLastWasKill = true;
+
+        if (currentLine.Count > 0 && CurrentColumn == 0)
+        {
+            UpdateWrapModel ();
+
+            DeleteTextBackwards ();
+
+            return;
+        }
+
+        _historyText.Add ([ [.. currentLine]], CursorPosition);
+
+        if (currentLine.Count == 0)
+        {
+            if (CurrentRow > 0)
+            {
+                _model.RemoveLine (CurrentRow);
+
+                if (_model.Count > 0 || _lastWasKill)
+                {
+                    string val = Environment.NewLine;
+
+                    if (_lastWasKill)
+                    {
+                        AppendClipboard (val);
+                    }
+                    else
+                    {
+                        SetClipboard (val);
+                    }
+                }
+
+                if (_model.Count == 0)
+                {
+                    // Prevents from adding line feeds if there is no more lines.
+                    setLastWasKill = false;
+                }
+
+                CurrentRow--;
+                currentLine = _model.GetLine (CurrentRow);
+
+                List<List<Cell>> removedLine =
+                [
+                    [..currentLine],
+                    []
+                ];
+
+                _historyText.Add (
+                                  [.. removedLine],
+                                  CursorPosition,
+                                  TextEditingLineStatus.Removed
+                                 );
+
+                CurrentColumn = currentLine.Count;
+            }
+        }
+        else
+        {
+            int restCount = CurrentColumn;
+            List<Cell> rest = currentLine.GetRange (0, restCount);
+            var val = string.Empty;
+            val += StringFromCells (rest);
+
+            if (_lastWasKill)
+            {
+                AppendClipboard (val);
+            }
+            else
+            {
+                SetClipboard (val);
+            }
+
+            currentLine.RemoveRange (0, restCount);
+            CurrentColumn = 0;
+        }
+
+        _historyText.Add (
+                          [ [.. GetCurrentLine ()]],
+                          CursorPosition,
+                          TextEditingLineStatus.Replaced
+                         );
+
+        UpdateWrapModel ();
+
+        DoSetNeedsDraw (new (0, CurrentRow - _topRow, Viewport.Width, Viewport.Height));
+
+        _lastWasKill = setLastWasKill;
+        DoNeededAction ();
+    }
+
+    private void KillToEndOfLine ()
+    {
+        if (_isReadOnly)
+        {
+            return;
+        }
+
+        if (_model.Count == 1 && GetCurrentLine ().Count == 0)
+        {
+            // Prevents from adding line feeds if there is no more lines.
+            return;
+        }
+
+        SetWrapModel ();
+
+        List<Cell> currentLine = GetCurrentLine ();
+        var setLastWasKill = true;
+
+        if (currentLine.Count > 0 && CurrentColumn == currentLine.Count)
+        {
+            UpdateWrapModel ();
+
+            DeleteTextForwards ();
+
+            return;
+        }
+
+        _historyText.Add (new () { new (currentLine) }, CursorPosition);
+
+        if (currentLine.Count == 0)
+        {
+            if (CurrentRow < _model.Count - 1)
+            {
+                List<List<Cell>> removedLines = new () { new (currentLine) };
+
+                _model.RemoveLine (CurrentRow);
+
+                removedLines.Add (new (GetCurrentLine ()));
+
+                _historyText.Add (
+                                  new (removedLines),
+                                  CursorPosition,
+                                  TextEditingLineStatus.Removed
+                                 );
+            }
+
+            if (_model.Count > 0 || _lastWasKill)
+            {
+                string val = Environment.NewLine;
+
+                if (_lastWasKill)
+                {
+                    AppendClipboard (val);
+                }
+                else
+                {
+                    SetClipboard (val);
+                }
+            }
+
+            if (_model.Count == 0)
+            {
+                // Prevents from adding line feeds if there is no more lines.
+                setLastWasKill = false;
+            }
+        }
+        else
+        {
+            int restCount = currentLine.Count - CurrentColumn;
+            List<Cell> rest = currentLine.GetRange (CurrentColumn, restCount);
+            var val = string.Empty;
+            val += StringFromCells (rest);
+
+            if (_lastWasKill)
+            {
+                AppendClipboard (val);
+            }
+            else
+            {
+                SetClipboard (val);
+            }
+
+            currentLine.RemoveRange (CurrentColumn, restCount);
+        }
+
+        _historyText.Add (
+                          [ [.. GetCurrentLine ()]],
+                          CursorPosition,
+                          TextEditingLineStatus.Replaced
+                         );
+
+        UpdateWrapModel ();
+
+        DoSetNeedsDraw (new (0, CurrentRow - _topRow, Viewport.Width, Viewport.Height));
+
+        _lastWasKill = setLastWasKill;
+        DoNeededAction ();
+    }
+}

+ 348 - 0
Terminal.Gui/Views/TextInput/TextView/TextView.Drawing.cs

@@ -0,0 +1,348 @@
+namespace Terminal.Gui.Views;
+
+public partial class TextView
+{
+    internal void ApplyCellsAttribute (Attribute attribute)
+    {
+        if (!ReadOnly && SelectedLength > 0)
+        {
+            int startRow = Math.Min (SelectionStartRow, CurrentRow);
+            int endRow = Math.Max (CurrentRow, SelectionStartRow);
+            int startCol = SelectionStartRow <= CurrentRow ? SelectionStartColumn : CurrentColumn;
+            int endCol = CurrentRow >= SelectionStartRow ? CurrentColumn : SelectionStartColumn;
+            List<List<Cell>> selectedCellsOriginal = [];
+            List<List<Cell>> selectedCellsChanged = [];
+
+            for (int r = startRow; r <= endRow; r++)
+            {
+                List<Cell> line = GetLine (r);
+
+                selectedCellsOriginal.Add ([.. line]);
+
+                for (int c = r == startRow ? startCol : 0;
+                     c < (r == endRow ? endCol : line.Count);
+                     c++)
+                {
+                    Cell cell = line [c]; // Copy value to a new variable
+                    cell.Attribute = attribute; // Modify the copy
+                    line [c] = cell; // Assign the modified copy back
+                }
+
+                selectedCellsChanged.Add ([.. GetLine (r)]);
+            }
+
+            GetSelectedRegion ();
+            IsSelecting = false;
+
+            _historyText.Add (
+                              [.. selectedCellsOriginal],
+                              new Point (startCol, startRow)
+                             );
+
+            _historyText.Add (
+                              [.. selectedCellsChanged],
+                              new Point (startCol, startRow),
+                              TextEditingLineStatus.Attribute
+                             );
+        }
+    }
+
+    private Attribute? GetSelectedCellAttribute ()
+    {
+        List<Cell> line;
+
+        if (SelectedLength > 0)
+        {
+            line = GetLine (SelectionStartRow);
+
+            if (line [Math.Min (SelectionStartColumn, line.Count - 1)].Attribute is { } attributeSel)
+            {
+                return new (attributeSel);
+            }
+
+            return GetAttributeForRole (VisualRole.Active);
+        }
+
+        line = GetCurrentLine ();
+
+        if (line [Math.Min (CurrentColumn, line.Count - 1)].Attribute is { } attribute)
+        {
+            return new (attribute);
+        }
+
+        return GetAttributeForRole (VisualRole.Active);
+    }
+
+    /// <summary>Invoked when the normal color is drawn.</summary>
+    public event EventHandler<CellEventArgs>? DrawNormalColor;
+
+    /// <summary>Invoked when the ready only color is drawn.</summary>
+    public event EventHandler<CellEventArgs>? DrawReadOnlyColor;
+
+    /// <summary>Invoked when the selection color is drawn.</summary>
+    public event EventHandler<CellEventArgs>? DrawSelectionColor;
+
+    /// <summary>
+    ///     Invoked when the used color is drawn. The Used Color is used to indicate if the <see cref="Key.InsertChar"/>
+    ///     was pressed and enabled.
+    /// </summary>
+    public event EventHandler<CellEventArgs>? DrawUsedColor;
+
+    /// <inheritdoc/>
+    protected override bool OnDrawingContent ()
+    {
+        _isDrawing = true;
+
+        SetAttributeForRole (Enabled ? VisualRole.Editable : VisualRole.Disabled);
+
+        (int width, int height) offB = OffSetBackground ();
+        int right = Viewport.Width + offB.width;
+        int bottom = Viewport.Height + offB.height;
+        var row = 0;
+
+        for (int idxRow = _topRow; idxRow < _model.Count; idxRow++)
+        {
+            List<Cell> line = _model.GetLine (idxRow);
+            int lineRuneCount = line.Count;
+            var col = 0;
+
+            Move (0, row);
+
+            for (int idxCol = _leftColumn; idxCol < lineRuneCount; idxCol++)
+            {
+                string text = idxCol >= lineRuneCount ? " " : line [idxCol].Grapheme;
+                int cols = text.GetColumns (false);
+
+                if (idxCol < line.Count && IsSelecting && PointInSelection (idxCol, idxRow))
+                {
+                    OnDrawSelectionColor (line, idxCol, idxRow);
+                }
+                else if (idxCol == CurrentColumn && idxRow == CurrentRow && !IsSelecting && !Used && HasFocus && idxCol < lineRuneCount)
+                {
+                    OnDrawUsedColor (line, idxCol, idxRow);
+                }
+                else if (ReadOnly)
+                {
+                    OnDrawReadOnlyColor (line, idxCol, idxRow);
+                }
+                else
+                {
+                    OnDrawNormalColor (line, idxCol, idxRow);
+                }
+
+                if (text == "\t")
+                {
+                    cols += TabWidth + 1;
+
+                    if (col + cols > right)
+                    {
+                        cols = right - col;
+                    }
+
+                    for (var i = 0; i < cols; i++)
+                    {
+                        if (col + i < right)
+                        {
+                            AddRune (col + i, row, (Rune)' ');
+                        }
+                    }
+                }
+                else
+                {
+                    AddStr (col, row, text);
+
+                    // Ensures that cols less than 0 to be 1 because it will be converted to a printable rune
+                    cols = Math.Max (cols, 1);
+                }
+
+                if (!TextModel.SetCol (ref col, Viewport.Right, cols))
+                {
+                    break;
+                }
+
+                if (idxCol + 1 < lineRuneCount && col + line [idxCol + 1].Grapheme.GetColumns () > right)
+                {
+                    break;
+                }
+            }
+
+            if (col < right)
+            {
+                SetAttributeForRole (ReadOnly ? VisualRole.ReadOnly : VisualRole.Editable);
+                ClearRegion (col, row, right, row + 1);
+            }
+
+            row++;
+        }
+
+        if (row < bottom)
+        {
+            SetAttributeForRole (ReadOnly ? VisualRole.ReadOnly : VisualRole.Editable);
+            ClearRegion (Viewport.Left, row, right, bottom);
+        }
+
+        _isDrawing = false;
+
+        return false;
+    }
+
+    /// <summary>
+    ///     Sets the <see cref="View.Driver"/> to an appropriate color for rendering the given <paramref name="idxCol"/>
+    ///     of the current <paramref name="line"/>. Override to provide custom coloring by calling
+    ///     <see cref="View.SetAttribute"/> Defaults to <see cref="Scheme.Normal"/>.
+    /// </summary>
+    /// <param name="line">The line.</param>
+    /// <param name="idxCol">The col index.</param>
+    /// <param name="idxRow">The row index.</param>
+    protected virtual void OnDrawNormalColor (List<Cell> line, int idxCol, int idxRow)
+    {
+        (int Row, int Col) unwrappedPos = GetUnwrappedPosition (idxRow, idxCol);
+        var ev = new CellEventArgs (line, idxCol, unwrappedPos);
+        DrawNormalColor?.Invoke (this, ev);
+
+        if (line [idxCol].Attribute is { })
+        {
+            Attribute? attribute = line [idxCol].Attribute;
+            SetAttribute ((Attribute)attribute!);
+        }
+        else
+        {
+            SetAttribute (GetAttributeForRole (VisualRole.Normal));
+        }
+    }
+
+    /// <summary>
+    ///     Sets the <see cref="View.Driver"/> to an appropriate color for rendering the given <paramref name="idxCol"/>
+    ///     of the current <paramref name="line"/>. Override to provide custom coloring by calling
+    ///     <see cref="View.SetAttribute(Attribute)"/> Defaults to <see cref="Scheme.Focus"/>.
+    /// </summary>
+    /// <param name="line">The line.</param>
+    /// <param name="idxCol">The col index.</param>
+    /// ///
+    /// <param name="idxRow">The row index.</param>
+    protected virtual void OnDrawReadOnlyColor (List<Cell> line, int idxCol, int idxRow)
+    {
+        (int Row, int Col) unwrappedPos = GetUnwrappedPosition (idxRow, idxCol);
+        var ev = new CellEventArgs (line, idxCol, unwrappedPos);
+        DrawReadOnlyColor?.Invoke (this, ev);
+
+        Attribute? cellAttribute = line [idxCol].Attribute is { } ? line [idxCol].Attribute : GetAttributeForRole (VisualRole.ReadOnly);
+
+        if (cellAttribute!.Value.Foreground == cellAttribute.Value.Background)
+        {
+            SetAttribute (new (cellAttribute.Value.Foreground, cellAttribute.Value.Background, cellAttribute.Value.Style));
+        }
+        else
+        {
+            SetAttributeForRole (VisualRole.ReadOnly);
+        }
+    }
+
+    /// <summary>
+    ///     Sets the <see cref="View.Driver"/> to an appropriate color for rendering the given <paramref name="idxCol"/>
+    ///     of the current <paramref name="line"/>. Override to provide custom coloring by calling
+    ///     <see cref="View.SetAttribute(Attribute)"/> Defaults to <see cref="Scheme.Focus"/>.
+    /// </summary>
+    /// <param name="line">The line.</param>
+    /// <param name="idxCol">The col index.</param>
+    /// ///
+    /// <param name="idxRow">The row index.</param>
+    protected virtual void OnDrawSelectionColor (List<Cell> line, int idxCol, int idxRow)
+    {
+        (int Row, int Col) unwrappedPos = GetUnwrappedPosition (idxRow, idxCol);
+        var ev = new CellEventArgs (line, idxCol, unwrappedPos);
+        DrawSelectionColor?.Invoke (this, ev);
+
+        if (line [idxCol].Attribute is { })
+        {
+            Attribute? attribute = line [idxCol].Attribute;
+            Attribute? active = GetAttributeForRole (VisualRole.Active);
+            SetAttribute (new (active!.Value.Foreground, active.Value.Background, attribute!.Value.Style));
+        }
+        else
+        {
+            SetAttributeForRole (VisualRole.Active);
+        }
+    }
+
+    /// <summary>
+    ///     Sets the <see cref="View.Driver"/> to an appropriate color for rendering the given <paramref name="idxCol"/>
+    ///     of the current <paramref name="line"/>. Override to provide custom coloring by calling
+    ///     <see cref="View.SetAttribute(Attribute)"/> Defaults to <see cref="Scheme.HotFocus"/>.
+    /// </summary>
+    /// <param name="line">The line.</param>
+    /// <param name="idxCol">The col index.</param>
+    /// ///
+    /// <param name="idxRow">The row index.</param>
+    protected virtual void OnDrawUsedColor (List<Cell> line, int idxCol, int idxRow)
+    {
+        (int Row, int Col) unwrappedPos = GetUnwrappedPosition (idxRow, idxCol);
+        var ev = new CellEventArgs (line, idxCol, unwrappedPos);
+        DrawUsedColor?.Invoke (this, ev);
+
+        if (line [idxCol].Attribute is { })
+        {
+            Attribute? attribute = line [idxCol].Attribute;
+            SetValidUsedColor (attribute!);
+        }
+        else
+        {
+            SetValidUsedColor (GetAttributeForRole (VisualRole.Focus));
+        }
+    }
+
+    private void DoSetNeedsDraw (Rectangle rect)
+    {
+        if (_wrapNeeded)
+        {
+            SetNeedsDraw ();
+        }
+        else
+        {
+            // BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method.
+            //SetNeedsDraw (rect);
+            SetNeedsDraw ();
+        }
+    }
+
+    private Attribute? GetSelectedAttribute (int row, int col)
+    {
+        if (!InheritsPreviousAttribute || (Lines == 1 && GetLine (Lines).Count == 0))
+        {
+            return null;
+        }
+
+        List<Cell> line = GetLine (row);
+        int foundRow = row;
+
+        while (line.Count == 0)
+        {
+            if (foundRow == 0 && line.Count == 0)
+            {
+                return null;
+            }
+
+            foundRow--;
+            line = GetLine (foundRow);
+        }
+
+        int foundCol = foundRow < row ? line.Count - 1 : Math.Min (col, line.Count - 1);
+
+        Cell cell = line [foundCol];
+
+        return cell.Attribute;
+    }
+
+    /// <inheritdoc/>
+    protected override bool OnGettingAttributeForRole (in VisualRole role, ref Attribute currentAttribute)
+    {
+        if (role == VisualRole.Normal)
+        {
+            currentAttribute = GetAttributeForRole (VisualRole.Editable);
+
+            return true;
+        }
+
+        return base.OnGettingAttributeForRole (role, ref currentAttribute);
+    }
+}

+ 210 - 0
Terminal.Gui/Views/TextInput/TextView/TextView.Find.cs

@@ -0,0 +1,210 @@
+namespace Terminal.Gui.Views;
+
+/// <summary>Find and Replace functionality</summary>
+public partial class TextView
+{
+    #region Public Find/Replace Methods
+
+    /// <summary>Find the next text based on the match case with the option to replace it.</summary>
+    /// <param name="textToFind">The text to find.</param>
+    /// <param name="gaveFullTurn"><c>true</c>If all the text was forward searched.<c>false</c>otherwise.</param>
+    /// <param name="matchCase">The match case setting.</param>
+    /// <param name="matchWholeWord">The match whole word setting.</param>
+    /// <param name="textToReplace">The text to replace.</param>
+    /// <param name="replace"><c>true</c>If is replacing.<c>false</c>otherwise.</param>
+    /// <returns><c>true</c>If the text was found.<c>false</c>otherwise.</returns>
+    public bool FindNextText (
+        string textToFind,
+        out bool gaveFullTurn,
+        bool matchCase = false,
+        bool matchWholeWord = false,
+        string? textToReplace = null,
+        bool replace = false
+    )
+    {
+        if (_model.Count == 0)
+        {
+            gaveFullTurn = false;
+
+            return false;
+        }
+
+        SetWrapModel ();
+        ResetContinuousFind ();
+
+        (Point current, bool found) foundPos =
+            _model.FindNextText (textToFind, out gaveFullTurn, matchCase, matchWholeWord);
+
+        return SetFoundText (textToFind, foundPos, textToReplace, replace);
+    }
+
+    /// <summary>Find the previous text based on the match case with the option to replace it.</summary>
+    /// <param name="textToFind">The text to find.</param>
+    /// <param name="gaveFullTurn"><c>true</c>If all the text was backward searched.<c>false</c>otherwise.</param>
+    /// <param name="matchCase">The match case setting.</param>
+    /// <param name="matchWholeWord">The match whole word setting.</param>
+    /// <param name="textToReplace">The text to replace.</param>
+    /// <param name="replace"><c>true</c>If the text was found.<c>false</c>otherwise.</param>
+    /// <returns><c>true</c>If the text was found.<c>false</c>otherwise.</returns>
+    public bool FindPreviousText (
+        string textToFind,
+        out bool gaveFullTurn,
+        bool matchCase = false,
+        bool matchWholeWord = false,
+        string? textToReplace = null,
+        bool replace = false
+    )
+    {
+        if (_model.Count == 0)
+        {
+            gaveFullTurn = false;
+
+            return false;
+        }
+
+        SetWrapModel ();
+        ResetContinuousFind ();
+
+        (Point current, bool found) foundPos =
+            _model.FindPreviousText (textToFind, out gaveFullTurn, matchCase, matchWholeWord);
+
+        return SetFoundText (textToFind, foundPos, textToReplace, replace);
+    }
+
+    /// <summary>Reset the flag to stop continuous find.</summary>
+    public void FindTextChanged () { _continuousFind = false; }
+
+    /// <summary>Replaces all the text based on the match case.</summary>
+    /// <param name="textToFind">The text to find.</param>
+    /// <param name="matchCase">The match case setting.</param>
+    /// <param name="matchWholeWord">The match whole word setting.</param>
+    /// <param name="textToReplace">The text to replace.</param>
+    /// <returns><c>true</c>If the text was found.<c>false</c>otherwise.</returns>
+    public bool ReplaceAllText (
+        string textToFind,
+        bool matchCase = false,
+        bool matchWholeWord = false,
+        string? textToReplace = null
+    )
+    {
+        if (_isReadOnly || _model.Count == 0)
+        {
+            return false;
+        }
+
+        SetWrapModel ();
+        ResetContinuousFind ();
+
+        (Point current, bool found) foundPos =
+            _model.ReplaceAllText (textToFind, matchCase, matchWholeWord, textToReplace);
+
+        return SetFoundText (textToFind, foundPos, textToReplace, false, true);
+    }
+
+    #endregion
+
+    #region Private Find Helper Methods
+
+    private void ResetContinuousFind ()
+    {
+        if (!_continuousFind)
+        {
+            int col = IsSelecting ? _selectionStartColumn : CurrentColumn;
+            int row = IsSelecting ? _selectionStartRow : CurrentRow;
+            _model.ResetContinuousFind (new (col, row));
+        }
+    }
+
+    private void ResetContinuousFindTrack ()
+    {
+        // Handle some state here - whether the last command was a kill
+        // operation and the column tracking (up/down)
+        _lastWasKill = false;
+        _continuousFind = false;
+    }
+
+    private bool SetFoundText (
+        string text,
+        (Point current, bool found) foundPos,
+        string? textToReplace = null,
+        bool replace = false,
+        bool replaceAll = false
+    )
+    {
+        if (foundPos.found)
+        {
+            StartSelecting ();
+            _selectionStartColumn = foundPos.current.X;
+            _selectionStartRow = foundPos.current.Y;
+
+            if (!replaceAll)
+            {
+                CurrentColumn = _selectionStartColumn + text.GetRuneCount ();
+            }
+            else
+            {
+                CurrentColumn = _selectionStartColumn + textToReplace!.GetRuneCount ();
+            }
+
+            CurrentRow = foundPos.current.Y;
+
+            if (!_isReadOnly && replace)
+            {
+                Adjust ();
+                ClearSelectedRegion ();
+                InsertAllText (textToReplace!);
+                StartSelecting ();
+                _selectionStartColumn = CurrentColumn - textToReplace!.GetRuneCount ();
+            }
+            else
+            {
+                UpdateWrapModel ();
+                SetNeedsDraw ();
+                Adjust ();
+            }
+
+            _continuousFind = true;
+
+            return foundPos.found;
+        }
+
+        UpdateWrapModel ();
+        _continuousFind = false;
+
+        return foundPos.found;
+    }
+
+    private IEnumerable<(int col, int row, Cell rune)> ForwardIterator (int col, int row)
+    {
+        if (col < 0 || row < 0)
+        {
+            yield break;
+        }
+
+        if (row >= _model.Count)
+        {
+            yield break;
+        }
+
+        List<Cell> line = GetCurrentLine ();
+
+        if (col >= line.Count)
+        {
+            yield break;
+        }
+
+        while (row < _model.Count)
+        {
+            for (int c = col; c < line.Count; c++)
+            {
+                yield return (c, row, line [c]);
+            }
+
+            col = 0;
+            row++;
+            line = GetCurrentLine ();
+        }
+    }
+
+    #endregion
+}

+ 309 - 0
Terminal.Gui/Views/TextInput/TextView/TextView.Insert.cs

@@ -0,0 +1,309 @@
+namespace Terminal.Gui.Views;
+
+public partial class TextView
+{
+    /// <summary>
+    ///     Inserts the given <paramref name="toAdd"/> text at the current cursor position exactly as if the user had just
+    ///     typed it
+    /// </summary>
+    /// <param name="toAdd">Text to add</param>
+    public void InsertText (string toAdd)
+    {
+        foreach (char ch in toAdd)
+        {
+            Key key;
+
+            try
+            {
+                key = new (ch);
+            }
+            catch (Exception)
+            {
+                throw new ArgumentException (
+                                             $"Cannot insert character '{ch}' because it does not map to a Key"
+                                            );
+            }
+
+            InsertText (key);
+
+            if (NeedsDraw)
+            {
+                Adjust ();
+            }
+            else
+            {
+                PositionCursor ();
+            }
+        }
+    }
+
+    private void Insert (Cell cell)
+    {
+        List<Cell> line = GetCurrentLine ();
+
+        if (Used)
+        {
+            line.Insert (Math.Min (CurrentColumn, line.Count), cell);
+        }
+        else
+        {
+            if (CurrentColumn < line.Count)
+            {
+                line.RemoveAt (CurrentColumn);
+            }
+
+            line.Insert (Math.Min (CurrentColumn, line.Count), cell);
+        }
+
+        int prow = CurrentRow - _topRow;
+
+        if (!_wrapNeeded)
+        {
+            // BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method.
+            //SetNeedsDraw (new (0, prow, Math.Max (Viewport.Width, 0), Math.Max (prow + 1, 0)));
+            SetNeedsDraw ();
+        }
+    }
+
+    private void InsertAllText (string text, bool fromClipboard = false)
+    {
+        if (string.IsNullOrEmpty (text))
+        {
+            return;
+        }
+
+        List<List<Cell>> lines;
+
+        if (fromClipboard && text == _copiedText)
+        {
+            lines = _copiedCellsList;
+        }
+        else
+        {
+            // Get selected attribute
+            Attribute? attribute = GetSelectedAttribute (CurrentRow, CurrentColumn);
+            lines = Cell.StringToLinesOfCells (text, attribute);
+        }
+
+        if (lines.Count == 0)
+        {
+            return;
+        }
+
+        SetWrapModel ();
+
+        List<Cell> line = GetCurrentLine ();
+
+        _historyText.Add ([new (line)], CursorPosition);
+
+        // Optimize single line
+        if (lines.Count == 1)
+        {
+            line.InsertRange (CurrentColumn, lines [0]);
+            CurrentColumn += lines [0].Count;
+
+            _historyText.Add (
+                              [new (line)],
+                              CursorPosition,
+                              TextEditingLineStatus.Replaced
+                             );
+
+            if (!_wordWrap && CurrentColumn - _leftColumn > Viewport.Width)
+            {
+                _leftColumn = Math.Max (CurrentColumn - Viewport.Width + 1, 0);
+            }
+
+            if (_wordWrap)
+            {
+                SetNeedsDraw ();
+            }
+            else
+            {
+                // BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method.
+                //SetNeedsDraw (new (0, currentRow - topRow, Viewport.Width, Math.Max (currentRow - topRow + 1, 0)));
+                SetNeedsDraw ();
+            }
+
+            UpdateWrapModel ();
+
+            OnContentsChanged ();
+
+            return;
+        }
+
+        List<Cell>? rest = null;
+        var lastPosition = 0;
+
+        if (_model.Count > 0 && line.Count > 0 && !_copyWithoutSelection)
+        {
+            // Keep a copy of the rest of the line
+            int restCount = line.Count - CurrentColumn;
+            rest = line.GetRange (CurrentColumn, restCount);
+            line.RemoveRange (CurrentColumn, restCount);
+        }
+
+        // First line is inserted at the current location, the rest is appended
+        line.InsertRange (CurrentColumn, lines [0]);
+
+        //model.AddLine (currentRow, lines [0]);
+
+        List<List<Cell>> addedLines = [new (line)];
+
+        for (var i = 1; i < lines.Count; i++)
+        {
+            _model.AddLine (CurrentRow + i, lines [i]);
+
+            addedLines.Add ([.. lines [i]]);
+        }
+
+        if (rest is { })
+        {
+            List<Cell> last = _model.GetLine (CurrentRow + lines.Count - 1);
+            lastPosition = last.Count;
+            last.InsertRange (last.Count, rest);
+
+            addedLines.Last ().InsertRange (addedLines.Last ().Count, rest);
+        }
+
+        _historyText.Add (addedLines, CursorPosition, TextEditingLineStatus.Added);
+
+        // Now adjust column and row positions
+        CurrentRow += lines.Count - 1;
+        CurrentColumn = rest is { } ? lastPosition : lines [^1].Count;
+        Adjust ();
+
+        _historyText.Add (
+                          [new (line)],
+                          CursorPosition,
+                          TextEditingLineStatus.Replaced
+                         );
+
+        UpdateWrapModel ();
+        OnContentsChanged ();
+    }
+
+    private bool InsertText (Key a, Attribute? attribute = null)
+    {
+        //So that special keys like tab can be processed
+        if (_isReadOnly)
+        {
+            return true;
+        }
+
+        SetWrapModel ();
+
+        _historyText.Add ([new (GetCurrentLine ())], CursorPosition);
+
+        if (IsSelecting)
+        {
+            ClearSelectedRegion ();
+        }
+
+        if ((uint)a.KeyCode == '\n')
+        {
+            _model.AddLine (CurrentRow + 1, []);
+            CurrentRow++;
+            CurrentColumn = 0;
+        }
+        else if ((uint)a.KeyCode == '\r')
+        {
+            CurrentColumn = 0;
+        }
+        else
+        {
+            if (Used)
+            {
+                Insert (new () { Grapheme = a.AsRune.ToString (), Attribute = attribute });
+                CurrentColumn++;
+
+                if (CurrentColumn >= _leftColumn + Viewport.Width)
+                {
+                    _leftColumn++;
+                    SetNeedsDraw ();
+                }
+            }
+            else
+            {
+                Insert (new () { Grapheme = a.AsRune.ToString (), Attribute = attribute });
+                CurrentColumn++;
+            }
+        }
+
+        _historyText.Add (
+                          [new (GetCurrentLine ())],
+                          CursorPosition,
+                          TextEditingLineStatus.Replaced
+                         );
+
+        UpdateWrapModel ();
+        OnContentsChanged ();
+
+        return true;
+    }
+
+    #region History Event Handlers
+
+    private void HistoryText_ChangeText (object sender, HistoryTextItemEventArgs obj)
+    {
+        SetWrapModel ();
+
+        if (obj is { })
+        {
+            int startLine = obj.CursorPosition.Y;
+
+            if (obj.RemovedOnAdded is { })
+            {
+                int offset;
+
+                if (obj.IsUndoing)
+                {
+                    offset = Math.Max (obj.RemovedOnAdded.Lines.Count - obj.Lines.Count, 1);
+                }
+                else
+                {
+                    offset = obj.RemovedOnAdded.Lines.Count - 1;
+                }
+
+                for (var i = 0; i < offset; i++)
+                {
+                    if (Lines > obj.RemovedOnAdded.CursorPosition.Y)
+                    {
+                        _model.RemoveLine (obj.RemovedOnAdded.CursorPosition.Y);
+                    }
+                    else
+                    {
+                        break;
+                    }
+                }
+            }
+
+            for (var i = 0; i < obj.Lines.Count; i++)
+            {
+                if (i == 0 || obj.LineStatus == TextEditingLineStatus.Original || obj.LineStatus == TextEditingLineStatus.Attribute)
+                {
+                    _model.ReplaceLine (startLine, obj.Lines [i]);
+                }
+                else if (obj is { IsUndoing: true, LineStatus: TextEditingLineStatus.Removed }
+                                or { IsUndoing: false, LineStatus: TextEditingLineStatus.Added })
+                {
+                    _model.AddLine (startLine, obj.Lines [i]);
+                }
+                else if (Lines > obj.CursorPosition.Y + 1)
+                {
+                    _model.RemoveLine (obj.CursorPosition.Y + 1);
+                }
+
+                startLine++;
+            }
+
+            CursorPosition = obj.FinalCursorPosition;
+        }
+
+        UpdateWrapModel ();
+
+        Adjust ();
+        OnContentsChanged ();
+    }
+
+    #endregion
+}

+ 599 - 0
Terminal.Gui/Views/TextInput/TextView/TextView.Navigation.cs

@@ -0,0 +1,599 @@
+namespace Terminal.Gui.Views;
+
+/// <summary>Navigation functionality - cursor movement and scrolling</summary>
+public partial class TextView
+{
+    #region Public Navigation Methods
+
+    /// <summary>Will scroll the <see cref="TextView"/> to the last line and position the cursor there.</summary>
+    public void MoveEnd ()
+    {
+        CurrentRow = _model.Count - 1;
+        List<Cell> line = GetCurrentLine ();
+        CurrentColumn = line.Count;
+        TrackColumn ();
+        DoNeededAction ();
+    }
+
+    /// <summary>Will scroll the <see cref="TextView"/> to the first line and position the cursor there.</summary>
+    public void MoveHome ()
+    {
+        CurrentRow = 0;
+        _topRow = 0;
+        CurrentColumn = 0;
+        _leftColumn = 0;
+        TrackColumn ();
+        DoNeededAction ();
+    }
+
+    /// <summary>
+    ///     Will scroll the <see cref="TextView"/> to display the specified row at the top if <paramref name="isRow"/> is
+    ///     true or will scroll the <see cref="TextView"/> to display the specified column at the left if
+    ///     <paramref name="isRow"/> is false.
+    /// </summary>
+    /// <param name="idx">
+    ///     Row that should be displayed at the top or Column that should be displayed at the left, if the value
+    ///     is negative it will be reset to zero
+    /// </param>
+    /// <param name="isRow">If true (default) the <paramref name="idx"/> is a row, column otherwise.</param>
+    public void ScrollTo (int idx, bool isRow = true)
+    {
+        if (idx < 0)
+        {
+            idx = 0;
+        }
+
+        if (isRow)
+        {
+            _topRow = Math.Max (idx > _model.Count - 1 ? _model.Count - 1 : idx, 0);
+
+            if (IsInitialized && Viewport.Y != _topRow)
+            {
+                Viewport = Viewport with { Y = _topRow };
+            }
+        }
+        else if (!_wordWrap)
+        {
+            int maxlength = _model.GetMaxVisibleLine (_topRow, _topRow + Viewport.Height, TabWidth);
+            _leftColumn = Math.Max (!_wordWrap && idx > maxlength - 1 ? maxlength - 1 : idx, 0);
+
+            if (IsInitialized && Viewport.X != _leftColumn)
+            {
+                Viewport = Viewport with { X = _leftColumn };
+            }
+        }
+
+        SetNeedsDraw ();
+    }
+
+    #endregion
+
+    #region Private Navigation Methods
+
+    private void MoveBottomEnd ()
+    {
+        ResetAllTrack ();
+
+        if (_shiftSelecting && IsSelecting)
+        {
+            StopSelecting ();
+        }
+
+        MoveEnd ();
+    }
+
+    private void MoveBottomEndExtend ()
+    {
+        ResetAllTrack ();
+        StartSelecting ();
+        MoveEnd ();
+    }
+
+    private bool MoveDown ()
+    {
+        if (CurrentRow + 1 < _model.Count)
+        {
+            if (_columnTrack == -1)
+            {
+                _columnTrack = CurrentColumn;
+            }
+
+            CurrentRow++;
+
+            if (CurrentRow >= _topRow + Viewport.Height)
+            {
+                _topRow++;
+                SetNeedsDraw ();
+            }
+
+            TrackColumn ();
+            PositionCursor ();
+        }
+        else if (CurrentRow > Viewport.Height)
+        {
+            Adjust ();
+        }
+        else
+        {
+            return false;
+        }
+
+        DoNeededAction ();
+
+        return true;
+    }
+
+    private void MoveEndOfLine ()
+    {
+        List<Cell> currentLine = GetCurrentLine ();
+        CurrentColumn = currentLine.Count;
+        DoNeededAction ();
+    }
+
+    private bool MoveLeft ()
+    {
+        if (CurrentColumn > 0)
+        {
+            CurrentColumn--;
+        }
+        else
+        {
+            if (CurrentRow > 0)
+            {
+                CurrentRow--;
+
+                if (CurrentRow < _topRow)
+                {
+                    _topRow--;
+                    SetNeedsDraw ();
+                }
+
+                List<Cell> currentLine = GetCurrentLine ();
+                CurrentColumn = Math.Max (currentLine.Count - (ReadOnly ? 1 : 0), 0);
+            }
+            else
+            {
+                return false;
+            }
+        }
+
+        DoNeededAction ();
+
+        return true;
+    }
+
+    private void MovePageDown ()
+    {
+        int nPageDnShift = Viewport.Height - 1;
+
+        if (CurrentRow >= 0 && CurrentRow < _model.Count)
+        {
+            if (_columnTrack == -1)
+            {
+                _columnTrack = CurrentColumn;
+            }
+
+            CurrentRow = CurrentRow + nPageDnShift > _model.Count
+                             ? _model.Count > 0 ? _model.Count - 1 : 0
+                             : CurrentRow + nPageDnShift;
+
+            if (_topRow < CurrentRow - nPageDnShift)
+            {
+                _topRow = CurrentRow >= _model.Count
+                              ? CurrentRow - nPageDnShift
+                              : _topRow + nPageDnShift;
+                SetNeedsDraw ();
+            }
+
+            TrackColumn ();
+            PositionCursor ();
+        }
+
+        DoNeededAction ();
+    }
+
+    private void MovePageUp ()
+    {
+        int nPageUpShift = Viewport.Height - 1;
+
+        if (CurrentRow > 0)
+        {
+            if (_columnTrack == -1)
+            {
+                _columnTrack = CurrentColumn;
+            }
+
+            CurrentRow = CurrentRow - nPageUpShift < 0 ? 0 : CurrentRow - nPageUpShift;
+
+            if (CurrentRow < _topRow)
+            {
+                _topRow = _topRow - nPageUpShift < 0 ? 0 : _topRow - nPageUpShift;
+                SetNeedsDraw ();
+            }
+
+            TrackColumn ();
+            PositionCursor ();
+        }
+
+        DoNeededAction ();
+    }
+
+    private bool MoveRight ()
+    {
+        List<Cell> currentLine = GetCurrentLine ();
+
+        if ((ReadOnly ? CurrentColumn + 1 : CurrentColumn) < currentLine.Count)
+        {
+            CurrentColumn++;
+        }
+        else
+        {
+            if (CurrentRow + 1 < _model.Count)
+            {
+                CurrentRow++;
+                CurrentColumn = 0;
+
+                if (CurrentRow >= _topRow + Viewport.Height)
+                {
+                    _topRow++;
+                    SetNeedsDraw ();
+                }
+            }
+            else
+            {
+                return false;
+            }
+        }
+
+        DoNeededAction ();
+
+        return true;
+    }
+
+    private void MoveLeftStart ()
+    {
+        if (_leftColumn > 0)
+        {
+            SetNeedsDraw ();
+        }
+
+        CurrentColumn = 0;
+        _leftColumn = 0;
+        DoNeededAction ();
+    }
+
+    private void MoveTopHome ()
+    {
+        ResetAllTrack ();
+
+        if (_shiftSelecting && IsSelecting)
+        {
+            StopSelecting ();
+        }
+
+        MoveHome ();
+    }
+
+    private void MoveTopHomeExtend ()
+    {
+        ResetColumnTrack ();
+        StartSelecting ();
+        MoveHome ();
+    }
+
+    private bool MoveUp ()
+    {
+        if (CurrentRow > 0)
+        {
+            if (_columnTrack == -1)
+            {
+                _columnTrack = CurrentColumn;
+            }
+
+            CurrentRow--;
+
+            if (CurrentRow < _topRow)
+            {
+                _topRow--;
+                SetNeedsDraw ();
+            }
+
+            TrackColumn ();
+            PositionCursor ();
+        }
+        else
+        {
+            return false;
+        }
+
+        DoNeededAction ();
+
+        return true;
+    }
+
+    private void MoveWordBackward ()
+    {
+        (int col, int row)? newPos = _model.WordBackward (CurrentColumn, CurrentRow, UseSameRuneTypeForWords);
+
+        if (newPos.HasValue)
+        {
+            CurrentColumn = newPos.Value.col;
+            CurrentRow = newPos.Value.row;
+        }
+
+        DoNeededAction ();
+    }
+
+    private void MoveWordForward ()
+    {
+        (int col, int row)? newPos = _model.WordForward (CurrentColumn, CurrentRow, UseSameRuneTypeForWords);
+
+        if (newPos.HasValue)
+        {
+            CurrentColumn = newPos.Value.col;
+            CurrentRow = newPos.Value.row;
+        }
+
+        DoNeededAction ();
+    }
+
+    #endregion
+
+    #region Process Navigation Methods
+
+    private bool ProcessMoveDown ()
+    {
+        ResetContinuousFindTrack ();
+
+        if (_shiftSelecting && IsSelecting)
+        {
+            StopSelecting ();
+        }
+
+        return MoveDown ();
+    }
+
+    private void ProcessMoveDownExtend ()
+    {
+        ResetColumnTrack ();
+        StartSelecting ();
+        MoveDown ();
+    }
+
+    private void ProcessMoveEndOfLine ()
+    {
+        ResetAllTrack ();
+
+        if (_shiftSelecting && IsSelecting)
+        {
+            StopSelecting ();
+        }
+
+        MoveEndOfLine ();
+    }
+
+    private void ProcessMoveRightEndExtend ()
+    {
+        ResetAllTrack ();
+        StartSelecting ();
+        MoveEndOfLine ();
+    }
+
+    private bool ProcessMoveLeft ()
+    {
+        // if the user presses Left (without any control keys) and they are at the start of the text
+        if (CurrentColumn == 0 && CurrentRow == 0)
+        {
+            if (IsSelecting)
+            {
+                StopSelecting ();
+
+                return true;
+            }
+
+            // do not respond (this lets the key press fall through to navigation system - which usually changes focus backward)
+            return false;
+        }
+
+        ResetAllTrack ();
+
+        if (_shiftSelecting && IsSelecting)
+        {
+            StopSelecting ();
+        }
+
+        MoveLeft ();
+
+        return true;
+    }
+
+    private void ProcessMoveLeftExtend ()
+    {
+        ResetAllTrack ();
+        StartSelecting ();
+        MoveLeft ();
+    }
+
+    private bool ProcessMoveRight ()
+    {
+        // if the user presses Right (without any control keys)
+        // determine where the last cursor position in the text is
+        int lastRow = _model.Count - 1;
+        int lastCol = _model.GetLine (lastRow).Count;
+
+        // if they are at the very end of all the text do not respond (this lets the key press fall through to navigation system - which usually changes focus forward)
+        if (CurrentColumn == lastCol && CurrentRow == lastRow)
+        {
+            // Unless they have text selected
+            if (IsSelecting)
+            {
+                // In which case clear
+                StopSelecting ();
+
+                return true;
+            }
+
+            return false;
+        }
+
+        ResetAllTrack ();
+
+        if (_shiftSelecting && IsSelecting)
+        {
+            StopSelecting ();
+        }
+
+        MoveRight ();
+
+        return true;
+    }
+
+    private void ProcessMoveRightExtend ()
+    {
+        ResetAllTrack ();
+        StartSelecting ();
+        MoveRight ();
+    }
+
+    private void ProcessMoveLeftStart ()
+    {
+        ResetAllTrack ();
+
+        if (_shiftSelecting && IsSelecting)
+        {
+            StopSelecting ();
+        }
+
+        MoveLeftStart ();
+    }
+
+    private void ProcessMoveLeftStartExtend ()
+    {
+        ResetAllTrack ();
+        StartSelecting ();
+        MoveLeftStart ();
+    }
+
+    private bool ProcessMoveUp ()
+    {
+        ResetContinuousFindTrack ();
+
+        if (_shiftSelecting && IsSelecting)
+        {
+            StopSelecting ();
+        }
+
+        return MoveUp ();
+    }
+
+    private void ProcessMoveUpExtend ()
+    {
+        ResetColumnTrack ();
+        StartSelecting ();
+        MoveUp ();
+    }
+
+    private void ProcessMoveWordBackward ()
+    {
+        ResetAllTrack ();
+
+        if (_shiftSelecting && IsSelecting)
+        {
+            StopSelecting ();
+        }
+
+        MoveWordBackward ();
+    }
+
+    private void ProcessMoveWordBackwardExtend ()
+    {
+        ResetAllTrack ();
+        StartSelecting ();
+        MoveWordBackward ();
+    }
+
+    private void ProcessMoveWordForward ()
+    {
+        ResetAllTrack ();
+
+        if (_shiftSelecting && IsSelecting)
+        {
+            StopSelecting ();
+        }
+
+        MoveWordForward ();
+    }
+
+    private void ProcessMoveWordForwardExtend ()
+    {
+        ResetAllTrack ();
+        StartSelecting ();
+        MoveWordForward ();
+    }
+
+    private void ProcessPageDown ()
+    {
+        ResetColumnTrack ();
+
+        if (_shiftSelecting && IsSelecting)
+        {
+            StopSelecting ();
+        }
+
+        MovePageDown ();
+    }
+
+    private void ProcessPageDownExtend ()
+    {
+        ResetColumnTrack ();
+        StartSelecting ();
+        MovePageDown ();
+    }
+
+    private void ProcessPageUp ()
+    {
+        ResetColumnTrack ();
+
+        if (_shiftSelecting && IsSelecting)
+        {
+            StopSelecting ();
+        }
+
+        MovePageUp ();
+    }
+
+    private void ProcessPageUpExtend ()
+    {
+        ResetColumnTrack ();
+        StartSelecting ();
+        MovePageUp ();
+    }
+
+    #endregion
+
+    #region Column Tracking
+
+    // Tries to snap the cursor to the tracking column
+    private void TrackColumn ()
+    {
+        // Now track the column
+        List<Cell> line = GetCurrentLine ();
+
+        if (line.Count < _columnTrack)
+        {
+            CurrentColumn = line.Count;
+        }
+        else if (_columnTrack != -1)
+        {
+            CurrentColumn = _columnTrack;
+        }
+        else if (CurrentColumn > line.Count)
+        {
+            CurrentColumn = line.Count;
+        }
+
+        Adjust ();
+    }
+
+    #endregion
+}

+ 399 - 0
Terminal.Gui/Views/TextInput/TextView/TextView.Selection.cs

@@ -0,0 +1,399 @@
+namespace Terminal.Gui.Views;
+
+public partial class TextView
+{
+
+    /// <summary>Get or sets whether the user is currently selecting text.</summary>
+    public bool IsSelecting { 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>Start row position of the selected text.</summary>
+    public int SelectionStartRow
+    {
+        get => _selectionStartRow;
+        set
+        {
+            _selectionStartRow = value < 0 ? 0 :
+                                 value > _model.Count - 1 ? Math.Max (_model.Count - 1, 0) : value;
+            IsSelecting = true;
+            SetNeedsDraw ();
+            Adjust ();
+        }
+    }
+
+    /// <summary>Start column position of the selected text.</summary>
+    public int SelectionStartColumn
+    {
+        get => _selectionStartColumn;
+        set
+        {
+            List<Cell> line = _model.GetLine (_selectionStartRow);
+
+            _selectionStartColumn = value < 0 ? 0 :
+                                    value > line.Count ? line.Count : value;
+            IsSelecting = true;
+            SetNeedsDraw ();
+            Adjust ();
+        }
+    }
+
+    private void StartSelecting ()
+    {
+        if (_shiftSelecting && IsSelecting)
+        {
+            return;
+        }
+
+        _shiftSelecting = true;
+        IsSelecting = true;
+        _selectionStartColumn = CurrentColumn;
+        _selectionStartRow = CurrentRow;
+    }
+
+    private void StopSelecting ()
+    {
+        if (IsSelecting)
+        {
+            SetNeedsDraw ();
+        }
+
+        _shiftSelecting = false;
+        IsSelecting = false;
+        _isButtonShift = false;
+    }
+
+
+    /// <summary>Length of the selected text.</summary>
+    public int SelectedLength => GetSelectedLength ();
+
+    /// <summary>
+    ///     Gets the selected text as
+    ///     <see>
+    ///         <cref>List{List{Cell}}</cref>
+    ///     </see>
+    /// </summary>
+    public List<List<Cell>> SelectedCellsList
+    {
+        get
+        {
+            GetRegion (out List<List<Cell>> selectedCellsList);
+
+            return selectedCellsList;
+        }
+    }
+
+    /// <summary>The selected text.</summary>
+    public string SelectedText
+    {
+        get
+        {
+            if (!IsSelecting || (_model.Count == 1 && _model.GetLine (0).Count == 0))
+            {
+                return string.Empty;
+            }
+
+            return GetSelectedRegion ();
+        }
+    }
+
+
+    // Returns an encoded region start..end (top 32 bits are the row, low32 the column)
+    private void GetEncodedRegionBounds (
+        out long start,
+        out long end,
+        int? startRow = null,
+        int? startCol = null,
+        int? cRow = null,
+        int? cCol = null
+    )
+    {
+        long selection;
+        long point;
+
+        if (startRow is null || startCol is null || cRow is null || cCol is null)
+        {
+            selection = ((long)(uint)_selectionStartRow << 32) | (uint)_selectionStartColumn;
+            point = ((long)(uint)CurrentRow << 32) | (uint)CurrentColumn;
+        }
+        else
+        {
+            selection = ((long)(uint)startRow << 32) | (uint)startCol;
+            point = ((long)(uint)cRow << 32) | (uint)cCol;
+        }
+
+        if (selection > point)
+        {
+            start = point;
+            end = selection;
+        }
+        else
+        {
+            start = selection;
+            end = point;
+        }
+    }
+
+    //
+    // Returns a string with the text in the selected 
+    // region.
+    //
+    internal string GetRegion (
+        out List<List<Cell>> cellsList,
+        int? sRow = null,
+        int? sCol = null,
+        int? cRow = null,
+        int? cCol = null,
+        TextModel? model = null
+    )
+    {
+        GetEncodedRegionBounds (out long start, out long end, sRow, sCol, cRow, cCol);
+
+        cellsList = [];
+
+        if (start == end)
+        {
+            return string.Empty;
+        }
+
+        var startRow = (int)(start >> 32);
+        var maxRow = (int)(end >> 32);
+        var startCol = (int)(start & 0xffffffff);
+        var endCol = (int)(end & 0xffffffff);
+        List<Cell> line = model is null ? _model.GetLine (startRow) : model.GetLine (startRow);
+        List<Cell> cells;
+
+        if (startRow == maxRow)
+        {
+            cells = line.GetRange (startCol, endCol - startCol);
+            cellsList.Add (cells);
+
+            return StringFromCells (cells);
+        }
+
+        cells = line.GetRange (startCol, line.Count - startCol);
+        cellsList.Add (cells);
+        string res = StringFromCells (cells);
+
+        for (int row = startRow + 1; row < maxRow; row++)
+        {
+            cellsList.AddRange ([]);
+            cells = model == null ? _model.GetLine (row) : model.GetLine (row);
+            cellsList.Add (cells);
+
+            res = res
+                  + Environment.NewLine
+                  + StringFromCells (cells);
+        }
+
+        line = model is null ? _model.GetLine (maxRow) : model.GetLine (maxRow);
+        cellsList.AddRange ([]);
+        cells = line.GetRange (0, endCol);
+        cellsList.Add (cells);
+        res = res + Environment.NewLine + StringFromCells (cells);
+
+        return res;
+    }
+
+    private int GetSelectedLength () { return SelectedText.Length; }
+
+    private string GetSelectedRegion ()
+    {
+        int cRow = CurrentRow;
+        int cCol = CurrentColumn;
+        int startRow = _selectionStartRow;
+        int startCol = _selectionStartColumn;
+        TextModel model = _model;
+
+        if (_wordWrap)
+        {
+            cRow = _wrapManager!.GetModelLineFromWrappedLines (CurrentRow);
+            cCol = _wrapManager.GetModelColFromWrappedLines (CurrentRow, CurrentColumn);
+            startRow = _wrapManager.GetModelLineFromWrappedLines (_selectionStartRow);
+            startCol = _wrapManager.GetModelColFromWrappedLines (_selectionStartRow, _selectionStartColumn);
+            model = _wrapManager.Model;
+        }
+
+        OnUnwrappedCursorPosition (cRow, cCol);
+
+        return GetRegion (out _, startRow, startCol, cRow, cCol, model);
+    }
+
+
+    private string StringFromCells (List<Cell> cells)
+    {
+        ArgumentNullException.ThrowIfNull (cells);
+
+        var size = 0;
+        foreach (Cell cell in cells)
+        {
+            string t = cell.Grapheme;
+            size += Encoding.Unicode.GetByteCount (t);
+        }
+
+        byte [] encoded = new byte [size];
+        var offset = 0;
+        foreach (Cell cell in cells)
+        {
+            string t = cell.Grapheme;
+            int bytesWritten = Encoding.Unicode.GetBytes (t, 0, t.Length, encoded, offset);
+            offset += bytesWritten;
+        }
+
+        // decode using the same encoding and the bytes actually written
+        return Encoding.Unicode.GetString (encoded, 0, offset);
+    }
+
+    /// <inheritdoc />
+    public bool EnableForDesign ()
+    {
+        Text = """
+               TextView provides a fully featured multi-line text editor.
+               It supports word wrap and history for undo.
+               """;
+
+        return true;
+    }
+
+
+    /// <inheritdoc/>
+    protected override void Dispose (bool disposing)
+    {
+        if (disposing && ContextMenu is { })
+        {
+            ContextMenu.Visible = false;
+            ContextMenu.Dispose ();
+            ContextMenu = null;
+        }
+
+        base.Dispose (disposing);
+    }
+
+    private void ClearRegion ()
+    {
+        SetWrapModel ();
+
+        long start, end;
+        long currentEncoded = ((long)(uint)CurrentRow << 32) | (uint)CurrentColumn;
+        GetEncodedRegionBounds (out start, out end);
+        var startRow = (int)(start >> 32);
+        var maxrow = (int)(end >> 32);
+        var startCol = (int)(start & 0xffffffff);
+        var endCol = (int)(end & 0xffffffff);
+        List<Cell> line = _model.GetLine (startRow);
+
+        _historyText.Add (new () { new (line) }, new (startCol, startRow));
+
+        List<List<Cell>> removedLines = new ();
+
+        if (startRow == maxrow)
+        {
+            removedLines.Add (new (line));
+
+            line.RemoveRange (startCol, endCol - startCol);
+            CurrentColumn = startCol;
+
+            if (_wordWrap)
+            {
+                SetNeedsDraw ();
+            }
+            else
+            {
+                //QUESTION: Is the below comment still relevant?
+                // BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method.
+                //SetNeedsDraw (new (0, startRow - topRow, Viewport.Width, startRow - topRow + 1));
+                SetNeedsDraw ();
+            }
+
+            _historyText.Add (
+                              new (removedLines),
+                              CursorPosition,
+                              TextEditingLineStatus.Removed
+                             );
+
+            UpdateWrapModel ();
+
+            return;
+        }
+
+        removedLines.Add (new (line));
+
+        line.RemoveRange (startCol, line.Count - startCol);
+        List<Cell> line2 = _model.GetLine (maxrow);
+        line.AddRange (line2.Skip (endCol));
+
+        for (int row = startRow + 1; row <= maxrow; row++)
+        {
+            removedLines.Add (new (_model.GetLine (startRow + 1)));
+
+            _model.RemoveLine (startRow + 1);
+        }
+
+        if (currentEncoded == end)
+        {
+            CurrentRow -= maxrow - startRow;
+        }
+
+        CurrentColumn = startCol;
+
+        _historyText.Add (
+                          new (removedLines),
+                          CursorPosition,
+                          TextEditingLineStatus.Removed
+                         );
+
+        UpdateWrapModel ();
+
+        SetNeedsDraw ();
+    }
+
+    private void ClearSelectedRegion ()
+    {
+        SetWrapModel ();
+
+        if (!_isReadOnly)
+        {
+            ClearRegion ();
+        }
+
+        UpdateWrapModel ();
+        IsSelecting = false;
+        DoNeededAction ();
+    }
+
+    /// <summary>Select all text.</summary>
+    public void SelectAll ()
+    {
+        if (_model.Count == 0)
+        {
+            return;
+        }
+
+        StartSelecting ();
+        _selectionStartColumn = 0;
+        _selectionStartRow = 0;
+        CurrentColumn = _model.GetLine (_model.Count - 1).Count;
+        CurrentRow = _model.Count - 1;
+        SetNeedsDraw ();
+    }
+
+    private void ProcessSelectAll ()
+    {
+        ResetColumnTrack ();
+        SelectAll ();
+    }
+
+    private bool PointInSelection (int col, int row)
+    {
+        long start, end;
+        GetEncodedRegionBounds (out start, out end);
+        long q = ((long)(uint)row << 32) | (uint)col;
+
+        return q >= start && q <= end - 1;
+    }
+}

+ 175 - 0
Terminal.Gui/Views/TextInput/TextView/TextView.Utilities.cs

@@ -0,0 +1,175 @@
+namespace Terminal.Gui.Views;
+
+/// <summary>Utility and helper methods for TextView</summary>
+public partial class TextView
+{
+    private void Adjust ()
+    {
+        (int width, int height) offB = OffSetBackground ();
+        List<Cell> line = GetCurrentLine ();
+        bool need = NeedsDraw || _wrapNeeded || !Used;
+        (int size, int length) tSize = TextModel.DisplaySize (line, -1, -1, false, TabWidth);
+        (int size, int length) dSize = TextModel.DisplaySize (line, _leftColumn, CurrentColumn, true, TabWidth);
+
+        if (!_wordWrap && CurrentColumn < _leftColumn)
+        {
+            _leftColumn = CurrentColumn;
+            need = true;
+        }
+        else if (!_wordWrap
+                 && (CurrentColumn - _leftColumn + 1 > Viewport.Width + offB.width || dSize.size + 1 >= Viewport.Width + offB.width))
+        {
+            _leftColumn = TextModel.CalculateLeftColumn (
+                                                         line,
+                                                         _leftColumn,
+                                                         CurrentColumn,
+                                                         Viewport.Width + offB.width,
+                                                         TabWidth
+                                                        );
+            need = true;
+        }
+        else if ((_wordWrap && _leftColumn > 0) || (dSize.size < Viewport.Width + offB.width && tSize.size < Viewport.Width + offB.width))
+        {
+            if (_leftColumn > 0)
+            {
+                _leftColumn = 0;
+                need = true;
+            }
+        }
+
+        if (CurrentRow < _topRow)
+        {
+            _topRow = CurrentRow;
+            need = true;
+        }
+        else if (CurrentRow - _topRow >= Viewport.Height + offB.height)
+        {
+            _topRow = Math.Min (Math.Max (CurrentRow - Viewport.Height + 1, 0), CurrentRow);
+            need = true;
+        }
+        else if (_topRow > 0 && CurrentRow < _topRow)
+        {
+            _topRow = Math.Max (_topRow - 1, 0);
+            need = true;
+        }
+
+        // Sync Viewport with the internal scroll position
+        if (IsInitialized && (_leftColumn != Viewport.X || _topRow != Viewport.Y))
+        {
+            Viewport = new Rectangle (_leftColumn, _topRow, Viewport.Width, Viewport.Height);
+        }
+
+        if (need)
+        {
+            if (_wrapNeeded)
+            {
+                WrapTextModel ();
+                _wrapNeeded = false;
+            }
+
+            SetNeedsDraw ();
+        }
+        else
+        {
+            if (IsInitialized)
+            {
+                PositionCursor ();
+            }
+        }
+
+        OnUnwrappedCursorPosition ();
+    }
+
+    private void DoNeededAction ()
+    {
+        if (!NeedsDraw && (IsSelecting || _wrapNeeded || !Used))
+        {
+            SetNeedsDraw ();
+        }
+
+        if (NeedsDraw)
+        {
+            Adjust ();
+        }
+        else
+        {
+            PositionCursor ();
+            OnUnwrappedCursorPosition ();
+        }
+    }
+
+    private (int width, int height) OffSetBackground ()
+    {
+        var w = 0;
+        var h = 0;
+
+        if (SuperView?.Viewport.Right - Viewport.Right < 0)
+        {
+            w = SuperView!.Viewport.Right - Viewport.Right - 1;
+        }
+
+        if (SuperView?.Viewport.Bottom - Viewport.Bottom < 0)
+        {
+            h = SuperView!.Viewport.Bottom - Viewport.Bottom - 1;
+        }
+
+        return (w, h);
+    }
+
+    /// <summary>
+    ///     Updates the content size based on the text model dimensions.
+    /// </summary>
+    private void UpdateContentSize ()
+    {
+        int contentHeight = Math.Max (_model.Count, 1);
+
+        // For horizontal size: if word wrap is enabled, content width equals viewport width
+        // Otherwise, calculate the maximum line width (but only if we have a reasonable viewport)
+        int contentWidth;
+
+        if (_wordWrap)
+        {
+            // Word wrap: content width follows viewport width
+            contentWidth = Math.Max (Viewport.Width, 1);
+        }
+        else
+        {
+            // No word wrap: calculate max line width
+            // Cache the current value to avoid recalculating on every call
+            contentWidth = Math.Max (_model.GetMaxVisibleLine (0, _model.Count, TabWidth), 1);
+        }
+
+        SetContentSize (new Size (contentWidth, contentHeight));
+    }
+
+    private void ResetPosition ()
+    {
+        _topRow = _leftColumn = CurrentRow = CurrentColumn = 0;
+        StopSelecting ();
+    }
+
+    private void ResetAllTrack ()
+    {
+        // Handle some state here - whether the last command was a kill
+        // operation and the column tracking (up/down)
+        _lastWasKill = false;
+        _columnTrack = -1;
+        _continuousFind = false;
+    }
+
+    private void ResetColumnTrack ()
+    {
+        // Handle some state here - whether the last command was a kill
+        // operation and the column tracking (up/down)
+        _lastWasKill = false;
+        _columnTrack = -1;
+    }
+
+    private void ToggleSelecting ()
+    {
+        ResetColumnTrack ();
+        IsSelecting = !IsSelecting;
+        _selectionStartColumn = CurrentColumn;
+        _selectionStartRow = CurrentRow;
+    }
+}

+ 125 - 0
Terminal.Gui/Views/TextInput/TextView/TextView.WordWrap.cs

@@ -0,0 +1,125 @@
+using System.Runtime.CompilerServices;
+
+namespace Terminal.Gui.Views;
+
+/// <summary>Word wrap functionality</summary>
+public partial class TextView
+{
+    /// <summary>Invoke the <see cref="UnwrappedCursorPosition"/> event with the unwrapped <see cref="CursorPosition"/>.</summary>
+    public virtual void OnUnwrappedCursorPosition (int? cRow = null, int? cCol = null)
+    {
+        int? row = cRow ?? CurrentRow;
+        int? col = cCol ?? CurrentColumn;
+
+        if (cRow is null && cCol is null && _wordWrap)
+        {
+            row = _wrapManager!.GetModelLineFromWrappedLines (CurrentRow);
+            col = _wrapManager.GetModelColFromWrappedLines (CurrentRow, CurrentColumn);
+        }
+
+        UnwrappedCursorPosition?.Invoke (this, new (col.Value, row.Value));
+    }
+
+    /// <summary>Invoked with the unwrapped <see cref="CursorPosition"/>.</summary>
+    public event EventHandler<Point>? UnwrappedCursorPosition;
+
+    private (int Row, int Col) GetUnwrappedPosition (int line, int col)
+    {
+        if (WordWrap)
+        {
+            return new ValueTuple<int, int> (
+                                             _wrapManager!.GetModelLineFromWrappedLines (line),
+                                             _wrapManager.GetModelColFromWrappedLines (line, col)
+                                            );
+        }
+
+        return new ValueTuple<int, int> (line, col);
+    }
+
+    /// <summary>Restore from original model.</summary>
+    private void SetWrapModel ([CallerMemberName] string? caller = null)
+    {
+        if (_currentCaller is { })
+        {
+            return;
+        }
+
+        if (_wordWrap)
+        {
+            _currentCaller = caller;
+
+            CurrentColumn = _wrapManager!.GetModelColFromWrappedLines (CurrentRow, CurrentColumn);
+            CurrentRow = _wrapManager.GetModelLineFromWrappedLines (CurrentRow);
+
+            _selectionStartColumn =
+                _wrapManager.GetModelColFromWrappedLines (_selectionStartRow, _selectionStartColumn);
+            _selectionStartRow = _wrapManager.GetModelLineFromWrappedLines (_selectionStartRow);
+            _model = _wrapManager.Model;
+        }
+    }
+
+    /// <summary>Update the original model.</summary>
+    private void UpdateWrapModel ([CallerMemberName] string? caller = null)
+    {
+        if (_currentCaller is { } && _currentCaller != caller)
+        {
+            return;
+        }
+
+        if (_wordWrap)
+        {
+            _currentCaller = null;
+
+            _wrapManager!.UpdateModel (
+                                       _model,
+                                       out int nRow,
+                                       out int nCol,
+                                       out int nStartRow,
+                                       out int nStartCol,
+                                       CurrentRow,
+                                       CurrentColumn,
+                                       _selectionStartRow,
+                                       _selectionStartColumn,
+                                       true
+                                      );
+            CurrentRow = nRow;
+            CurrentColumn = nCol;
+            _selectionStartRow = nStartRow;
+            _selectionStartColumn = nStartCol;
+            _wrapNeeded = true;
+
+            SetNeedsDraw ();
+        }
+
+        if (_currentCaller is { })
+        {
+            throw new InvalidOperationException (
+                                                 $"WordWrap settings was changed after the {_currentCaller} call."
+                                                );
+        }
+    }
+
+    private void WrapTextModel ()
+    {
+        if (_wordWrap && _wrapManager is { })
+        {
+            _model = _wrapManager.WrapModel (
+                                             Math.Max (Viewport.Width - (ReadOnly ? 0 : 1), 0), // For the cursor on the last column of a line
+                                             out int nRow,
+                                             out int nCol,
+                                             out int nStartRow,
+                                             out int nStartCol,
+                                             CurrentRow,
+                                             CurrentColumn,
+                                             _selectionStartRow,
+                                             _selectionStartColumn,
+                                             _tabWidth
+                                            );
+            CurrentRow = nRow;
+            CurrentColumn = nCol;
+            _selectionStartRow = nStartRow;
+            _selectionStartColumn = nStartCol;
+            SetNeedsDraw ();
+        }
+    }
+}

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 8 - 990
Terminal.Gui/Views/TextInput/TextView/TextView.cs


+ 13 - 5
Terminal.sln.DotSettings

@@ -1,7 +1,9 @@
 <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
 	<s:String x:Key="/Default/CodeEditing/GenerateMemberBody/AccessorImplementationKind/@EntryValue">BackingField</s:String>
 	<s:String x:Key="/Default/CodeEditing/GenerateMemberBody/DocumentationGenerationKind/@EntryValue">Inherit</s:String>
+	<s:String x:Key="/Default/CodeEditing/GenerateMemberBody/MethodImplementationKind/@EntryValue">ReturnDefaultValue</s:String>
 	<s:Boolean x:Key="/Default/CodeEditing/GenerateMemberBody/PlaceBackingFieldAboveProperty/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeEditing/GenerateMemberBody/WrapIntoRegions/@EntryValue">True</s:Boolean>
 	<s:Int64 x:Key="/Default/CodeEditing/NullCheckPatterns/PatternTypeNamesToPriority/=JetBrains_002EReSharper_002EFeature_002EServices_002ECSharp_002ENullChecking_002EArgumentNullExceptionThrowIfNullPattern/@EntryIndexedValue">5000</s:Int64>
 	<s:Int64 x:Key="/Default/CodeEditing/NullCheckPatterns/PatternTypeNamesToPriority/=JetBrains_002EReSharper_002EFeature_002EServices_002ECSharp_002ENullChecking_002EIfThenThrowPattern/@EntryIndexedValue">1000</s:Int64>
 	<s:Int64 x:Key="/Default/CodeEditing/NullCheckPatterns/PatternTypeNamesToPriority/=JetBrains_002EReSharper_002EFeature_002EServices_002ECSharp_002ENullChecking_002EPatternMatchingIfThenThrowPattern/@EntryIndexedValue">3000</s:Int64>
@@ -14,7 +16,7 @@
 	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeMissingParentheses/@EntryIndexedValue">SUGGESTION</s:String>
 	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeModifiersOrder/@EntryIndexedValue">WARNING</s:String>
 	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeNamespaceBody/@EntryIndexedValue">ERROR</s:String>
-	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeNullCheckingPattern/@EntryIndexedValue">ERROR</s:String>
+	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeNullCheckingPattern/@EntryIndexedValue">SUGGESTION</s:String>
 	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeObjectCreationWhenTypeEvident/@EntryIndexedValue">WARNING</s:String>
 	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeObjectCreationWhenTypeNotEvident/@EntryIndexedValue">SUGGESTION</s:String>
 	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeRedundantParentheses/@EntryIndexedValue">WARNING</s:String>
@@ -331,6 +333,7 @@
       &lt;Entry.SortBy&gt;&#xD;
         &lt;Access Is="0" /&gt;&#xD;
         &lt;Readonly /&gt;&#xD;
+        &lt;PropertyName /&gt;&#xD;
       &lt;/Entry.SortBy&gt;&#xD;
     &lt;/Entry&gt;&#xD;
     &lt;Property DisplayName="Properties w/ Backing Field" Priority="100"&gt;&#xD;
@@ -353,14 +356,18 @@
         &lt;/And&gt;&#xD;
       &lt;/Entry.Match&gt;&#xD;
       &lt;Entry.SortBy&gt;&#xD;
-        &lt;ImplementsInterface Immediate="True" /&gt;&#xD;
+        &lt;ImplementsInterface /&gt;&#xD;
+        &lt;Name /&gt;&#xD;
       &lt;/Entry.SortBy&gt;&#xD;
     &lt;/Entry&gt;&#xD;
     &lt;Entry DisplayName="All other members"&gt;&#xD;
       &lt;Entry.SortBy&gt;&#xD;
         &lt;Access Is="0" /&gt;&#xD;
-        &lt;Name /&gt;&#xD;
+        &lt;Static /&gt;&#xD;
+        &lt;Virtual /&gt;&#xD;
         &lt;Override /&gt;&#xD;
+        &lt;ImplementsInterface /&gt;&#xD;
+        &lt;Name /&gt;&#xD;
       &lt;/Entry.SortBy&gt;&#xD;
     &lt;/Entry&gt;&#xD;
     &lt;Entry DisplayName="Nested Types"&gt;&#xD;
@@ -374,10 +381,11 @@
     &lt;/Entry&gt;&#xD;
   &lt;/TypePattern&gt;&#xD;
 &lt;/Patterns&gt;</s:String>
-	<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForBuiltInTypes/@EntryValue">UseVarWhenEvident</s:String>
+	<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForBuiltInTypes/@EntryValue">UseExplicitType</s:String>
 	<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForOtherTypes/@EntryValue">UseExplicitType</s:String>
-	<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForSimpleTypes/@EntryValue">UseVarWhenEvident</s:String>
+	<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForSimpleTypes/@EntryValue">UseVar</s:String>
 	<s:Boolean x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/PreferSeparateDeconstructedVariablesDeclaration/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/UseRoslynLogicForEvidentTypes/@EntryValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/CodeStyle/EditorConfig/EnableClangFormatSupport/@EntryValue">False</s:Boolean>
 	<s:Boolean x:Key="/Default/CodeStyle/EditorConfig/EnableEditorConfigSupport/@EntryValue">False</s:Boolean>
 	<s:Boolean x:Key="/Default/CodeStyle/EditorConfig/ShowEditorConfigStatusBarIndicator/@EntryValue">True</s:Boolean>

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä