mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 15:57:56 +01:00
Reorganize TextView event handlers by logical functionality
Event handlers moved from TextView.EventHandlers.cs to their logical locations: - Initialization handlers (TextView_Initialized, TextView_SuperViewChanged, TextView_ViewportChanged, TextView_LayoutComplete, Model_LinesLoaded) -> TextView.Core.cs - History handler (HistoryText_ChangeText) -> TextView.Insert.cs - Context menu handler (ContextMenu_KeyChanged) -> TextView.ContextMenu.cs - Removed TextView.EventHandlers.cs Build successful, all 163 tests pass
This commit is contained in:
151
Terminal.Gui/Views/TextInput/TextView/TextView.Clipboard.cs
Normal file
151
Terminal.Gui/Views/TextInput/TextView/TextView.Clipboard.cs
Normal file
@@ -0,0 +1,151 @@
|
||||
namespace Terminal.Gui.Views;
|
||||
|
||||
public partial class TextView
|
||||
{
|
||||
private void SetClipboard (string text)
|
||||
{
|
||||
if (text is { })
|
||||
{
|
||||
Clipboard.Contents = text;
|
||||
}
|
||||
}
|
||||
/// <summary>Copy the selected text to the clipboard contents.</summary>
|
||||
public void Copy ()
|
||||
{
|
||||
SetWrapModel ();
|
||||
|
||||
if (IsSelecting)
|
||||
{
|
||||
_copiedText = GetRegion (out _copiedCellsList);
|
||||
SetClipboard (_copiedText);
|
||||
_copyWithoutSelection = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
List<Cell> currentLine = GetCurrentLine ();
|
||||
_copiedCellsList.Add (currentLine);
|
||||
_copiedText = Cell.ToString (currentLine);
|
||||
SetClipboard (_copiedText);
|
||||
_copyWithoutSelection = true;
|
||||
}
|
||||
|
||||
UpdateWrapModel ();
|
||||
DoNeededAction ();
|
||||
}
|
||||
|
||||
/// <summary>Cut the selected text to the clipboard contents.</summary>
|
||||
public void Cut ()
|
||||
{
|
||||
SetWrapModel ();
|
||||
_copiedText = GetRegion (out _copiedCellsList);
|
||||
SetClipboard (_copiedText);
|
||||
|
||||
if (!_isReadOnly)
|
||||
{
|
||||
ClearRegion ();
|
||||
|
||||
_historyText.Add (
|
||||
[new (GetCurrentLine ())],
|
||||
CursorPosition,
|
||||
TextEditingLineStatus.Replaced
|
||||
);
|
||||
}
|
||||
|
||||
UpdateWrapModel ();
|
||||
IsSelecting = false;
|
||||
DoNeededAction ();
|
||||
OnContentsChanged ();
|
||||
}
|
||||
|
||||
/// <summary>Paste the clipboard contents into the current selected position.</summary>
|
||||
public void Paste ()
|
||||
{
|
||||
if (_isReadOnly)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SetWrapModel ();
|
||||
string? contents = Clipboard.Contents;
|
||||
|
||||
if (_copyWithoutSelection && contents!.FirstOrDefault (x => x is '\n' or '\r') == 0)
|
||||
{
|
||||
List<Cell> runeList = contents is null ? [] : Cell.ToCellList (contents);
|
||||
List<Cell> currentLine = GetCurrentLine ();
|
||||
|
||||
_historyText.Add ([new (currentLine)], CursorPosition);
|
||||
|
||||
List<List<Cell>> addedLine = [new (currentLine), runeList];
|
||||
|
||||
_historyText.Add (
|
||||
[.. addedLine],
|
||||
CursorPosition,
|
||||
TextEditingLineStatus.Added
|
||||
);
|
||||
|
||||
_model.AddLine (CurrentRow, runeList);
|
||||
CurrentRow++;
|
||||
|
||||
_historyText.Add (
|
||||
[new (GetCurrentLine ())],
|
||||
CursorPosition,
|
||||
TextEditingLineStatus.Replaced
|
||||
);
|
||||
|
||||
SetNeedsDraw ();
|
||||
OnContentsChanged ();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (IsSelecting)
|
||||
{
|
||||
ClearRegion ();
|
||||
}
|
||||
|
||||
_copyWithoutSelection = false;
|
||||
InsertAllText (contents!, true);
|
||||
|
||||
if (IsSelecting)
|
||||
{
|
||||
_historyText.ReplaceLast (
|
||||
[new (GetCurrentLine ())],
|
||||
CursorPosition,
|
||||
TextEditingLineStatus.Original
|
||||
);
|
||||
}
|
||||
|
||||
SetNeedsDraw ();
|
||||
}
|
||||
|
||||
UpdateWrapModel ();
|
||||
IsSelecting = false;
|
||||
DoNeededAction ();
|
||||
}
|
||||
|
||||
private void ProcessCopy ()
|
||||
{
|
||||
ResetColumnTrack ();
|
||||
Copy ();
|
||||
}
|
||||
|
||||
private void ProcessCut ()
|
||||
{
|
||||
ResetColumnTrack ();
|
||||
Cut ();
|
||||
}
|
||||
|
||||
private void ProcessPaste ()
|
||||
{
|
||||
ResetColumnTrack ();
|
||||
|
||||
if (_isReadOnly)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Paste ();
|
||||
}
|
||||
|
||||
|
||||
private void AppendClipboard (string text) { Clipboard.Contents += text; }
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using System.Globalization;
|
||||
|
||||
namespace Terminal.Gui.Views;
|
||||
|
||||
/// <summary>Context menu functionality</summary>
|
||||
public partial class TextView
|
||||
{
|
||||
private PopoverMenu CreateContextMenu ()
|
||||
{
|
||||
PopoverMenu menu = new (
|
||||
new List<View>
|
||||
{
|
||||
new MenuItem (this, Command.SelectAll, Strings.ctxSelectAll),
|
||||
new MenuItem (this, Command.DeleteAll, Strings.ctxDeleteAll),
|
||||
new MenuItem (this, Command.Copy, Strings.ctxCopy),
|
||||
new MenuItem (this, Command.Cut, Strings.ctxCut),
|
||||
new MenuItem (this, Command.Paste, Strings.ctxPaste),
|
||||
new MenuItem (this, Command.Undo, Strings.ctxUndo),
|
||||
new MenuItem (this, Command.Redo, Strings.ctxRedo)
|
||||
});
|
||||
|
||||
menu.KeyChanged += ContextMenu_KeyChanged;
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
private void ShowContextMenu (Point? mousePosition)
|
||||
{
|
||||
if (!Equals (_currentCulture, Thread.CurrentThread.CurrentUICulture))
|
||||
{
|
||||
_currentCulture = Thread.CurrentThread.CurrentUICulture;
|
||||
}
|
||||
|
||||
if (mousePosition is null)
|
||||
{
|
||||
mousePosition = ViewportToScreen (new Point (CursorPosition.X, CursorPosition.Y));
|
||||
}
|
||||
|
||||
ContextMenu?.MakeVisible (mousePosition);
|
||||
}
|
||||
|
||||
private void ContextMenu_KeyChanged (object? sender, KeyChangedEventArgs e)
|
||||
{
|
||||
KeyBindings.Replace (e.OldKey, e.NewKey);
|
||||
}
|
||||
}
|
||||
661
Terminal.Gui/Views/TextInput/TextView/TextView.Core.cs
Normal file
661
Terminal.Gui/Views/TextInput/TextView/TextView.Core.cs
Normal file
@@ -0,0 +1,661 @@
|
||||
using System.Globalization;
|
||||
|
||||
namespace Terminal.Gui.Views;
|
||||
|
||||
/// <summary>Core functionality - Fields, Constructor, and fundamental properties</summary>
|
||||
public partial class TextView
|
||||
{
|
||||
#region Fields
|
||||
|
||||
private readonly HistoryText _historyText = new ();
|
||||
private bool _allowsReturn = true;
|
||||
private bool _allowsTab = true;
|
||||
private bool _clickWithSelecting;
|
||||
|
||||
// The column we are tracking, or -1 if we are not tracking any column
|
||||
private int _columnTrack = -1;
|
||||
private bool _continuousFind;
|
||||
private bool _copyWithoutSelection;
|
||||
private string? _currentCaller;
|
||||
private CultureInfo? _currentCulture;
|
||||
private bool _isButtonShift;
|
||||
private bool _isButtonReleased;
|
||||
private bool _isDrawing;
|
||||
private bool _isReadOnly;
|
||||
private bool _lastWasKill;
|
||||
private int _leftColumn;
|
||||
private TextModel _model = new ();
|
||||
private bool _multiline = true;
|
||||
private Dim? _savedHeight;
|
||||
private int _selectionStartColumn, _selectionStartRow;
|
||||
private bool _shiftSelecting;
|
||||
private int _tabWidth = 4;
|
||||
private int _topRow;
|
||||
private bool _wordWrap;
|
||||
private WordWrapManager? _wrapManager;
|
||||
private bool _wrapNeeded;
|
||||
|
||||
private string? _copiedText;
|
||||
private List<List<Cell>> _copiedCellsList = [];
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a <see cref="TextView"/> on the specified area, with dimensions controlled with the X, Y, Width
|
||||
/// and Height properties.
|
||||
/// </summary>
|
||||
public TextView ()
|
||||
{
|
||||
CanFocus = true;
|
||||
CursorVisibility = CursorVisibility.Default;
|
||||
Used = true;
|
||||
|
||||
// By default, disable hotkeys (in case someone sets Title)
|
||||
base.HotKeySpecifier = new ('\xffff');
|
||||
|
||||
_model.LinesLoaded += Model_LinesLoaded!;
|
||||
_historyText.ChangeText += HistoryText_ChangeText!;
|
||||
|
||||
Initialized += TextView_Initialized!;
|
||||
|
||||
SuperViewChanged += TextView_SuperViewChanged!;
|
||||
|
||||
SubViewsLaidOut += TextView_LayoutComplete;
|
||||
|
||||
// Things this view knows how to do
|
||||
|
||||
// Note - NewLine is only bound to Enter if Multiline is true
|
||||
AddCommand (Command.NewLine, ctx => ProcessEnterKey (ctx));
|
||||
|
||||
AddCommand (
|
||||
Command.PageDown,
|
||||
() =>
|
||||
{
|
||||
ProcessPageDown ();
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
AddCommand (
|
||||
Command.PageDownExtend,
|
||||
() =>
|
||||
{
|
||||
ProcessPageDownExtend ();
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
AddCommand (
|
||||
Command.PageUp,
|
||||
() =>
|
||||
{
|
||||
ProcessPageUp ();
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
AddCommand (
|
||||
Command.PageUpExtend,
|
||||
() =>
|
||||
{
|
||||
ProcessPageUpExtend ();
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
AddCommand (Command.Down, () => ProcessMoveDown ());
|
||||
|
||||
AddCommand (
|
||||
Command.DownExtend,
|
||||
() =>
|
||||
{
|
||||
ProcessMoveDownExtend ();
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
AddCommand (Command.Up, () => ProcessMoveUp ());
|
||||
|
||||
AddCommand (
|
||||
Command.UpExtend,
|
||||
() =>
|
||||
{
|
||||
ProcessMoveUpExtend ();
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
AddCommand (Command.Right, () => ProcessMoveRight ());
|
||||
|
||||
AddCommand (
|
||||
Command.RightExtend,
|
||||
() =>
|
||||
{
|
||||
ProcessMoveRightExtend ();
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
AddCommand (Command.Left, () => ProcessMoveLeft ());
|
||||
|
||||
AddCommand (
|
||||
Command.LeftExtend,
|
||||
() =>
|
||||
{
|
||||
ProcessMoveLeftExtend ();
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
AddCommand (
|
||||
Command.DeleteCharLeft,
|
||||
() =>
|
||||
{
|
||||
ProcessDeleteCharLeft ();
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
AddCommand (
|
||||
Command.LeftStart,
|
||||
() =>
|
||||
{
|
||||
ProcessMoveLeftStart ();
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
AddCommand (
|
||||
Command.LeftStartExtend,
|
||||
() =>
|
||||
{
|
||||
ProcessMoveLeftStartExtend ();
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
AddCommand (
|
||||
Command.DeleteCharRight,
|
||||
() =>
|
||||
{
|
||||
ProcessDeleteCharRight ();
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
AddCommand (
|
||||
Command.RightEnd,
|
||||
() =>
|
||||
{
|
||||
ProcessMoveEndOfLine ();
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
AddCommand (
|
||||
Command.RightEndExtend,
|
||||
() =>
|
||||
{
|
||||
ProcessMoveRightEndExtend ();
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
AddCommand (
|
||||
Command.CutToEndLine,
|
||||
() =>
|
||||
{
|
||||
KillToEndOfLine ();
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
AddCommand (
|
||||
Command.CutToStartLine,
|
||||
() =>
|
||||
{
|
||||
KillToLeftStart ();
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
AddCommand (
|
||||
Command.Paste,
|
||||
() =>
|
||||
{
|
||||
ProcessPaste ();
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
AddCommand (
|
||||
Command.ToggleExtend,
|
||||
() =>
|
||||
{
|
||||
ToggleSelecting ();
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
AddCommand (
|
||||
Command.Copy,
|
||||
() =>
|
||||
{
|
||||
ProcessCopy ();
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
AddCommand (
|
||||
Command.Cut,
|
||||
() =>
|
||||
{
|
||||
ProcessCut ();
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
AddCommand (
|
||||
Command.WordLeft,
|
||||
() =>
|
||||
{
|
||||
ProcessMoveWordBackward ();
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
AddCommand (
|
||||
Command.WordLeftExtend,
|
||||
() =>
|
||||
{
|
||||
ProcessMoveWordBackwardExtend ();
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
AddCommand (
|
||||
Command.WordRight,
|
||||
() =>
|
||||
{
|
||||
ProcessMoveWordForward ();
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
AddCommand (
|
||||
Command.WordRightExtend,
|
||||
() =>
|
||||
{
|
||||
ProcessMoveWordForwardExtend ();
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
AddCommand (
|
||||
Command.KillWordForwards,
|
||||
() =>
|
||||
{
|
||||
ProcessKillWordForward ();
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
AddCommand (
|
||||
Command.KillWordBackwards,
|
||||
() =>
|
||||
{
|
||||
ProcessKillWordBackward ();
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
AddCommand (
|
||||
Command.End,
|
||||
() =>
|
||||
{
|
||||
MoveBottomEnd ();
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
AddCommand (
|
||||
Command.EndExtend,
|
||||
() =>
|
||||
{
|
||||
MoveBottomEndExtend ();
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
AddCommand (
|
||||
Command.Start,
|
||||
() =>
|
||||
{
|
||||
MoveTopHome ();
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
AddCommand (
|
||||
Command.StartExtend,
|
||||
() =>
|
||||
{
|
||||
MoveTopHomeExtend ();
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
AddCommand (
|
||||
Command.SelectAll,
|
||||
() =>
|
||||
{
|
||||
ProcessSelectAll ();
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
AddCommand (
|
||||
Command.ToggleOverwrite,
|
||||
() =>
|
||||
{
|
||||
ProcessSetOverwrite ();
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
AddCommand (
|
||||
Command.EnableOverwrite,
|
||||
() =>
|
||||
{
|
||||
SetOverwrite (true);
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
AddCommand (
|
||||
Command.DisableOverwrite,
|
||||
() =>
|
||||
{
|
||||
SetOverwrite (false);
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
AddCommand (Command.Tab, () => ProcessTab ());
|
||||
AddCommand (Command.BackTab, () => ProcessBackTab ());
|
||||
|
||||
AddCommand (
|
||||
Command.Undo,
|
||||
() =>
|
||||
{
|
||||
Undo ();
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
AddCommand (
|
||||
Command.Redo,
|
||||
() =>
|
||||
{
|
||||
Redo ();
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
AddCommand (
|
||||
Command.DeleteAll,
|
||||
() =>
|
||||
{
|
||||
DeleteAll ();
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
AddCommand (
|
||||
Command.Context,
|
||||
() =>
|
||||
{
|
||||
ShowContextMenu (null);
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
AddCommand (
|
||||
Command.Open,
|
||||
() =>
|
||||
{
|
||||
PromptForColors ();
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
// Default keybindings for this view
|
||||
KeyBindings.Remove (Key.Space);
|
||||
|
||||
KeyBindings.Remove (Key.Enter);
|
||||
KeyBindings.Add (Key.Enter, Multiline ? Command.NewLine : Command.Accept);
|
||||
|
||||
KeyBindings.Add (Key.PageDown, Command.PageDown);
|
||||
KeyBindings.Add (Key.V.WithCtrl, Command.PageDown);
|
||||
|
||||
KeyBindings.Add (Key.PageDown.WithShift, Command.PageDownExtend);
|
||||
|
||||
KeyBindings.Add (Key.PageUp, Command.PageUp);
|
||||
|
||||
KeyBindings.Add (Key.PageUp.WithShift, Command.PageUpExtend);
|
||||
|
||||
KeyBindings.Add (Key.N.WithCtrl, Command.Down);
|
||||
KeyBindings.Add (Key.CursorDown, Command.Down);
|
||||
|
||||
KeyBindings.Add (Key.CursorDown.WithShift, Command.DownExtend);
|
||||
|
||||
KeyBindings.Add (Key.P.WithCtrl, Command.Up);
|
||||
KeyBindings.Add (Key.CursorUp, Command.Up);
|
||||
|
||||
KeyBindings.Add (Key.CursorUp.WithShift, Command.UpExtend);
|
||||
|
||||
KeyBindings.Add (Key.F.WithCtrl, Command.Right);
|
||||
KeyBindings.Add (Key.CursorRight, Command.Right);
|
||||
|
||||
KeyBindings.Add (Key.CursorRight.WithShift, Command.RightExtend);
|
||||
|
||||
KeyBindings.Add (Key.B.WithCtrl, Command.Left);
|
||||
KeyBindings.Add (Key.CursorLeft, Command.Left);
|
||||
|
||||
KeyBindings.Add (Key.CursorLeft.WithShift, Command.LeftExtend);
|
||||
|
||||
KeyBindings.Add (Key.Backspace, Command.DeleteCharLeft);
|
||||
|
||||
KeyBindings.Add (Key.Home, Command.LeftStart);
|
||||
|
||||
KeyBindings.Add (Key.Home.WithShift, Command.LeftStartExtend);
|
||||
|
||||
KeyBindings.Add (Key.Delete, Command.DeleteCharRight);
|
||||
KeyBindings.Add (Key.D.WithCtrl, Command.DeleteCharRight);
|
||||
|
||||
KeyBindings.Add (Key.End, Command.RightEnd);
|
||||
KeyBindings.Add (Key.E.WithCtrl, Command.RightEnd);
|
||||
|
||||
KeyBindings.Add (Key.End.WithShift, Command.RightEndExtend);
|
||||
|
||||
KeyBindings.Add (Key.K.WithCtrl, Command.CutToEndLine); // kill-to-end
|
||||
|
||||
KeyBindings.Add (Key.Delete.WithCtrl.WithShift, Command.CutToEndLine); // kill-to-end
|
||||
|
||||
KeyBindings.Add (Key.Backspace.WithCtrl.WithShift, Command.CutToStartLine); // kill-to-start
|
||||
|
||||
KeyBindings.Add (Key.Y.WithCtrl, Command.Paste); // Control-y, yank
|
||||
KeyBindings.Add (Key.Space.WithCtrl, Command.ToggleExtend);
|
||||
|
||||
KeyBindings.Add (Key.C.WithCtrl, Command.Copy);
|
||||
|
||||
KeyBindings.Add (Key.W.WithCtrl, Command.Cut); // Move to Unix?
|
||||
KeyBindings.Add (Key.X.WithCtrl, Command.Cut);
|
||||
|
||||
KeyBindings.Add (Key.CursorLeft.WithCtrl, Command.WordLeft);
|
||||
|
||||
KeyBindings.Add (Key.CursorLeft.WithCtrl.WithShift, Command.WordLeftExtend);
|
||||
|
||||
KeyBindings.Add (Key.CursorRight.WithCtrl, Command.WordRight);
|
||||
|
||||
KeyBindings.Add (Key.CursorRight.WithCtrl.WithShift, Command.WordRightExtend);
|
||||
KeyBindings.Add (Key.Delete.WithCtrl, Command.KillWordForwards); // kill-word-forwards
|
||||
KeyBindings.Add (Key.Backspace.WithCtrl, Command.KillWordBackwards); // kill-word-backwards
|
||||
|
||||
KeyBindings.Add (Key.End.WithCtrl, Command.End);
|
||||
KeyBindings.Add (Key.End.WithCtrl.WithShift, Command.EndExtend);
|
||||
KeyBindings.Add (Key.Home.WithCtrl, Command.Start);
|
||||
KeyBindings.Add (Key.Home.WithCtrl.WithShift, Command.StartExtend);
|
||||
KeyBindings.Add (Key.A.WithCtrl, Command.SelectAll);
|
||||
KeyBindings.Add (Key.InsertChar, Command.ToggleOverwrite);
|
||||
KeyBindings.Add (Key.Tab, Command.Tab);
|
||||
KeyBindings.Add (Key.Tab.WithShift, Command.BackTab);
|
||||
|
||||
KeyBindings.Add (Key.Z.WithCtrl, Command.Undo);
|
||||
KeyBindings.Add (Key.R.WithCtrl, Command.Redo);
|
||||
|
||||
KeyBindings.Add (Key.G.WithCtrl, Command.DeleteAll);
|
||||
KeyBindings.Add (Key.D.WithCtrl.WithShift, Command.DeleteAll);
|
||||
|
||||
KeyBindings.Add (Key.L.WithCtrl, Command.Open);
|
||||
|
||||
#if UNIX_KEY_BINDINGS
|
||||
KeyBindings.Add (Key.C.WithAlt, Command.Copy);
|
||||
KeyBindings.Add (Key.B.WithAlt, Command.WordLeft);
|
||||
KeyBindings.Add (Key.W.WithAlt, Command.Cut);
|
||||
KeyBindings.Add (Key.V.WithAlt, Command.PageUp);
|
||||
KeyBindings.Add (Key.F.WithAlt, Command.WordRight);
|
||||
KeyBindings.Add (Key.K.WithAlt, Command.CutToStartLine); // kill-to-start
|
||||
#endif
|
||||
|
||||
_currentCulture = Thread.CurrentThread.CurrentUICulture;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Initialization and Configuration
|
||||
|
||||
/// <summary>
|
||||
/// Configures the ScrollBars to work with the modern View scrolling system.
|
||||
/// </summary>
|
||||
private void ConfigureScrollBars ()
|
||||
{
|
||||
// Subscribe to ViewportChanged to sync internal scroll fields
|
||||
ViewportChanged += TextView_ViewportChanged;
|
||||
|
||||
// Vertical ScrollBar: AutoShow enabled by default as per requirements
|
||||
VerticalScrollBar.AutoShow = true;
|
||||
|
||||
// Horizontal ScrollBar: AutoShow tracks WordWrap as per requirements
|
||||
HorizontalScrollBar.AutoShow = !WordWrap;
|
||||
}
|
||||
|
||||
private void TextView_Initialized (object sender, EventArgs e)
|
||||
{
|
||||
if (Autocomplete.HostControl is null)
|
||||
{
|
||||
Autocomplete.HostControl = this;
|
||||
}
|
||||
|
||||
ContextMenu = CreateContextMenu ();
|
||||
App?.Popover?.Register (ContextMenu);
|
||||
KeyBindings.Add (ContextMenu.Key, Command.Context);
|
||||
|
||||
// Configure ScrollBars to use modern View scrolling infrastructure
|
||||
ConfigureScrollBars ();
|
||||
|
||||
OnContentsChanged ();
|
||||
}
|
||||
|
||||
private void TextView_SuperViewChanged (object sender, SuperViewChangedEventArgs e)
|
||||
{
|
||||
if (e.SuperView is { })
|
||||
{
|
||||
if (Autocomplete.HostControl is null)
|
||||
{
|
||||
Autocomplete.HostControl = this;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Autocomplete.HostControl = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void TextView_ViewportChanged (object? sender, DrawEventArgs e)
|
||||
{
|
||||
// Sync internal scroll position fields with Viewport
|
||||
// Only update if values actually changed to prevent infinite loops
|
||||
if (_topRow != Viewport.Y)
|
||||
{
|
||||
_topRow = Viewport.Y;
|
||||
}
|
||||
|
||||
if (_leftColumn != Viewport.X)
|
||||
{
|
||||
_leftColumn = Viewport.X;
|
||||
}
|
||||
}
|
||||
|
||||
private void TextView_LayoutComplete (object? sender, LayoutEventArgs e)
|
||||
{
|
||||
WrapTextModel ();
|
||||
UpdateContentSize ();
|
||||
Adjust ();
|
||||
}
|
||||
|
||||
private void Model_LinesLoaded (object sender, EventArgs e)
|
||||
{
|
||||
// This call is not needed. Model_LinesLoaded gets invoked when
|
||||
// model.LoadString (value) is called. LoadString is called from one place
|
||||
// (Text.set) and historyText.Clear() is called immediately after.
|
||||
// If this call happens, HistoryText_ChangeText will get called multiple times
|
||||
// when Text is set, which is wrong.
|
||||
//historyText.Clear (Text);
|
||||
|
||||
if (!_multiline && !IsInitialized)
|
||||
{
|
||||
CurrentColumn = Text.GetRuneCount ();
|
||||
_leftColumn = CurrentColumn > Viewport.Width + 1 ? CurrentColumn - Viewport.Width + 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
641
Terminal.Gui/Views/TextInput/TextView/TextView.Delete.cs
Normal file
641
Terminal.Gui/Views/TextInput/TextView/TextView.Delete.cs
Normal file
@@ -0,0 +1,641 @@
|
||||
namespace Terminal.Gui.Views;
|
||||
|
||||
public partial class TextView
|
||||
{
|
||||
/// <summary>Deletes all text.</summary>
|
||||
public void DeleteAll ()
|
||||
{
|
||||
if (Lines == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_selectionStartColumn = 0;
|
||||
_selectionStartRow = 0;
|
||||
MoveBottomEndExtend ();
|
||||
DeleteCharLeft ();
|
||||
SetNeedsDraw ();
|
||||
}
|
||||
|
||||
/// <summary>Deletes all the selected or a single character at left from the position of the cursor.</summary>
|
||||
public void DeleteCharLeft ()
|
||||
{
|
||||
if (_isReadOnly)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SetWrapModel ();
|
||||
|
||||
if (IsSelecting)
|
||||
{
|
||||
_historyText.Add (new () { new (GetCurrentLine ()) }, CursorPosition);
|
||||
|
||||
ClearSelectedRegion ();
|
||||
|
||||
List<Cell> currentLine = GetCurrentLine ();
|
||||
|
||||
_historyText.Add (
|
||||
new () { new (currentLine) },
|
||||
CursorPosition,
|
||||
TextEditingLineStatus.Replaced
|
||||
);
|
||||
|
||||
UpdateWrapModel ();
|
||||
OnContentsChanged ();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (DeleteTextBackwards ())
|
||||
{
|
||||
UpdateWrapModel ();
|
||||
OnContentsChanged ();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateWrapModel ();
|
||||
|
||||
DoNeededAction ();
|
||||
OnContentsChanged ();
|
||||
}
|
||||
|
||||
/// <summary>Deletes all the selected or a single character at right from the position of the cursor.</summary>
|
||||
public void DeleteCharRight ()
|
||||
{
|
||||
if (_isReadOnly)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SetWrapModel ();
|
||||
|
||||
if (IsSelecting)
|
||||
{
|
||||
_historyText.Add (new () { new (GetCurrentLine ()) }, CursorPosition);
|
||||
|
||||
ClearSelectedRegion ();
|
||||
|
||||
List<Cell> currentLine = GetCurrentLine ();
|
||||
|
||||
_historyText.Add (
|
||||
new () { new (currentLine) },
|
||||
CursorPosition,
|
||||
TextEditingLineStatus.Replaced
|
||||
);
|
||||
|
||||
UpdateWrapModel ();
|
||||
OnContentsChanged ();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (DeleteTextForwards ())
|
||||
{
|
||||
UpdateWrapModel ();
|
||||
OnContentsChanged ();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateWrapModel ();
|
||||
|
||||
DoNeededAction ();
|
||||
OnContentsChanged ();
|
||||
}
|
||||
|
||||
private bool DeleteTextBackwards ()
|
||||
{
|
||||
SetWrapModel ();
|
||||
|
||||
if (CurrentColumn > 0)
|
||||
{
|
||||
// Delete backwards
|
||||
List<Cell> currentLine = GetCurrentLine ();
|
||||
|
||||
_historyText.Add (new () { new (currentLine) }, CursorPosition);
|
||||
|
||||
currentLine.RemoveAt (CurrentColumn - 1);
|
||||
|
||||
if (_wordWrap)
|
||||
{
|
||||
_wrapNeeded = true;
|
||||
}
|
||||
|
||||
CurrentColumn--;
|
||||
|
||||
_historyText.Add (
|
||||
new () { new (currentLine) },
|
||||
CursorPosition,
|
||||
TextEditingLineStatus.Replaced
|
||||
);
|
||||
|
||||
if (CurrentColumn < _leftColumn)
|
||||
{
|
||||
_leftColumn--;
|
||||
SetNeedsDraw ();
|
||||
}
|
||||
else
|
||||
{
|
||||
// BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method.
|
||||
//SetNeedsDraw (new (0, currentRow - topRow, 1, Viewport.Width));
|
||||
SetNeedsDraw ();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Merges the current line with the previous one.
|
||||
if (CurrentRow == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
int prowIdx = CurrentRow - 1;
|
||||
List<Cell> prevRow = _model.GetLine (prowIdx);
|
||||
|
||||
_historyText.Add (new () { new (prevRow) }, CursorPosition);
|
||||
|
||||
List<List<Cell>> removedLines = new () { new (prevRow) };
|
||||
|
||||
removedLines.Add (new (GetCurrentLine ()));
|
||||
|
||||
_historyText.Add (
|
||||
removedLines,
|
||||
new (CurrentColumn, prowIdx),
|
||||
TextEditingLineStatus.Removed
|
||||
);
|
||||
|
||||
int prevCount = prevRow.Count;
|
||||
_model.GetLine (prowIdx).AddRange (GetCurrentLine ());
|
||||
_model.RemoveLine (CurrentRow);
|
||||
|
||||
if (_wordWrap)
|
||||
{
|
||||
_wrapNeeded = true;
|
||||
}
|
||||
|
||||
CurrentRow--;
|
||||
|
||||
_historyText.Add (
|
||||
new () { GetCurrentLine () },
|
||||
new (CurrentColumn, prowIdx),
|
||||
TextEditingLineStatus.Replaced
|
||||
);
|
||||
|
||||
CurrentColumn = prevCount;
|
||||
SetNeedsDraw ();
|
||||
}
|
||||
|
||||
UpdateWrapModel ();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool DeleteTextForwards ()
|
||||
{
|
||||
SetWrapModel ();
|
||||
|
||||
List<Cell> currentLine = GetCurrentLine ();
|
||||
|
||||
if (CurrentColumn == currentLine.Count)
|
||||
{
|
||||
if (CurrentRow + 1 == _model.Count)
|
||||
{
|
||||
UpdateWrapModel ();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_historyText.Add (new () { new (currentLine) }, CursorPosition);
|
||||
|
||||
List<List<Cell>> removedLines = new () { new (currentLine) };
|
||||
|
||||
List<Cell> nextLine = _model.GetLine (CurrentRow + 1);
|
||||
|
||||
removedLines.Add (new (nextLine));
|
||||
|
||||
_historyText.Add (removedLines, CursorPosition, TextEditingLineStatus.Removed);
|
||||
|
||||
currentLine.AddRange (nextLine);
|
||||
_model.RemoveLine (CurrentRow + 1);
|
||||
|
||||
_historyText.Add (
|
||||
new () { new (currentLine) },
|
||||
CursorPosition,
|
||||
TextEditingLineStatus.Replaced
|
||||
);
|
||||
|
||||
if (_wordWrap)
|
||||
{
|
||||
_wrapNeeded = true;
|
||||
}
|
||||
|
||||
DoSetNeedsDraw (new (0, CurrentRow - _topRow, Viewport.Width, CurrentRow - _topRow + 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
_historyText.Add ([ [.. currentLine]], CursorPosition);
|
||||
|
||||
currentLine.RemoveAt (CurrentColumn);
|
||||
|
||||
_historyText.Add (
|
||||
[ [.. currentLine]],
|
||||
CursorPosition,
|
||||
TextEditingLineStatus.Replaced
|
||||
);
|
||||
|
||||
if (_wordWrap)
|
||||
{
|
||||
_wrapNeeded = true;
|
||||
}
|
||||
|
||||
DoSetNeedsDraw (
|
||||
new (
|
||||
CurrentColumn - _leftColumn,
|
||||
CurrentRow - _topRow,
|
||||
Viewport.Width,
|
||||
Math.Max (CurrentRow - _topRow + 1, 0)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
UpdateWrapModel ();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ProcessKillWordForward ()
|
||||
{
|
||||
ResetColumnTrack ();
|
||||
StopSelecting ();
|
||||
KillWordForward ();
|
||||
}
|
||||
|
||||
private void ProcessKillWordBackward ()
|
||||
{
|
||||
ResetColumnTrack ();
|
||||
KillWordBackward ();
|
||||
}
|
||||
|
||||
private void ProcessDeleteCharRight ()
|
||||
{
|
||||
ResetColumnTrack ();
|
||||
DeleteCharRight ();
|
||||
}
|
||||
|
||||
private void ProcessDeleteCharLeft ()
|
||||
{
|
||||
ResetColumnTrack ();
|
||||
DeleteCharLeft ();
|
||||
}
|
||||
|
||||
private void KillWordForward ()
|
||||
{
|
||||
if (_isReadOnly)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SetWrapModel ();
|
||||
|
||||
List<Cell> currentLine = GetCurrentLine ();
|
||||
|
||||
_historyText.Add ([ [.. GetCurrentLine ()]], CursorPosition);
|
||||
|
||||
if (currentLine.Count == 0 || CurrentColumn == currentLine.Count)
|
||||
{
|
||||
DeleteTextForwards ();
|
||||
|
||||
_historyText.ReplaceLast (
|
||||
[ [.. GetCurrentLine ()]],
|
||||
CursorPosition,
|
||||
TextEditingLineStatus.Replaced
|
||||
);
|
||||
|
||||
UpdateWrapModel ();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
(int col, int row)? newPos = _model.WordForward (CurrentColumn, CurrentRow, UseSameRuneTypeForWords);
|
||||
var restCount = 0;
|
||||
|
||||
if (newPos.HasValue && CurrentRow == newPos.Value.row)
|
||||
{
|
||||
restCount = newPos.Value.col - CurrentColumn;
|
||||
currentLine.RemoveRange (CurrentColumn, restCount);
|
||||
}
|
||||
else if (newPos.HasValue)
|
||||
{
|
||||
restCount = currentLine.Count - CurrentColumn;
|
||||
currentLine.RemoveRange (CurrentColumn, restCount);
|
||||
}
|
||||
|
||||
if (_wordWrap)
|
||||
{
|
||||
_wrapNeeded = true;
|
||||
}
|
||||
|
||||
_historyText.Add (
|
||||
[ [.. GetCurrentLine ()]],
|
||||
CursorPosition,
|
||||
TextEditingLineStatus.Replaced
|
||||
);
|
||||
|
||||
UpdateWrapModel ();
|
||||
|
||||
DoSetNeedsDraw (new (0, CurrentRow - _topRow, Viewport.Width, Viewport.Height));
|
||||
DoNeededAction ();
|
||||
}
|
||||
|
||||
private void KillWordBackward ()
|
||||
{
|
||||
if (_isReadOnly)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SetWrapModel ();
|
||||
|
||||
List<Cell> currentLine = GetCurrentLine ();
|
||||
|
||||
_historyText.Add ([ [.. GetCurrentLine ()]], CursorPosition);
|
||||
|
||||
if (CurrentColumn == 0)
|
||||
{
|
||||
DeleteTextBackwards ();
|
||||
|
||||
_historyText.ReplaceLast (
|
||||
[ [.. GetCurrentLine ()]],
|
||||
CursorPosition,
|
||||
TextEditingLineStatus.Replaced
|
||||
);
|
||||
|
||||
UpdateWrapModel ();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
(int col, int row)? newPos = _model.WordBackward (CurrentColumn, CurrentRow, UseSameRuneTypeForWords);
|
||||
|
||||
if (newPos.HasValue && CurrentRow == newPos.Value.row)
|
||||
{
|
||||
int restCount = CurrentColumn - newPos.Value.col;
|
||||
currentLine.RemoveRange (newPos.Value.col, restCount);
|
||||
|
||||
if (_wordWrap)
|
||||
{
|
||||
_wrapNeeded = true;
|
||||
}
|
||||
|
||||
CurrentColumn = newPos.Value.col;
|
||||
}
|
||||
else if (newPos.HasValue)
|
||||
{
|
||||
int restCount;
|
||||
|
||||
if (newPos.Value.row == CurrentRow)
|
||||
{
|
||||
restCount = currentLine.Count - CurrentColumn;
|
||||
currentLine.RemoveRange (CurrentColumn, restCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
while (CurrentRow != newPos.Value.row)
|
||||
{
|
||||
restCount = currentLine.Count;
|
||||
currentLine.RemoveRange (0, restCount);
|
||||
|
||||
CurrentRow--;
|
||||
currentLine = GetCurrentLine ();
|
||||
}
|
||||
}
|
||||
|
||||
if (_wordWrap)
|
||||
{
|
||||
_wrapNeeded = true;
|
||||
}
|
||||
|
||||
CurrentColumn = newPos.Value.col;
|
||||
CurrentRow = newPos.Value.row;
|
||||
}
|
||||
|
||||
_historyText.Add (
|
||||
[ [.. GetCurrentLine ()]],
|
||||
CursorPosition,
|
||||
TextEditingLineStatus.Replaced
|
||||
);
|
||||
|
||||
UpdateWrapModel ();
|
||||
|
||||
DoSetNeedsDraw (new (0, CurrentRow - _topRow, Viewport.Width, Viewport.Height));
|
||||
DoNeededAction ();
|
||||
}
|
||||
|
||||
private void KillToLeftStart ()
|
||||
{
|
||||
if (_isReadOnly)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_model.Count == 1 && GetCurrentLine ().Count == 0)
|
||||
{
|
||||
// Prevents from adding line feeds if there is no more lines.
|
||||
return;
|
||||
}
|
||||
|
||||
SetWrapModel ();
|
||||
|
||||
List<Cell> currentLine = GetCurrentLine ();
|
||||
var setLastWasKill = true;
|
||||
|
||||
if (currentLine.Count > 0 && CurrentColumn == 0)
|
||||
{
|
||||
UpdateWrapModel ();
|
||||
|
||||
DeleteTextBackwards ();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_historyText.Add ([ [.. currentLine]], CursorPosition);
|
||||
|
||||
if (currentLine.Count == 0)
|
||||
{
|
||||
if (CurrentRow > 0)
|
||||
{
|
||||
_model.RemoveLine (CurrentRow);
|
||||
|
||||
if (_model.Count > 0 || _lastWasKill)
|
||||
{
|
||||
string val = Environment.NewLine;
|
||||
|
||||
if (_lastWasKill)
|
||||
{
|
||||
AppendClipboard (val);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetClipboard (val);
|
||||
}
|
||||
}
|
||||
|
||||
if (_model.Count == 0)
|
||||
{
|
||||
// Prevents from adding line feeds if there is no more lines.
|
||||
setLastWasKill = false;
|
||||
}
|
||||
|
||||
CurrentRow--;
|
||||
currentLine = _model.GetLine (CurrentRow);
|
||||
|
||||
List<List<Cell>> removedLine =
|
||||
[
|
||||
[..currentLine],
|
||||
[]
|
||||
];
|
||||
|
||||
_historyText.Add (
|
||||
[.. removedLine],
|
||||
CursorPosition,
|
||||
TextEditingLineStatus.Removed
|
||||
);
|
||||
|
||||
CurrentColumn = currentLine.Count;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int restCount = CurrentColumn;
|
||||
List<Cell> rest = currentLine.GetRange (0, restCount);
|
||||
var val = string.Empty;
|
||||
val += StringFromCells (rest);
|
||||
|
||||
if (_lastWasKill)
|
||||
{
|
||||
AppendClipboard (val);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetClipboard (val);
|
||||
}
|
||||
|
||||
currentLine.RemoveRange (0, restCount);
|
||||
CurrentColumn = 0;
|
||||
}
|
||||
|
||||
_historyText.Add (
|
||||
[ [.. GetCurrentLine ()]],
|
||||
CursorPosition,
|
||||
TextEditingLineStatus.Replaced
|
||||
);
|
||||
|
||||
UpdateWrapModel ();
|
||||
|
||||
DoSetNeedsDraw (new (0, CurrentRow - _topRow, Viewport.Width, Viewport.Height));
|
||||
|
||||
_lastWasKill = setLastWasKill;
|
||||
DoNeededAction ();
|
||||
}
|
||||
|
||||
private void KillToEndOfLine ()
|
||||
{
|
||||
if (_isReadOnly)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_model.Count == 1 && GetCurrentLine ().Count == 0)
|
||||
{
|
||||
// Prevents from adding line feeds if there is no more lines.
|
||||
return;
|
||||
}
|
||||
|
||||
SetWrapModel ();
|
||||
|
||||
List<Cell> currentLine = GetCurrentLine ();
|
||||
var setLastWasKill = true;
|
||||
|
||||
if (currentLine.Count > 0 && CurrentColumn == currentLine.Count)
|
||||
{
|
||||
UpdateWrapModel ();
|
||||
|
||||
DeleteTextForwards ();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_historyText.Add (new () { new (currentLine) }, CursorPosition);
|
||||
|
||||
if (currentLine.Count == 0)
|
||||
{
|
||||
if (CurrentRow < _model.Count - 1)
|
||||
{
|
||||
List<List<Cell>> removedLines = new () { new (currentLine) };
|
||||
|
||||
_model.RemoveLine (CurrentRow);
|
||||
|
||||
removedLines.Add (new (GetCurrentLine ()));
|
||||
|
||||
_historyText.Add (
|
||||
new (removedLines),
|
||||
CursorPosition,
|
||||
TextEditingLineStatus.Removed
|
||||
);
|
||||
}
|
||||
|
||||
if (_model.Count > 0 || _lastWasKill)
|
||||
{
|
||||
string val = Environment.NewLine;
|
||||
|
||||
if (_lastWasKill)
|
||||
{
|
||||
AppendClipboard (val);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetClipboard (val);
|
||||
}
|
||||
}
|
||||
|
||||
if (_model.Count == 0)
|
||||
{
|
||||
// Prevents from adding line feeds if there is no more lines.
|
||||
setLastWasKill = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int restCount = currentLine.Count - CurrentColumn;
|
||||
List<Cell> rest = currentLine.GetRange (CurrentColumn, restCount);
|
||||
var val = string.Empty;
|
||||
val += StringFromCells (rest);
|
||||
|
||||
if (_lastWasKill)
|
||||
{
|
||||
AppendClipboard (val);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetClipboard (val);
|
||||
}
|
||||
|
||||
currentLine.RemoveRange (CurrentColumn, restCount);
|
||||
}
|
||||
|
||||
_historyText.Add (
|
||||
[ [.. GetCurrentLine ()]],
|
||||
CursorPosition,
|
||||
TextEditingLineStatus.Replaced
|
||||
);
|
||||
|
||||
UpdateWrapModel ();
|
||||
|
||||
DoSetNeedsDraw (new (0, CurrentRow - _topRow, Viewport.Width, Viewport.Height));
|
||||
|
||||
_lastWasKill = setLastWasKill;
|
||||
DoNeededAction ();
|
||||
}
|
||||
}
|
||||
348
Terminal.Gui/Views/TextInput/TextView/TextView.Drawing.cs
Normal file
348
Terminal.Gui/Views/TextInput/TextView/TextView.Drawing.cs
Normal file
@@ -0,0 +1,348 @@
|
||||
namespace Terminal.Gui.Views;
|
||||
|
||||
public partial class TextView
|
||||
{
|
||||
internal void ApplyCellsAttribute (Attribute attribute)
|
||||
{
|
||||
if (!ReadOnly && SelectedLength > 0)
|
||||
{
|
||||
int startRow = Math.Min (SelectionStartRow, CurrentRow);
|
||||
int endRow = Math.Max (CurrentRow, SelectionStartRow);
|
||||
int startCol = SelectionStartRow <= CurrentRow ? SelectionStartColumn : CurrentColumn;
|
||||
int endCol = CurrentRow >= SelectionStartRow ? CurrentColumn : SelectionStartColumn;
|
||||
List<List<Cell>> selectedCellsOriginal = [];
|
||||
List<List<Cell>> selectedCellsChanged = [];
|
||||
|
||||
for (int r = startRow; r <= endRow; r++)
|
||||
{
|
||||
List<Cell> line = GetLine (r);
|
||||
|
||||
selectedCellsOriginal.Add ([.. line]);
|
||||
|
||||
for (int c = r == startRow ? startCol : 0;
|
||||
c < (r == endRow ? endCol : line.Count);
|
||||
c++)
|
||||
{
|
||||
Cell cell = line [c]; // Copy value to a new variable
|
||||
cell.Attribute = attribute; // Modify the copy
|
||||
line [c] = cell; // Assign the modified copy back
|
||||
}
|
||||
|
||||
selectedCellsChanged.Add ([.. GetLine (r)]);
|
||||
}
|
||||
|
||||
GetSelectedRegion ();
|
||||
IsSelecting = false;
|
||||
|
||||
_historyText.Add (
|
||||
[.. selectedCellsOriginal],
|
||||
new Point (startCol, startRow)
|
||||
);
|
||||
|
||||
_historyText.Add (
|
||||
[.. selectedCellsChanged],
|
||||
new Point (startCol, startRow),
|
||||
TextEditingLineStatus.Attribute
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private Attribute? GetSelectedCellAttribute ()
|
||||
{
|
||||
List<Cell> line;
|
||||
|
||||
if (SelectedLength > 0)
|
||||
{
|
||||
line = GetLine (SelectionStartRow);
|
||||
|
||||
if (line [Math.Min (SelectionStartColumn, line.Count - 1)].Attribute is { } attributeSel)
|
||||
{
|
||||
return new (attributeSel);
|
||||
}
|
||||
|
||||
return GetAttributeForRole (VisualRole.Active);
|
||||
}
|
||||
|
||||
line = GetCurrentLine ();
|
||||
|
||||
if (line [Math.Min (CurrentColumn, line.Count - 1)].Attribute is { } attribute)
|
||||
{
|
||||
return new (attribute);
|
||||
}
|
||||
|
||||
return GetAttributeForRole (VisualRole.Active);
|
||||
}
|
||||
|
||||
/// <summary>Invoked when the normal color is drawn.</summary>
|
||||
public event EventHandler<CellEventArgs>? DrawNormalColor;
|
||||
|
||||
/// <summary>Invoked when the ready only color is drawn.</summary>
|
||||
public event EventHandler<CellEventArgs>? DrawReadOnlyColor;
|
||||
|
||||
/// <summary>Invoked when the selection color is drawn.</summary>
|
||||
public event EventHandler<CellEventArgs>? DrawSelectionColor;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the used color is drawn. The Used Color is used to indicate if the <see cref="Key.InsertChar"/>
|
||||
/// was pressed and enabled.
|
||||
/// </summary>
|
||||
public event EventHandler<CellEventArgs>? DrawUsedColor;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override bool OnDrawingContent ()
|
||||
{
|
||||
_isDrawing = true;
|
||||
|
||||
SetAttributeForRole (Enabled ? VisualRole.Editable : VisualRole.Disabled);
|
||||
|
||||
(int width, int height) offB = OffSetBackground ();
|
||||
int right = Viewport.Width + offB.width;
|
||||
int bottom = Viewport.Height + offB.height;
|
||||
var row = 0;
|
||||
|
||||
for (int idxRow = _topRow; idxRow < _model.Count; idxRow++)
|
||||
{
|
||||
List<Cell> line = _model.GetLine (idxRow);
|
||||
int lineRuneCount = line.Count;
|
||||
var col = 0;
|
||||
|
||||
Move (0, row);
|
||||
|
||||
for (int idxCol = _leftColumn; idxCol < lineRuneCount; idxCol++)
|
||||
{
|
||||
string text = idxCol >= lineRuneCount ? " " : line [idxCol].Grapheme;
|
||||
int cols = text.GetColumns (false);
|
||||
|
||||
if (idxCol < line.Count && IsSelecting && PointInSelection (idxCol, idxRow))
|
||||
{
|
||||
OnDrawSelectionColor (line, idxCol, idxRow);
|
||||
}
|
||||
else if (idxCol == CurrentColumn && idxRow == CurrentRow && !IsSelecting && !Used && HasFocus && idxCol < lineRuneCount)
|
||||
{
|
||||
OnDrawUsedColor (line, idxCol, idxRow);
|
||||
}
|
||||
else if (ReadOnly)
|
||||
{
|
||||
OnDrawReadOnlyColor (line, idxCol, idxRow);
|
||||
}
|
||||
else
|
||||
{
|
||||
OnDrawNormalColor (line, idxCol, idxRow);
|
||||
}
|
||||
|
||||
if (text == "\t")
|
||||
{
|
||||
cols += TabWidth + 1;
|
||||
|
||||
if (col + cols > right)
|
||||
{
|
||||
cols = right - col;
|
||||
}
|
||||
|
||||
for (var i = 0; i < cols; i++)
|
||||
{
|
||||
if (col + i < right)
|
||||
{
|
||||
AddRune (col + i, row, (Rune)' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddStr (col, row, text);
|
||||
|
||||
// Ensures that cols less than 0 to be 1 because it will be converted to a printable rune
|
||||
cols = Math.Max (cols, 1);
|
||||
}
|
||||
|
||||
if (!TextModel.SetCol (ref col, Viewport.Right, cols))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (idxCol + 1 < lineRuneCount && col + line [idxCol + 1].Grapheme.GetColumns () > right)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (col < right)
|
||||
{
|
||||
SetAttributeForRole (ReadOnly ? VisualRole.ReadOnly : VisualRole.Editable);
|
||||
ClearRegion (col, row, right, row + 1);
|
||||
}
|
||||
|
||||
row++;
|
||||
}
|
||||
|
||||
if (row < bottom)
|
||||
{
|
||||
SetAttributeForRole (ReadOnly ? VisualRole.ReadOnly : VisualRole.Editable);
|
||||
ClearRegion (Viewport.Left, row, right, bottom);
|
||||
}
|
||||
|
||||
_isDrawing = false;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="View.Driver"/> to an appropriate color for rendering the given <paramref name="idxCol"/>
|
||||
/// of the current <paramref name="line"/>. Override to provide custom coloring by calling
|
||||
/// <see cref="View.SetAttribute"/> Defaults to <see cref="Scheme.Normal"/>.
|
||||
/// </summary>
|
||||
/// <param name="line">The line.</param>
|
||||
/// <param name="idxCol">The col index.</param>
|
||||
/// <param name="idxRow">The row index.</param>
|
||||
protected virtual void OnDrawNormalColor (List<Cell> line, int idxCol, int idxRow)
|
||||
{
|
||||
(int Row, int Col) unwrappedPos = GetUnwrappedPosition (idxRow, idxCol);
|
||||
var ev = new CellEventArgs (line, idxCol, unwrappedPos);
|
||||
DrawNormalColor?.Invoke (this, ev);
|
||||
|
||||
if (line [idxCol].Attribute is { })
|
||||
{
|
||||
Attribute? attribute = line [idxCol].Attribute;
|
||||
SetAttribute ((Attribute)attribute!);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetAttribute (GetAttributeForRole (VisualRole.Normal));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="View.Driver"/> to an appropriate color for rendering the given <paramref name="idxCol"/>
|
||||
/// of the current <paramref name="line"/>. Override to provide custom coloring by calling
|
||||
/// <see cref="View.SetAttribute(Attribute)"/> Defaults to <see cref="Scheme.Focus"/>.
|
||||
/// </summary>
|
||||
/// <param name="line">The line.</param>
|
||||
/// <param name="idxCol">The col index.</param>
|
||||
/// ///
|
||||
/// <param name="idxRow">The row index.</param>
|
||||
protected virtual void OnDrawReadOnlyColor (List<Cell> line, int idxCol, int idxRow)
|
||||
{
|
||||
(int Row, int Col) unwrappedPos = GetUnwrappedPosition (idxRow, idxCol);
|
||||
var ev = new CellEventArgs (line, idxCol, unwrappedPos);
|
||||
DrawReadOnlyColor?.Invoke (this, ev);
|
||||
|
||||
Attribute? cellAttribute = line [idxCol].Attribute is { } ? line [idxCol].Attribute : GetAttributeForRole (VisualRole.ReadOnly);
|
||||
|
||||
if (cellAttribute!.Value.Foreground == cellAttribute.Value.Background)
|
||||
{
|
||||
SetAttribute (new (cellAttribute.Value.Foreground, cellAttribute.Value.Background, cellAttribute.Value.Style));
|
||||
}
|
||||
else
|
||||
{
|
||||
SetAttributeForRole (VisualRole.ReadOnly);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="View.Driver"/> to an appropriate color for rendering the given <paramref name="idxCol"/>
|
||||
/// of the current <paramref name="line"/>. Override to provide custom coloring by calling
|
||||
/// <see cref="View.SetAttribute(Attribute)"/> Defaults to <see cref="Scheme.Focus"/>.
|
||||
/// </summary>
|
||||
/// <param name="line">The line.</param>
|
||||
/// <param name="idxCol">The col index.</param>
|
||||
/// ///
|
||||
/// <param name="idxRow">The row index.</param>
|
||||
protected virtual void OnDrawSelectionColor (List<Cell> line, int idxCol, int idxRow)
|
||||
{
|
||||
(int Row, int Col) unwrappedPos = GetUnwrappedPosition (idxRow, idxCol);
|
||||
var ev = new CellEventArgs (line, idxCol, unwrappedPos);
|
||||
DrawSelectionColor?.Invoke (this, ev);
|
||||
|
||||
if (line [idxCol].Attribute is { })
|
||||
{
|
||||
Attribute? attribute = line [idxCol].Attribute;
|
||||
Attribute? active = GetAttributeForRole (VisualRole.Active);
|
||||
SetAttribute (new (active!.Value.Foreground, active.Value.Background, attribute!.Value.Style));
|
||||
}
|
||||
else
|
||||
{
|
||||
SetAttributeForRole (VisualRole.Active);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="View.Driver"/> to an appropriate color for rendering the given <paramref name="idxCol"/>
|
||||
/// of the current <paramref name="line"/>. Override to provide custom coloring by calling
|
||||
/// <see cref="View.SetAttribute(Attribute)"/> Defaults to <see cref="Scheme.HotFocus"/>.
|
||||
/// </summary>
|
||||
/// <param name="line">The line.</param>
|
||||
/// <param name="idxCol">The col index.</param>
|
||||
/// ///
|
||||
/// <param name="idxRow">The row index.</param>
|
||||
protected virtual void OnDrawUsedColor (List<Cell> line, int idxCol, int idxRow)
|
||||
{
|
||||
(int Row, int Col) unwrappedPos = GetUnwrappedPosition (idxRow, idxCol);
|
||||
var ev = new CellEventArgs (line, idxCol, unwrappedPos);
|
||||
DrawUsedColor?.Invoke (this, ev);
|
||||
|
||||
if (line [idxCol].Attribute is { })
|
||||
{
|
||||
Attribute? attribute = line [idxCol].Attribute;
|
||||
SetValidUsedColor (attribute!);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetValidUsedColor (GetAttributeForRole (VisualRole.Focus));
|
||||
}
|
||||
}
|
||||
|
||||
private void DoSetNeedsDraw (Rectangle rect)
|
||||
{
|
||||
if (_wrapNeeded)
|
||||
{
|
||||
SetNeedsDraw ();
|
||||
}
|
||||
else
|
||||
{
|
||||
// BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method.
|
||||
//SetNeedsDraw (rect);
|
||||
SetNeedsDraw ();
|
||||
}
|
||||
}
|
||||
|
||||
private Attribute? GetSelectedAttribute (int row, int col)
|
||||
{
|
||||
if (!InheritsPreviousAttribute || (Lines == 1 && GetLine (Lines).Count == 0))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
List<Cell> line = GetLine (row);
|
||||
int foundRow = row;
|
||||
|
||||
while (line.Count == 0)
|
||||
{
|
||||
if (foundRow == 0 && line.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
foundRow--;
|
||||
line = GetLine (foundRow);
|
||||
}
|
||||
|
||||
int foundCol = foundRow < row ? line.Count - 1 : Math.Min (col, line.Count - 1);
|
||||
|
||||
Cell cell = line [foundCol];
|
||||
|
||||
return cell.Attribute;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override bool OnGettingAttributeForRole (in VisualRole role, ref Attribute currentAttribute)
|
||||
{
|
||||
if (role == VisualRole.Normal)
|
||||
{
|
||||
currentAttribute = GetAttributeForRole (VisualRole.Editable);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnGettingAttributeForRole (role, ref currentAttribute);
|
||||
}
|
||||
}
|
||||
210
Terminal.Gui/Views/TextInput/TextView/TextView.Find.cs
Normal file
210
Terminal.Gui/Views/TextInput/TextView/TextView.Find.cs
Normal file
@@ -0,0 +1,210 @@
|
||||
namespace Terminal.Gui.Views;
|
||||
|
||||
/// <summary>Find and Replace functionality</summary>
|
||||
public partial class TextView
|
||||
{
|
||||
#region Public Find/Replace Methods
|
||||
|
||||
/// <summary>Find the next text based on the match case with the option to replace it.</summary>
|
||||
/// <param name="textToFind">The text to find.</param>
|
||||
/// <param name="gaveFullTurn"><c>true</c>If all the text was forward searched.<c>false</c>otherwise.</param>
|
||||
/// <param name="matchCase">The match case setting.</param>
|
||||
/// <param name="matchWholeWord">The match whole word setting.</param>
|
||||
/// <param name="textToReplace">The text to replace.</param>
|
||||
/// <param name="replace"><c>true</c>If is replacing.<c>false</c>otherwise.</param>
|
||||
/// <returns><c>true</c>If the text was found.<c>false</c>otherwise.</returns>
|
||||
public bool FindNextText (
|
||||
string textToFind,
|
||||
out bool gaveFullTurn,
|
||||
bool matchCase = false,
|
||||
bool matchWholeWord = false,
|
||||
string? textToReplace = null,
|
||||
bool replace = false
|
||||
)
|
||||
{
|
||||
if (_model.Count == 0)
|
||||
{
|
||||
gaveFullTurn = false;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
SetWrapModel ();
|
||||
ResetContinuousFind ();
|
||||
|
||||
(Point current, bool found) foundPos =
|
||||
_model.FindNextText (textToFind, out gaveFullTurn, matchCase, matchWholeWord);
|
||||
|
||||
return SetFoundText (textToFind, foundPos, textToReplace, replace);
|
||||
}
|
||||
|
||||
/// <summary>Find the previous text based on the match case with the option to replace it.</summary>
|
||||
/// <param name="textToFind">The text to find.</param>
|
||||
/// <param name="gaveFullTurn"><c>true</c>If all the text was backward searched.<c>false</c>otherwise.</param>
|
||||
/// <param name="matchCase">The match case setting.</param>
|
||||
/// <param name="matchWholeWord">The match whole word setting.</param>
|
||||
/// <param name="textToReplace">The text to replace.</param>
|
||||
/// <param name="replace"><c>true</c>If the text was found.<c>false</c>otherwise.</param>
|
||||
/// <returns><c>true</c>If the text was found.<c>false</c>otherwise.</returns>
|
||||
public bool FindPreviousText (
|
||||
string textToFind,
|
||||
out bool gaveFullTurn,
|
||||
bool matchCase = false,
|
||||
bool matchWholeWord = false,
|
||||
string? textToReplace = null,
|
||||
bool replace = false
|
||||
)
|
||||
{
|
||||
if (_model.Count == 0)
|
||||
{
|
||||
gaveFullTurn = false;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
SetWrapModel ();
|
||||
ResetContinuousFind ();
|
||||
|
||||
(Point current, bool found) foundPos =
|
||||
_model.FindPreviousText (textToFind, out gaveFullTurn, matchCase, matchWholeWord);
|
||||
|
||||
return SetFoundText (textToFind, foundPos, textToReplace, replace);
|
||||
}
|
||||
|
||||
/// <summary>Reset the flag to stop continuous find.</summary>
|
||||
public void FindTextChanged () { _continuousFind = false; }
|
||||
|
||||
/// <summary>Replaces all the text based on the match case.</summary>
|
||||
/// <param name="textToFind">The text to find.</param>
|
||||
/// <param name="matchCase">The match case setting.</param>
|
||||
/// <param name="matchWholeWord">The match whole word setting.</param>
|
||||
/// <param name="textToReplace">The text to replace.</param>
|
||||
/// <returns><c>true</c>If the text was found.<c>false</c>otherwise.</returns>
|
||||
public bool ReplaceAllText (
|
||||
string textToFind,
|
||||
bool matchCase = false,
|
||||
bool matchWholeWord = false,
|
||||
string? textToReplace = null
|
||||
)
|
||||
{
|
||||
if (_isReadOnly || _model.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
SetWrapModel ();
|
||||
ResetContinuousFind ();
|
||||
|
||||
(Point current, bool found) foundPos =
|
||||
_model.ReplaceAllText (textToFind, matchCase, matchWholeWord, textToReplace);
|
||||
|
||||
return SetFoundText (textToFind, foundPos, textToReplace, false, true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Find Helper Methods
|
||||
|
||||
private void ResetContinuousFind ()
|
||||
{
|
||||
if (!_continuousFind)
|
||||
{
|
||||
int col = IsSelecting ? _selectionStartColumn : CurrentColumn;
|
||||
int row = IsSelecting ? _selectionStartRow : CurrentRow;
|
||||
_model.ResetContinuousFind (new (col, row));
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetContinuousFindTrack ()
|
||||
{
|
||||
// Handle some state here - whether the last command was a kill
|
||||
// operation and the column tracking (up/down)
|
||||
_lastWasKill = false;
|
||||
_continuousFind = false;
|
||||
}
|
||||
|
||||
private bool SetFoundText (
|
||||
string text,
|
||||
(Point current, bool found) foundPos,
|
||||
string? textToReplace = null,
|
||||
bool replace = false,
|
||||
bool replaceAll = false
|
||||
)
|
||||
{
|
||||
if (foundPos.found)
|
||||
{
|
||||
StartSelecting ();
|
||||
_selectionStartColumn = foundPos.current.X;
|
||||
_selectionStartRow = foundPos.current.Y;
|
||||
|
||||
if (!replaceAll)
|
||||
{
|
||||
CurrentColumn = _selectionStartColumn + text.GetRuneCount ();
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentColumn = _selectionStartColumn + textToReplace!.GetRuneCount ();
|
||||
}
|
||||
|
||||
CurrentRow = foundPos.current.Y;
|
||||
|
||||
if (!_isReadOnly && replace)
|
||||
{
|
||||
Adjust ();
|
||||
ClearSelectedRegion ();
|
||||
InsertAllText (textToReplace!);
|
||||
StartSelecting ();
|
||||
_selectionStartColumn = CurrentColumn - textToReplace!.GetRuneCount ();
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateWrapModel ();
|
||||
SetNeedsDraw ();
|
||||
Adjust ();
|
||||
}
|
||||
|
||||
_continuousFind = true;
|
||||
|
||||
return foundPos.found;
|
||||
}
|
||||
|
||||
UpdateWrapModel ();
|
||||
_continuousFind = false;
|
||||
|
||||
return foundPos.found;
|
||||
}
|
||||
|
||||
private IEnumerable<(int col, int row, Cell rune)> ForwardIterator (int col, int row)
|
||||
{
|
||||
if (col < 0 || row < 0)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (row >= _model.Count)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
List<Cell> line = GetCurrentLine ();
|
||||
|
||||
if (col >= line.Count)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
while (row < _model.Count)
|
||||
{
|
||||
for (int c = col; c < line.Count; c++)
|
||||
{
|
||||
yield return (c, row, line [c]);
|
||||
}
|
||||
|
||||
col = 0;
|
||||
row++;
|
||||
line = GetCurrentLine ();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
309
Terminal.Gui/Views/TextInput/TextView/TextView.Insert.cs
Normal file
309
Terminal.Gui/Views/TextInput/TextView/TextView.Insert.cs
Normal file
@@ -0,0 +1,309 @@
|
||||
namespace Terminal.Gui.Views;
|
||||
|
||||
public partial class TextView
|
||||
{
|
||||
/// <summary>
|
||||
/// Inserts the given <paramref name="toAdd"/> text at the current cursor position exactly as if the user had just
|
||||
/// typed it
|
||||
/// </summary>
|
||||
/// <param name="toAdd">Text to add</param>
|
||||
public void InsertText (string toAdd)
|
||||
{
|
||||
foreach (char ch in toAdd)
|
||||
{
|
||||
Key key;
|
||||
|
||||
try
|
||||
{
|
||||
key = new (ch);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw new ArgumentException (
|
||||
$"Cannot insert character '{ch}' because it does not map to a Key"
|
||||
);
|
||||
}
|
||||
|
||||
InsertText (key);
|
||||
|
||||
if (NeedsDraw)
|
||||
{
|
||||
Adjust ();
|
||||
}
|
||||
else
|
||||
{
|
||||
PositionCursor ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Insert (Cell cell)
|
||||
{
|
||||
List<Cell> line = GetCurrentLine ();
|
||||
|
||||
if (Used)
|
||||
{
|
||||
line.Insert (Math.Min (CurrentColumn, line.Count), cell);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (CurrentColumn < line.Count)
|
||||
{
|
||||
line.RemoveAt (CurrentColumn);
|
||||
}
|
||||
|
||||
line.Insert (Math.Min (CurrentColumn, line.Count), cell);
|
||||
}
|
||||
|
||||
int prow = CurrentRow - _topRow;
|
||||
|
||||
if (!_wrapNeeded)
|
||||
{
|
||||
// BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method.
|
||||
//SetNeedsDraw (new (0, prow, Math.Max (Viewport.Width, 0), Math.Max (prow + 1, 0)));
|
||||
SetNeedsDraw ();
|
||||
}
|
||||
}
|
||||
|
||||
private void InsertAllText (string text, bool fromClipboard = false)
|
||||
{
|
||||
if (string.IsNullOrEmpty (text))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<List<Cell>> lines;
|
||||
|
||||
if (fromClipboard && text == _copiedText)
|
||||
{
|
||||
lines = _copiedCellsList;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Get selected attribute
|
||||
Attribute? attribute = GetSelectedAttribute (CurrentRow, CurrentColumn);
|
||||
lines = Cell.StringToLinesOfCells (text, attribute);
|
||||
}
|
||||
|
||||
if (lines.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SetWrapModel ();
|
||||
|
||||
List<Cell> line = GetCurrentLine ();
|
||||
|
||||
_historyText.Add ([new (line)], CursorPosition);
|
||||
|
||||
// Optimize single line
|
||||
if (lines.Count == 1)
|
||||
{
|
||||
line.InsertRange (CurrentColumn, lines [0]);
|
||||
CurrentColumn += lines [0].Count;
|
||||
|
||||
_historyText.Add (
|
||||
[new (line)],
|
||||
CursorPosition,
|
||||
TextEditingLineStatus.Replaced
|
||||
);
|
||||
|
||||
if (!_wordWrap && CurrentColumn - _leftColumn > Viewport.Width)
|
||||
{
|
||||
_leftColumn = Math.Max (CurrentColumn - Viewport.Width + 1, 0);
|
||||
}
|
||||
|
||||
if (_wordWrap)
|
||||
{
|
||||
SetNeedsDraw ();
|
||||
}
|
||||
else
|
||||
{
|
||||
// BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method.
|
||||
//SetNeedsDraw (new (0, currentRow - topRow, Viewport.Width, Math.Max (currentRow - topRow + 1, 0)));
|
||||
SetNeedsDraw ();
|
||||
}
|
||||
|
||||
UpdateWrapModel ();
|
||||
|
||||
OnContentsChanged ();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
List<Cell>? rest = null;
|
||||
var lastPosition = 0;
|
||||
|
||||
if (_model.Count > 0 && line.Count > 0 && !_copyWithoutSelection)
|
||||
{
|
||||
// Keep a copy of the rest of the line
|
||||
int restCount = line.Count - CurrentColumn;
|
||||
rest = line.GetRange (CurrentColumn, restCount);
|
||||
line.RemoveRange (CurrentColumn, restCount);
|
||||
}
|
||||
|
||||
// First line is inserted at the current location, the rest is appended
|
||||
line.InsertRange (CurrentColumn, lines [0]);
|
||||
|
||||
//model.AddLine (currentRow, lines [0]);
|
||||
|
||||
List<List<Cell>> addedLines = [new (line)];
|
||||
|
||||
for (var i = 1; i < lines.Count; i++)
|
||||
{
|
||||
_model.AddLine (CurrentRow + i, lines [i]);
|
||||
|
||||
addedLines.Add ([.. lines [i]]);
|
||||
}
|
||||
|
||||
if (rest is { })
|
||||
{
|
||||
List<Cell> last = _model.GetLine (CurrentRow + lines.Count - 1);
|
||||
lastPosition = last.Count;
|
||||
last.InsertRange (last.Count, rest);
|
||||
|
||||
addedLines.Last ().InsertRange (addedLines.Last ().Count, rest);
|
||||
}
|
||||
|
||||
_historyText.Add (addedLines, CursorPosition, TextEditingLineStatus.Added);
|
||||
|
||||
// Now adjust column and row positions
|
||||
CurrentRow += lines.Count - 1;
|
||||
CurrentColumn = rest is { } ? lastPosition : lines [^1].Count;
|
||||
Adjust ();
|
||||
|
||||
_historyText.Add (
|
||||
[new (line)],
|
||||
CursorPosition,
|
||||
TextEditingLineStatus.Replaced
|
||||
);
|
||||
|
||||
UpdateWrapModel ();
|
||||
OnContentsChanged ();
|
||||
}
|
||||
|
||||
private bool InsertText (Key a, Attribute? attribute = null)
|
||||
{
|
||||
//So that special keys like tab can be processed
|
||||
if (_isReadOnly)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
SetWrapModel ();
|
||||
|
||||
_historyText.Add ([new (GetCurrentLine ())], CursorPosition);
|
||||
|
||||
if (IsSelecting)
|
||||
{
|
||||
ClearSelectedRegion ();
|
||||
}
|
||||
|
||||
if ((uint)a.KeyCode == '\n')
|
||||
{
|
||||
_model.AddLine (CurrentRow + 1, []);
|
||||
CurrentRow++;
|
||||
CurrentColumn = 0;
|
||||
}
|
||||
else if ((uint)a.KeyCode == '\r')
|
||||
{
|
||||
CurrentColumn = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Used)
|
||||
{
|
||||
Insert (new () { Grapheme = a.AsRune.ToString (), Attribute = attribute });
|
||||
CurrentColumn++;
|
||||
|
||||
if (CurrentColumn >= _leftColumn + Viewport.Width)
|
||||
{
|
||||
_leftColumn++;
|
||||
SetNeedsDraw ();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Insert (new () { Grapheme = a.AsRune.ToString (), Attribute = attribute });
|
||||
CurrentColumn++;
|
||||
}
|
||||
}
|
||||
|
||||
_historyText.Add (
|
||||
[new (GetCurrentLine ())],
|
||||
CursorPosition,
|
||||
TextEditingLineStatus.Replaced
|
||||
);
|
||||
|
||||
UpdateWrapModel ();
|
||||
OnContentsChanged ();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#region History Event Handlers
|
||||
|
||||
private void HistoryText_ChangeText (object sender, HistoryTextItemEventArgs obj)
|
||||
{
|
||||
SetWrapModel ();
|
||||
|
||||
if (obj is { })
|
||||
{
|
||||
int startLine = obj.CursorPosition.Y;
|
||||
|
||||
if (obj.RemovedOnAdded is { })
|
||||
{
|
||||
int offset;
|
||||
|
||||
if (obj.IsUndoing)
|
||||
{
|
||||
offset = Math.Max (obj.RemovedOnAdded.Lines.Count - obj.Lines.Count, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
offset = obj.RemovedOnAdded.Lines.Count - 1;
|
||||
}
|
||||
|
||||
for (var i = 0; i < offset; i++)
|
||||
{
|
||||
if (Lines > obj.RemovedOnAdded.CursorPosition.Y)
|
||||
{
|
||||
_model.RemoveLine (obj.RemovedOnAdded.CursorPosition.Y);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < obj.Lines.Count; i++)
|
||||
{
|
||||
if (i == 0 || obj.LineStatus == TextEditingLineStatus.Original || obj.LineStatus == TextEditingLineStatus.Attribute)
|
||||
{
|
||||
_model.ReplaceLine (startLine, obj.Lines [i]);
|
||||
}
|
||||
else if (obj is { IsUndoing: true, LineStatus: TextEditingLineStatus.Removed }
|
||||
or { IsUndoing: false, LineStatus: TextEditingLineStatus.Added })
|
||||
{
|
||||
_model.AddLine (startLine, obj.Lines [i]);
|
||||
}
|
||||
else if (Lines > obj.CursorPosition.Y + 1)
|
||||
{
|
||||
_model.RemoveLine (obj.CursorPosition.Y + 1);
|
||||
}
|
||||
|
||||
startLine++;
|
||||
}
|
||||
|
||||
CursorPosition = obj.FinalCursorPosition;
|
||||
}
|
||||
|
||||
UpdateWrapModel ();
|
||||
|
||||
Adjust ();
|
||||
OnContentsChanged ();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
599
Terminal.Gui/Views/TextInput/TextView/TextView.Navigation.cs
Normal file
599
Terminal.Gui/Views/TextInput/TextView/TextView.Navigation.cs
Normal file
@@ -0,0 +1,599 @@
|
||||
namespace Terminal.Gui.Views;
|
||||
|
||||
/// <summary>Navigation functionality - cursor movement and scrolling</summary>
|
||||
public partial class TextView
|
||||
{
|
||||
#region Public Navigation Methods
|
||||
|
||||
/// <summary>Will scroll the <see cref="TextView"/> to the last line and position the cursor there.</summary>
|
||||
public void MoveEnd ()
|
||||
{
|
||||
CurrentRow = _model.Count - 1;
|
||||
List<Cell> line = GetCurrentLine ();
|
||||
CurrentColumn = line.Count;
|
||||
TrackColumn ();
|
||||
DoNeededAction ();
|
||||
}
|
||||
|
||||
/// <summary>Will scroll the <see cref="TextView"/> to the first line and position the cursor there.</summary>
|
||||
public void MoveHome ()
|
||||
{
|
||||
CurrentRow = 0;
|
||||
_topRow = 0;
|
||||
CurrentColumn = 0;
|
||||
_leftColumn = 0;
|
||||
TrackColumn ();
|
||||
DoNeededAction ();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will scroll the <see cref="TextView"/> to display the specified row at the top if <paramref name="isRow"/> is
|
||||
/// true or will scroll the <see cref="TextView"/> to display the specified column at the left if
|
||||
/// <paramref name="isRow"/> is false.
|
||||
/// </summary>
|
||||
/// <param name="idx">
|
||||
/// Row that should be displayed at the top or Column that should be displayed at the left, if the value
|
||||
/// is negative it will be reset to zero
|
||||
/// </param>
|
||||
/// <param name="isRow">If true (default) the <paramref name="idx"/> is a row, column otherwise.</param>
|
||||
public void ScrollTo (int idx, bool isRow = true)
|
||||
{
|
||||
if (idx < 0)
|
||||
{
|
||||
idx = 0;
|
||||
}
|
||||
|
||||
if (isRow)
|
||||
{
|
||||
_topRow = Math.Max (idx > _model.Count - 1 ? _model.Count - 1 : idx, 0);
|
||||
|
||||
if (IsInitialized && Viewport.Y != _topRow)
|
||||
{
|
||||
Viewport = Viewport with { Y = _topRow };
|
||||
}
|
||||
}
|
||||
else if (!_wordWrap)
|
||||
{
|
||||
int maxlength = _model.GetMaxVisibleLine (_topRow, _topRow + Viewport.Height, TabWidth);
|
||||
_leftColumn = Math.Max (!_wordWrap && idx > maxlength - 1 ? maxlength - 1 : idx, 0);
|
||||
|
||||
if (IsInitialized && Viewport.X != _leftColumn)
|
||||
{
|
||||
Viewport = Viewport with { X = _leftColumn };
|
||||
}
|
||||
}
|
||||
|
||||
SetNeedsDraw ();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Navigation Methods
|
||||
|
||||
private void MoveBottomEnd ()
|
||||
{
|
||||
ResetAllTrack ();
|
||||
|
||||
if (_shiftSelecting && IsSelecting)
|
||||
{
|
||||
StopSelecting ();
|
||||
}
|
||||
|
||||
MoveEnd ();
|
||||
}
|
||||
|
||||
private void MoveBottomEndExtend ()
|
||||
{
|
||||
ResetAllTrack ();
|
||||
StartSelecting ();
|
||||
MoveEnd ();
|
||||
}
|
||||
|
||||
private bool MoveDown ()
|
||||
{
|
||||
if (CurrentRow + 1 < _model.Count)
|
||||
{
|
||||
if (_columnTrack == -1)
|
||||
{
|
||||
_columnTrack = CurrentColumn;
|
||||
}
|
||||
|
||||
CurrentRow++;
|
||||
|
||||
if (CurrentRow >= _topRow + Viewport.Height)
|
||||
{
|
||||
_topRow++;
|
||||
SetNeedsDraw ();
|
||||
}
|
||||
|
||||
TrackColumn ();
|
||||
PositionCursor ();
|
||||
}
|
||||
else if (CurrentRow > Viewport.Height)
|
||||
{
|
||||
Adjust ();
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
DoNeededAction ();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void MoveEndOfLine ()
|
||||
{
|
||||
List<Cell> currentLine = GetCurrentLine ();
|
||||
CurrentColumn = currentLine.Count;
|
||||
DoNeededAction ();
|
||||
}
|
||||
|
||||
private bool MoveLeft ()
|
||||
{
|
||||
if (CurrentColumn > 0)
|
||||
{
|
||||
CurrentColumn--;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (CurrentRow > 0)
|
||||
{
|
||||
CurrentRow--;
|
||||
|
||||
if (CurrentRow < _topRow)
|
||||
{
|
||||
_topRow--;
|
||||
SetNeedsDraw ();
|
||||
}
|
||||
|
||||
List<Cell> currentLine = GetCurrentLine ();
|
||||
CurrentColumn = Math.Max (currentLine.Count - (ReadOnly ? 1 : 0), 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
DoNeededAction ();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void MovePageDown ()
|
||||
{
|
||||
int nPageDnShift = Viewport.Height - 1;
|
||||
|
||||
if (CurrentRow >= 0 && CurrentRow < _model.Count)
|
||||
{
|
||||
if (_columnTrack == -1)
|
||||
{
|
||||
_columnTrack = CurrentColumn;
|
||||
}
|
||||
|
||||
CurrentRow = CurrentRow + nPageDnShift > _model.Count
|
||||
? _model.Count > 0 ? _model.Count - 1 : 0
|
||||
: CurrentRow + nPageDnShift;
|
||||
|
||||
if (_topRow < CurrentRow - nPageDnShift)
|
||||
{
|
||||
_topRow = CurrentRow >= _model.Count
|
||||
? CurrentRow - nPageDnShift
|
||||
: _topRow + nPageDnShift;
|
||||
SetNeedsDraw ();
|
||||
}
|
||||
|
||||
TrackColumn ();
|
||||
PositionCursor ();
|
||||
}
|
||||
|
||||
DoNeededAction ();
|
||||
}
|
||||
|
||||
private void MovePageUp ()
|
||||
{
|
||||
int nPageUpShift = Viewport.Height - 1;
|
||||
|
||||
if (CurrentRow > 0)
|
||||
{
|
||||
if (_columnTrack == -1)
|
||||
{
|
||||
_columnTrack = CurrentColumn;
|
||||
}
|
||||
|
||||
CurrentRow = CurrentRow - nPageUpShift < 0 ? 0 : CurrentRow - nPageUpShift;
|
||||
|
||||
if (CurrentRow < _topRow)
|
||||
{
|
||||
_topRow = _topRow - nPageUpShift < 0 ? 0 : _topRow - nPageUpShift;
|
||||
SetNeedsDraw ();
|
||||
}
|
||||
|
||||
TrackColumn ();
|
||||
PositionCursor ();
|
||||
}
|
||||
|
||||
DoNeededAction ();
|
||||
}
|
||||
|
||||
private bool MoveRight ()
|
||||
{
|
||||
List<Cell> currentLine = GetCurrentLine ();
|
||||
|
||||
if ((ReadOnly ? CurrentColumn + 1 : CurrentColumn) < currentLine.Count)
|
||||
{
|
||||
CurrentColumn++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (CurrentRow + 1 < _model.Count)
|
||||
{
|
||||
CurrentRow++;
|
||||
CurrentColumn = 0;
|
||||
|
||||
if (CurrentRow >= _topRow + Viewport.Height)
|
||||
{
|
||||
_topRow++;
|
||||
SetNeedsDraw ();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
DoNeededAction ();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void MoveLeftStart ()
|
||||
{
|
||||
if (_leftColumn > 0)
|
||||
{
|
||||
SetNeedsDraw ();
|
||||
}
|
||||
|
||||
CurrentColumn = 0;
|
||||
_leftColumn = 0;
|
||||
DoNeededAction ();
|
||||
}
|
||||
|
||||
private void MoveTopHome ()
|
||||
{
|
||||
ResetAllTrack ();
|
||||
|
||||
if (_shiftSelecting && IsSelecting)
|
||||
{
|
||||
StopSelecting ();
|
||||
}
|
||||
|
||||
MoveHome ();
|
||||
}
|
||||
|
||||
private void MoveTopHomeExtend ()
|
||||
{
|
||||
ResetColumnTrack ();
|
||||
StartSelecting ();
|
||||
MoveHome ();
|
||||
}
|
||||
|
||||
private bool MoveUp ()
|
||||
{
|
||||
if (CurrentRow > 0)
|
||||
{
|
||||
if (_columnTrack == -1)
|
||||
{
|
||||
_columnTrack = CurrentColumn;
|
||||
}
|
||||
|
||||
CurrentRow--;
|
||||
|
||||
if (CurrentRow < _topRow)
|
||||
{
|
||||
_topRow--;
|
||||
SetNeedsDraw ();
|
||||
}
|
||||
|
||||
TrackColumn ();
|
||||
PositionCursor ();
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
DoNeededAction ();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void MoveWordBackward ()
|
||||
{
|
||||
(int col, int row)? newPos = _model.WordBackward (CurrentColumn, CurrentRow, UseSameRuneTypeForWords);
|
||||
|
||||
if (newPos.HasValue)
|
||||
{
|
||||
CurrentColumn = newPos.Value.col;
|
||||
CurrentRow = newPos.Value.row;
|
||||
}
|
||||
|
||||
DoNeededAction ();
|
||||
}
|
||||
|
||||
private void MoveWordForward ()
|
||||
{
|
||||
(int col, int row)? newPos = _model.WordForward (CurrentColumn, CurrentRow, UseSameRuneTypeForWords);
|
||||
|
||||
if (newPos.HasValue)
|
||||
{
|
||||
CurrentColumn = newPos.Value.col;
|
||||
CurrentRow = newPos.Value.row;
|
||||
}
|
||||
|
||||
DoNeededAction ();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Process Navigation Methods
|
||||
|
||||
private bool ProcessMoveDown ()
|
||||
{
|
||||
ResetContinuousFindTrack ();
|
||||
|
||||
if (_shiftSelecting && IsSelecting)
|
||||
{
|
||||
StopSelecting ();
|
||||
}
|
||||
|
||||
return MoveDown ();
|
||||
}
|
||||
|
||||
private void ProcessMoveDownExtend ()
|
||||
{
|
||||
ResetColumnTrack ();
|
||||
StartSelecting ();
|
||||
MoveDown ();
|
||||
}
|
||||
|
||||
private void ProcessMoveEndOfLine ()
|
||||
{
|
||||
ResetAllTrack ();
|
||||
|
||||
if (_shiftSelecting && IsSelecting)
|
||||
{
|
||||
StopSelecting ();
|
||||
}
|
||||
|
||||
MoveEndOfLine ();
|
||||
}
|
||||
|
||||
private void ProcessMoveRightEndExtend ()
|
||||
{
|
||||
ResetAllTrack ();
|
||||
StartSelecting ();
|
||||
MoveEndOfLine ();
|
||||
}
|
||||
|
||||
private bool ProcessMoveLeft ()
|
||||
{
|
||||
// if the user presses Left (without any control keys) and they are at the start of the text
|
||||
if (CurrentColumn == 0 && CurrentRow == 0)
|
||||
{
|
||||
if (IsSelecting)
|
||||
{
|
||||
StopSelecting ();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// do not respond (this lets the key press fall through to navigation system - which usually changes focus backward)
|
||||
return false;
|
||||
}
|
||||
|
||||
ResetAllTrack ();
|
||||
|
||||
if (_shiftSelecting && IsSelecting)
|
||||
{
|
||||
StopSelecting ();
|
||||
}
|
||||
|
||||
MoveLeft ();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ProcessMoveLeftExtend ()
|
||||
{
|
||||
ResetAllTrack ();
|
||||
StartSelecting ();
|
||||
MoveLeft ();
|
||||
}
|
||||
|
||||
private bool ProcessMoveRight ()
|
||||
{
|
||||
// if the user presses Right (without any control keys)
|
||||
// determine where the last cursor position in the text is
|
||||
int lastRow = _model.Count - 1;
|
||||
int lastCol = _model.GetLine (lastRow).Count;
|
||||
|
||||
// if they are at the very end of all the text do not respond (this lets the key press fall through to navigation system - which usually changes focus forward)
|
||||
if (CurrentColumn == lastCol && CurrentRow == lastRow)
|
||||
{
|
||||
// Unless they have text selected
|
||||
if (IsSelecting)
|
||||
{
|
||||
// In which case clear
|
||||
StopSelecting ();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ResetAllTrack ();
|
||||
|
||||
if (_shiftSelecting && IsSelecting)
|
||||
{
|
||||
StopSelecting ();
|
||||
}
|
||||
|
||||
MoveRight ();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ProcessMoveRightExtend ()
|
||||
{
|
||||
ResetAllTrack ();
|
||||
StartSelecting ();
|
||||
MoveRight ();
|
||||
}
|
||||
|
||||
private void ProcessMoveLeftStart ()
|
||||
{
|
||||
ResetAllTrack ();
|
||||
|
||||
if (_shiftSelecting && IsSelecting)
|
||||
{
|
||||
StopSelecting ();
|
||||
}
|
||||
|
||||
MoveLeftStart ();
|
||||
}
|
||||
|
||||
private void ProcessMoveLeftStartExtend ()
|
||||
{
|
||||
ResetAllTrack ();
|
||||
StartSelecting ();
|
||||
MoveLeftStart ();
|
||||
}
|
||||
|
||||
private bool ProcessMoveUp ()
|
||||
{
|
||||
ResetContinuousFindTrack ();
|
||||
|
||||
if (_shiftSelecting && IsSelecting)
|
||||
{
|
||||
StopSelecting ();
|
||||
}
|
||||
|
||||
return MoveUp ();
|
||||
}
|
||||
|
||||
private void ProcessMoveUpExtend ()
|
||||
{
|
||||
ResetColumnTrack ();
|
||||
StartSelecting ();
|
||||
MoveUp ();
|
||||
}
|
||||
|
||||
private void ProcessMoveWordBackward ()
|
||||
{
|
||||
ResetAllTrack ();
|
||||
|
||||
if (_shiftSelecting && IsSelecting)
|
||||
{
|
||||
StopSelecting ();
|
||||
}
|
||||
|
||||
MoveWordBackward ();
|
||||
}
|
||||
|
||||
private void ProcessMoveWordBackwardExtend ()
|
||||
{
|
||||
ResetAllTrack ();
|
||||
StartSelecting ();
|
||||
MoveWordBackward ();
|
||||
}
|
||||
|
||||
private void ProcessMoveWordForward ()
|
||||
{
|
||||
ResetAllTrack ();
|
||||
|
||||
if (_shiftSelecting && IsSelecting)
|
||||
{
|
||||
StopSelecting ();
|
||||
}
|
||||
|
||||
MoveWordForward ();
|
||||
}
|
||||
|
||||
private void ProcessMoveWordForwardExtend ()
|
||||
{
|
||||
ResetAllTrack ();
|
||||
StartSelecting ();
|
||||
MoveWordForward ();
|
||||
}
|
||||
|
||||
private void ProcessPageDown ()
|
||||
{
|
||||
ResetColumnTrack ();
|
||||
|
||||
if (_shiftSelecting && IsSelecting)
|
||||
{
|
||||
StopSelecting ();
|
||||
}
|
||||
|
||||
MovePageDown ();
|
||||
}
|
||||
|
||||
private void ProcessPageDownExtend ()
|
||||
{
|
||||
ResetColumnTrack ();
|
||||
StartSelecting ();
|
||||
MovePageDown ();
|
||||
}
|
||||
|
||||
private void ProcessPageUp ()
|
||||
{
|
||||
ResetColumnTrack ();
|
||||
|
||||
if (_shiftSelecting && IsSelecting)
|
||||
{
|
||||
StopSelecting ();
|
||||
}
|
||||
|
||||
MovePageUp ();
|
||||
}
|
||||
|
||||
private void ProcessPageUpExtend ()
|
||||
{
|
||||
ResetColumnTrack ();
|
||||
StartSelecting ();
|
||||
MovePageUp ();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Column Tracking
|
||||
|
||||
// Tries to snap the cursor to the tracking column
|
||||
private void TrackColumn ()
|
||||
{
|
||||
// Now track the column
|
||||
List<Cell> line = GetCurrentLine ();
|
||||
|
||||
if (line.Count < _columnTrack)
|
||||
{
|
||||
CurrentColumn = line.Count;
|
||||
}
|
||||
else if (_columnTrack != -1)
|
||||
{
|
||||
CurrentColumn = _columnTrack;
|
||||
}
|
||||
else if (CurrentColumn > line.Count)
|
||||
{
|
||||
CurrentColumn = line.Count;
|
||||
}
|
||||
|
||||
Adjust ();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
399
Terminal.Gui/Views/TextInput/TextView/TextView.Selection.cs
Normal file
399
Terminal.Gui/Views/TextInput/TextView/TextView.Selection.cs
Normal file
@@ -0,0 +1,399 @@
|
||||
namespace Terminal.Gui.Views;
|
||||
|
||||
public partial class TextView
|
||||
{
|
||||
|
||||
/// <summary>Get or sets whether the user is currently selecting text.</summary>
|
||||
public bool IsSelecting { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the word navigation should select only the word itself without spaces around it or with the
|
||||
/// spaces at right.
|
||||
/// Default is <c>false</c> meaning that the spaces at right are included in the selection.
|
||||
/// </summary>
|
||||
public bool SelectWordOnlyOnDoubleClick { get; set; }
|
||||
|
||||
/// <summary>Start row position of the selected text.</summary>
|
||||
public int SelectionStartRow
|
||||
{
|
||||
get => _selectionStartRow;
|
||||
set
|
||||
{
|
||||
_selectionStartRow = value < 0 ? 0 :
|
||||
value > _model.Count - 1 ? Math.Max (_model.Count - 1, 0) : value;
|
||||
IsSelecting = true;
|
||||
SetNeedsDraw ();
|
||||
Adjust ();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Start column position of the selected text.</summary>
|
||||
public int SelectionStartColumn
|
||||
{
|
||||
get => _selectionStartColumn;
|
||||
set
|
||||
{
|
||||
List<Cell> line = _model.GetLine (_selectionStartRow);
|
||||
|
||||
_selectionStartColumn = value < 0 ? 0 :
|
||||
value > line.Count ? line.Count : value;
|
||||
IsSelecting = true;
|
||||
SetNeedsDraw ();
|
||||
Adjust ();
|
||||
}
|
||||
}
|
||||
|
||||
private void StartSelecting ()
|
||||
{
|
||||
if (_shiftSelecting && IsSelecting)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_shiftSelecting = true;
|
||||
IsSelecting = true;
|
||||
_selectionStartColumn = CurrentColumn;
|
||||
_selectionStartRow = CurrentRow;
|
||||
}
|
||||
|
||||
private void StopSelecting ()
|
||||
{
|
||||
if (IsSelecting)
|
||||
{
|
||||
SetNeedsDraw ();
|
||||
}
|
||||
|
||||
_shiftSelecting = false;
|
||||
IsSelecting = false;
|
||||
_isButtonShift = false;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Length of the selected text.</summary>
|
||||
public int SelectedLength => GetSelectedLength ();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the selected text as
|
||||
/// <see>
|
||||
/// <cref>List{List{Cell}}</cref>
|
||||
/// </see>
|
||||
/// </summary>
|
||||
public List<List<Cell>> SelectedCellsList
|
||||
{
|
||||
get
|
||||
{
|
||||
GetRegion (out List<List<Cell>> selectedCellsList);
|
||||
|
||||
return selectedCellsList;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The selected text.</summary>
|
||||
public string SelectedText
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!IsSelecting || (_model.Count == 1 && _model.GetLine (0).Count == 0))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return GetSelectedRegion ();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Returns an encoded region start..end (top 32 bits are the row, low32 the column)
|
||||
private void GetEncodedRegionBounds (
|
||||
out long start,
|
||||
out long end,
|
||||
int? startRow = null,
|
||||
int? startCol = null,
|
||||
int? cRow = null,
|
||||
int? cCol = null
|
||||
)
|
||||
{
|
||||
long selection;
|
||||
long point;
|
||||
|
||||
if (startRow is null || startCol is null || cRow is null || cCol is null)
|
||||
{
|
||||
selection = ((long)(uint)_selectionStartRow << 32) | (uint)_selectionStartColumn;
|
||||
point = ((long)(uint)CurrentRow << 32) | (uint)CurrentColumn;
|
||||
}
|
||||
else
|
||||
{
|
||||
selection = ((long)(uint)startRow << 32) | (uint)startCol;
|
||||
point = ((long)(uint)cRow << 32) | (uint)cCol;
|
||||
}
|
||||
|
||||
if (selection > point)
|
||||
{
|
||||
start = point;
|
||||
end = selection;
|
||||
}
|
||||
else
|
||||
{
|
||||
start = selection;
|
||||
end = point;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Returns a string with the text in the selected
|
||||
// region.
|
||||
//
|
||||
internal string GetRegion (
|
||||
out List<List<Cell>> cellsList,
|
||||
int? sRow = null,
|
||||
int? sCol = null,
|
||||
int? cRow = null,
|
||||
int? cCol = null,
|
||||
TextModel? model = null
|
||||
)
|
||||
{
|
||||
GetEncodedRegionBounds (out long start, out long end, sRow, sCol, cRow, cCol);
|
||||
|
||||
cellsList = [];
|
||||
|
||||
if (start == end)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var startRow = (int)(start >> 32);
|
||||
var maxRow = (int)(end >> 32);
|
||||
var startCol = (int)(start & 0xffffffff);
|
||||
var endCol = (int)(end & 0xffffffff);
|
||||
List<Cell> line = model is null ? _model.GetLine (startRow) : model.GetLine (startRow);
|
||||
List<Cell> cells;
|
||||
|
||||
if (startRow == maxRow)
|
||||
{
|
||||
cells = line.GetRange (startCol, endCol - startCol);
|
||||
cellsList.Add (cells);
|
||||
|
||||
return StringFromCells (cells);
|
||||
}
|
||||
|
||||
cells = line.GetRange (startCol, line.Count - startCol);
|
||||
cellsList.Add (cells);
|
||||
string res = StringFromCells (cells);
|
||||
|
||||
for (int row = startRow + 1; row < maxRow; row++)
|
||||
{
|
||||
cellsList.AddRange ([]);
|
||||
cells = model == null ? _model.GetLine (row) : model.GetLine (row);
|
||||
cellsList.Add (cells);
|
||||
|
||||
res = res
|
||||
+ Environment.NewLine
|
||||
+ StringFromCells (cells);
|
||||
}
|
||||
|
||||
line = model is null ? _model.GetLine (maxRow) : model.GetLine (maxRow);
|
||||
cellsList.AddRange ([]);
|
||||
cells = line.GetRange (0, endCol);
|
||||
cellsList.Add (cells);
|
||||
res = res + Environment.NewLine + StringFromCells (cells);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private int GetSelectedLength () { return SelectedText.Length; }
|
||||
|
||||
private string GetSelectedRegion ()
|
||||
{
|
||||
int cRow = CurrentRow;
|
||||
int cCol = CurrentColumn;
|
||||
int startRow = _selectionStartRow;
|
||||
int startCol = _selectionStartColumn;
|
||||
TextModel model = _model;
|
||||
|
||||
if (_wordWrap)
|
||||
{
|
||||
cRow = _wrapManager!.GetModelLineFromWrappedLines (CurrentRow);
|
||||
cCol = _wrapManager.GetModelColFromWrappedLines (CurrentRow, CurrentColumn);
|
||||
startRow = _wrapManager.GetModelLineFromWrappedLines (_selectionStartRow);
|
||||
startCol = _wrapManager.GetModelColFromWrappedLines (_selectionStartRow, _selectionStartColumn);
|
||||
model = _wrapManager.Model;
|
||||
}
|
||||
|
||||
OnUnwrappedCursorPosition (cRow, cCol);
|
||||
|
||||
return GetRegion (out _, startRow, startCol, cRow, cCol, model);
|
||||
}
|
||||
|
||||
|
||||
private string StringFromCells (List<Cell> cells)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull (cells);
|
||||
|
||||
var size = 0;
|
||||
foreach (Cell cell in cells)
|
||||
{
|
||||
string t = cell.Grapheme;
|
||||
size += Encoding.Unicode.GetByteCount (t);
|
||||
}
|
||||
|
||||
byte [] encoded = new byte [size];
|
||||
var offset = 0;
|
||||
foreach (Cell cell in cells)
|
||||
{
|
||||
string t = cell.Grapheme;
|
||||
int bytesWritten = Encoding.Unicode.GetBytes (t, 0, t.Length, encoded, offset);
|
||||
offset += bytesWritten;
|
||||
}
|
||||
|
||||
// decode using the same encoding and the bytes actually written
|
||||
return Encoding.Unicode.GetString (encoded, 0, offset);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool EnableForDesign ()
|
||||
{
|
||||
Text = """
|
||||
TextView provides a fully featured multi-line text editor.
|
||||
It supports word wrap and history for undo.
|
||||
""";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Dispose (bool disposing)
|
||||
{
|
||||
if (disposing && ContextMenu is { })
|
||||
{
|
||||
ContextMenu.Visible = false;
|
||||
ContextMenu.Dispose ();
|
||||
ContextMenu = null;
|
||||
}
|
||||
|
||||
base.Dispose (disposing);
|
||||
}
|
||||
|
||||
private void ClearRegion ()
|
||||
{
|
||||
SetWrapModel ();
|
||||
|
||||
long start, end;
|
||||
long currentEncoded = ((long)(uint)CurrentRow << 32) | (uint)CurrentColumn;
|
||||
GetEncodedRegionBounds (out start, out end);
|
||||
var startRow = (int)(start >> 32);
|
||||
var maxrow = (int)(end >> 32);
|
||||
var startCol = (int)(start & 0xffffffff);
|
||||
var endCol = (int)(end & 0xffffffff);
|
||||
List<Cell> line = _model.GetLine (startRow);
|
||||
|
||||
_historyText.Add (new () { new (line) }, new (startCol, startRow));
|
||||
|
||||
List<List<Cell>> removedLines = new ();
|
||||
|
||||
if (startRow == maxrow)
|
||||
{
|
||||
removedLines.Add (new (line));
|
||||
|
||||
line.RemoveRange (startCol, endCol - startCol);
|
||||
CurrentColumn = startCol;
|
||||
|
||||
if (_wordWrap)
|
||||
{
|
||||
SetNeedsDraw ();
|
||||
}
|
||||
else
|
||||
{
|
||||
//QUESTION: Is the below comment still relevant?
|
||||
// BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method.
|
||||
//SetNeedsDraw (new (0, startRow - topRow, Viewport.Width, startRow - topRow + 1));
|
||||
SetNeedsDraw ();
|
||||
}
|
||||
|
||||
_historyText.Add (
|
||||
new (removedLines),
|
||||
CursorPosition,
|
||||
TextEditingLineStatus.Removed
|
||||
);
|
||||
|
||||
UpdateWrapModel ();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
removedLines.Add (new (line));
|
||||
|
||||
line.RemoveRange (startCol, line.Count - startCol);
|
||||
List<Cell> line2 = _model.GetLine (maxrow);
|
||||
line.AddRange (line2.Skip (endCol));
|
||||
|
||||
for (int row = startRow + 1; row <= maxrow; row++)
|
||||
{
|
||||
removedLines.Add (new (_model.GetLine (startRow + 1)));
|
||||
|
||||
_model.RemoveLine (startRow + 1);
|
||||
}
|
||||
|
||||
if (currentEncoded == end)
|
||||
{
|
||||
CurrentRow -= maxrow - startRow;
|
||||
}
|
||||
|
||||
CurrentColumn = startCol;
|
||||
|
||||
_historyText.Add (
|
||||
new (removedLines),
|
||||
CursorPosition,
|
||||
TextEditingLineStatus.Removed
|
||||
);
|
||||
|
||||
UpdateWrapModel ();
|
||||
|
||||
SetNeedsDraw ();
|
||||
}
|
||||
|
||||
private void ClearSelectedRegion ()
|
||||
{
|
||||
SetWrapModel ();
|
||||
|
||||
if (!_isReadOnly)
|
||||
{
|
||||
ClearRegion ();
|
||||
}
|
||||
|
||||
UpdateWrapModel ();
|
||||
IsSelecting = false;
|
||||
DoNeededAction ();
|
||||
}
|
||||
|
||||
/// <summary>Select all text.</summary>
|
||||
public void SelectAll ()
|
||||
{
|
||||
if (_model.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StartSelecting ();
|
||||
_selectionStartColumn = 0;
|
||||
_selectionStartRow = 0;
|
||||
CurrentColumn = _model.GetLine (_model.Count - 1).Count;
|
||||
CurrentRow = _model.Count - 1;
|
||||
SetNeedsDraw ();
|
||||
}
|
||||
|
||||
private void ProcessSelectAll ()
|
||||
{
|
||||
ResetColumnTrack ();
|
||||
SelectAll ();
|
||||
}
|
||||
|
||||
private bool PointInSelection (int col, int row)
|
||||
{
|
||||
long start, end;
|
||||
GetEncodedRegionBounds (out start, out end);
|
||||
long q = ((long)(uint)row << 32) | (uint)col;
|
||||
|
||||
return q >= start && q <= end - 1;
|
||||
}
|
||||
}
|
||||
175
Terminal.Gui/Views/TextInput/TextView/TextView.Utilities.cs
Normal file
175
Terminal.Gui/Views/TextInput/TextView/TextView.Utilities.cs
Normal file
@@ -0,0 +1,175 @@
|
||||
namespace Terminal.Gui.Views;
|
||||
|
||||
/// <summary>Utility and helper methods for TextView</summary>
|
||||
public partial class TextView
|
||||
{
|
||||
private void Adjust ()
|
||||
{
|
||||
(int width, int height) offB = OffSetBackground ();
|
||||
List<Cell> line = GetCurrentLine ();
|
||||
bool need = NeedsDraw || _wrapNeeded || !Used;
|
||||
(int size, int length) tSize = TextModel.DisplaySize (line, -1, -1, false, TabWidth);
|
||||
(int size, int length) dSize = TextModel.DisplaySize (line, _leftColumn, CurrentColumn, true, TabWidth);
|
||||
|
||||
if (!_wordWrap && CurrentColumn < _leftColumn)
|
||||
{
|
||||
_leftColumn = CurrentColumn;
|
||||
need = true;
|
||||
}
|
||||
else if (!_wordWrap
|
||||
&& (CurrentColumn - _leftColumn + 1 > Viewport.Width + offB.width || dSize.size + 1 >= Viewport.Width + offB.width))
|
||||
{
|
||||
_leftColumn = TextModel.CalculateLeftColumn (
|
||||
line,
|
||||
_leftColumn,
|
||||
CurrentColumn,
|
||||
Viewport.Width + offB.width,
|
||||
TabWidth
|
||||
);
|
||||
need = true;
|
||||
}
|
||||
else if ((_wordWrap && _leftColumn > 0) || (dSize.size < Viewport.Width + offB.width && tSize.size < Viewport.Width + offB.width))
|
||||
{
|
||||
if (_leftColumn > 0)
|
||||
{
|
||||
_leftColumn = 0;
|
||||
need = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (CurrentRow < _topRow)
|
||||
{
|
||||
_topRow = CurrentRow;
|
||||
need = true;
|
||||
}
|
||||
else if (CurrentRow - _topRow >= Viewport.Height + offB.height)
|
||||
{
|
||||
_topRow = Math.Min (Math.Max (CurrentRow - Viewport.Height + 1, 0), CurrentRow);
|
||||
need = true;
|
||||
}
|
||||
else if (_topRow > 0 && CurrentRow < _topRow)
|
||||
{
|
||||
_topRow = Math.Max (_topRow - 1, 0);
|
||||
need = true;
|
||||
}
|
||||
|
||||
// Sync Viewport with the internal scroll position
|
||||
if (IsInitialized && (_leftColumn != Viewport.X || _topRow != Viewport.Y))
|
||||
{
|
||||
Viewport = new Rectangle (_leftColumn, _topRow, Viewport.Width, Viewport.Height);
|
||||
}
|
||||
|
||||
if (need)
|
||||
{
|
||||
if (_wrapNeeded)
|
||||
{
|
||||
WrapTextModel ();
|
||||
_wrapNeeded = false;
|
||||
}
|
||||
|
||||
SetNeedsDraw ();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (IsInitialized)
|
||||
{
|
||||
PositionCursor ();
|
||||
}
|
||||
}
|
||||
|
||||
OnUnwrappedCursorPosition ();
|
||||
}
|
||||
|
||||
private void DoNeededAction ()
|
||||
{
|
||||
if (!NeedsDraw && (IsSelecting || _wrapNeeded || !Used))
|
||||
{
|
||||
SetNeedsDraw ();
|
||||
}
|
||||
|
||||
if (NeedsDraw)
|
||||
{
|
||||
Adjust ();
|
||||
}
|
||||
else
|
||||
{
|
||||
PositionCursor ();
|
||||
OnUnwrappedCursorPosition ();
|
||||
}
|
||||
}
|
||||
|
||||
private (int width, int height) OffSetBackground ()
|
||||
{
|
||||
var w = 0;
|
||||
var h = 0;
|
||||
|
||||
if (SuperView?.Viewport.Right - Viewport.Right < 0)
|
||||
{
|
||||
w = SuperView!.Viewport.Right - Viewport.Right - 1;
|
||||
}
|
||||
|
||||
if (SuperView?.Viewport.Bottom - Viewport.Bottom < 0)
|
||||
{
|
||||
h = SuperView!.Viewport.Bottom - Viewport.Bottom - 1;
|
||||
}
|
||||
|
||||
return (w, h);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the content size based on the text model dimensions.
|
||||
/// </summary>
|
||||
private void UpdateContentSize ()
|
||||
{
|
||||
int contentHeight = Math.Max (_model.Count, 1);
|
||||
|
||||
// For horizontal size: if word wrap is enabled, content width equals viewport width
|
||||
// Otherwise, calculate the maximum line width (but only if we have a reasonable viewport)
|
||||
int contentWidth;
|
||||
|
||||
if (_wordWrap)
|
||||
{
|
||||
// Word wrap: content width follows viewport width
|
||||
contentWidth = Math.Max (Viewport.Width, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// No word wrap: calculate max line width
|
||||
// Cache the current value to avoid recalculating on every call
|
||||
contentWidth = Math.Max (_model.GetMaxVisibleLine (0, _model.Count, TabWidth), 1);
|
||||
}
|
||||
|
||||
SetContentSize (new Size (contentWidth, contentHeight));
|
||||
}
|
||||
|
||||
private void ResetPosition ()
|
||||
{
|
||||
_topRow = _leftColumn = CurrentRow = CurrentColumn = 0;
|
||||
StopSelecting ();
|
||||
}
|
||||
|
||||
private void ResetAllTrack ()
|
||||
{
|
||||
// Handle some state here - whether the last command was a kill
|
||||
// operation and the column tracking (up/down)
|
||||
_lastWasKill = false;
|
||||
_columnTrack = -1;
|
||||
_continuousFind = false;
|
||||
}
|
||||
|
||||
private void ResetColumnTrack ()
|
||||
{
|
||||
// Handle some state here - whether the last command was a kill
|
||||
// operation and the column tracking (up/down)
|
||||
_lastWasKill = false;
|
||||
_columnTrack = -1;
|
||||
}
|
||||
|
||||
private void ToggleSelecting ()
|
||||
{
|
||||
ResetColumnTrack ();
|
||||
IsSelecting = !IsSelecting;
|
||||
_selectionStartColumn = CurrentColumn;
|
||||
_selectionStartRow = CurrentRow;
|
||||
}
|
||||
}
|
||||
125
Terminal.Gui/Views/TextInput/TextView/TextView.WordWrap.cs
Normal file
125
Terminal.Gui/Views/TextInput/TextView/TextView.WordWrap.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Terminal.Gui.Views;
|
||||
|
||||
/// <summary>Word wrap functionality</summary>
|
||||
public partial class TextView
|
||||
{
|
||||
/// <summary>Invoke the <see cref="UnwrappedCursorPosition"/> event with the unwrapped <see cref="CursorPosition"/>.</summary>
|
||||
public virtual void OnUnwrappedCursorPosition (int? cRow = null, int? cCol = null)
|
||||
{
|
||||
int? row = cRow ?? CurrentRow;
|
||||
int? col = cCol ?? CurrentColumn;
|
||||
|
||||
if (cRow is null && cCol is null && _wordWrap)
|
||||
{
|
||||
row = _wrapManager!.GetModelLineFromWrappedLines (CurrentRow);
|
||||
col = _wrapManager.GetModelColFromWrappedLines (CurrentRow, CurrentColumn);
|
||||
}
|
||||
|
||||
UnwrappedCursorPosition?.Invoke (this, new (col.Value, row.Value));
|
||||
}
|
||||
|
||||
/// <summary>Invoked with the unwrapped <see cref="CursorPosition"/>.</summary>
|
||||
public event EventHandler<Point>? UnwrappedCursorPosition;
|
||||
|
||||
private (int Row, int Col) GetUnwrappedPosition (int line, int col)
|
||||
{
|
||||
if (WordWrap)
|
||||
{
|
||||
return new ValueTuple<int, int> (
|
||||
_wrapManager!.GetModelLineFromWrappedLines (line),
|
||||
_wrapManager.GetModelColFromWrappedLines (line, col)
|
||||
);
|
||||
}
|
||||
|
||||
return new ValueTuple<int, int> (line, col);
|
||||
}
|
||||
|
||||
/// <summary>Restore from original model.</summary>
|
||||
private void SetWrapModel ([CallerMemberName] string? caller = null)
|
||||
{
|
||||
if (_currentCaller is { })
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_wordWrap)
|
||||
{
|
||||
_currentCaller = caller;
|
||||
|
||||
CurrentColumn = _wrapManager!.GetModelColFromWrappedLines (CurrentRow, CurrentColumn);
|
||||
CurrentRow = _wrapManager.GetModelLineFromWrappedLines (CurrentRow);
|
||||
|
||||
_selectionStartColumn =
|
||||
_wrapManager.GetModelColFromWrappedLines (_selectionStartRow, _selectionStartColumn);
|
||||
_selectionStartRow = _wrapManager.GetModelLineFromWrappedLines (_selectionStartRow);
|
||||
_model = _wrapManager.Model;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Update the original model.</summary>
|
||||
private void UpdateWrapModel ([CallerMemberName] string? caller = null)
|
||||
{
|
||||
if (_currentCaller is { } && _currentCaller != caller)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_wordWrap)
|
||||
{
|
||||
_currentCaller = null;
|
||||
|
||||
_wrapManager!.UpdateModel (
|
||||
_model,
|
||||
out int nRow,
|
||||
out int nCol,
|
||||
out int nStartRow,
|
||||
out int nStartCol,
|
||||
CurrentRow,
|
||||
CurrentColumn,
|
||||
_selectionStartRow,
|
||||
_selectionStartColumn,
|
||||
true
|
||||
);
|
||||
CurrentRow = nRow;
|
||||
CurrentColumn = nCol;
|
||||
_selectionStartRow = nStartRow;
|
||||
_selectionStartColumn = nStartCol;
|
||||
_wrapNeeded = true;
|
||||
|
||||
SetNeedsDraw ();
|
||||
}
|
||||
|
||||
if (_currentCaller is { })
|
||||
{
|
||||
throw new InvalidOperationException (
|
||||
$"WordWrap settings was changed after the {_currentCaller} call."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void WrapTextModel ()
|
||||
{
|
||||
if (_wordWrap && _wrapManager is { })
|
||||
{
|
||||
_model = _wrapManager.WrapModel (
|
||||
Math.Max (Viewport.Width - (ReadOnly ? 0 : 1), 0), // For the cursor on the last column of a line
|
||||
out int nRow,
|
||||
out int nCol,
|
||||
out int nStartRow,
|
||||
out int nStartCol,
|
||||
CurrentRow,
|
||||
CurrentColumn,
|
||||
_selectionStartRow,
|
||||
_selectionStartColumn,
|
||||
_tabWidth
|
||||
);
|
||||
CurrentRow = nRow;
|
||||
CurrentColumn = nCol;
|
||||
_selectionStartRow = nStartRow;
|
||||
_selectionStartColumn = nStartCol;
|
||||
SetNeedsDraw ();
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,9 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeEditing/GenerateMemberBody/AccessorImplementationKind/@EntryValue">BackingField</s:String>
|
||||
<s:String x:Key="/Default/CodeEditing/GenerateMemberBody/DocumentationGenerationKind/@EntryValue">Inherit</s:String>
|
||||
<s:String x:Key="/Default/CodeEditing/GenerateMemberBody/MethodImplementationKind/@EntryValue">ReturnDefaultValue</s:String>
|
||||
<s:Boolean x:Key="/Default/CodeEditing/GenerateMemberBody/PlaceBackingFieldAboveProperty/@EntryValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeEditing/GenerateMemberBody/WrapIntoRegions/@EntryValue">True</s:Boolean>
|
||||
<s:Int64 x:Key="/Default/CodeEditing/NullCheckPatterns/PatternTypeNamesToPriority/=JetBrains_002EReSharper_002EFeature_002EServices_002ECSharp_002ENullChecking_002EArgumentNullExceptionThrowIfNullPattern/@EntryIndexedValue">5000</s:Int64>
|
||||
<s:Int64 x:Key="/Default/CodeEditing/NullCheckPatterns/PatternTypeNamesToPriority/=JetBrains_002EReSharper_002EFeature_002EServices_002ECSharp_002ENullChecking_002EIfThenThrowPattern/@EntryIndexedValue">1000</s:Int64>
|
||||
<s:Int64 x:Key="/Default/CodeEditing/NullCheckPatterns/PatternTypeNamesToPriority/=JetBrains_002EReSharper_002EFeature_002EServices_002ECSharp_002ENullChecking_002EPatternMatchingIfThenThrowPattern/@EntryIndexedValue">3000</s:Int64>
|
||||
@@ -14,7 +16,7 @@
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeMissingParentheses/@EntryIndexedValue">SUGGESTION</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeModifiersOrder/@EntryIndexedValue">WARNING</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeNamespaceBody/@EntryIndexedValue">ERROR</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeNullCheckingPattern/@EntryIndexedValue">ERROR</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeNullCheckingPattern/@EntryIndexedValue">SUGGESTION</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeObjectCreationWhenTypeEvident/@EntryIndexedValue">WARNING</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeObjectCreationWhenTypeNotEvident/@EntryIndexedValue">SUGGESTION</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeRedundantParentheses/@EntryIndexedValue">WARNING</s:String>
|
||||
@@ -331,6 +333,7 @@
|
||||
<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></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForBuiltInTypes/@EntryValue">UseVarWhenEvident</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForBuiltInTypes/@EntryValue">UseExplicitType</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForOtherTypes/@EntryValue">UseExplicitType</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForSimpleTypes/@EntryValue">UseVarWhenEvident</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForSimpleTypes/@EntryValue">UseVar</s:String>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/PreferSeparateDeconstructedVariablesDeclaration/@EntryValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/UseRoslynLogicForEvidentTypes/@EntryValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/EditorConfig/EnableClangFormatSupport/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/EditorConfig/EnableEditorConfigSupport/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/EditorConfig/ShowEditorConfigStatusBarIndicator/@EntryValue">True</s:Boolean>
|
||||
|
||||
Reference in New Issue
Block a user