using System.Globalization;
using System.Runtime.CompilerServices;
namespace Terminal.Gui.Views;
/// Fully featured multi-line text editor
///
///
///
/// Shortcut Action performed
///
/// -
/// Left cursor, Control-b Moves the editing point left.
///
/// -
/// Right cursor, Control-f Moves the editing point right.
///
/// -
/// Alt-b Moves one word back.
///
/// -
/// Alt-f Moves one word forward.
///
/// -
/// Up cursor, Control-p Moves the editing point one line up.
///
/// -
/// Down cursor, Control-n Moves the editing point one line down
///
/// -
/// Home key, Control-a Moves the cursor to the beginning of the line.
///
/// -
/// End key, Control-e Moves the cursor to the end of the line.
///
/// -
/// Control-Home Scrolls to the first line and moves the cursor there.
///
/// -
/// Control-End Scrolls to the last line and moves the cursor there.
///
/// -
/// Delete, Control-d Deletes the character in front of the cursor.
///
/// -
/// Backspace Deletes the character behind the cursor.
///
/// -
/// Control-k
///
/// Deletes the text until the end of the line and replaces the kill buffer with the deleted text.
/// You can paste this text in a different place by using Control-y.
///
///
/// -
/// Control-y
/// Pastes the content of the kill ring into the current position.
///
/// -
/// Alt-d
///
/// Deletes the word above the cursor and adds it to the kill ring. You can paste the contents of
/// the kill ring with Control-y.
///
///
/// -
/// Control-q
///
/// Quotes the next input character, to prevent the normal processing of key handling to take
/// place.
///
///
///
///
public partial class TextView : View, IDesignable
{
// BUGBUG: AllowsReturn is mis-named. It should be EnterKeyAccepts.
///
/// Gets or sets whether pressing ENTER in a creates a new line of text
/// in the view or invokes the event.
///
///
///
/// Setting this property alters .
/// If is set to , then is also set to
/// `true` and
/// vice-versa.
///
///
/// If is set to , then gets set to
/// .
///
///
public bool AllowsReturn
{
get => _allowsReturn;
set
{
_allowsReturn = value;
if (_allowsReturn && !_multiline)
{
// BUGBUG: Setting properties should not have side-effects like this. Multiline and AllowsReturn should be independent.
Multiline = true;
}
if (!_allowsReturn && _multiline)
{
Multiline = false;
// BUGBUG: Setting properties should not have side-effects like this. Multiline and AllowsTab should be independent.
AllowsTab = false;
}
SetNeedsDraw ();
}
}
///
/// Gets or sets whether the inserts a tab character into the text or ignores tab input. If
/// set to `false` and the user presses the tab key (or shift-tab) the focus will move to the next view (or previous
/// with shift-tab). The default is `true`; if the user presses the tab key, a tab character will be inserted into the
/// text.
///
public bool AllowsTab
{
get => _allowsTab;
set
{
_allowsTab = value;
if (_allowsTab && _tabWidth == 0)
{
_tabWidth = 4;
}
if (_allowsTab && !_multiline)
{
Multiline = true;
}
if (!_allowsTab && _tabWidth > 0)
{
_tabWidth = 0;
}
SetNeedsDraw ();
}
}
///
/// Provides autocomplete context menu based on suggestions at the current cursor position. Configure
/// to enable this feature
///
public IAutocomplete Autocomplete { get; protected set; } = new TextViewAutocomplete ();
/// Get the Context Menu.
public PopoverMenu? ContextMenu { get; private set; }
/// Gets the cursor column.
/// The cursor column.
public int CurrentColumn { get; private set; }
/// Gets the current cursor row.
public int CurrentRow { get; private set; }
/// Sets or gets the current cursor position.
public Point CursorPosition
{
get => new (CurrentColumn, CurrentRow);
set
{
List line = _model.GetLine (Math.Max (Math.Min (value.Y, _model.Count - 1), 0));
CurrentColumn = value.X < 0 ? 0 :
value.X > line.Count ? line.Count : value.X;
CurrentRow = value.Y < 0 ? 0 :
value.Y > _model.Count - 1 ? Math.Max (_model.Count - 1, 0) : value.Y;
SetNeedsDraw ();
Adjust ();
}
}
///
/// Indicates whatever the text has history changes or not. if the text has history changes
/// otherwise.
///
public bool HasHistoryChanges => _historyText.HasHistoryChanges;
///
/// If and the current is null will inherit from the
/// previous, otherwise if (default) do nothing. If the text is load with
/// this property is automatically sets to .
///
public bool InheritsPreviousAttribute { get; set; }
///
/// Indicates whatever the text was changed or not. if the text was changed
/// otherwise.
///
public bool IsDirty
{
get => _historyText.IsDirty (_model.GetAllLines ());
set => _historyText.Clear (_model.GetAllLines ());
}
/// Gets or sets the left column.
public int LeftColumn
{
get => _leftColumn;
set
{
if (value > 0 && _wordWrap)
{
return;
}
int clampedValue = Math.Max (Math.Min (value, Maxlength - 1), 0);
_leftColumn = clampedValue;
if (IsInitialized && Viewport.X != _leftColumn)
{
Viewport = Viewport with { X = _leftColumn };
}
}
}
/// Gets the number of lines.
public int Lines => _model.Count;
/// Gets the maximum visible length line.
public int Maxlength => _model.GetMaxVisibleLine (_topRow, _topRow + Viewport.Height, TabWidth);
/// Gets or sets a value indicating whether this is a multiline text view.
public bool Multiline
{
get => _multiline;
set
{
_multiline = value;
if (_multiline && !_allowsTab)
{
AllowsTab = true;
}
if (_multiline && !_allowsReturn)
{
AllowsReturn = true;
}
if (!_multiline)
{
AllowsReturn = false;
AllowsTab = false;
WordWrap = false;
CurrentColumn = 0;
CurrentRow = 0;
_savedHeight = Height;
Height = Dim.Auto (DimAutoStyle.Text, 1);
if (!IsInitialized)
{
_model.LoadString (Text);
}
SetNeedsDraw ();
}
else if (_multiline && _savedHeight is { })
{
Height = _savedHeight;
SetNeedsDraw ();
}
KeyBindings.Remove (Key.Enter);
KeyBindings.Add (Key.Enter, Multiline ? Command.NewLine : Command.Accept);
}
}
/// Gets or sets whether the is in read-only mode or not
/// Boolean value(Default false)
public bool ReadOnly
{
get => _isReadOnly;
set
{
if (value != _isReadOnly)
{
_isReadOnly = value;
SetNeedsDraw ();
WrapTextModel ();
Adjust ();
}
}
}
/// Gets or sets a value indicating the number of whitespace when pressing the TAB key.
public int TabWidth
{
get => _tabWidth;
set
{
_tabWidth = Math.Max (value, 0);
if (_tabWidth > 0 && !AllowsTab)
{
AllowsTab = true;
}
SetNeedsDraw ();
}
}
/// Sets or gets the text in the .
///
/// The event is fired whenever this property is set. Note, however, that Text is not
/// set by as the user types.
///
public override string Text
{
get
{
if (_wordWrap)
{
return _wrapManager!.Model.ToString ();
}
return _model.ToString ();
}
set
{
ResetPosition ();
_model.LoadString (value);
if (_wordWrap)
{
_wrapManager = new (_model);
_model = _wrapManager.WrapModel (Viewport.Width, out _, out _, out _, out _);
}
OnTextChanged ();
SetNeedsDraw ();
_historyText.Clear (_model.GetAllLines ());
}
}
/// Gets or sets the top row.
public int TopRow
{
get => _topRow;
set
{
int clampedValue = Math.Max (Math.Min (value, Lines - 1), 0);
_topRow = clampedValue;
if (IsInitialized && Viewport.Y != _topRow)
{
Viewport = Viewport with { Y = _topRow };
}
}
}
///
/// Tracks whether the text view 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; }
/// Allows word wrap the to fit the available container width.
public bool WordWrap
{
get => _wordWrap;
set
{
if (value == _wordWrap)
{
return;
}
if (value && !_multiline)
{
return;
}
_wordWrap = value;
ResetPosition ();
if (_wordWrap)
{
_wrapManager = new (_model);
WrapTextModel ();
}
else if (!_wordWrap && _wrapManager is { })
{
_model = _wrapManager.Model;
}
// Update horizontal scrollbar AutoShow based on WordWrap
if (IsInitialized)
{
HorizontalScrollBar.AutoShow = !_wordWrap;
UpdateContentSize ();
}
SetNeedsDraw ();
}
}
///
/// 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; }
/// Allows clearing the items updating the original text.
public void ClearHistoryChanges () { _historyText?.Clear (_model.GetAllLines ()); }
/// Closes the contents of the stream into the .
/// true, if stream was closed, false otherwise.
public bool CloseFile ()
{
SetWrapModel ();
bool res = _model.CloseFile ();
ResetPosition ();
SetNeedsDraw ();
UpdateWrapModel ();
return res;
}
/// Raised when the contents of the are changed.
///
/// Unlike the event, this event is raised whenever the user types or otherwise changes
/// the contents of the .
///
public event EventHandler? ContentsChanged;
///
/// Open a dialog to set the foreground and background colors.
///
public void PromptForColors ()
{
if (!ColorPicker.Prompt (
"Colors",
GetSelectedCellAttribute (),
out Attribute newAttribute
))
{
return;
}
var attribute = new Attribute (
newAttribute.Foreground,
newAttribute.Background,
newAttribute.Style
);
ApplyCellsAttribute (attribute);
}
/// Gets all lines of characters.
///
public List> GetAllLines () { return _model.GetAllLines (); }
///
/// Returns the characters on the current line (where the cursor is positioned). Use
/// to determine the position of the cursor within that line
///
///
public List GetCurrentLine () { return _model.GetLine (CurrentRow); }
/// Returns the characters on the .
/// The intended line.
///
public List GetLine (int line) { return _model.GetLine (line); }
/// Loads the contents of the file into the .
/// true, if file was loaded, false otherwise.
/// Path to the file to load.
public bool Load (string path)
{
SetWrapModel ();
bool res;
try
{
SetWrapModel ();
res = _model.LoadFile (path);
_historyText.Clear (_model.GetAllLines ());
ResetPosition ();
}
finally
{
UpdateWrapModel ();
SetNeedsDraw ();
Adjust ();
}
UpdateWrapModel ();
return res;
}
/// Loads the contents of the stream into the .
/// true, if stream was loaded, false otherwise.
/// Stream to load the contents from.
public void Load (Stream stream)
{
SetWrapModel ();
_model.LoadStream (stream);
_historyText.Clear (_model.GetAllLines ());
ResetPosition ();
SetNeedsDraw ();
UpdateWrapModel ();
}
/// Loads the contents of the list into the .
/// Text cells list to load the contents from.
public void Load (List cells)
{
SetWrapModel ();
_model.LoadCells (cells, GetAttributeForRole (VisualRole.Focus));
_historyText.Clear (_model.GetAllLines ());
ResetPosition ();
SetNeedsDraw ();
UpdateWrapModel ();
InheritsPreviousAttribute = true;
}
/// Loads the contents of the list of list into the .
/// List of rune cells list to load the contents from.
public void Load (List> cellsList)
{
SetWrapModel ();
InheritsPreviousAttribute = true;
_model.LoadListCells (cellsList, GetAttributeForRole (VisualRole.Focus));
_historyText.Clear (_model.GetAllLines ());
ResetPosition ();
SetNeedsDraw ();
UpdateWrapModel ();
}
///
protected override bool OnMouseEvent (MouseEventArgs ev)
{
if (ev is { IsSingleDoubleOrTripleClicked: false, IsPressed: false, IsReleased: false, IsWheel: false }
&& !ev.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)
&& !ev.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ButtonShift)
&& !ev.Flags.HasFlag (MouseFlags.Button1DoubleClicked | MouseFlags.ButtonShift)
&& !ev.Flags.HasFlag (ContextMenu!.MouseFlags))
{
return false;
}
if (!CanFocus)
{
return true;
}
if (!HasFocus)
{
SetFocus ();
}
_continuousFind = false;
// Give autocomplete first opportunity to respond to mouse clicks
if (SelectedLength == 0 && Autocomplete.OnMouseEvent (ev, true))
{
return true;
}
if (ev.Flags == MouseFlags.Button1Clicked)
{
if (_isButtonReleased)
{
_isButtonReleased = false;
if (SelectedLength == 0)
{
StopSelecting ();
}
return true;
}
if (_shiftSelecting && !_isButtonShift)
{
StopSelecting ();
}
ProcessMouseClick (ev, out _);
if (Used)
{
PositionCursor ();
}
else
{
SetNeedsDraw ();
}
_lastWasKill = false;
_columnTrack = CurrentColumn;
}
else if (ev.Flags == MouseFlags.WheeledDown)
{
_lastWasKill = false;
_columnTrack = CurrentColumn;
ScrollTo (_topRow + 1);
}
else if (ev.Flags == MouseFlags.WheeledUp)
{
_lastWasKill = false;
_columnTrack = CurrentColumn;
ScrollTo (_topRow - 1);
}
else if (ev.Flags == MouseFlags.WheeledRight)
{
_lastWasKill = false;
_columnTrack = CurrentColumn;
ScrollTo (_leftColumn + 1, false);
}
else if (ev.Flags == MouseFlags.WheeledLeft)
{
_lastWasKill = false;
_columnTrack = CurrentColumn;
ScrollTo (_leftColumn - 1, false);
}
else if (ev.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition))
{
ProcessMouseClick (ev, out List line);
PositionCursor ();
if (_model.Count > 0 && _shiftSelecting && IsSelecting)
{
if (CurrentRow - _topRow >= Viewport.Height - 1 && _model.Count > _topRow + CurrentRow)
{
ScrollTo (_topRow + Viewport.Height);
}
else if (_topRow > 0 && CurrentRow <= _topRow)
{
ScrollTo (_topRow - Viewport.Height);
}
else if (ev.Position.Y >= Viewport.Height)
{
ScrollTo (_model.Count);
}
else if (ev.Position.Y < 0 && _topRow > 0)
{
ScrollTo (0);
}
if (CurrentColumn - _leftColumn >= Viewport.Width - 1 && line.Count > _leftColumn + CurrentColumn)
{
ScrollTo (_leftColumn + Viewport.Width, false);
}
else if (_leftColumn > 0 && CurrentColumn <= _leftColumn)
{
ScrollTo (_leftColumn - Viewport.Width, false);
}
else if (ev.Position.X >= Viewport.Width)
{
ScrollTo (line.Count, false);
}
else if (ev.Position.X < 0 && _leftColumn > 0)
{
ScrollTo (0, false);
}
}
_lastWasKill = false;
_columnTrack = CurrentColumn;
}
else if (ev.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ButtonShift))
{
if (!_shiftSelecting)
{
_isButtonShift = true;
StartSelecting ();
}
ProcessMouseClick (ev, out _);
PositionCursor ();
_lastWasKill = false;
_columnTrack = CurrentColumn;
}
else if (ev.Flags.HasFlag (MouseFlags.Button1Pressed))
{
if (_shiftSelecting)
{
_clickWithSelecting = true;
StopSelecting ();
}
ProcessMouseClick (ev, out _);
PositionCursor ();
if (!IsSelecting)
{
StartSelecting ();
}
_lastWasKill = false;
_columnTrack = CurrentColumn;
if (App?.Mouse.MouseGrabView is null)
{
App?.Mouse.GrabMouse (this);
}
}
else if (ev.Flags.HasFlag (MouseFlags.Button1Released))
{
_isButtonReleased = true;
App?.Mouse.UngrabMouse ();
}
else if (ev.Flags.HasFlag (MouseFlags.Button1DoubleClicked))
{
if (ev.Flags.HasFlag (MouseFlags.ButtonShift))
{
if (!IsSelecting)
{
StartSelecting ();
}
}
else if (IsSelecting)
{
StopSelecting ();
}
ProcessMouseClick (ev, out List line);
if (!IsSelecting)
{
StartSelecting ();
}
(int startCol, int col, int row)? newPos = _model.ProcessDoubleClickSelection (SelectionStartColumn, CurrentColumn, CurrentRow, UseSameRuneTypeForWords, SelectWordOnlyOnDoubleClick);
if (newPos.HasValue)
{
SelectionStartColumn = newPos.Value.startCol;
CurrentColumn = newPos.Value.col;
CurrentRow = newPos.Value.row;
}
PositionCursor ();
_lastWasKill = false;
_columnTrack = CurrentColumn;
SetNeedsDraw ();
}
else if (ev.Flags.HasFlag (MouseFlags.Button1TripleClicked))
{
if (IsSelecting)
{
StopSelecting ();
}
ProcessMouseClick (ev, out List line);
CurrentColumn = 0;
if (!IsSelecting)
{
StartSelecting ();
}
CurrentColumn = line.Count;
PositionCursor ();
_lastWasKill = false;
_columnTrack = CurrentColumn;
SetNeedsDraw ();
}
else if (ev.Flags == ContextMenu!.MouseFlags)
{
ShowContextMenu (ev.ScreenPosition);
}
OnUnwrappedCursorPosition ();
return true;
}
///
/// Called when the contents of the TextView change. E.g. when the user types text or deletes text. Raises the
/// event.
///
public virtual void OnContentsChanged ()
{
ContentsChanged?.Invoke (this, new (CurrentRow, CurrentColumn));
ProcessInheritsPreviousScheme (CurrentRow, CurrentColumn);
ProcessAutocomplete ();
// Update content size when content changes
if (IsInitialized)
{
UpdateContentSize ();
}
}
///
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)
{
if (!key.IsValid)
{
return false;
}
// 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)
{
if (!CanFocus)
{
return true;
}
ResetColumnTrack ();
// Ignore control characters and other special keys
if (!a.IsKeyCodeAtoZ && (a.KeyCode < KeyCode.Space || a.KeyCode > KeyCode.CharMask))
{
return false;
}
InsertText (a);
DoNeededAction ();
return true;
}
///
public override bool OnKeyUp (Key key)
{
if (key == Key.Space.WithCtrl)
{
return true;
}
return false;
}
/// Positions the cursor on the current row and column
public override Point? PositionCursor ()
{
ProcessAutocomplete ();
if (!CanFocus || !Enabled || Driver is null)
{
return null;
}
if (App?.Mouse.MouseGrabView == this && IsSelecting)
{
// BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method.
//var minRow = Math.Min (Math.Max (Math.Min (selectionStartRow, currentRow) - topRow, 0), Viewport.Height);
//var maxRow = Math.Min (Math.Max (Math.Max (selectionStartRow, currentRow) - topRow, 0), Viewport.Height);
//SetNeedsDraw (new (0, minRow, Viewport.Width, maxRow));
SetNeedsDraw ();
}
List line = _model.GetLine (CurrentRow);
var col = 0;
if (line.Count > 0)
{
for (int idx = _leftColumn; idx < line.Count; idx++)
{
if (idx >= CurrentColumn)
{
break;
}
int cols = line [idx].Grapheme.GetColumns ();
if (line [idx].Grapheme == "\t")
{
cols += TabWidth + 1;
}
else
{
// 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.Width, cols))
{
col = CurrentColumn;
break;
}
}
}
int posX = CurrentColumn - _leftColumn;
int posY = CurrentRow - _topRow;
if (posX > -1 && col >= posX && posX < Viewport.Width && _topRow <= CurrentRow && posY < Viewport.Height)
{
Move (col, CurrentRow - _topRow);
return new (col, CurrentRow - _topRow);
}
return null; // Hide cursor
}
/// Redoes the latest changes.
public void Redo ()
{
if (ReadOnly)
{
return;
}
_historyText.Redo ();
}
///// Raised when the property of the changes.
/////
///// The property of only changes when it is explicitly set, not as the
///// user types. To be notified as the user changes the contents of the TextView see .
/////
//public event EventHandler? TextChanged;
/// Undoes the latest changes.
public void Undo ()
{
if (ReadOnly)
{
return;
}
_historyText.Undo ();
}
private void ClearRegion (int left, int top, int right, int bottom)
{
for (int row = top; row < bottom; row++)
{
Move (left, row);
for (int col = left; col < right; col++)
{
AddRune (col, row, (Rune)' ');
}
}
}
private void GenerateSuggestions ()
{
List currentLine = GetCurrentLine ();
int cursorPosition = Math.Min (CurrentColumn, currentLine.Count);
Autocomplete.Context = new (
currentLine,
cursorPosition,
Autocomplete.Context != null
? Autocomplete.Context.Canceled
: false
);
Autocomplete.GenerateSuggestions (
Autocomplete.Context
);
}
private void ProcessAutocomplete ()
{
if (_isDrawing)
{
return;
}
if (_clickWithSelecting)
{
_clickWithSelecting = false;
return;
}
if (SelectedLength > 0)
{
return;
}
// draw autocomplete
GenerateSuggestions ();
var renderAt = new Point (
Autocomplete.Context.CursorPosition,
Autocomplete.PopupInsideContainer
? CursorPosition.Y + 1 - TopRow
: 0
);
Autocomplete.RenderOverlay (renderAt);
}
private bool ProcessBackTab ()
{
ResetColumnTrack ();
if (!AllowsTab || _isReadOnly)
{
return false;
}
if (CurrentColumn > 0)
{
SetWrapModel ();
List currentLine = GetCurrentLine ();
if (currentLine.Count > 0 && currentLine [CurrentColumn - 1].Grapheme == "\t")
{
_historyText.Add (new () { new (currentLine) }, CursorPosition);
currentLine.RemoveAt (CurrentColumn - 1);
CurrentColumn--;
_historyText.Add (
new () { new (GetCurrentLine ()) },
CursorPosition,
TextEditingLineStatus.Replaced
);
}
SetNeedsDraw ();
UpdateWrapModel ();
}
DoNeededAction ();
return true;
}
// If InheritsPreviousScheme is enabled this method will check if the rune cell on
// the row and col location and around has a not null scheme. If it's null will set it with
// the very most previous valid scheme.
private void ProcessInheritsPreviousScheme (int row, int col)
{
if (!InheritsPreviousAttribute || (Lines == 1 && GetLine (Lines).Count == 0))
{
return;
}
List line = GetLine (row);
List lineToSet = line;
while (line.Count == 0)
{
if (row == 0 && line.Count == 0)
{
return;
}
row--;
line = GetLine (row);
lineToSet = line;
}
int colWithColor = Math.Max (Math.Min (col - 2, line.Count - 1), 0);
Cell cell = line [colWithColor];
int colWithoutColor = Math.Max (col - 1, 0);
Cell lineTo = lineToSet [colWithoutColor];
if (cell.Attribute is { } && colWithColor == 0 && lineTo.Attribute is { })
{
for (int r = row - 1; r > -1; r--)
{
List l = GetLine (r);
for (int c = l.Count - 1; c > -1; c--)
{
Cell cell1 = l [c];
if (cell1.Attribute is null)
{
cell1.Attribute = cell.Attribute;
l [c] = cell1;
}
else
{
return;
}
}
}
return;
}
if (cell.Attribute is null)
{
for (int r = row; r > -1; r--)
{
List l = GetLine (r);
colWithColor = l.FindLastIndex (
colWithColor > -1 ? colWithColor : l.Count - 1,
c => c.Attribute != null
);
if (colWithColor > -1 && l [colWithColor].Attribute is { })
{
cell = l [colWithColor];
break;
}
}
}
else
{
int cRow = row;
while (cell.Attribute is null)
{
if ((colWithColor == 0 || cell.Attribute is null) && cRow > 0)
{
line = GetLine (--cRow);
colWithColor = line.Count - 1;
cell = line [colWithColor];
}
else if (cRow == 0 && colWithColor < line.Count)
{
cell = line [colWithColor + 1];
}
}
}
if (cell.Attribute is { } && colWithColor > -1 && colWithoutColor < lineToSet.Count && lineTo.Attribute is null)
{
while (lineTo.Attribute is null)
{
lineTo.Attribute = cell.Attribute;
lineToSet [colWithoutColor] = lineTo;
colWithoutColor--;
if (colWithoutColor == -1 && row > 0)
{
lineToSet = GetLine (--row);
colWithoutColor = lineToSet.Count - 1;
}
}
}
}
private void ProcessMouseClick (MouseEventArgs ev, out List line)
{
List? r = null;
if (_model.Count > 0)
{
int maxCursorPositionableLine = Math.Max (_model.Count - 1 - _topRow, 0);
if (Math.Max (ev.Position.Y, 0) > maxCursorPositionableLine)
{
CurrentRow = maxCursorPositionableLine + _topRow;
}
else
{
CurrentRow = Math.Max (ev.Position.Y + _topRow, 0);
}
r = GetCurrentLine ();
int idx = TextModel.GetColFromX (r, _leftColumn, Math.Max (ev.Position.X, 0), TabWidth);
if (idx - _leftColumn >= r.Count)
{
CurrentColumn = Math.Max (r.Count - _leftColumn - (ReadOnly ? 1 : 0), 0);
}
else
{
CurrentColumn = idx + _leftColumn;
}
}
line = r!;
}
private bool ProcessEnterKey (ICommandContext? commandContext)
{
ResetColumnTrack ();
if (_isReadOnly)
{
return false;
}
if (!AllowsReturn)
{
// By Default pressing ENTER should be ignored (OnAccept will return false or null). Only cancel if the
// event was fired and set Cancel = true.
return RaiseAccepting (commandContext) is null or false;
}
SetWrapModel ();
List currentLine = GetCurrentLine ();
_historyText.Add (new () { new (currentLine) }, CursorPosition);
if (IsSelecting)
{
ClearSelectedRegion ();
currentLine = GetCurrentLine ();
}
int restCount = currentLine.Count - CurrentColumn;
List rest = currentLine.GetRange (CurrentColumn, restCount);
currentLine.RemoveRange (CurrentColumn, restCount);
List> addedLines = new () { new (currentLine) };
_model.AddLine (CurrentRow + 1, rest);
addedLines.Add (new (_model.GetLine (CurrentRow + 1)));
_historyText.Add (addedLines, CursorPosition, TextEditingLineStatus.Added);
CurrentRow++;
var fullNeedsDraw = false;
if (CurrentRow >= _topRow + Viewport.Height)
{
_topRow++;
fullNeedsDraw = true;
}
CurrentColumn = 0;
_historyText.Add (
new () { new (GetCurrentLine ()) },
CursorPosition,
TextEditingLineStatus.Replaced
);
if (!_wordWrap && CurrentColumn < _leftColumn)
{
fullNeedsDraw = true;
_leftColumn = 0;
}
if (fullNeedsDraw)
{
SetNeedsDraw ();
}
else
{
// BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method.
//SetNeedsDraw (new (0, currentRow - topRow, 2, Viewport.Height));
SetNeedsDraw ();
}
UpdateWrapModel ();
DoNeededAction ();
OnContentsChanged ();
return true;
}
private void ProcessSetOverwrite ()
{
ResetColumnTrack ();
SetOverwrite (!Used);
}
private bool ProcessTab ()
{
ResetColumnTrack ();
if (!AllowsTab || _isReadOnly)
{
return false;
}
InsertText (new Key ((KeyCode)'\t'));
DoNeededAction ();
return true;
}
private void SetOverwrite (bool overwrite)
{
Used = overwrite;
SetNeedsDraw ();
DoNeededAction ();
}
private void SetValidUsedColor (Attribute? attribute)
{
// BUGBUG: (v2 truecolor) This code depends on 8-bit color names; disabling for now
//if ((scheme!.HotNormal.Foreground & scheme.Focus.Background) == scheme.Focus.Foreground) {
SetAttribute (new (attribute!.Value.Background, attribute!.Value.Foreground, attribute!.Value.Style));
}
} | | | | | | | | | | | | | | | | | |