#nullable disable
using System.Globalization;
namespace Terminal.Gui.Views;
/// Single-line text editor.
/// The provides editing functionality and mouse support.
public class TextField : View, IDesignable
{
private readonly HistoryText _historyText;
private CultureInfo _currentCulture;
private int _cursorPosition;
private bool _isButtonPressed;
private bool _isButtonReleased;
private bool _isDrawing;
private int _preTextChangedCursorPos;
private int _selectedStart; // -1 represents there is no text selection.
private string _selectedText;
private int _start;
private List _text;
///
/// Initializes a new instance of the class.
///
public TextField ()
{
_historyText = new ();
_isButtonReleased = true;
_selectedStart = -1;
_text = new ();
ReadOnly = false;
Autocomplete = new TextFieldAutocomplete ();
Height = Dim.Auto (DimAutoStyle.Text, 1);
CanFocus = true;
CursorVisibility = CursorVisibility.Default;
Used = true;
WantMousePositionReports = true;
_historyText.ChangeText += HistoryText_ChangeText;
Initialized += TextField_Initialized;
SuperViewChanged += TextField_SuperViewChanged;
// Things this view knows how to do
AddCommand (
Command.DeleteCharRight,
() =>
{
DeleteCharRight ();
return true;
}
);
AddCommand (
Command.DeleteCharLeft,
() =>
{
DeleteCharLeft (false);
return true;
}
);
AddCommand (
Command.LeftStartExtend,
() =>
{
MoveHomeExtend ();
return true;
}
);
AddCommand (
Command.RightEndExtend,
() =>
{
MoveEndExtend ();
return true;
}
);
AddCommand (
Command.LeftStart,
() =>
{
MoveHome ();
return true;
}
);
AddCommand (
Command.LeftExtend,
() =>
{
MoveLeftExtend ();
return true;
}
);
AddCommand (
Command.RightExtend,
() =>
{
MoveRightExtend ();
return true;
}
);
AddCommand (
Command.WordLeftExtend,
() =>
{
MoveWordLeftExtend ();
return true;
}
);
AddCommand (
Command.WordRightExtend,
() =>
{
MoveWordRightExtend ();
return true;
}
);
AddCommand (Command.Left, () => MoveLeft ());
AddCommand (
Command.RightEnd,
() =>
{
MoveEnd ();
return true;
}
);
AddCommand (Command.Right, () => MoveRight ());
AddCommand (
Command.CutToEndLine,
() =>
{
KillToEnd ();
return true;
}
);
AddCommand (
Command.CutToStartLine,
() =>
{
KillToStart ();
return true;
}
);
AddCommand (
Command.Undo,
() =>
{
Undo ();
return true;
}
);
AddCommand (
Command.Redo,
() =>
{
Redo ();
return true;
}
);
AddCommand (
Command.WordLeft,
() =>
{
MoveWordLeft ();
return true;
}
);
AddCommand (
Command.WordRight,
() =>
{
MoveWordRight ();
return true;
}
);
AddCommand (
Command.KillWordForwards,
() =>
{
KillWordForwards ();
return true;
}
);
AddCommand (
Command.KillWordBackwards,
() =>
{
KillWordBackwards ();
return true;
}
);
AddCommand (
Command.ToggleOverwrite,
() =>
{
SetOverwrite (!Used);
return true;
}
);
AddCommand (
Command.EnableOverwrite,
() =>
{
SetOverwrite (true);
return true;
}
);
AddCommand (
Command.DisableOverwrite,
() =>
{
SetOverwrite (false);
return true;
}
);
AddCommand (
Command.Copy,
() =>
{
Copy ();
return true;
}
);
AddCommand (
Command.Cut,
() =>
{
Cut ();
return true;
}
);
AddCommand (
Command.Paste,
() =>
{
Paste ();
return true;
}
);
AddCommand (
Command.SelectAll,
() =>
{
SelectAll ();
return true;
}
);
AddCommand (
Command.DeleteAll,
() =>
{
DeleteAll ();
return true;
}
);
AddCommand (
Command.Context,
() =>
{
ShowContextMenu (true);
return true;
}
);
AddCommand (
Command.HotKey,
ctx =>
{
if (RaiseHandlingHotKey (ctx) is true)
{
return true;
}
// If we have focus, then ignore the hotkey because the user
// means to enter it
if (HasFocus)
{
return false;
}
// This is what the default HotKey handler does:
SetFocus ();
// Always return true on hotkey, even if SetFocus fails because
// hotkeys are always handled by the View (unless RaiseHandlingHotKey cancels).
return true;
});
// Default keybindings for this view
// We follow this as closely as possible: https://en.wikipedia.org/wiki/Table_of_keyboard_shortcuts
KeyBindings.Add (Key.Delete, Command.DeleteCharRight);
KeyBindings.Add (Key.D.WithCtrl, Command.DeleteCharRight);
KeyBindings.Add (Key.Backspace, Command.DeleteCharLeft);
KeyBindings.Add (Key.Home.WithShift, Command.LeftStartExtend);
KeyBindings.Add (Key.Home.WithShift.WithCtrl, Command.LeftStartExtend);
KeyBindings.Add (Key.A.WithShift.WithCtrl, Command.LeftStartExtend);
KeyBindings.Add (Key.End.WithShift, Command.RightEndExtend);
KeyBindings.Add (Key.End.WithShift.WithCtrl, Command.RightEndExtend);
KeyBindings.Add (Key.E.WithShift.WithCtrl, Command.RightEndExtend);
KeyBindings.Add (Key.Home, Command.LeftStart);
KeyBindings.Add (Key.Home.WithCtrl, Command.LeftStart);
KeyBindings.Add (Key.CursorLeft.WithShift, Command.LeftExtend);
KeyBindings.Add (Key.CursorUp.WithShift, Command.LeftExtend);
KeyBindings.Add (Key.CursorRight.WithShift, Command.RightExtend);
KeyBindings.Add (Key.CursorDown.WithShift, Command.RightExtend);
KeyBindings.Add (Key.CursorLeft.WithShift.WithCtrl, Command.WordLeftExtend);
KeyBindings.Add (Key.CursorUp.WithShift.WithCtrl, Command.WordLeftExtend);
KeyBindings.Add (Key.CursorRight.WithShift.WithCtrl, Command.WordRightExtend);
KeyBindings.Add (Key.CursorDown.WithShift.WithCtrl, Command.WordRightExtend);
KeyBindings.Add (Key.CursorLeft, Command.Left);
KeyBindings.Add (Key.B.WithCtrl, Command.Left);
KeyBindings.Add (Key.End, Command.RightEnd);
KeyBindings.Add (Key.End.WithCtrl, Command.RightEnd);
KeyBindings.Add (Key.E.WithCtrl, Command.RightEnd);
KeyBindings.Add (Key.CursorRight, Command.Right);
KeyBindings.Add (Key.F.WithCtrl, Command.Right);
KeyBindings.Add (Key.K.WithCtrl, Command.CutToEndLine);
KeyBindings.Add (Key.K.WithCtrl.WithShift, Command.CutToStartLine);
KeyBindings.Add (Key.Z.WithCtrl, Command.Undo);
KeyBindings.Add (Key.Y.WithCtrl, Command.Redo);
KeyBindings.Add (Key.CursorLeft.WithCtrl, Command.WordLeft);
KeyBindings.Add (Key.CursorUp.WithCtrl, Command.WordLeft);
KeyBindings.Add (Key.CursorRight.WithCtrl, Command.WordRight);
KeyBindings.Add (Key.CursorDown.WithCtrl, Command.WordRight);
#if UNIX_KEY_BINDINGS
KeyBindings.Add (Key.F.WithShift.WithAlt, Command.WordRightExtend);
KeyBindings.Add (Key.K.WithAlt, Command.CutToStartLine);
KeyBindings.Add (Key.B.WithShift.WithAlt, Command.WordLeftExtend);
KeyBindings.Add (Key.B.WithAlt, Command.WordLeft);
KeyBindings.Add (Key.F.WithAlt, Command.WordRight);
KeyBindings.Add (Key.Backspace.WithAlt, Command.Undo);
#endif
KeyBindings.Add (Key.Delete.WithCtrl, Command.KillWordForwards);
KeyBindings.Add (Key.Backspace.WithCtrl, Command.KillWordBackwards);
KeyBindings.Add (Key.InsertChar, Command.ToggleOverwrite);
KeyBindings.Add (Key.C.WithCtrl, Command.Copy);
KeyBindings.Add (Key.X.WithCtrl, Command.Cut);
KeyBindings.Add (Key.V.WithCtrl, Command.Paste);
KeyBindings.Add (Key.A.WithCtrl, Command.SelectAll);
KeyBindings.Add (Key.R.WithCtrl, Command.DeleteAll);
KeyBindings.Add (Key.D.WithCtrl.WithShift, Command.DeleteAll);
KeyBindings.Remove (Key.Space);
_currentCulture = Thread.CurrentThread.CurrentUICulture;
}
///
/// Provides autocomplete context menu based on suggestions at the current cursor position. Configure
/// to enable this feature.
///
public IAutocomplete Autocomplete { get; set; }
/// Get the Context Menu for this view.
[CanBeNull]
public PopoverMenu ContextMenu { get; private set; }
/// Sets or gets the current cursor position.
public virtual int CursorPosition
{
get => _cursorPosition;
set
{
if (value < 0)
{
_cursorPosition = 0;
}
else if (value > _text.Count)
{
_cursorPosition = _text.Count;
}
else
{
_cursorPosition = value;
}
PrepareSelection (_selectedStart, _cursorPosition - _selectedStart);
}
}
///
/// Indicates whatever the text has history changes or not. if the text has history changes
/// otherwise.
///
public bool HasHistoryChanges => _historyText.HasHistoryChanges;
///
/// Indicates whatever the text was changed or not. if the text was changed
/// otherwise.
///
public bool IsDirty => _historyText.IsDirty ([Cell.StringToCells (Text)]);
/// If set to true its not allow any changes in the text.
public bool ReadOnly { get; set; }
/// Gets the left offset position.
public int ScrollOffset { get; private set; }
///
/// Sets the secret property.
/// This makes the text entry suitable for entering passwords.
///
public bool Secret { get; set; }
/// Length of the selected text.
public int SelectedLength { get; private set; }
/// Start position of the selected text.
public int SelectedStart
{
get => _selectedStart;
set
{
if (value < -1)
{
_selectedStart = -1;
}
else if (value > _text.Count)
{
_selectedStart = _text.Count;
}
else
{
_selectedStart = value;
}
PrepareSelection (_selectedStart, _cursorPosition - _selectedStart);
}
}
/// The selected text.
public string SelectedText
{
get => Secret ? null : _selectedText;
private set => _selectedText = value;
}
/// Sets or gets the text held by the view.
public new string Text
{
get => StringExtensions.ToString (_text);
set
{
var oldText = StringExtensions.ToString (_text);
if (oldText == value)
{
return;
}
string newText = value.Replace ("\t", "").Split ("\n") [0];
ResultEventArgs args = new (newText);
RaiseTextChanging (args);
if (args.Handled)
{
if (_cursorPosition > _text.Count)
{
_cursorPosition = _text.Count;
}
return;
}
ClearAllSelection ();
// Note we use NewValue here; TextChanging subscribers may have changed it
_text = args.Result.ToStringList ();
if (!Secret && !_historyText.IsFromHistory)
{
_historyText.Add (
[Cell.ToCellList (oldText)],
new (_cursorPosition, 0)
);
_historyText.Add (
[Cell.ToCells (_text)],
new (_cursorPosition, 0),
TextEditingLineStatus.Replaced
);
}
OnTextChanged ();
ProcessAutocomplete ();
if (_cursorPosition > _text.Count)
{
_cursorPosition = Math.Max (TextModel.DisplaySize (_text, 0).size - 1, 0);
}
Adjust ();
SetNeedsDraw ();
}
}
///
/// Tracks whether the text field should be considered "used", that is, that the user has moved in the entry, so
/// new input should be appended at the cursor position, rather than clearing the entry
///
public bool Used { get; set; }
///
/// Gets or sets whether the word forward and word backward navigation should use the same or equivalent rune type.
/// Default is false meaning using equivalent rune type.
///
public bool UseSameRuneTypeForWords { get; set; }
///
/// Gets or sets whether the word navigation should select only the word itself without spaces around it or with the
/// spaces at right.
/// Default is false meaning that the spaces at right are included in the selection.
///
public bool SelectWordOnlyOnDoubleClick { get; set; }
/// Clear the selected text.
public void ClearAllSelection ()
{
if (_selectedStart == -1 && SelectedLength == 0 && string.IsNullOrEmpty (_selectedText))
{
return;
}
_selectedStart = -1;
SelectedLength = 0;
_selectedText = null;
_start = 0;
SelectedLength = 0;
SetNeedsDraw ();
}
/// Clears the history.
public void ClearHistoryChanges () { _historyText.Clear ([Cell.StringToCells (Text)]); }
/// Copy the selected text to the clipboard.
public virtual void Copy ()
{
if (Secret || SelectedLength == 0)
{
return;
}
App?.Clipboard?.SetClipboardData (SelectedText);
}
/// Cut the selected text to the clipboard.
public virtual void Cut ()
{
if (ReadOnly || Secret || SelectedLength == 0)
{
return;
}
App?.Clipboard?.SetClipboardData (SelectedText);
List newText = DeleteSelectedText ();
Text = StringExtensions.ToString (newText);
Adjust ();
}
/// Deletes all text.
public void DeleteAll ()
{
if (_text.Count == 0)
{
return;
}
_selectedStart = 0;
MoveEndExtend ();
DeleteCharLeft (false);
SetNeedsDraw ();
}
/// Deletes the character to the left.
///
/// If set to true use the cursor position cached ;
/// otherwise use . use .
///
public virtual void DeleteCharLeft (bool usePreTextChangedCursorPos)
{
if (ReadOnly)
{
return;
}
_historyText.Add (
new () { Cell.ToCells (_text) },
new (_cursorPosition, 0)
);
if (SelectedLength == 0)
{
if (_cursorPosition == 0)
{
return;
}
if (!usePreTextChangedCursorPos)
{
_preTextChangedCursorPos = _cursorPosition;
}
_cursorPosition--;
if (_preTextChangedCursorPos < _text.Count)
{
SetText (
_text.GetRange (0, _preTextChangedCursorPos - 1)
.Concat (
_text.GetRange (
_preTextChangedCursorPos,
_text.Count - _preTextChangedCursorPos
)
)
);
}
else
{
SetText (_text.GetRange (0, _preTextChangedCursorPos - 1));
}
Adjust ();
}
else
{
List newText = DeleteSelectedText ();
Text = StringExtensions.ToString (newText);
Adjust ();
}
}
/// Deletes the character to the right.
public virtual void DeleteCharRight ()
{
if (ReadOnly)
{
return;
}
_historyText.Add (
new () { Cell.ToCells (_text) },
new (_cursorPosition, 0)
);
if (SelectedLength == 0)
{
if (_text.Count == 0 || _text.Count == _cursorPosition)
{
return;
}
SetText (
_text.GetRange (0, _cursorPosition)
.Concat (_text.GetRange (_cursorPosition + 1, _text.Count - (_cursorPosition + 1)))
);
Adjust ();
}
else
{
List newText = DeleteSelectedText ();
Text = StringExtensions.ToString (newText);
Adjust ();
}
}
///
protected override bool OnGettingAttributeForRole (in VisualRole role, ref Attribute currentAttribute)
{
if (role == VisualRole.Normal)
{
currentAttribute = GetAttributeForRole (VisualRole.Focus);
return true;
}
return base.OnGettingAttributeForRole (role, ref currentAttribute);
}
///
/// Inserts the given text at the current cursor position exactly as if the user had just
/// typed it
///
/// Text to add
/// Use the previous cursor position.
public void InsertText (string toAdd, bool useOldCursorPos = true)
{
foreach (Rune rune in toAdd.EnumerateRunes ())
{
// All rune can be mapped to a Key and no exception will throw here because
// EnumerateRunes will replace a surrogate char with the Rune.ReplacementChar
Key key = rune.Value;
InsertText (key, useOldCursorPos);
}
}
/// Deletes word backwards.
public virtual void KillWordBackwards ()
{
ClearAllSelection ();
(int col, int row)? newPos = GetModel ().WordBackward (_cursorPosition, 0, UseSameRuneTypeForWords);
if (newPos is null)
{
return;
}
if (newPos.Value.col != -1)
{
SetText (
_text.GetRange (0, newPos.Value.col)
.Concat (_text.GetRange (_cursorPosition, _text.Count - _cursorPosition))
);
_cursorPosition = newPos.Value.col;
}
Adjust ();
}
/// Deletes word forwards.
public virtual void KillWordForwards ()
{
ClearAllSelection ();
(int col, int row)? newPos = GetModel ().WordForward (_cursorPosition, 0, UseSameRuneTypeForWords);
if (newPos is null)
{
return;
}
if (newPos.Value.col != -1)
{
SetText (
_text.GetRange (0, _cursorPosition)
.Concat (_text.GetRange (newPos.Value.col, _text.Count - newPos.Value.col))
);
}
Adjust ();
}
///
protected override bool OnMouseEvent (MouseEventArgs ev)
{
if (ev is { IsPressed: false, IsReleased: false }
&& !ev.Flags.HasFlag (MouseFlags.ReportMousePosition)
&& !ev.Flags.HasFlag (MouseFlags.Button1DoubleClicked)
&& !ev.Flags.HasFlag (MouseFlags.Button1TripleClicked)
&& !ev.Flags.HasFlag (ContextMenu!.MouseFlags))
{
return false;
}
if (!CanFocus)
{
return true;
}
if (!HasFocus && ev.Flags != MouseFlags.ReportMousePosition)
{
SetFocus ();
}
// Give autocomplete first opportunity to respond to mouse clicks
if (SelectedLength == 0 && Autocomplete.OnMouseEvent (ev, true))
{
return true;
}
if (ev.Flags == MouseFlags.Button1Pressed)
{
EnsureHasFocus ();
PositionCursor (ev);
if (_isButtonReleased)
{
ClearAllSelection ();
}
_isButtonReleased = true;
_isButtonPressed = true;
}
else if (ev.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) && _isButtonPressed)
{
int x = PositionCursor (ev);
_isButtonReleased = false;
PrepareSelection (x);
if (App?.Mouse.MouseGrabView is null)
{
App?.Mouse.GrabMouse (this);
}
}
else if (ev.Flags == MouseFlags.Button1Released)
{
_isButtonReleased = true;
_isButtonPressed = false;
App?.Mouse.UngrabMouse ();
}
else if (ev.Flags == MouseFlags.Button1DoubleClicked)
{
EnsureHasFocus ();
int x = PositionCursor (ev);
(int startCol, int col, int row)? newPos = GetModel ().ProcessDoubleClickSelection (x, x, 0, UseSameRuneTypeForWords, SelectWordOnlyOnDoubleClick);
if (newPos is null)
{
return true;
}
SelectedStart = newPos.Value.startCol;
CursorPosition = newPos.Value.col;
}
else if (ev.Flags == MouseFlags.Button1TripleClicked)
{
EnsureHasFocus ();
PositionCursor (0);
ClearAllSelection ();
PrepareSelection (0, _text.Count);
}
else if (ev.Flags == ContextMenu!.MouseFlags)
{
PositionCursor (ev);
ShowContextMenu (false);
}
//SetNeedsDraw ();
return true;
void EnsureHasFocus ()
{
if (!HasFocus)
{
SetFocus ();
}
}
}
/// Moves cursor to the end of the typed text.
public void MoveEnd ()
{
ClearAllSelection ();
_cursorPosition = _text.Count;
Adjust ();
}
///
protected override bool OnDrawingContent ()
{
_isDrawing = true;
// Cache attributes as GetAttributeForRole might raise events
var selectedAttribute = new Attribute (GetAttributeForRole (VisualRole.Active));
Attribute readonlyAttribute = GetAttributeForRole (VisualRole.ReadOnly);
Attribute normalAttribute = GetAttributeForRole (VisualRole.Editable);
SetSelectedStartSelectedLength ();
SetAttribute (GetAttributeForRole (VisualRole.Normal));
Move (0, 0);
int p = ScrollOffset;
var col = 0;
int width = Viewport.Width + OffSetBackground ();
int tcount = _text.Count;
for (int idx = p; idx < tcount; idx++)
{
string text = _text [idx];
int cols = text.GetColumns ();
if (!Enabled)
{
// Disabled
SetAttributeForRole (VisualRole.Disabled);
}
else if (idx == _cursorPosition && HasFocus && !Used && SelectedLength == 0 && !ReadOnly)
{
// Selected text
SetAttribute (selectedAttribute);
}
else if (ReadOnly)
{
SetAttribute (
idx >= _start && SelectedLength > 0 && idx < _start + SelectedLength
? selectedAttribute
: readonlyAttribute
);
}
else if (!HasFocus && Enabled)
{
// Normal text
SetAttribute (normalAttribute);
}
else
{
SetAttribute (
idx >= _start && SelectedLength > 0 && idx < _start + SelectedLength
? selectedAttribute
: normalAttribute
);
}
if (col + cols <= width)
{
AddStr (Secret ? Glyphs.Dot.ToString () : text);
}
if (!TextModel.SetCol (ref col, width, cols))
{
break;
}
if (idx + 1 < tcount && col + _text [idx + 1].GetColumns () > width)
{
break;
}
}
SetAttribute (normalAttribute);
// Fill rest of line with spaces
for (int i = col; i < width; i++)
{
AddRune ((Rune)' ');
}
PositionCursor ();
RenderCaption ();
_isDrawing = false;
return true;
}
///
protected override void OnHasFocusChanged (bool newHasFocus, View previousFocusedView, View view)
{
if (App?.Mouse.MouseGrabView is { } && App?.Mouse.MouseGrabView == this)
{
App?.Mouse.UngrabMouse ();
}
}
///
protected override bool OnKeyDown (Key key)
{
// Give autocomplete first opportunity to respond to key presses
if (SelectedLength == 0 && Autocomplete.Suggestions.Count > 0 && Autocomplete.ProcessKey (key))
{
return true;
}
return false;
}
///
protected override bool OnKeyDownNotHandled (Key a)
{
// Remember the cursor position because the new calculated cursor position is needed
// to be set BEFORE the TextChanged event is triggered.
// Needed for the Elmish Wrapper issue https://github.com/DieselMeister/Terminal.Gui.Elmish/issues/2
_preTextChangedCursorPos = _cursorPosition;
// Ignore other control characters.
if (!a.IsKeyCodeAtoZ && (a.KeyCode < KeyCode.Space || a.KeyCode > KeyCode.CharMask))
{
return false;
}
if (ReadOnly)
{
return true;
}
InsertText (a, true);
return true;
}
/// Raises the event, enabling canceling the change or adjusting the text.
/// The event arguments.
/// if the event was cancelled or the text was adjusted by the event.
public bool RaiseTextChanging (ResultEventArgs args)
{
// TODO: CWP: Add an OnTextChanging protected virtual method that can be overridden to handle text changing events.
TextChanging?.Invoke (this, args);
return args.Handled;
}
/// Raised before changes. The change can be canceled the text adjusted.
public event EventHandler> TextChanging;
/// Paste the selected text from the clipboard.
public virtual void Paste ()
{
if (ReadOnly)
{
return;
}
string cbTxt = App?.Clipboard?.GetClipboardData ()?.Split ("\n") [0];
if (string.IsNullOrEmpty (cbTxt))
{
return;
}
SetSelectedStartSelectedLength ();
int selStart = _start == -1 ? CursorPosition : _start;
Text = StringExtensions.ToString (_text.GetRange (0, selStart))
+ cbTxt
+ StringExtensions.ToString (
_text.GetRange (
selStart + SelectedLength,
_text.Count - (selStart + SelectedLength)
)
);
_cursorPosition = Math.Min (selStart + cbTxt.GetRuneCount (), _text.Count);
ClearAllSelection ();
SetNeedsDraw ();
Adjust ();
}
/// Sets the cursor position.
public override Point? PositionCursor ()
{
ProcessAutocomplete ();
var col = 0;
for (int idx = ScrollOffset < 0 ? 0 : ScrollOffset; idx < _text.Count; idx++)
{
if (idx == _cursorPosition)
{
break;
}
int cols = _text [idx].GetColumns ();
TextModel.SetCol (ref col, Viewport.Width - 1, cols);
}
int pos = col + Math.Min (Viewport.X, 0);
Move (pos, 0);
return new Point (pos, 0);
}
/// Redoes the latest changes.
public void Redo ()
{
if (ReadOnly)
{
return;
}
_historyText.Redo ();
}
/// Selects all text.
public void SelectAll ()
{
if (_text.Count == 0)
{
return;
}
_selectedStart = 0;
MoveEndExtend ();
SetNeedsDraw ();
}
/////
///// Changed event, raised when the text has changed.
/////
///// This event is raised when the changes. The passed is a
///// containing the old value.
/////
/////
//public event EventHandler> TextChanged;
/// Undoes the latest changes.
public void Undo ()
{
if (ReadOnly)
{
return;
}
_historyText.Undo ();
}
///
/// Returns if the current cursor position is at the end of the . This
/// includes when it is empty.
///
///
internal bool CursorIsAtEnd () { return CursorPosition == Text.Length; }
/// Returns if the current cursor position is at the start of the .
///
internal bool CursorIsAtStart () { return CursorPosition <= 0; }
private void Adjust ()
{
if (SuperView is null)
{
return;
}
// TODO: This is a lame prototype proving it should be easy for TextField to
// TODO: support Width = Dim.Auto (DimAutoStyle: Content).
//SetContentSize(new (TextModel.DisplaySize (_text).size, 1));
int offB = OffSetBackground ();
bool need = NeedsDraw || !Used;
if (_cursorPosition < ScrollOffset)
{
ScrollOffset = _cursorPosition;
need = true;
}
else if (Viewport.Width > 0
&& (ScrollOffset + _cursorPosition - (Viewport.Width + offB) == 0
|| TextModel.DisplaySize (_text, ScrollOffset, _cursorPosition).size >= Viewport.Width + offB))
{
ScrollOffset = Math.Max (
TextModel.CalculateLeftColumn (
_text,
ScrollOffset,
_cursorPosition,
Viewport.Width + offB
),
0
);
need = true;
}
if (need)
{
SetNeedsDraw ();
}
else
{
PositionCursor ();
}
}
private void CreateContextMenu ()
{
DisposeContextMenu ();
PopoverMenu menu = new (
new List