namespace Terminal.Gui.Views;
public partial class TextView
{
/// Get or sets whether the user is currently selecting text.
public bool IsSelecting { get; set; }
///
/// Gets or sets whether the word navigation should select only the word itself without spaces around it or with the
/// spaces at right.
/// Default is false meaning that the spaces at right are included in the selection.
///
public bool SelectWordOnlyOnDoubleClick { get; set; }
/// Start row position of the selected text.
public int SelectionStartRow
{
get => _selectionStartRow;
set
{
_selectionStartRow = value < 0 ? 0 :
value > _model.Count - 1 ? Math.Max (_model.Count - 1, 0) : value;
IsSelecting = true;
SetNeedsDraw ();
Adjust ();
}
}
/// Start column position of the selected text.
public int SelectionStartColumn
{
get => _selectionStartColumn;
set
{
List line = _model.GetLine (_selectionStartRow);
_selectionStartColumn = value < 0 ? 0 :
value > line.Count ? line.Count : value;
IsSelecting = true;
SetNeedsDraw ();
Adjust ();
}
}
private void StartSelecting ()
{
if (_shiftSelecting && IsSelecting)
{
return;
}
_shiftSelecting = true;
IsSelecting = true;
_selectionStartColumn = CurrentColumn;
_selectionStartRow = CurrentRow;
}
private void StopSelecting ()
{
if (IsSelecting)
{
SetNeedsDraw ();
}
_shiftSelecting = false;
IsSelecting = false;
_isButtonShift = false;
}
/// Length of the selected text.
public int SelectedLength => GetSelectedLength ();
///
/// Gets the selected text as
///
/// List{List{Cell}}
///
///
public List> SelectedCellsList
{
get
{
GetRegion (out List> selectedCellsList);
return selectedCellsList;
}
}
/// The selected text.
public string SelectedText
{
get
{
if (!IsSelecting || (_model.Count == 1 && _model.GetLine (0).Count == 0))
{
return string.Empty;
}
return GetSelectedRegion ();
}
}
// Returns an encoded region start..end (top 32 bits are the row, low32 the column)
private void GetEncodedRegionBounds (
out long start,
out long end,
int? startRow = null,
int? startCol = null,
int? cRow = null,
int? cCol = null
)
{
long selection;
long point;
if (startRow is null || startCol is null || cRow is null || cCol is null)
{
selection = ((long)(uint)_selectionStartRow << 32) | (uint)_selectionStartColumn;
point = ((long)(uint)CurrentRow << 32) | (uint)CurrentColumn;
}
else
{
selection = ((long)(uint)startRow << 32) | (uint)startCol;
point = ((long)(uint)cRow << 32) | (uint)cCol;
}
if (selection > point)
{
start = point;
end = selection;
}
else
{
start = selection;
end = point;
}
}
//
// Returns a string with the text in the selected
// region.
//
internal string GetRegion (
out List> cellsList,
int? sRow = null,
int? sCol = null,
int? cRow = null,
int? cCol = null,
TextModel? model = null
)
{
GetEncodedRegionBounds (out long start, out long end, sRow, sCol, cRow, cCol);
cellsList = [];
if (start == end)
{
return string.Empty;
}
var startRow = (int)(start >> 32);
var maxRow = (int)(end >> 32);
var startCol = (int)(start & 0xffffffff);
var endCol = (int)(end & 0xffffffff);
List line = model is null ? _model.GetLine (startRow) : model.GetLine (startRow);
List cells;
if (startRow == maxRow)
{
cells = line.GetRange (startCol, endCol - startCol);
cellsList.Add (cells);
return StringFromCells (cells);
}
cells = line.GetRange (startCol, line.Count - startCol);
cellsList.Add (cells);
string res = StringFromCells (cells);
for (int row = startRow + 1; row < maxRow; row++)
{
cellsList.AddRange ([]);
cells = model == null ? _model.GetLine (row) : model.GetLine (row);
cellsList.Add (cells);
res = res
+ Environment.NewLine
+ StringFromCells (cells);
}
line = model is null ? _model.GetLine (maxRow) : model.GetLine (maxRow);
cellsList.AddRange ([]);
cells = line.GetRange (0, endCol);
cellsList.Add (cells);
res = res + Environment.NewLine + StringFromCells (cells);
return res;
}
private int GetSelectedLength () { return SelectedText.Length; }
private string GetSelectedRegion ()
{
int cRow = CurrentRow;
int cCol = CurrentColumn;
int startRow = _selectionStartRow;
int startCol = _selectionStartColumn;
TextModel model = _model;
if (_wordWrap)
{
cRow = _wrapManager!.GetModelLineFromWrappedLines (CurrentRow);
cCol = _wrapManager.GetModelColFromWrappedLines (CurrentRow, CurrentColumn);
startRow = _wrapManager.GetModelLineFromWrappedLines (_selectionStartRow);
startCol = _wrapManager.GetModelColFromWrappedLines (_selectionStartRow, _selectionStartColumn);
model = _wrapManager.Model;
}
OnUnwrappedCursorPosition (cRow, cCol);
return GetRegion (out _, startRow, startCol, cRow, cCol, model);
}
private string StringFromCells (List cells)
{
ArgumentNullException.ThrowIfNull (cells);
var size = 0;
foreach (Cell cell in cells)
{
string t = cell.Grapheme;
size += Encoding.Unicode.GetByteCount (t);
}
byte [] encoded = new byte [size];
var offset = 0;
foreach (Cell cell in cells)
{
string t = cell.Grapheme;
int bytesWritten = Encoding.Unicode.GetBytes (t, 0, t.Length, encoded, offset);
offset += bytesWritten;
}
// decode using the same encoding and the bytes actually written
return Encoding.Unicode.GetString (encoded, 0, offset);
}
///
public bool EnableForDesign ()
{
Text = """
TextView provides a fully featured multi-line text editor.
It supports word wrap and history for undo.
""";
return true;
}
///
protected override void Dispose (bool disposing)
{
if (disposing && ContextMenu is { })
{
ContextMenu.Visible = false;
ContextMenu.Dispose ();
ContextMenu = null;
}
base.Dispose (disposing);
}
private void ClearRegion ()
{
SetWrapModel ();
long start, end;
long currentEncoded = ((long)(uint)CurrentRow << 32) | (uint)CurrentColumn;
GetEncodedRegionBounds (out start, out end);
var startRow = (int)(start >> 32);
var maxrow = (int)(end >> 32);
var startCol = (int)(start & 0xffffffff);
var endCol = (int)(end & 0xffffffff);
List line = _model.GetLine (startRow);
_historyText.Add (new () { new (line) }, new (startCol, startRow));
List> removedLines = new ();
if (startRow == maxrow)
{
removedLines.Add (new (line));
line.RemoveRange (startCol, endCol - startCol);
CurrentColumn = startCol;
if (_wordWrap)
{
SetNeedsDraw ();
}
else
{
//QUESTION: Is the below comment still relevant?
// BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method.
//SetNeedsDraw (new (0, startRow - topRow, Viewport.Width, startRow - topRow + 1));
SetNeedsDraw ();
}
_historyText.Add (
new (removedLines),
CursorPosition,
TextEditingLineStatus.Removed
);
UpdateWrapModel ();
return;
}
removedLines.Add (new (line));
line.RemoveRange (startCol, line.Count - startCol);
List| line2 = _model.GetLine (maxrow);
line.AddRange (line2.Skip (endCol));
for (int row = startRow + 1; row <= maxrow; row++)
{
removedLines.Add (new (_model.GetLine (startRow + 1)));
_model.RemoveLine (startRow + 1);
}
if (currentEncoded == end)
{
CurrentRow -= maxrow - startRow;
}
CurrentColumn = startCol;
_historyText.Add (
new (removedLines),
CursorPosition,
TextEditingLineStatus.Removed
);
UpdateWrapModel ();
SetNeedsDraw ();
}
private void ClearSelectedRegion ()
{
SetWrapModel ();
if (!_isReadOnly)
{
ClearRegion ();
}
UpdateWrapModel ();
IsSelecting = false;
DoNeededAction ();
}
/// Select all text.
public void SelectAll ()
{
if (_model.Count == 0)
{
return;
}
StartSelecting ();
_selectionStartColumn = 0;
_selectionStartRow = 0;
CurrentColumn = _model.GetLine (_model.Count - 1).Count;
CurrentRow = _model.Count - 1;
SetNeedsDraw ();
}
private void ProcessSelectAll ()
{
ResetColumnTrack ();
SelectAll ();
}
private bool PointInSelection (int col, int row)
{
long start, end;
GetEncodedRegionBounds (out start, out end);
long q = ((long)(uint)row << 32) | (uint)col;
return q >= start && q <= end - 1;
}
}
| | | | | |