Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #3771. TextView doesn't consider no-printable rune in draw and cursor position. #3772

Open
wants to merge 18 commits into
base: v2_develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 131 additions & 6 deletions Terminal.Gui/Drawing/Cell.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,18 @@
/// Represents a single row/column in a Terminal.Gui rendering surface (e.g. <see cref="LineCanvas"/> and
/// <see cref="ConsoleDriver"/>).
/// </summary>
public record struct Cell ()
public record struct Cell (Attribute? Attribute = null, bool IsDirty = false, Rune Rune = default)
{

/// <summary>The attributes to use when drawing the Glyph.</summary>
public Attribute? Attribute { get; set; } = null;
public Attribute? Attribute { get; set; } = Attribute;

/// <summary>
/// Gets or sets a value indicating whether this <see cref="T:Terminal.Gui.Cell"/> has been modified since the
/// last time it was drawn.
/// </summary>
public bool IsDirty { get; set; } = false;
public bool IsDirty { get; set; } = IsDirty;

private Rune _rune = default;
private Rune _rune = Rune;

/// <summary>The character to display. If <see cref="Rune"/> is <see langword="null"/>, then <see cref="Rune"/> is ignored.</summary>
public Rune Rune
Expand All @@ -29,6 +28,8 @@ public Rune Rune
}
}

private List<Rune> _combiningMarks;

/// <summary>
/// The combining marks for <see cref="Rune"/> that when combined makes this Cell a combining sequence. If
/// <see cref="CombiningMarks"/> empty, then <see cref="CombiningMarks"/> is ignored.
Expand All @@ -37,8 +38,132 @@ public Rune Rune
/// Only valid in the rare case where <see cref="Rune"/> is a combining sequence that could not be normalized to a
/// single Rune.
/// </remarks>
internal List<Rune> CombiningMarks { get; } = new ();
internal List<Rune> CombiningMarks
{
get => _combiningMarks ?? [];
private set => _combiningMarks = value ?? [];
}

/// <inheritdoc/>
public override string ToString () { return $"[{Rune}, {Attribute}]"; }

/// <summary>Converts the string into a <see cref="List{Cell}"/>.</summary>
/// <param name="str">The string to convert.</param>
/// <param name="attribute">The <see cref="Gui.ColorScheme"/> to use.</param>
/// <returns></returns>
public static List<Cell> ToCellList (string str, Attribute? attribute = null)
{
List<Cell> cells = new ();

foreach (Rune rune in str.EnumerateRunes ())
{
cells.Add (new () { Rune = rune, Attribute = attribute });
}

return cells;
}

/// <summary>
/// Splits a string into a List that will contain a <see cref="List{Cell}"/> for each line.
/// </summary>
/// <param name="content">The string content.</param>
/// <param name="attribute">The color scheme.</param>
/// <returns>A <see cref="List{Cell}"/> for each line.</returns>
public static List<List<Cell>> StringToLinesOfCells (string content, Attribute? attribute = null)
{
List<Cell> cells = content.EnumerateRunes ()
.Select (x => new Cell { Rune = x, Attribute = attribute })
.ToList ();

return SplitNewLines (cells);
}

/// <summary>Converts a <see cref="Cell"/> generic collection into a string.</summary>
/// <param name="cells">The enumerable cell to convert.</param>
/// <returns></returns>
public static string ToString (IEnumerable<Cell> cells)
{
var str = string.Empty;

foreach (Cell cell in cells)
{
str += cell.Rune.ToString ();
}

return str;
}

// Turns the string into cells, this does not split the contents on a newline if it is present.

internal static List<Cell> StringToCells (string str, Attribute? attribute = null)
{
List<Cell> cells = [];

foreach (Rune rune in str.ToRunes ())
{
cells.Add (new () { Rune = rune, Attribute = attribute });
}

return cells;
}

internal static List<Cell> ToCells (IEnumerable<Rune> runes, Attribute? attribute = null)
{
List<Cell> cells = new ();

foreach (Rune rune in runes)
{
cells.Add (new () { Rune = rune, Attribute = attribute });
}

return cells;
}

private static List<List<Cell>> SplitNewLines (List<Cell> cells)
{
List<List<Cell>> lines = [];
int start = 0, i = 0;
var hasCR = false;

// ASCII code 13 = Carriage Return.
// ASCII code 10 = Line Feed.
for (; i < cells.Count; i++)
{
if (cells [i].Rune.Value == 13)
{
hasCR = true;

continue;
}

if (cells [i].Rune.Value == 10)
{
if (i - start > 0)
{
lines.Add (cells.GetRange (start, hasCR ? i - 1 - start : i - start));
}
else
{
lines.Add (StringToCells (string.Empty));
}

start = i + 1;
hasCR = false;
}
}

if (i - start >= 0)
{
lines.Add (cells.GetRange (start, i - start));
}

return lines;
}

/// <summary>
/// Splits a rune cell list into a List that will contain a <see cref="List{Cell}"/> for each line.
/// </summary>
/// <param name="cells">The cells list.</param>
/// <returns></returns>
public static List<List<Cell>> ToCells (List<Cell> cells) { return SplitNewLines (cells); }
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
namespace Terminal.Gui;

/// <summary>Args for events that relate to a specific <see cref="RuneCell"/>.</summary>
public class RuneCellEventArgs
/// <summary>Args for events that relate to a specific <see cref="Cell"/>.</summary>
public class CellEventArgs
{
/// <summary>Creates a new instance of the <see cref="RuneCellEventArgs"/> class.</summary>
/// <summary>Creates a new instance of the <see cref="CellEventArgs"/> class.</summary>
/// <param name="line">The line.</param>
/// <param name="col">The col index.</param>
/// <param name="unwrappedPosition">The unwrapped row and col index.</param>
public RuneCellEventArgs (List<RuneCell> line, int col, (int Row, int Col) unwrappedPosition)
public CellEventArgs (List<Cell> line, int col, (int Row, int Col) unwrappedPosition)
{
Line = line;
Col = col;
UnwrappedPosition = unwrappedPosition;
}

/// <summary>The index of the RuneCell in the line.</summary>
/// <summary>The index of the Cell in the line.</summary>
public int Col { get; }

/// <summary>The list of runes the RuneCell is part of.</summary>
public List<RuneCell> Line { get; }
/// <summary>The list of runes the Cell is part of.</summary>
public List<Cell> Line { get; }

/// <summary>
/// The unwrapped row and column index into the text containing the RuneCell. Unwrapped means the text without
/// The unwrapped row and column index into the text containing the Cell. Unwrapped means the text without
/// word wrapping or other visual formatting having been applied.
/// </summary>
public (int Row, int Col) UnwrappedPosition { get; }
Expand Down
2 changes: 1 addition & 1 deletion Terminal.Gui/Drawing/LineCanvas.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ public void AddLine (
int length,
Orientation orientation,
LineStyle style,
Attribute? attribute = default
Attribute? attribute = null
)
{
_cachedViewport = Rectangle.Empty;
Expand Down
2 changes: 1 addition & 1 deletion Terminal.Gui/Drawing/StraightLine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public StraightLine (
int length,
Orientation orientation,
LineStyle style,
Attribute? attribute = default
Attribute? attribute = null
)
{
Start = start;
Expand Down
4 changes: 2 additions & 2 deletions Terminal.Gui/Text/Autocomplete/AutocompleteContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace Terminal.Gui;
public class AutocompleteContext
{
/// <summary>Creates a new instance of the <see cref="AutocompleteContext"/> class</summary>
public AutocompleteContext (List<RuneCell> currentLine, int cursorPosition, bool canceled = false)
public AutocompleteContext (List<Cell> currentLine, int cursorPosition, bool canceled = false)
{
CurrentLine = currentLine;
CursorPosition = cursorPosition;
Expand All @@ -18,7 +18,7 @@ public AutocompleteContext (List<RuneCell> currentLine, int cursorPosition, bool
public bool Canceled { get; set; }

/// <summary>The text on the current line.</summary>
public List<RuneCell> CurrentLine { get; set; }
public List<Cell> CurrentLine { get; set; }

/// <summary>The position of the input cursor within the <see cref="CurrentLine"/>.</summary>
public int CursorPosition { get; set; }
Expand Down
4 changes: 2 additions & 2 deletions Terminal.Gui/Views/AutocompleteFilepathContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace Terminal.Gui;
internal class AutocompleteFilepathContext : AutocompleteContext
{
public AutocompleteFilepathContext (string currentLine, int cursorPosition, FileDialogState state)
: base (TextModel.ToRuneCellList (currentLine), cursorPosition)
: base (Cell.ToCellList (currentLine), cursorPosition)
{
State = state;
}
Expand All @@ -30,7 +30,7 @@ public IEnumerable<Suggestion> GenerateSuggestions (AutocompleteContext context)
return Enumerable.Empty<Suggestion> ();
}

var path = TextModel.ToString (context.CurrentLine);
var path = Cell.ToString (context.CurrentLine);
int last = path.LastIndexOfAny (FileDialog.Separators);

if (string.IsNullOrWhiteSpace (path) || !Path.IsPathRooted (path))
Expand Down
6 changes: 3 additions & 3 deletions Terminal.Gui/Views/HistoryTextItemEventArgs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ public class HistoryTextItemEventArgs : EventArgs
public Point CursorPosition;
public Point FinalCursorPosition;
public bool IsUndoing;
public List<List<RuneCell>> Lines;
public List<List<Cell>> Lines;
public LineStatus LineStatus;
public HistoryTextItemEventArgs RemovedOnAdded;

public HistoryTextItemEventArgs (List<List<RuneCell>> lines, Point curPos, LineStatus linesStatus)
public HistoryTextItemEventArgs (List<List<Cell>> lines, Point curPos, LineStatus linesStatus)
{
Lines = lines;
CursorPosition = curPos;
Expand All @@ -22,7 +22,7 @@ public HistoryTextItemEventArgs (List<List<RuneCell>> lines, Point curPos, LineS

public HistoryTextItemEventArgs (HistoryTextItemEventArgs historyTextItem)
{
Lines = new List<List<RuneCell>> (historyTextItem.Lines);
Lines = new List<List<Cell>> (historyTextItem.Lines);
CursorPosition = new Point (historyTextItem.CursorPosition.X, historyTextItem.CursorPosition.Y);
LineStatus = historyTextItem.LineStatus;
}
Expand Down
28 changes: 14 additions & 14 deletions Terminal.Gui/Views/TextField.cs
Original file line number Diff line number Diff line change
Expand Up @@ -546,12 +546,12 @@ public string SelectedText
if (!Secret && !_historyText.IsFromHistory)
{
_historyText.Add (
new List<List<RuneCell>> { TextModel.ToRuneCellList (oldText) },
new List<List<Cell>> { Cell.ToCellList (oldText) },
new Point (_cursorPosition, 0)
);

_historyText.Add (
new List<List<RuneCell>> { TextModel.ToRuneCells (_text) },
new List<List<Cell>> { Cell.ToCells (_text) },
new Point (_cursorPosition, 0),
HistoryText.LineStatus.Replaced
);
Expand Down Expand Up @@ -648,7 +648,7 @@ public virtual void DeleteCharLeft (bool usePreTextChangedCursorPos)
}

_historyText.Add (
new List<List<RuneCell>> { TextModel.ToRuneCells (_text) },
new List<List<Cell>> { Cell.ToCells (_text) },
new Point (_cursorPosition, 0)
);

Expand Down Expand Up @@ -702,7 +702,7 @@ public virtual void DeleteCharRight ()
}

_historyText.Add (
new List<List<RuneCell>> { TextModel.ToRuneCells (_text) },
new List<List<Cell>> { Cell.ToCells (_text) },
new Point (_cursorPosition, 0)
);

Expand Down Expand Up @@ -952,7 +952,7 @@ public override void OnDrawContent (Rectangle viewport)

int p = ScrollOffset;
var col = 0;
int width = Frame.Width + OffSetBackground ();
int width = Viewport.Width + OffSetBackground ();
int tcount = _text.Count;
Attribute roc = GetReadOnlyColor ();

Expand Down Expand Up @@ -1140,10 +1140,10 @@ public virtual void Paste ()
}

int cols = _text [idx].GetColumns ();
TextModel.SetCol (ref col, Frame.Width - 1, cols);
TextModel.SetCol (ref col, Viewport.Width - 1, cols);
}

int pos = _cursorPosition - ScrollOffset + Math.Min (Frame.X, 0);
int pos = _cursorPosition - ScrollOffset + Math.Min (Viewport.X, 0);
Move (pos, 0);
return new Point (pos, 0);
}
Expand Down Expand Up @@ -1225,16 +1225,16 @@ private void Adjust ()
ScrollOffset = _cursorPosition;
need = true;
}
else if (Frame.Width > 0
&& (ScrollOffset + _cursorPosition - (Frame.Width + offB) == 0
|| TextModel.DisplaySize (_text, ScrollOffset, _cursorPosition).size >= Frame.Width + offB))
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,
Frame.Width + offB
Viewport.Width + offB
),
0
);
Expand Down Expand Up @@ -1342,7 +1342,7 @@ private List<Rune> DeleteSelectedText ()

private void GenerateSuggestions ()
{
List<RuneCell> currentLine = TextModel.ToRuneCellList (Text);
List<Cell> currentLine = Cell.ToCellList (Text);
int cursorPosition = Math.Min (CursorPosition, currentLine.Count);

Autocomplete.Context = new AutocompleteContext (
Expand Down Expand Up @@ -1390,15 +1390,15 @@ private void HistoryText_ChangeText (object sender, HistoryText.HistoryTextItemE
return;
}

Text = TextModel.ToString (obj?.Lines [obj.CursorPosition.Y]);
Text = Cell.ToString (obj?.Lines [obj.CursorPosition.Y]);
CursorPosition = obj.CursorPosition.X;
Adjust ();
}

private void InsertText (Key a, bool usePreTextChangedCursorPos)
{
_historyText.Add (
new List<List<RuneCell>> { TextModel.ToRuneCells (_text) },
new List<List<Cell>> { Cell.ToCells (_text) },
new Point (_cursorPosition, 0)
);

Expand Down
Loading
Loading