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