diff --git a/Terminal.Gui/Views/TextInput/TextView/PARTIAL-SPLIT-PLAN.md b/Terminal.Gui/Views/TextInput/TextView/PARTIAL-SPLIT-PLAN.md
new file mode 100644
index 000000000..e69de29bb
diff --git a/Terminal.Gui/Views/TextInput/TextView/TextView.Clipboard.cs b/Terminal.Gui/Views/TextInput/TextView/TextView.Clipboard.cs
new file mode 100644
index 000000000..4a515ac17
--- /dev/null
+++ b/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;
+ }
+ }
+ /// Copy the selected text to the clipboard contents.
+ public void Copy ()
+ {
+ SetWrapModel ();
+
+ if (IsSelecting)
+ {
+ _copiedText = GetRegion (out _copiedCellsList);
+ SetClipboard (_copiedText);
+ _copyWithoutSelection = false;
+ }
+ else
+ {
+ List currentLine = GetCurrentLine ();
+ _copiedCellsList.Add (currentLine);
+ _copiedText = Cell.ToString (currentLine);
+ SetClipboard (_copiedText);
+ _copyWithoutSelection = true;
+ }
+
+ UpdateWrapModel ();
+ DoNeededAction ();
+ }
+
+ /// Cut the selected text to the clipboard contents.
+ 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 ();
+ }
+
+ /// Paste the clipboard contents into the current selected position.
+ public void Paste ()
+ {
+ if (_isReadOnly)
+ {
+ return;
+ }
+
+ SetWrapModel ();
+ string? contents = Clipboard.Contents;
+
+ if (_copyWithoutSelection && contents!.FirstOrDefault (x => x is '\n' or '\r') == 0)
+ {
+ List runeList = contents is null ? [] : Cell.ToCellList (contents);
+ List currentLine = GetCurrentLine ();
+
+ _historyText.Add ([new (currentLine)], CursorPosition);
+
+ List> 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; }
+}
diff --git a/Terminal.Gui/Views/TextInput/TextView/TextView.ContextMenu.cs b/Terminal.Gui/Views/TextInput/TextView/TextView.ContextMenu.cs
new file mode 100644
index 000000000..d535c21cb
--- /dev/null
+++ b/Terminal.Gui/Views/TextInput/TextView/TextView.ContextMenu.cs
@@ -0,0 +1,46 @@
+using System.Globalization;
+
+namespace Terminal.Gui.Views;
+
+/// Context menu functionality
+public partial class TextView
+{
+ private PopoverMenu CreateContextMenu ()
+ {
+ PopoverMenu menu = new (
+ new List
+ {
+ 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);
+ }
+}
diff --git a/Terminal.Gui/Views/TextInput/TextView/TextView.Core.cs b/Terminal.Gui/Views/TextInput/TextView/TextView.Core.cs
new file mode 100644
index 000000000..25c08f87b
--- /dev/null
+++ b/Terminal.Gui/Views/TextInput/TextView/TextView.Core.cs
@@ -0,0 +1,661 @@
+using System.Globalization;
+
+namespace Terminal.Gui.Views;
+
+/// Core functionality - Fields, Constructor, and fundamental properties
+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> _copiedCellsList = [];
+
+ #endregion
+
+ #region Constructor
+
+ ///
+ /// Initializes a on the specified area, with dimensions controlled with the X, Y, Width
+ /// and Height properties.
+ ///
+ 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
+
+ ///
+ /// Configures the ScrollBars to work with the modern View scrolling system.
+ ///
+ 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
+}
diff --git a/Terminal.Gui/Views/TextInput/TextView/TextView.Delete.cs b/Terminal.Gui/Views/TextInput/TextView/TextView.Delete.cs
new file mode 100644
index 000000000..01fb83e71
--- /dev/null
+++ b/Terminal.Gui/Views/TextInput/TextView/TextView.Delete.cs
@@ -0,0 +1,641 @@
+namespace Terminal.Gui.Views;
+
+public partial class TextView
+{
+ /// Deletes all text.
+ public void DeleteAll ()
+ {
+ if (Lines == 0)
+ {
+ return;
+ }
+
+ _selectionStartColumn = 0;
+ _selectionStartRow = 0;
+ MoveBottomEndExtend ();
+ DeleteCharLeft ();
+ SetNeedsDraw ();
+ }
+
+ /// Deletes all the selected or a single character at left from the position of the cursor.
+ public void DeleteCharLeft ()
+ {
+ if (_isReadOnly)
+ {
+ return;
+ }
+
+ SetWrapModel ();
+
+ if (IsSelecting)
+ {
+ _historyText.Add (new () { new (GetCurrentLine ()) }, CursorPosition);
+
+ ClearSelectedRegion ();
+
+ List currentLine = GetCurrentLine ();
+
+ _historyText.Add (
+ new () { new (currentLine) },
+ CursorPosition,
+ TextEditingLineStatus.Replaced
+ );
+
+ UpdateWrapModel ();
+ OnContentsChanged ();
+
+ return;
+ }
+
+ if (DeleteTextBackwards ())
+ {
+ UpdateWrapModel ();
+ OnContentsChanged ();
+
+ return;
+ }
+
+ UpdateWrapModel ();
+
+ DoNeededAction ();
+ OnContentsChanged ();
+ }
+
+ /// Deletes all the selected or a single character at right from the position of the cursor.
+ public void DeleteCharRight ()
+ {
+ if (_isReadOnly)
+ {
+ return;
+ }
+
+ SetWrapModel ();
+
+ if (IsSelecting)
+ {
+ _historyText.Add (new () { new (GetCurrentLine ()) }, CursorPosition);
+
+ ClearSelectedRegion ();
+
+ List 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 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 prevRow = _model.GetLine (prowIdx);
+
+ _historyText.Add (new () { new (prevRow) }, CursorPosition);
+
+ List> 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 currentLine = GetCurrentLine ();
+
+ if (CurrentColumn == currentLine.Count)
+ {
+ if (CurrentRow + 1 == _model.Count)
+ {
+ UpdateWrapModel ();
+
+ return true;
+ }
+
+ _historyText.Add (new () { new (currentLine) }, CursorPosition);
+
+ List> removedLines = new () { new (currentLine) };
+
+ List 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 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 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 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> removedLine =
+ [
+ [..currentLine],
+ []
+ ];
+
+ _historyText.Add (
+ [.. removedLine],
+ CursorPosition,
+ TextEditingLineStatus.Removed
+ );
+
+ CurrentColumn = currentLine.Count;
+ }
+ }
+ else
+ {
+ int restCount = CurrentColumn;
+ List 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 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> 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 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 ();
+ }
+}
diff --git a/Terminal.Gui/Views/TextInput/TextView/TextView.Drawing.cs b/Terminal.Gui/Views/TextInput/TextView/TextView.Drawing.cs
new file mode 100644
index 000000000..24dff6c2c
--- /dev/null
+++ b/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> selectedCellsOriginal = [];
+ List> selectedCellsChanged = [];
+
+ for (int r = startRow; r <= endRow; r++)
+ {
+ List 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 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);
+ }
+
+ /// Invoked when the normal color is drawn.
+ public event EventHandler? DrawNormalColor;
+
+ /// Invoked when the ready only color is drawn.
+ public event EventHandler? DrawReadOnlyColor;
+
+ /// Invoked when the selection color is drawn.
+ public event EventHandler? DrawSelectionColor;
+
+ ///
+ /// Invoked when the used color is drawn. The Used Color is used to indicate if the
+ /// was pressed and enabled.
+ ///
+ public event EventHandler? DrawUsedColor;
+
+ ///
+ 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 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;
+ }
+
+ ///
+ /// Sets the to an appropriate color for rendering the given
+ /// of the current . Override to provide custom coloring by calling
+ /// Defaults to .
+ ///
+ /// The line.
+ /// The col index.
+ /// The row index.
+ protected virtual void OnDrawNormalColor (List 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));
+ }
+ }
+
+ ///
+ /// Sets the to an appropriate color for rendering the given
+ /// of the current . Override to provide custom coloring by calling
+ /// Defaults to .
+ ///
+ /// The line.
+ /// The col index.
+ /// ///
+ /// The row index.
+ protected virtual void OnDrawReadOnlyColor (List 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);
+ }
+ }
+
+ ///
+ /// Sets the to an appropriate color for rendering the given
+ /// of the current . Override to provide custom coloring by calling
+ /// Defaults to .
+ ///
+ /// The line.
+ /// The col index.
+ /// ///
+ /// The row index.
+ protected virtual void OnDrawSelectionColor (List 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);
+ }
+ }
+
+ ///
+ /// Sets the to an appropriate color for rendering the given
+ /// of the current . Override to provide custom coloring by calling
+ /// Defaults to .
+ ///
+ /// The line.
+ /// The col index.
+ /// ///
+ /// The row index.
+ protected virtual void OnDrawUsedColor (List 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 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;
+ }
+
+ ///
+ 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);
+ }
+}
diff --git a/Terminal.Gui/Views/TextInput/TextView/TextView.Find.cs b/Terminal.Gui/Views/TextInput/TextView/TextView.Find.cs
new file mode 100644
index 000000000..5d0650242
--- /dev/null
+++ b/Terminal.Gui/Views/TextInput/TextView/TextView.Find.cs
@@ -0,0 +1,210 @@
+namespace Terminal.Gui.Views;
+
+/// Find and Replace functionality
+public partial class TextView
+{
+ #region Public Find/Replace Methods
+
+ /// Find the next text based on the match case with the option to replace it.
+ /// The text to find.
+ /// trueIf all the text was forward searched.falseotherwise.
+ /// The match case setting.
+ /// The match whole word setting.
+ /// The text to replace.
+ /// trueIf is replacing.falseotherwise.
+ /// trueIf the text was found.falseotherwise.
+ 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);
+ }
+
+ /// Find the previous text based on the match case with the option to replace it.
+ /// The text to find.
+ /// trueIf all the text was backward searched.falseotherwise.
+ /// The match case setting.
+ /// The match whole word setting.
+ /// The text to replace.
+ /// trueIf the text was found.falseotherwise.
+ /// trueIf the text was found.falseotherwise.
+ 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);
+ }
+
+ /// Reset the flag to stop continuous find.
+ public void FindTextChanged () { _continuousFind = false; }
+
+ /// Replaces all the text based on the match case.
+ /// The text to find.
+ /// The match case setting.
+ /// The match whole word setting.
+ /// The text to replace.
+ /// trueIf the text was found.falseotherwise.
+ 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 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
+}
diff --git a/Terminal.Gui/Views/TextInput/TextView/TextView.Insert.cs b/Terminal.Gui/Views/TextInput/TextView/TextView.Insert.cs
new file mode 100644
index 000000000..136487483
--- /dev/null
+++ b/Terminal.Gui/Views/TextInput/TextView/TextView.Insert.cs
@@ -0,0 +1,309 @@
+namespace Terminal.Gui.Views;
+
+public partial class TextView
+{
+ ///
+ /// Inserts the given text at the current cursor position exactly as if the user had just
+ /// typed it
+ ///
+ /// Text to add
+ 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 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> 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 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? 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> 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 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
+}
diff --git a/Terminal.Gui/Views/TextInput/TextView/TextView.Navigation.cs b/Terminal.Gui/Views/TextInput/TextView/TextView.Navigation.cs
new file mode 100644
index 000000000..6b64d784f
--- /dev/null
+++ b/Terminal.Gui/Views/TextInput/TextView/TextView.Navigation.cs
@@ -0,0 +1,599 @@
+namespace Terminal.Gui.Views;
+
+/// Navigation functionality - cursor movement and scrolling
+public partial class TextView
+{
+ #region Public Navigation Methods
+
+ /// Will scroll the to the last line and position the cursor there.
+ public void MoveEnd ()
+ {
+ CurrentRow = _model.Count - 1;
+ List line = GetCurrentLine ();
+ CurrentColumn = line.Count;
+ TrackColumn ();
+ DoNeededAction ();
+ }
+
+ /// Will scroll the to the first line and position the cursor there.
+ public void MoveHome ()
+ {
+ CurrentRow = 0;
+ _topRow = 0;
+ CurrentColumn = 0;
+ _leftColumn = 0;
+ TrackColumn ();
+ DoNeededAction ();
+ }
+
+ ///
+ /// Will scroll the to display the specified row at the top if is
+ /// true or will scroll the to display the specified column at the left if
+ /// is false.
+ ///
+ ///
+ /// 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
+ ///
+ /// If true (default) the is a row, column otherwise.
+ 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 currentLine = GetCurrentLine ();
+ CurrentColumn = currentLine.Count;
+ DoNeededAction ();
+ }
+
+ private bool MoveLeft ()
+ {
+ if (CurrentColumn > 0)
+ {
+ CurrentColumn--;
+ }
+ else
+ {
+ if (CurrentRow > 0)
+ {
+ CurrentRow--;
+
+ if (CurrentRow < _topRow)
+ {
+ _topRow--;
+ SetNeedsDraw ();
+ }
+
+ List 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 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 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
+}
diff --git a/Terminal.Gui/Views/TextInput/TextView/TextView.Selection.cs b/Terminal.Gui/Views/TextInput/TextView/TextView.Selection.cs
new file mode 100644
index 000000000..a6593371d
--- /dev/null
+++ b/Terminal.Gui/Views/TextInput/TextView/TextView.Selection.cs
@@ -0,0 +1,399 @@
+namespace Terminal.Gui.Views;
+
+public partial class TextView
+{
+
+ /// Get or sets whether the user is currently selecting text.
+ public bool IsSelecting { get; set; }
+
+ ///
+ /// 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 false meaning that the spaces at right are included in the selection.
+ ///
+ public bool SelectWordOnlyOnDoubleClick { get; set; }
+
+ /// Start row position of the selected text.
+ 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 ();
+ }
+ }
+
+ /// Start column position of the selected text.
+ public int SelectionStartColumn
+ {
+ get => _selectionStartColumn;
+ set
+ {
+ List 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;
+ }
+
+
+ /// Length of the selected text.
+ public int SelectedLength => GetSelectedLength ();
+
+ ///
+ /// Gets the selected text as
+ ///
+ /// List{List{Cell}}
+ ///
+ ///
+ public List> SelectedCellsList
+ {
+ get
+ {
+ GetRegion (out List> selectedCellsList);
+
+ return selectedCellsList;
+ }
+ }
+
+ /// The selected text.
+ 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> 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 line = model is null ? _model.GetLine (startRow) : model.GetLine (startRow);
+ List 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 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);
+ }
+
+ ///
+ public bool EnableForDesign ()
+ {
+ Text = """
+ TextView provides a fully featured multi-line text editor.
+ It supports word wrap and history for undo.
+ """;
+
+ return true;
+ }
+
+
+ ///
+ 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 line = _model.GetLine (startRow);
+
+ _historyText.Add (new () { new (line) }, new (startCol, startRow));
+
+ List> 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 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 ();
+ }
+
+ /// Select all text.
+ 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;
+ }
+}
diff --git a/Terminal.Gui/Views/TextInput/TextView/TextView.Utilities.cs b/Terminal.Gui/Views/TextInput/TextView/TextView.Utilities.cs
new file mode 100644
index 000000000..3cd27a393
--- /dev/null
+++ b/Terminal.Gui/Views/TextInput/TextView/TextView.Utilities.cs
@@ -0,0 +1,175 @@
+namespace Terminal.Gui.Views;
+
+/// Utility and helper methods for TextView
+public partial class TextView
+{
+ private void Adjust ()
+ {
+ (int width, int height) offB = OffSetBackground ();
+ List| 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);
+ }
+
+ ///
+ /// Updates the content size based on the text model dimensions.
+ ///
+ 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;
+ }
+}
diff --git a/Terminal.Gui/Views/TextInput/TextView/TextView.WordWrap.cs b/Terminal.Gui/Views/TextInput/TextView/TextView.WordWrap.cs
new file mode 100644
index 000000000..99b26a8e4
--- /dev/null
+++ b/Terminal.Gui/Views/TextInput/TextView/TextView.WordWrap.cs
@@ -0,0 +1,125 @@
+using System.Runtime.CompilerServices;
+
+namespace Terminal.Gui.Views;
+
+/// Word wrap functionality
+public partial class TextView
+{
+ /// Invoke the event with the unwrapped .
+ 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));
+ }
+
+ /// Invoked with the unwrapped .
+ public event EventHandler? UnwrappedCursorPosition;
+
+ private (int Row, int Col) GetUnwrappedPosition (int line, int col)
+ {
+ if (WordWrap)
+ {
+ return new ValueTuple (
+ _wrapManager!.GetModelLineFromWrappedLines (line),
+ _wrapManager.GetModelColFromWrappedLines (line, col)
+ );
+ }
+
+ return new ValueTuple (line, col);
+ }
+
+ /// Restore from original model.
+ 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;
+ }
+ }
+
+ /// Update the original model.
+ 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 ();
+ }
+ }
+}
diff --git a/Terminal.Gui/Views/TextInput/TextView/TextView.cs b/Terminal.Gui/Views/TextInput/TextView/TextView.cs
index 0864a6672..3b0625cb1 100644
--- a/Terminal.Gui/Views/TextInput/TextView/TextView.cs
+++ b/Terminal.Gui/Views/TextInput/TextView/TextView.cs
@@ -72,562 +72,8 @@ namespace Terminal.Gui.Views;
///
/// | |
///
-public class TextView : View, IDesignable
+public partial class TextView : View, IDesignable
{
- 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;
-
- ///
- /// Initializes a on the specified area, with dimensions controlled with the X, Y, Width
- /// and Height properties.
- ///
- 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;
- }
-
// BUGBUG: AllowsReturn is mis-named. It should be EnterKeyAccepts.
///
/// Gets or sets whether pressing ENTER in a creates a new line of text
@@ -772,7 +218,7 @@ public class TextView : View, IDesignable
int clampedValue = Math.Max (Math.Min (value, Maxlength - 1), 0);
_leftColumn = clampedValue;
-
+
if (IsInitialized && Viewport.X != _leftColumn)
{
Viewport = Viewport with { X = _leftColumn };
@@ -851,72 +297,6 @@ public class TextView : View, IDesignable
}
}
- /// Length of the selected text.
- public int SelectedLength => GetSelectedLength ();
-
- ///
- /// Gets the selected text as
- ///
- /// List{List{Cell}}
- ///
- ///
- public List> SelectedCellsList
- {
- get
- {
- GetRegion (out List> selectedCellsList);
-
- return selectedCellsList;
- }
- }
-
- /// The selected text.
- public string SelectedText
- {
- get
- {
- if (!IsSelecting || (_model.Count == 1 && _model.GetLine (0).Count == 0))
- {
- return string.Empty;
- }
-
- return GetSelectedRegion ();
- }
- }
-
- /// Get or sets whether the user is currently selecting text.
- public bool IsSelecting { get; set; }
-
- /// Start column position of the selected text.
- public int SelectionStartColumn
- {
- get => _selectionStartColumn;
- set
- {
- List| line = _model.GetLine (_selectionStartRow);
-
- _selectionStartColumn = value < 0 ? 0 :
- value > line.Count ? line.Count : value;
- IsSelecting = true;
- SetNeedsDraw ();
- Adjust ();
- }
- }
-
- /// Start row position of the selected text.
- 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 ();
- }
- }
-
/// Gets or sets a value indicating the number of whitespace when pressing the TAB key.
public int TabWidth
{
@@ -976,7 +356,7 @@ public class TextView : View, IDesignable
{
int clampedValue = Math.Max (Math.Min (value, Lines - 1), 0);
_topRow = clampedValue;
-
+
if (IsInitialized && Viewport.Y != _topRow)
{
Viewport = Viewport with { Y = _topRow };
@@ -1036,12 +416,7 @@ public class TextView : View, IDesignable
/// |
public bool UseSameRuneTypeForWords { get; set; }
- ///
- /// 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 false meaning that the spaces at right are included in the selection.
- ///
- public bool SelectWordOnlyOnDoubleClick { get; set; }
+
/// Allows clearing the items updating the original text.
public void ClearHistoryChanges () { _historyText?.Clear (_model.GetAllLines ()); }
@@ -1066,77 +441,6 @@ public class TextView : View, IDesignable
///
public event EventHandler? ContentsChanged;
- 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> selectedCellsOriginal = [];
- List> selectedCellsChanged = [];
-
- for (int r = startRow; r <= endRow; r++)
- {
- List 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 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);
- }
-
///
/// Open a dialog to set the foreground and background colors.
///
@@ -1160,244 +464,6 @@ public class TextView : View, IDesignable
ApplyCellsAttribute (attribute);
}
- private string? _copiedText;
- private List> _copiedCellsList = [];
-
- /// Copy the selected text to the clipboard contents.
- public void Copy ()
- {
- SetWrapModel ();
-
- if (IsSelecting)
- {
- _copiedText = GetRegion (out _copiedCellsList);
- SetClipboard (_copiedText);
- _copyWithoutSelection = false;
- }
- else
- {
- List currentLine = GetCurrentLine ();
- _copiedCellsList.Add (currentLine);
- _copiedText = Cell.ToString (currentLine);
- SetClipboard (_copiedText);
- _copyWithoutSelection = true;
- }
-
- UpdateWrapModel ();
- DoNeededAction ();
- }
-
- /// Cut the selected text to the clipboard contents.
- 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 ();
- }
-
- /// Deletes all text.
- public void DeleteAll ()
- {
- if (Lines == 0)
- {
- return;
- }
-
- _selectionStartColumn = 0;
- _selectionStartRow = 0;
- MoveBottomEndExtend ();
- DeleteCharLeft ();
- SetNeedsDraw ();
- }
-
- /// Deletes all the selected or a single character at left from the position of the cursor.
- public void DeleteCharLeft ()
- {
- if (_isReadOnly)
- {
- return;
- }
-
- SetWrapModel ();
-
- if (IsSelecting)
- {
- _historyText.Add (new () { new (GetCurrentLine ()) }, CursorPosition);
-
- ClearSelectedRegion ();
-
- List currentLine = GetCurrentLine ();
-
- _historyText.Add (
- new () { new (currentLine) },
- CursorPosition,
- TextEditingLineStatus.Replaced
- );
-
- UpdateWrapModel ();
- OnContentsChanged ();
-
- return;
- }
-
- if (DeleteTextBackwards ())
- {
- UpdateWrapModel ();
- OnContentsChanged ();
-
- return;
- }
-
- UpdateWrapModel ();
-
- DoNeededAction ();
- OnContentsChanged ();
- }
-
- /// Deletes all the selected or a single character at right from the position of the cursor.
- public void DeleteCharRight ()
- {
- if (_isReadOnly)
- {
- return;
- }
-
- SetWrapModel ();
-
- if (IsSelecting)
- {
- _historyText.Add (new () { new (GetCurrentLine ()) }, CursorPosition);
-
- ClearSelectedRegion ();
-
- List currentLine = GetCurrentLine ();
-
- _historyText.Add (
- new () { new (currentLine) },
- CursorPosition,
- TextEditingLineStatus.Replaced
- );
-
- UpdateWrapModel ();
- OnContentsChanged ();
-
- return;
- }
-
- if (DeleteTextForwards ())
- {
- UpdateWrapModel ();
- OnContentsChanged ();
-
- return;
- }
-
- UpdateWrapModel ();
-
- DoNeededAction ();
- OnContentsChanged ();
- }
-
- /// Invoked when the normal color is drawn.
- public event EventHandler? DrawNormalColor;
-
- /// Invoked when the ready only color is drawn.
- public event EventHandler? DrawReadOnlyColor;
-
- /// Invoked when the selection color is drawn.
- public event EventHandler? DrawSelectionColor;
-
- ///
- /// Invoked when the used color is drawn. The Used Color is used to indicate if the
- /// was pressed and enabled.
- ///
- public event EventHandler? DrawUsedColor;
-
- /// Find the next text based on the match case with the option to replace it.
- /// The text to find.
- /// trueIf all the text was forward searched.falseotherwise.
- /// The match case setting.
- /// The match whole word setting.
- /// The text to replace.
- /// trueIf is replacing.falseotherwise.
- /// trueIf the text was found.falseotherwise.
- 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);
- }
-
- /// Find the previous text based on the match case with the option to replace it.
- /// The text to find.
- /// trueIf all the text was backward searched.falseotherwise.
- /// The match case setting.
- /// The match whole word setting.
- /// The text to replace.
- /// trueIf the text was found.falseotherwise.
- /// trueIf the text was found.falseotherwise.
- 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);
- }
-
- /// Reset the flag to stop continuous find.
- public void FindTextChanged () { _continuousFind = false; }
-
/// Gets all lines of characters.
///
public List> GetAllLines () { return _model.GetAllLines (); }
@@ -1414,54 +480,6 @@ public class TextView : View, IDesignable
///
public List GetLine (int line) { return _model.GetLine (line); }
- ///
- 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);
- }
-
- ///
- /// Inserts the given text at the current cursor position exactly as if the user had just
- /// typed it
- ///
- /// Text to add
- 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 ();
- }
- }
- }
-
/// Loads the contents of the file into the .
/// true, if file was loaded, false otherwise.
/// Path to the file to load.
@@ -1768,27 +786,6 @@ public class TextView : View, IDesignable
return true;
}
- /// Will scroll the to the last line and position the cursor there.
- public void MoveEnd ()
- {
- CurrentRow = _model.Count - 1;
- List line = GetCurrentLine ();
- CurrentColumn = line.Count;
- TrackColumn ();
- DoNeededAction ();
- }
-
- /// Will scroll the to the first line and position the cursor there.
- public void MoveHome ()
- {
- CurrentRow = 0;
- _topRow = 0;
- CurrentColumn = 0;
- _leftColumn = 0;
- TrackColumn ();
- DoNeededAction ();
- }
-
///
/// Called when the contents of the TextView change. E.g. when the user types text or deletes text. Raises the
/// event.
@@ -1799,7 +796,7 @@ public class TextView : View, IDesignable
ProcessInheritsPreviousScheme (CurrentRow, CurrentColumn);
ProcessAutocomplete ();
-
+
// Update content size when content changes
if (IsInitialized)
{
@@ -1807,104 +804,6 @@ public class TextView : View, IDesignable
}
}
- ///
- 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 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;
- }
-
///
protected override void OnHasFocusChanged (bool newHasFocus, View? previousFocusedView, View? view)
{
@@ -1964,86 +863,6 @@ public class TextView : View, IDesignable
return false;
}
- /// Invoke the event with the unwrapped .
- 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));
- }
-
- /// Paste the clipboard contents into the current selected position.
- public void Paste ()
- {
- if (_isReadOnly)
- {
- return;
- }
-
- SetWrapModel ();
- string? contents = Clipboard.Contents;
-
- if (_copyWithoutSelection && contents!.FirstOrDefault (x => x is '\n' or '\r') == 0)
- {
- List runeList = contents is null ? [] : Cell.ToCellList (contents);
- List currentLine = GetCurrentLine ();
-
- _historyText.Add ([new (currentLine)], CursorPosition);
-
- List> 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 ();
- }
-
/// Positions the cursor on the current row and column
public override Point? PositionCursor ()
{
@@ -2120,90 +939,6 @@ public class TextView : View, IDesignable
_historyText.Redo ();
}
- /// Replaces all the text based on the match case.
- /// The text to find.
- /// The match case setting.
- /// The match whole word setting.
- /// The text to replace.
- /// trueIf the text was found.falseotherwise.
- 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);
- }
-
- ///
- /// Will scroll the to display the specified row at the top if is
- /// true or will scroll the to display the specified column at the left if
- /// is false.
- ///
- ///
- /// 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
- ///
- /// If true (default) the is a row, column otherwise.
- 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 ();
- }
-
- /// Select all text.
- 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 ();
- }
-
///// Raised when the property of the changes.
/////
///// The property of only changes when it is explicitly set, not as the
@@ -2222,212 +957,6 @@ public class TextView : View, IDesignable
_historyText.Undo ();
}
- /// Invoked with the unwrapped .
- public event EventHandler? UnwrappedCursorPosition;
-
- ///
- /// Sets the to an appropriate color for rendering the given
- /// of the current . Override to provide custom coloring by calling
- /// Defaults to .
- ///
- /// The line.
- /// The col index.
- /// The row index.
- protected virtual void OnDrawNormalColor (List 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));
- }
- }
-
- ///
- /// Sets the to an appropriate color for rendering the given
- /// of the current . Override to provide custom coloring by calling
- /// Defaults to .
- ///
- /// The line.
- /// The col index.
- /// ///
- /// The row index.
- protected virtual void OnDrawReadOnlyColor (List 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);
- }
- }
-
- ///
- /// Sets the to an appropriate color for rendering the given
- /// of the current . Override to provide custom coloring by calling
- /// Defaults to .
- ///
- /// The line.
- /// The col index.
- /// ///
- /// The row index.
- protected virtual void OnDrawSelectionColor (List 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);
- }
- }
-
- ///
- /// Sets the to an appropriate color for rendering the given
- /// of the current . Override to provide custom coloring by calling
- /// Defaults to .
- ///
- /// The line.
- /// The col index.
- /// ///
- /// The row index.
- protected virtual void OnDrawUsedColor (List 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 Adjust ()
- {
- (int width, int height) offB = OffSetBackground ();
- List 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 AppendClipboard (string text) { Clipboard.Contents += text; }
-
- private PopoverMenu CreateContextMenu ()
- {
- PopoverMenu menu = new (
- new List
- {
- 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 ClearRegion (int left, int top, int right, int bottom)
{
for (int row = top; row < bottom; row++)
@@ -2441,327 +970,6 @@ public class TextView : View, IDesignable
}
}
- //
- // Clears the contents of the selected region
- //
- 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 line = _model.GetLine (startRow);
-
- _historyText.Add (new () { new (line) }, new (startCol, startRow));
-
- List> 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 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 ();
- }
-
- private void ContextMenu_KeyChanged (object? sender, KeyChangedEventArgs e) { KeyBindings.Replace (e.OldKey, e.NewKey); }
-
- private bool DeleteTextBackwards ()
- {
- SetWrapModel ();
-
- if (CurrentColumn > 0)
- {
- // Delete backwards
- List 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 prevRow = _model.GetLine (prowIdx);
-
- _historyText.Add (new () { new (prevRow) }, CursorPosition);
-
- List> 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 currentLine = GetCurrentLine ();
-
- if (CurrentColumn == currentLine.Count)
- {
- if (CurrentRow + 1 == _model.Count)
- {
- UpdateWrapModel ();
-
- return true;
- }
-
- _historyText.Add (new () { new (currentLine) }, CursorPosition);
-
- List> removedLines = new () { new (currentLine) };
-
- List 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 DoNeededAction ()
- {
- if (!NeedsDraw && (IsSelecting || _wrapNeeded || !Used))
- {
- SetNeedsDraw ();
- }
-
- if (NeedsDraw)
- {
- Adjust ();
- }
- else
- {
- PositionCursor ();
- OnUnwrappedCursorPosition ();
- }
- }
-
- 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 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 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 ();
- }
- }
-
private void GenerateSuggestions ()
{
List currentLine = GetCurrentLine ();
@@ -2780,1065 +988,6 @@ public class TextView : View, IDesignable
);
}
- // 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> 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 line = model is null ? _model.GetLine (startRow) : model.GetLine (startRow);
- List 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 (int Row, int Col) GetUnwrappedPosition (int line, int col)
- {
- if (WordWrap)
- {
- return new ValueTuple (
- _wrapManager!.GetModelLineFromWrappedLines (line),
- _wrapManager.GetModelColFromWrappedLines (line, col)
- );
- }
-
- return new ValueTuple (line, col);
- }
-
- 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 ();
- }
-
- private void Insert (Cell cell)
- {
- List 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> 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 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? 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> 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 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;
- }
-
- 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 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> 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 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 ();
- }
-
- 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 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> removedLine =
- [
- [..currentLine],
- []
- ];
-
- _historyText.Add (
- [.. removedLine],
- CursorPosition,
- TextEditingLineStatus.Removed
- );
-
- CurrentColumn = currentLine.Count;
- }
- }
- else
- {
- int restCount = CurrentColumn;
- List 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 KillWordBackward ()
- {
- if (_isReadOnly)
- {
- return;
- }
-
- SetWrapModel ();
-
- List 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 KillWordForward ()
- {
- if (_isReadOnly)
- {
- return;
- }
-
- SetWrapModel ();
-
- List 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 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;
- }
- }
-
- 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 currentLine = GetCurrentLine ();
- CurrentColumn = currentLine.Count;
- DoNeededAction ();
- }
-
- private bool MoveLeft ()
- {
- if (CurrentColumn > 0)
- {
- CurrentColumn--;
- }
- else
- {
- if (CurrentRow > 0)
- {
- CurrentRow--;
-
- if (CurrentRow < _topRow)
- {
- _topRow--;
- SetNeedsDraw ();
- }
-
- List 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 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 ();
- }
-
- 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);
- }
-
- 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;
- }
-
private void ProcessAutocomplete ()
{
if (_isDrawing)
@@ -3886,7 +1035,7 @@ public class TextView : View, IDesignable
List currentLine = GetCurrentLine ();
- if (currentLine.Count > 0 && currentLine[CurrentColumn - 1].Grapheme == "\t")
+ if (currentLine.Count > 0 && currentLine [CurrentColumn - 1].Grapheme == "\t")
{
_historyText.Add (new () { new (currentLine) }, CursorPosition);
@@ -3910,58 +1059,6 @@ public class TextView : View, IDesignable
return true;
}
- private void ProcessCopy ()
- {
- ResetColumnTrack ();
- Copy ();
- }
-
- private void ProcessCut ()
- {
- ResetColumnTrack ();
- Cut ();
- }
-
- private void ProcessDeleteCharLeft ()
- {
- ResetColumnTrack ();
- DeleteCharLeft ();
- }
-
- private void ProcessDeleteCharRight ()
- {
- ResetColumnTrack ();
- DeleteCharRight ();
- }
-
- private Attribute? GetSelectedAttribute (int row, int col)
- {
- if (!InheritsPreviousAttribute || (Lines == 1 && GetLine (Lines).Count == 0))
- {
- return null;
- }
-
- List 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;
- }
-
// If InheritsPreviousScheme is enabled this method will check if the rune cell on
// the row and col location and around has a not null scheme. If it's null will set it with
// the very most previous valid scheme.
@@ -4073,19 +1170,6 @@ public class TextView : View, IDesignable
}
}
- private void ProcessKillWordBackward ()
- {
- ResetColumnTrack ();
- KillWordBackward ();
- }
-
- private void ProcessKillWordForward ()
- {
- ResetColumnTrack ();
- StopSelecting ();
- KillWordForward ();
- }
-
private void ProcessMouseClick (MouseEventArgs ev, out List line)
{
List? r = null;
@@ -4119,245 +1203,7 @@ public class TextView : View, IDesignable
line = r!;
}
- 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 ();
- }
-
- private void ProcessPaste ()
- {
- ResetColumnTrack ();
-
- if (_isReadOnly)
- {
- return;
- }
-
- Paste ();
- }
private bool ProcessEnterKey (ICommandContext? commandContext)
{
@@ -4442,12 +1288,6 @@ public class TextView : View, IDesignable
return true;
}
- private void ProcessSelectAll ()
- {
- ResetColumnTrack ();
- SelectAll ();
- }
-
private void ProcessSetOverwrite ()
{
ResetColumnTrack ();
@@ -4469,105 +1309,7 @@ public class TextView : View, IDesignable
return true;
}
- 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 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 void ResetPosition ()
- {
- _topRow = _leftColumn = CurrentRow = CurrentColumn = 0;
- StopSelecting ();
- }
-
- private void SetClipboard (string text)
- {
- if (text is { })
- {
- Clipboard.Contents = text;
- }
- }
-
- 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 void SetOverwrite (bool overwrite)
{
@@ -4583,305 +1325,4 @@ public class TextView : View, IDesignable
SetAttribute (new (attribute!.Value.Background, attribute!.Value.Foreground, attribute!.Value.Style));
}
- /// Restore from original model.
- 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;
- }
- }
-
- 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 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;
- }
-
- private string StringFromCells (List 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);
- }
-
- 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_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 ();
- }
-
- ///
- /// Configures the ScrollBars to work with the modern View scrolling system.
- ///
- 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_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;
- }
- }
-
- ///
- /// Updates the content size based on the text model dimensions.
- ///
- 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 TextView_LayoutComplete (object? sender, LayoutEventArgs e)
- {
- WrapTextModel ();
- UpdateContentSize ();
- Adjust ();
- }
-
- private void ToggleSelecting ()
- {
- ResetColumnTrack ();
- IsSelecting = !IsSelecting;
- _selectionStartColumn = CurrentColumn;
- _selectionStartRow = CurrentRow;
- }
-
- // Tries to snap the cursor to the tracking column
- private void TrackColumn ()
- {
- // Now track the column
- List| line = GetCurrentLine ();
-
- if (line.Count < _columnTrack)
- {
- CurrentColumn = line.Count;
- }
- else if (_columnTrack != -1)
- {
- CurrentColumn = _columnTrack;
- }
- else if (CurrentColumn > line.Count)
- {
- CurrentColumn = line.Count;
- }
-
- Adjust ();
- }
-
- /// Update the original model.
- 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 ();
- }
- }
-
- ///
- public bool EnableForDesign ()
- {
- Text = """
- TextView provides a fully featured multi-line text editor.
- It supports word wrap and history for undo.
- """;
-
- return true;
- }
-
-
- ///
- protected override void Dispose (bool disposing)
- {
- if (disposing && ContextMenu is { })
- {
- ContextMenu.Visible = false;
- ContextMenu.Dispose ();
- ContextMenu = null;
- }
-
- base.Dispose (disposing);
- }
}
\ No newline at end of file
diff --git a/Terminal.sln.DotSettings b/Terminal.sln.DotSettings
index ef25662a4..0662bf5a6 100644
--- a/Terminal.sln.DotSettings
+++ b/Terminal.sln.DotSettings
@@ -1,7 +1,9 @@
BackingField
Inherit
+ ReturnDefaultValue
True
+ True
5000
1000
3000
@@ -14,7 +16,7 @@
SUGGESTION
WARNING
ERROR
- ERROR
+ SUGGESTION
WARNING
SUGGESTION
WARNING
@@ -331,6 +333,7 @@
<Entry.SortBy>
<Access Is="0" />
<Readonly />
+ <PropertyName />
</Entry.SortBy>
</Entry>
<Property DisplayName="Properties w/ Backing Field" Priority="100">
@@ -353,14 +356,18 @@
</And>
</Entry.Match>
<Entry.SortBy>
- <ImplementsInterface Immediate="True" />
+ <ImplementsInterface />
+ <Name />
</Entry.SortBy>
</Entry>
<Entry DisplayName="All other members">
<Entry.SortBy>
<Access Is="0" />
- <Name />
+ <Static />
+ <Virtual />
<Override />
+ <ImplementsInterface />
+ <Name />
</Entry.SortBy>
</Entry>
<Entry DisplayName="Nested Types">
@@ -374,10 +381,11 @@
</Entry>
</TypePattern>
</Patterns>
- UseVarWhenEvident
+ UseExplicitType
UseExplicitType
- UseVarWhenEvident
+ UseVar
True
+ True
False
False
True
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |