diff --git a/Terminal.Gui/Drawing/Cell.cs b/Terminal.Gui/Drawing/Cell.cs index 55da051845..16af046a7f 100644 --- a/Terminal.Gui/Drawing/Cell.cs +++ b/Terminal.Gui/Drawing/Cell.cs @@ -4,19 +4,18 @@ /// Represents a single row/column in a Terminal.Gui rendering surface (e.g. and /// ). /// -public record struct Cell () +public record struct Cell (Attribute? Attribute = null, bool IsDirty = false, Rune Rune = default) { - /// The attributes to use when drawing the Glyph. - public Attribute? Attribute { get; set; } = null; + public Attribute? Attribute { get; set; } = Attribute; /// /// Gets or sets a value indicating whether this has been modified since the /// last time it was drawn. /// - public bool IsDirty { get; set; } = false; + public bool IsDirty { get; set; } = IsDirty; - private Rune _rune = default; + private Rune _rune = Rune; /// The character to display. If is , then is ignored. public Rune Rune @@ -29,6 +28,8 @@ public Rune Rune } } + private List _combiningMarks; + /// /// The combining marks for that when combined makes this Cell a combining sequence. If /// empty, then is ignored. @@ -37,8 +38,153 @@ public Rune Rune /// Only valid in the rare case where is a combining sequence that could not be normalized to a /// single Rune. /// - internal List CombiningMarks { get; } = new (); + internal List CombiningMarks + { + get => _combiningMarks ?? []; + private set => _combiningMarks = value ?? []; + } /// public override string ToString () { return $"[{Rune}, {Attribute}]"; } + + /// Converts the string into a . + /// The string to convert. + /// The to use. + /// + public static List ToCellList (string str, Attribute? attribute = null) + { + List cells = new (); + + foreach (Rune rune in str.EnumerateRunes ()) + { + cells.Add (new () { Rune = rune, Attribute = attribute }); + } + + return cells; + } + + /// + /// Splits a string into a List that will contain a for each line. + /// + /// The string content. + /// The color scheme. + /// A for each line. + public static List> StringToLinesOfCells (string content, Attribute? attribute = null) + { + List cells = content.EnumerateRunes () + .Select (x => new Cell { Rune = x, Attribute = attribute }) + .ToList (); + + return SplitNewLines (cells); + } + + /// Converts a generic collection into a string. + /// The enumerable cell to convert. + /// + public static string ToString (IEnumerable cells) + { + var str = string.Empty; + + foreach (Cell cell in cells) + { + str += cell.Rune.ToString (); + } + + return str; + } + + /// Converts a generic collection into a string. + /// The enumerable cell to convert. + /// + public static string ToString (List> cellsList) + { + var str = string.Empty; + + for (var i = 0; i < cellsList.Count; i++) + { + IEnumerable cellList = cellsList [i]; + str += ToString (cellList); + + if (i + 1 < cellsList.Count) + { + str += Environment.NewLine; + } + } + + return str; + } + + // Turns the string into cells, this does not split the contents on a newline if it is present. + + internal static List StringToCells (string str, Attribute? attribute = null) + { + List cells = []; + + foreach (Rune rune in str.ToRunes ()) + { + cells.Add (new () { Rune = rune, Attribute = attribute }); + } + + return cells; + } + + internal static List ToCells (IEnumerable runes, Attribute? attribute = null) + { + List cells = new (); + + foreach (Rune rune in runes) + { + cells.Add (new () { Rune = rune, Attribute = attribute }); + } + + return cells; + } + + private static List> SplitNewLines (List cells) + { + List> 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; + } + + /// + /// Splits a rune cell list into a List that will contain a for each line. + /// + /// The cells list. + /// + public static List> ToCells (List cells) { return SplitNewLines (cells); } } diff --git a/Terminal.Gui/Views/RuneCellEventArgs.cs b/Terminal.Gui/Drawing/CellEventArgs.cs similarity index 57% rename from Terminal.Gui/Views/RuneCellEventArgs.cs rename to Terminal.Gui/Drawing/CellEventArgs.cs index 1283cfe570..f2a8115dc9 100644 --- a/Terminal.Gui/Views/RuneCellEventArgs.cs +++ b/Terminal.Gui/Drawing/CellEventArgs.cs @@ -1,27 +1,27 @@ namespace Terminal.Gui; -/// Args for events that relate to a specific . -public class RuneCellEventArgs +/// Args for events that relate to a specific . +public record struct CellEventArgs { - /// Creates a new instance of the class. + /// Creates a new instance of the class. /// The line. /// The col index. /// The unwrapped row and col index. - public RuneCellEventArgs (List line, int col, (int Row, int Col) unwrappedPosition) + public CellEventArgs (List line, int col, (int Row, int Col) unwrappedPosition) { Line = line; Col = col; UnwrappedPosition = unwrappedPosition; } - /// The index of the RuneCell in the line. + /// The index of the Cell in the line. public int Col { get; } - /// The list of runes the RuneCell is part of. - public List Line { get; } + /// The list of runes the Cell is part of. + public List Line { get; } /// - /// 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. /// public (int Row, int Col) UnwrappedPosition { get; } diff --git a/Terminal.Gui/Drawing/LineCanvas.cs b/Terminal.Gui/Drawing/LineCanvas.cs index 9a7365f264..235d657d98 100644 --- a/Terminal.Gui/Drawing/LineCanvas.cs +++ b/Terminal.Gui/Drawing/LineCanvas.cs @@ -138,7 +138,7 @@ public void AddLine ( int length, Orientation orientation, LineStyle style, - Attribute? attribute = default + Attribute? attribute = null ) { _cachedViewport = Rectangle.Empty; diff --git a/Terminal.Gui/Drawing/StraightLine.cs b/Terminal.Gui/Drawing/StraightLine.cs index 2f36995df6..fe2ccdc1d0 100644 --- a/Terminal.Gui/Drawing/StraightLine.cs +++ b/Terminal.Gui/Drawing/StraightLine.cs @@ -16,7 +16,7 @@ public StraightLine ( int length, Orientation orientation, LineStyle style, - Attribute? attribute = default + Attribute? attribute = null ) { Start = start; diff --git a/Terminal.Gui/Resources/Strings.Designer.cs b/Terminal.Gui/Resources/Strings.Designer.cs index 491473014c..2befd2737b 100644 --- a/Terminal.Gui/Resources/Strings.Designer.cs +++ b/Terminal.Gui/Resources/Strings.Designer.cs @@ -1437,6 +1437,15 @@ internal static string btnYes { } } + /// + /// Looks up a localized string similar to Co_lors. + /// + internal static string ctxColors { + get { + return ResourceManager.GetString("ctxColors", resourceCulture); + } + } + /// /// Looks up a localized string similar to _Copy. /// diff --git a/Terminal.Gui/Resources/Strings.fr-FR.resx b/Terminal.Gui/Resources/Strings.fr-FR.resx index 746c454996..c20959da40 100644 --- a/Terminal.Gui/Resources/Strings.fr-FR.resx +++ b/Terminal.Gui/Resources/Strings.fr-FR.resx @@ -117,27 +117,27 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + Tout _sélectionner + + + _Tout supprimer + _Copier Co_uper - - _Tout supprimer - C_oller - - _Rétablir - - - Tout _sélectionner - _Annuler + + _Rétablir + _Dossier @@ -168,16 +168,19 @@ Prochai_n... + + Ouvrir + Enregistrer E_nregistrer sous - - Ouvrir - Sélecteur de Date + + Cou_leurs + \ No newline at end of file diff --git a/Terminal.Gui/Resources/Strings.ja-JP.resx b/Terminal.Gui/Resources/Strings.ja-JP.resx index 4a825c51c6..fa4bda4210 100644 --- a/Terminal.Gui/Resources/Strings.ja-JP.resx +++ b/Terminal.Gui/Resources/Strings.ja-JP.resx @@ -117,27 +117,27 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + 全て選択 (_S) + + + 全て削除 (_D) + コピー (_C) 切り取り (_T) - - 全て削除 (_D) - 貼り付け (_P) - - やり直し (_R) - - - 全て選択 (_S) - 元に戻す (_U) + + やり直し (_R) + ディレクトリ @@ -171,44 +171,47 @@ 同じ名前のディレクトリはすでに存在しました - - “{0}”を削除もよろしいですか?この操作は元に戻りません - - - タイプ + + すでに存在したディレクトリを選択してください - - サイズ + + 同じ名前のファイルはすでに存在しました - - パスを入力 + + すでに存在したファイルを選択してください ファイル名 - - 新規ディレクトリ - - - いいえ (_N) - - - はい (_Y) + + すでに存在したファイルまたはディレクトリを選択してください 変更日時 - - すでに存在したファイルまたはディレクトリを選択してください + + パスを入力 - - すでに存在したディレクトリを選択してください + + 検索を入力 - - すでに存在したファイルを選択してください + + サイズ - - 名前: + + タイプ + + + ファイルタイプが間違っでいます + + + 任意ファイル + + + “{0}”を削除もよろしいですか?この操作は元に戻りません + + + 削除失敗 {0} を削除 @@ -216,35 +219,26 @@ 新規失敗 - - 既存 + + 新規ディレクトリ - - 名前を変更 + + いいえ (_N) 変更失敗 - - 削除失敗 - - - 同じ名前のファイルはすでに存在しました - - - 検索を入力 - - - ファイルタイプが間違っでいます + + 名前: - - 任意ファイル + + 名前を変更 - - キャンセル (_C) + + はい (_Y) - - OK (_O) + + 既存 開く (_O) @@ -255,6 +249,12 @@ 名前を付けて保存 (_S) + + OK (_O) + + + キャンセル (_C) + 削除 (_D) @@ -276,4 +276,7 @@ 日付ピッカー + + 絵の具 (_L) + \ No newline at end of file diff --git a/Terminal.Gui/Resources/Strings.pt-PT.resx b/Terminal.Gui/Resources/Strings.pt-PT.resx index 2cffa6cd7e..28aabf522c 100644 --- a/Terminal.Gui/Resources/Strings.pt-PT.resx +++ b/Terminal.Gui/Resources/Strings.pt-PT.resx @@ -117,27 +117,27 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + _Selecionar Tudo + + + _Apagar Tudo + _Copiar Cor_tar - - _Apagar Tudo - Co_lar - - _Refazer - - - _Selecionar Tudo - _Desfazer + + _Refazer + Diretório @@ -168,16 +168,19 @@ S_eguir... - - Guardar como + + Abrir Guardar - - Abrir + + Guardar como Seletor de Data + + Co_res + \ No newline at end of file diff --git a/Terminal.Gui/Resources/Strings.resx b/Terminal.Gui/Resources/Strings.resx index 8380fb4089..9333d71578 100644 --- a/Terminal.Gui/Resources/Strings.resx +++ b/Terminal.Gui/Resources/Strings.resx @@ -1,6 +1,6 @@  - - - - + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + _Select All - + _Delete All - + _Copy - + Cu_t - + _Paste - + _Undo - + _Redo - + Directory - + File - + Save - + Save as - + Open - + Select folder - + Select Mixed - + _Back - + Fi_nish - + _Next... - + Directory already exists with that name When trying to save a file with a name already taken by a directory - + Must select an existing directory - + File already exists with that name - + Must select an existing file When trying to save a directory with a name already used by a file - + Filename - + Must select an existing file or directory - + Modified - + Enter Path - + Enter Search - + Size - + Type - + Wrong file type When trying to open/save a file that does not match the provided filter (e.g. csv) - + Any Files Describes an AllowedType that matches anything - + Are you sure you want to delete '{0}'? This operation is permanent - + Delete Failed - + Delete {0} - + New Failed - + New Folder - + _No - + Rename Failed - + Name: - + Rename - + _Yes - + Existing - + O_pen - + _Save - + Save _as - + _OK - + _Cancel - + _Delete - + _Hide {0} - + _New - + _Rename - + _Sort {0} ASC - + _Sort {0} DESC - + Date Picker - + AliceBlue - + AntiqueWhite - + Aquamarine - + Azure - + Beige - + Bisque - + Black - + BlanchedAlmond - + Blue - + BlueViolet - + Brown - + BurlyWood - + CadetBlue - + Chartreuse - + Chocolate - + Coral - + CornflowerBlue - + Cornsilk - + Crimson - + Cyan - + DarkBlue - + DarkCyan - + DarkGoldenRod - + DarkGrey - + DarkGreen - + DarkKhaki - + DarkMagenta - + DarkOliveGreen - + DarkOrange - + DarkOrchid - + DarkRed - + DarkSalmon - + DarkSeaGreen - + DarkSlateBlue - + DarkSlateGrey - + DarkTurquoise - + DarkViolet - + DeepPink - + DeepSkyBlue - + DimGray - + DodgerBlue - + FireBrick - + FloralWhite - + ForestGreen - + Gainsboro - + GhostWhite - + Gold - + GoldenRod - + Gray - + Green - + GreenYellow - + HoneyDew - + HotPink - + IndianRed - + Indigo - + Ivory - + Khaki - + Lavender - + LavenderBlush - + LawnGreen - + LemonChiffon - + LightBlue - + LightCoral - + LightCyan - + LightGoldenRodYellow - + LightGray - + LightGreen - + LightPink - + LightSalmon - + LightSeaGreen - + LightSkyBlue - + LightSlateGrey - + LightSteelBlue - + LightYellow - + Lime - + LimeGreen - + Linen - + Magenta - + Maroon - + MediumAquaMarine - + MediumBlue - + MediumOrchid - + MediumPurple - + MediumSeaGreen - + MediumSlateBlue - + MediumSpringGreen - + MediumTurquoise - + MediumVioletRed - + MidnightBlue - + MintCream - + MistyRose - + Moccasin - + NavajoWhite - + Navy - + OldLace - + Olive - + OliveDrab - + Orange - + OrangeRed - + Orchid - + PaleGoldenRod - + PaleGreen - + PaleTurquoise - + PaleVioletRed - + PapayaWhip - + PeachPuff - + Peru - + Pink - + Plum - + PowderBlue - + Purple - + RebeccaPurple - + Red - + RosyBrown - + RoyalBlue - + SaddleBrown - + Salmon - + SandyBrown - + SeaGreen - + SeaShell - + Sienna - + Silver - + SkyBlue - + SlateBlue - + SlateGray - + Snow - + SpringGreen - + SteelBlue - + Tan - + Teal - + Thistle - + Tomato - + Turquoise - + Violet - + Wheat - + White - + WhiteSmoke - + Yellow - + YellowGreen - + BrightBlue - + BrightCyan - + BrightRed - - + + BrightGreen - - + + BrightMagenta - - + + BrightYellow - - + + DarkGray - + + + Co_lors + \ No newline at end of file diff --git a/Terminal.Gui/Resources/Strings.zh-Hans.resx b/Terminal.Gui/Resources/Strings.zh-Hans.resx index 009fdd479f..8ea63e91d6 100644 --- a/Terminal.Gui/Resources/Strings.zh-Hans.resx +++ b/Terminal.Gui/Resources/Strings.zh-Hans.resx @@ -153,9 +153,6 @@ 打开 - - 下一步 (_N)... - 选择文件夹 (_S) @@ -168,6 +165,9 @@ 结束 (_N) + + 下一步 (_N)... + 已存在相同名称的目录 @@ -240,9 +240,6 @@ 已有 - - 确定 (_O) - 打开 (_O) @@ -252,6 +249,9 @@ 另存为 (_S) + + 确定 (_O) + 取消 (_C) @@ -276,4 +276,7 @@ 日期选择器 + + 旗帜 (_L) + \ No newline at end of file diff --git a/Terminal.Gui/Text/Autocomplete/AutocompleteContext.cs b/Terminal.Gui/Text/Autocomplete/AutocompleteContext.cs index 2686c10242..ed55b0d3f4 100644 --- a/Terminal.Gui/Text/Autocomplete/AutocompleteContext.cs +++ b/Terminal.Gui/Text/Autocomplete/AutocompleteContext.cs @@ -7,7 +7,7 @@ namespace Terminal.Gui; public class AutocompleteContext { /// Creates a new instance of the class - public AutocompleteContext (List currentLine, int cursorPosition, bool canceled = false) + public AutocompleteContext (List currentLine, int cursorPosition, bool canceled = false) { CurrentLine = currentLine; CursorPosition = cursorPosition; @@ -18,7 +18,7 @@ public AutocompleteContext (List currentLine, int cursorPosition, bool public bool Canceled { get; set; } /// The text on the current line. - public List CurrentLine { get; set; } + public List CurrentLine { get; set; } /// The position of the input cursor within the . public int CursorPosition { get; set; } diff --git a/Terminal.Gui/Views/AutocompleteFilepathContext.cs b/Terminal.Gui/Views/AutocompleteFilepathContext.cs index f577e554fe..b21724816e 100644 --- a/Terminal.Gui/Views/AutocompleteFilepathContext.cs +++ b/Terminal.Gui/Views/AutocompleteFilepathContext.cs @@ -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; } @@ -30,7 +30,7 @@ public IEnumerable GenerateSuggestions (AutocompleteContext context) return Enumerable.Empty (); } - 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)) diff --git a/Terminal.Gui/Views/ColorPicker16.cs b/Terminal.Gui/Views/ColorPicker.16.cs similarity index 100% rename from Terminal.Gui/Views/ColorPicker16.cs rename to Terminal.Gui/Views/ColorPicker.16.cs diff --git a/Terminal.Gui/Views/ColorPicker.Prompt.cs b/Terminal.Gui/Views/ColorPicker.Prompt.cs new file mode 100644 index 0000000000..3f2372db9a --- /dev/null +++ b/Terminal.Gui/Views/ColorPicker.Prompt.cs @@ -0,0 +1,123 @@ +namespace Terminal.Gui; + +public partial class ColorPicker +{ + /// + /// Open a with two or , based on the + /// is false or true, respectively, for + /// and colors. + /// + /// The title to show in the dialog. + /// The current attribute used. + /// The new attribute. + /// if a new color was accepted, otherwise . + public static bool Prompt (string title, Attribute? currentAttribute, out Attribute newAttribute) + { + var accept = false; + + var d = new Dialog + { + Title = title, + Width = Application.Force16Colors ? 37 : Dim.Auto (DimAutoStyle.Auto, Dim.Percent (80), Dim.Percent (90)), + Height = 20 + }; + + var btnOk = new Button + { + X = Pos.Center () - 5, + Y = Application.Force16Colors ? 6 : 4, + Text = "Ok", + Width = Dim.Auto (), + IsDefault = true + }; + + btnOk.Accepting += (s, e) => + { + accept = true; + e.Cancel = true; + Application.RequestStop (); + }; + + var btnCancel = new Button + { + X = Pos.Center () + 5, + Y = 4, + Text = "Cancel", + Width = Dim.Auto () + }; + + btnCancel.Accepting += (s, e) => + { + e.Cancel = true; + Application.RequestStop (); + }; + + d.Add (btnOk); + d.Add (btnCancel); + + d.AddButton (btnOk); + d.AddButton (btnCancel); + + View cpForeground; + + if (Application.Force16Colors) + { + cpForeground = new ColorPicker16 + { + SelectedColor = currentAttribute!.Value.Foreground.GetClosestNamedColor16 (), + Width = Dim.Fill (), + BorderStyle = LineStyle.Single, + Title = "Foreground" + }; + } + else + { + cpForeground = new ColorPicker + { + SelectedColor = currentAttribute!.Value.Foreground, + Width = Dim.Fill (), + Style = new () { ShowColorName = true, ShowTextFields = true }, + BorderStyle = LineStyle.Single, + Title = "Foreground" + }; + ((ColorPicker)cpForeground).ApplyStyleChanges (); + } + + View cpBackground; + + if (Application.Force16Colors) + { + cpBackground = new ColorPicker16 + { + SelectedColor = currentAttribute!.Value.Background.GetClosestNamedColor16 (), + Y = Pos.Bottom (cpForeground) + 1, + Width = Dim.Fill (), + BorderStyle = LineStyle.Single, + Title = "Background" + }; + } + else + { + cpBackground = new ColorPicker + { + SelectedColor = currentAttribute!.Value.Background, + Width = Dim.Fill (), + Y = Pos.Bottom (cpForeground) + 1, + Style = new () { ShowColorName = true, ShowTextFields = true }, + BorderStyle = LineStyle.Single, + Title = "Background" + }; + ((ColorPicker)cpBackground).ApplyStyleChanges (); + } + + d.Add (cpForeground, cpBackground); + + Application.Run (d); + d.Dispose (); + Color newForeColor = Application.Force16Colors ? ((ColorPicker16)cpForeground).SelectedColor : ((ColorPicker)cpForeground).SelectedColor; + Color newBackColor = Application.Force16Colors ? ((ColorPicker16)cpBackground).SelectedColor : ((ColorPicker)cpBackground).SelectedColor; + newAttribute = new (newForeColor, newBackColor); + + return accept; + } +} diff --git a/Terminal.Gui/Views/ColorPickerStyle.cs b/Terminal.Gui/Views/ColorPicker.Style.cs similarity index 100% rename from Terminal.Gui/Views/ColorPickerStyle.cs rename to Terminal.Gui/Views/ColorPicker.Style.cs diff --git a/Terminal.Gui/Views/ColorPicker.cs b/Terminal.Gui/Views/ColorPicker.cs index 83a0365d78..a4209ffe42 100644 --- a/Terminal.Gui/Views/ColorPicker.cs +++ b/Terminal.Gui/Views/ColorPicker.cs @@ -7,7 +7,7 @@ namespace Terminal.Gui; /// /// True color picker using HSL /// -public class ColorPicker : View +public partial class ColorPicker : View { /// /// Creates a new instance of . Use diff --git a/Terminal.Gui/Views/HistoryTextItemEventArgs.cs b/Terminal.Gui/Views/HistoryTextItemEventArgs.cs index ca2ad3ad7e..c75f4a5e8b 100644 --- a/Terminal.Gui/Views/HistoryTextItemEventArgs.cs +++ b/Terminal.Gui/Views/HistoryTextItemEventArgs.cs @@ -9,11 +9,11 @@ public class HistoryTextItemEventArgs : EventArgs public Point CursorPosition; public Point FinalCursorPosition; public bool IsUndoing; - public List> Lines; + public List> Lines; public LineStatus LineStatus; public HistoryTextItemEventArgs RemovedOnAdded; - public HistoryTextItemEventArgs (List> lines, Point curPos, LineStatus linesStatus) + public HistoryTextItemEventArgs (List> lines, Point curPos, LineStatus linesStatus) { Lines = lines; CursorPosition = curPos; @@ -22,7 +22,7 @@ public HistoryTextItemEventArgs (List> lines, Point curPos, LineS public HistoryTextItemEventArgs (HistoryTextItemEventArgs historyTextItem) { - Lines = new List> (historyTextItem.Lines); + Lines = new List> (historyTextItem.Lines); CursorPosition = new Point (historyTextItem.CursorPosition.X, historyTextItem.CursorPosition.Y); LineStatus = historyTextItem.LineStatus; } diff --git a/Terminal.Gui/Views/TextField.cs b/Terminal.Gui/Views/TextField.cs index e5bd503630..7d9785c22c 100644 --- a/Terminal.Gui/Views/TextField.cs +++ b/Terminal.Gui/Views/TextField.cs @@ -459,7 +459,7 @@ public virtual int CursorPosition /// Indicates whatever the text was changed or not. if the text was changed /// otherwise. /// - public bool IsDirty => _historyText.IsDirty (Text); + 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; } @@ -541,12 +541,12 @@ public string SelectedText if (!Secret && !_historyText.IsFromHistory) { _historyText.Add ( - new() { TextModel.ToRuneCellList (oldText) }, + new () { Cell.ToCellList (oldText) }, new (_cursorPosition, 0) ); _historyText.Add ( - new() { TextModel.ToRuneCells (_text) }, + new () { Cell.ToCells (_text) }, new (_cursorPosition, 0), HistoryText.LineStatus.Replaced ); @@ -589,7 +589,7 @@ public void ClearAllSelection () } /// Allows clearing the items updating the original text. - public void ClearHistoryChanges () { _historyText.Clear (Text); } + public void ClearHistoryChanges () { _historyText.Clear ([Cell.StringToCells (Text)]); } /// Copy the selected text to the clipboard. public virtual void Copy () @@ -643,7 +643,7 @@ public virtual void DeleteCharLeft (bool usePreTextChangedCursorPos) } _historyText.Add ( - new() { TextModel.ToRuneCells (_text) }, + new () { Cell.ToCells (_text) }, new (_cursorPosition, 0) ); @@ -697,7 +697,7 @@ public virtual void DeleteCharRight () } _historyText.Add ( - new() { TextModel.ToRuneCells (_text) }, + new () { Cell.ToCells (_text) }, new (_cursorPosition, 0) ); @@ -944,7 +944,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 (); @@ -1130,10 +1130,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); @@ -1216,16 +1216,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 ); @@ -1330,7 +1330,7 @@ private List DeleteSelectedText () private void GenerateSuggestions () { - List currentLine = TextModel.ToRuneCellList (Text); + List currentLine = Cell.ToCellList (Text); int cursorPosition = Math.Min (CursorPosition, currentLine.Count); Autocomplete.Context = new ( @@ -1378,7 +1378,7 @@ 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 (); } @@ -1386,7 +1386,7 @@ private void HistoryText_ChangeText (object sender, HistoryText.HistoryTextItemE private void InsertText (Key a, bool usePreTextChangedCursorPos) { _historyText.Add ( - new() { TextModel.ToRuneCells (_text) }, + new () { Cell.ToCells (_text) }, new (_cursorPosition, 0) ); diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs index 680a29c205..c34e9220bd 100644 --- a/Terminal.Gui/Views/TextView.cs +++ b/Terminal.Gui/Views/TextView.cs @@ -9,44 +9,9 @@ namespace Terminal.Gui; -/// -/// Represents a single row/column within the . Includes the glyph and the -/// foreground/background colors. -/// -[DebuggerDisplay ("{ColorSchemeDebuggerDisplay}")] -public class RuneCell : IEquatable -{ - /// The color sets to draw the glyph with. - [JsonConverter (typeof (ColorSchemeJsonConverter))] - public ColorScheme? ColorScheme { get; set; } - - /// The glyph to draw. - [JsonConverter (typeof (RuneJsonConverter))] - public Rune Rune { get; set; } - - private string ColorSchemeDebuggerDisplay => ToString (); - - /// Indicates whether the current object is equal to another object of the same type. - /// An object to compare with this object. - /// - /// if the current object is equal to the parameter; otherwise, - /// . - /// - public bool Equals (RuneCell? other) { return other is { } && Rune.Equals (other.Rune) && ColorScheme == other.ColorScheme; } - - /// Returns a string that represents the current object. - /// A string that represents the current object. - public override string ToString () - { - string colorSchemeStr = ColorScheme?.ToString () ?? "null"; - - return $"U+{Rune.Value:X4} '{Rune.ToString ()}'; {colorSchemeStr}"; - } -} - internal class TextModel { - private List> _lines = new (); + private List> _lines = new (); private (Point startPointToFind, Point currentPointToFind, bool found) _toFind; /// The number of text lines in the model @@ -56,8 +21,8 @@ internal class TextModel /// Adds a line to the model at the specified position. /// Line number where the line will be inserted. - /// The line of text and color, as a List of RuneCell. - public void AddLine (int pos, List cells) { _lines.Insert (pos, cells); } + /// The line of text and color, as a List of Cell. + public void AddLine (int pos, List cells) { _lines.Insert (pos, cells); } public bool CloseFile () { @@ -72,12 +37,12 @@ public bool CloseFile () return true; } - public List> GetAllLines () { return _lines; } + public List> GetAllLines () { return _lines; } /// Returns the specified line as a List of Rune /// The line. /// Line number to retrieve. - public List GetLine (int line) + public List GetLine (int line) { if (_lines.Count > 0) { @@ -105,7 +70,7 @@ public int GetMaxVisibleLine (int first, int last, int tabWidth) for (int i = first; i < last; i++) { - List line = GetLine (i); + List line = GetLine (i); int tabSum = line.Sum (c => c.Rune.Value == '\t' ? Math.Max (tabWidth - 1, 0) : 0); int l = line.Count + tabSum; @@ -132,17 +97,17 @@ public bool LoadFile (string file) } } - public void LoadListRuneCells (List> cellsList, ColorScheme? colorScheme) + public void LoadListCells (List> cellsList, Attribute? attribute) { _lines = cellsList; - SetColorSchemes (colorScheme); + SetAttributes (attribute); OnLinesLoaded (); } - public void LoadRuneCells (List cells, ColorScheme? colorScheme) + public void LoadCells (List cells, Attribute? attribute) { - _lines = ToRuneCells (cells); - SetColorSchemes (colorScheme); + _lines = Cell.ToCells ((List)cells); + SetAttributes (attribute); OnLinesLoaded (); } @@ -191,7 +156,7 @@ public void LoadStream (Stream input) public void LoadString (string content) { - _lines = StringToLinesOfRuneCells (content); + _lines = Cell.StringToLinesOfCells (content); OnLinesLoaded (); } @@ -211,11 +176,11 @@ public void RemoveLine (int pos) } } - public void ReplaceLine (int pos, List runes) + public void ReplaceLine (int pos, List runes) { if (_lines.Count > 0 && pos < _lines.Count) { - _lines [pos] = new (runes); + _lines [pos] = [..runes]; } else if (_lines.Count == 0 || (_lines.Count > 0 && pos >= _lines.Count)) { @@ -223,31 +188,7 @@ public void ReplaceLine (int pos, List runes) } } - // Splits a string into a List that contains a List for each line - public static List> StringToLinesOfRuneCells (string content, ColorScheme? colorScheme = null) - { - List cells = content.EnumerateRunes () - .Select (x => new RuneCell { Rune = x, ColorScheme = colorScheme }) - .ToList (); - - return SplitNewLines (cells); - } - - /// Converts the string into a . - /// The string to convert. - /// The to use. - /// - public static List ToRuneCellList (string str, ColorScheme? colorScheme = null) - { - List cells = new (); - - foreach (Rune rune in str.EnumerateRunes ()) - { - cells.Add (new () { Rune = rune, ColorScheme = colorScheme }); - } - return cells; - } public override string ToString () { @@ -255,7 +196,7 @@ public override string ToString () for (var i = 0; i < _lines.Count; i++) { - sb.Append (ToString (_lines [i])); + sb.Append (Cell.ToString (_lines [i])); if (i + 1 < _lines.Count) { @@ -266,21 +207,6 @@ public override string ToString () return sb.ToString (); } - /// Converts a generic collection into a string. - /// The enumerable cell to convert. - /// - public static string ToString (IEnumerable cells) - { - var str = string.Empty; - - foreach (RuneCell cell in cells) - { - str += cell.Rune.ToString (); - } - - return str; - } - public (int col, int row)? WordBackward (int fromCol, int fromRow) { if (fromRow == 0 && fromCol == 0) @@ -293,12 +219,12 @@ public static string ToString (IEnumerable cells) try { - RuneCell? cell = RuneAt (col, row); + Cell? cell = RuneAt (col, row); Rune rune; if (cell is { }) { - rune = cell.Rune; + rune = cell.Value.Rune; } else { @@ -310,7 +236,7 @@ public static string ToString (IEnumerable cells) if (col == 0 && row > 0) { row--; - List line = GetLine (row); + List line = GetLine (row); return (line.Count, row); } @@ -384,7 +310,7 @@ void ProcMovePrev (ref int nCol, ref int nRow, Rune nRune) return; } - List line = GetLine (nRow); + List line = GetLine (nRow); if (nCol == 0 && nRow == fromRow @@ -443,7 +369,7 @@ void ProcMovePrev (ref int nCol, ref int nRow, Rune nRune) try { - Rune rune = RuneAt (col, row).Rune; + Rune rune = RuneAt (col, row)!.Value.Rune; RuneType runeType = GetRuneType (rune); int lastValidCol = IsSameRuneType (rune, runeType) && (Rune.IsLetterOrDigit (rune) || Rune.IsPunctuation (rune) || Rune.IsSymbol (rune)) @@ -510,7 +436,7 @@ void ProcMoveNext (ref int nCol, ref int nRow, Rune nRune) return; } - List line = GetLine (nRow); + List line = GetLine (nRow); if (nCol == line.Count && nRow == fromRow @@ -550,11 +476,11 @@ void ProcMoveNext (ref int nCol, ref int nRow, Rune nRune) } } - internal static int CalculateLeftColumn (List t, int start, int end, int width, int tabWidth = 0) + internal static int CalculateLeftColumn (List t, int start, int end, int width, int tabWidth = 0) { List runes = new (); - foreach (RuneCell cell in t) + foreach (Cell cell in t) { runes.Add (cell.Rune); } @@ -606,7 +532,7 @@ internal static int CalculateLeftColumn (List t, int start, int end, int w } internal static (int size, int length) DisplaySize ( - List t, + List t, int start = -1, int end = -1, bool checkNextRune = true, @@ -615,7 +541,7 @@ internal static (int size, int length) DisplaySize ( { List runes = new (); - foreach (RuneCell cell in t) + foreach (Cell cell in t) { runes.Add (cell.Rune); } @@ -776,11 +702,11 @@ internal Size GetDisplaySize () return foundPos; } - internal static int GetColFromX (List t, int start, int x, int tabWidth = 0) + internal static int GetColFromX (List t, int start, int x, int tabWidth = 0) { List runes = new (); - foreach (RuneCell cell in t) + foreach (Cell cell in t) { runes.Add (cell.Rune); } @@ -829,7 +755,7 @@ internal static int GetColFromX (List t, int start, int x, int tabWidth = for (var i = 0; i < _lines.Count; i++) { - List x = _lines [i]; + List x = _lines [i]; string txt = GetText (x); string matchText = !matchCase ? text.ToUpper () : text; int col = txt.IndexOf (matchText); @@ -855,7 +781,7 @@ internal static int GetColFromX (List t, int start, int x, int tabWidth = found = true; } - _lines [i] = ToRuneCellList (ReplaceText (x, textToReplace!, matchText, col)); + _lines [i] = Cell.ToCellList (ReplaceText (x, textToReplace!, matchText, col)); x = _lines [i]; txt = GetText (x); pos = new (col, i); @@ -871,9 +797,9 @@ internal static int GetColFromX (List t, int start, int x, int tabWidth = } } - string GetText (List x) + string GetText (List x) { - string txt = ToString (x); + string txt = Cell.ToString (x); if (!matchCase) { @@ -906,36 +832,10 @@ internal static bool SetCol (ref int col, int width, int cols) return false; } - // Turns the string into cells, this does not split the - // contents on a newline if it is present. - internal static List StringToRuneCells (string str, ColorScheme? colorScheme = null) - { - List cells = new (); - - foreach (Rune rune in str.ToRunes ()) - { - cells.Add (new () { Rune = rune, ColorScheme = colorScheme }); - } - - return cells; - } - - internal static List ToRuneCells (IEnumerable runes, ColorScheme? colorScheme = null) - { - List cells = new (); - - foreach (Rune rune in runes) - { - cells.Add (new () { Rune = rune, ColorScheme = colorScheme }); - } - - return cells; - } - private void Append (List line) { var str = StringExtensions.ToString (line.ToArray ()); - _lines.Add (StringToRuneCells (str)); + _lines.Add (Cell.StringToCells (str)); } private bool ApplyToFind ((Point current, bool found) foundPos) @@ -971,8 +871,8 @@ Point start { for (int i = start.Y; i < linesCount; i++) { - List x = _lines [i]; - string txt = ToString (x); + List x = _lines [i]; + string txt = Cell.ToString (x); if (!matchCase) { @@ -1011,8 +911,8 @@ Point start { for (int i = linesCount; i >= 0; i--) { - List x = _lines [i]; - string txt = ToString (x); + List x = _lines [i]; + string txt = Cell.ToString (x); if (!matchCase) { @@ -1094,7 +994,7 @@ private bool MatchWholeWord (string source, string matchText, int index = 0) private bool MoveNext (ref int col, ref int row, out Rune rune) { - List line = GetLine (row); + List line = GetLine (row); if (col + 1 < line.Count) { @@ -1135,7 +1035,7 @@ private bool MoveNext (ref int col, ref int row, out Rune rune) private bool MovePrev (ref int col, ref int row, out Rune rune) { - List line = GetLine (row); + List line = GetLine (row); if (col > 0) { @@ -1173,9 +1073,9 @@ private bool MovePrev (ref int col, ref int row, out Rune rune) private void OnLinesLoaded () { LinesLoaded?.Invoke (this, EventArgs.Empty); } - private string ReplaceText (List source, string textToReplace, string matchText, int col) + private string ReplaceText (List source, string textToReplace, string matchText, int col) { - string origTxt = ToString (source); + string origTxt = Cell.ToString (source); (_, int len) = DisplaySize (source, 0, col, false); (_, int len2) = DisplaySize (source, col, col + matchText.Length, false); (_, int len3) = DisplaySize (source, col + matchText.Length, origTxt.GetRuneCount (), false); @@ -1183,72 +1083,31 @@ private string ReplaceText (List source, string textToReplace, string return origTxt [..len] + textToReplace + origTxt.Substring (len + len2, len3); } - private RuneCell RuneAt (int col, int row) + private Cell? RuneAt (int col, int row) { - List line = GetLine (row); + List line = GetLine (row); if (line.Count > 0) { return line [col > line.Count - 1 ? line.Count - 1 : col]; } - return default (RuneCell)!; - } - - private void SetColorSchemes (ColorScheme? colorScheme) - { - foreach (List line in _lines) - { - foreach (RuneCell cell in line) - { - cell.ColorScheme ??= colorScheme; - } - } + return null; } - private static List> SplitNewLines (List cells) + private void SetAttributes (Attribute? attribute) { - List> lines = new (); - int start = 0, i = 0; - var hasCR = false; - - // ASCII code 13 = Carriage Return. - // ASCII code 10 = Line Feed. - for (; i < cells.Count; i++) + foreach (List line in _lines) { - if (cells [i].Rune.Value == 13) - { - hasCR = true; - - continue; - } - - if (cells [i].Rune.Value == 10) + for (var i = 0; i < line.Count; i++) { - if (i - start > 0) - { - lines.Add (cells.GetRange (start, hasCR ? i - 1 - start : i - start)); - } - else - { - lines.Add (StringToRuneCells (string.Empty)); - } - - start = i + 1; - hasCR = false; + Cell cell = line [i]; + cell.Attribute ??= attribute; + line [i] = cell; } } - - if (i - start >= 0) - { - lines.Add (cells.GetRange (start, i - start)); - } - - return lines; } - private static List> ToRuneCells (List cells) { return SplitNewLines (cells); } - private enum RuneType { IsSymbol, @@ -1266,16 +1125,17 @@ public enum LineStatus Original, Replaced, Removed, - Added + Added, + Attribute } - private readonly List _historyTextItems = new (); + private readonly List _historyTextItems = []; private int _idxHistoryText = -1; - private string? _originalText; + private readonly List> _originalCellsList = []; public bool HasHistoryChanges => _idxHistoryText > -1; public bool IsFromHistory { get; private set; } - public void Add (List> lines, Point curPos, LineStatus lineStatus = LineStatus.Original) + public void Add (List> lines, Point curPos, LineStatus lineStatus = LineStatus.Original) { if (lineStatus == LineStatus.Original && _historyTextItems.Count > 0 && _historyTextItems.Last ().LineStatus == LineStatus.Original) { @@ -1306,15 +1166,51 @@ public void Add (List> lines, Point curPos, LineStatus lineStatus public event EventHandler? ChangeText; - public void Clear (string text) + public void Clear (List> cellsList) { _historyTextItems.Clear (); _idxHistoryText = -1; - _originalText = text; + _originalCellsList.Clear (); + // Save a copy of the original, not the reference + foreach (List cells in cellsList) + { + _originalCellsList.Add ([..cells]); + } + OnChangeText (null); } - public bool IsDirty (string text) { return _originalText != text; } + public bool IsDirty (List> cellsList) + { + if (cellsList.Count != _originalCellsList.Count) + { + return true; + } + + for (var r = 0; r < cellsList.Count; r++) + { + List cells = cellsList [r]; + List originalCells = _originalCellsList [r]; + + if (cells.Count != originalCells.Count) + { + return true; + } + + for (var c = 0; c < cells.Count; c++) + { + Cell cell = cells [c]; + Cell originalCell = originalCells [c]; + + if (!cell.Equals (originalCell)) + { + return true; + } + } + } + + return false; + } public void Redo () { @@ -1332,7 +1228,7 @@ public void Redo () } } - public void ReplaceLast (List> lines, Point curPos, LineStatus lineStatus) + public void ReplaceLast (List> lines, Point curPos, LineStatus lineStatus) { HistoryTextItemEventArgs? found = _historyTextItems.FindLast (x => x.LineStatus == lineStatus); @@ -1368,7 +1264,8 @@ private void ProcessChanges (ref HistoryTextItemEventArgs historyTextItem) if (_idxHistoryText - 1 > -1 && (_historyTextItems [_idxHistoryText - 1].LineStatus == LineStatus.Added || _historyTextItems [_idxHistoryText - 1].LineStatus == LineStatus.Removed - || (historyTextItem.LineStatus == LineStatus.Replaced && _historyTextItems [_idxHistoryText - 1].LineStatus == LineStatus.Original))) + || (historyTextItem.LineStatus == LineStatus.Replaced && _historyTextItems [_idxHistoryText - 1].LineStatus == LineStatus.Original) + || (historyTextItem.LineStatus == LineStatus.Attribute && _historyTextItems [_idxHistoryText - 1].LineStatus == LineStatus.Original))) { _idxHistoryText--; @@ -1487,9 +1384,9 @@ public void AddLine (int row, int col) { int modelRow = GetModelLineFromWrappedLines (row); int modelCol = GetModelColFromWrappedLines (row, col); - List line = GetCurrentLine (modelRow); + List line = GetCurrentLine (modelRow); int restCount = line.Count - modelCol; - List rest = line.GetRange (modelCol, restCount); + List rest = line.GetRange (modelCol, restCount); line.RemoveRange (modelCol, restCount); Model.AddLine (modelRow + 1, rest); _isWrapModelRefreshing = true; @@ -1573,9 +1470,9 @@ public int GetWrappedLineColWidth (int line, int col, WordWrapManager wrapManage return modelCol - colWidthOffset; } - public bool Insert (int row, int col, RuneCell cell) + public bool Insert (int row, int col, Cell cell) { - List line = GetCurrentLine (GetModelLineFromWrappedLines (row)); + List line = GetCurrentLine (GetModelLineFromWrappedLines (row)); line.Insert (GetModelColFromWrappedLines (row, col), cell); if (line.Count > _frameWidth) @@ -1589,7 +1486,7 @@ public bool Insert (int row, int col, RuneCell cell) public bool RemoveAt (int row, int col) { int modelRow = GetModelLineFromWrappedLines (row); - List line = GetCurrentLine (modelRow); + List line = GetCurrentLine (modelRow); int modelCol = GetModelColFromWrappedLines (row, col); if (modelCol > line.Count) @@ -1617,7 +1514,7 @@ public bool RemoveLine (int row, int col, out bool lineRemoved, bool forward = t { lineRemoved = false; int modelRow = GetModelLineFromWrappedLines (row); - List line = GetCurrentLine (modelRow); + List line = GetCurrentLine (modelRow); int modelCol = GetModelColFromWrappedLines (row, col); if (modelCol == 0 && line.Count == 0) @@ -1653,7 +1550,7 @@ public bool RemoveLine (int row, int col, out bool lineRemoved, bool forward = t return false; } - List nextLine = Model.GetLine (modelRow + 1); + List nextLine = Model.GetLine (modelRow + 1); line.AddRange (nextLine); Model.RemoveLine (modelRow + 1); @@ -1669,7 +1566,7 @@ public bool RemoveLine (int row, int col, out bool lineRemoved, bool forward = t return false; } - List prevLine = Model.GetLine (modelRow - 1); + List prevLine = Model.GetLine (modelRow - 1); prevLine.AddRange (line); Model.RemoveLine (modelRow); @@ -1685,7 +1582,7 @@ public bool RemoveLine (int row, int col, out bool lineRemoved, bool forward = t public bool RemoveRange (int row, int index, int count) { int modelRow = GetModelLineFromWrappedLines (row); - List line = GetCurrentLine (modelRow); + List line = GetCurrentLine (modelRow); int modelCol = GetModelColFromWrappedLines (row, index); try @@ -1700,13 +1597,13 @@ public bool RemoveRange (int row, int index, int count) return true; } - public List> ToListRune (List textList) + public List> ToListRune (List textList) { - List> runesList = new (); + List> runesList = new (); foreach (string text in textList) { - runesList.Add (TextModel.ToRuneCellList (text)); + runesList.Add (Cell.ToCellList (text)); } return runesList; @@ -1778,11 +1675,11 @@ public TextModel WrapModel ( for (var i = 0; i < Model.Count; i++) { - List line = Model.GetLine (i); + List line = Model.GetLine (i); - List> wrappedLines = ToListRune ( + List> wrappedLines = ToListRune ( TextFormatter.Format ( - TextModel.ToString (line), + Cell.ToString (line), width, Alignment.Start, true, @@ -1794,7 +1691,7 @@ public TextModel WrapModel ( for (var j = 0; j < wrappedLines.Count; j++) { - List wrapLine = wrappedLines [j]; + List wrapLine = wrappedLines [j]; if (!isRowAndColSet && modelRow == i) { @@ -1852,7 +1749,9 @@ public TextModel WrapModel ( for (int k = j; k < wrapLine.Count; k++) { - wrapLine [k].ColorScheme = line [k].ColorScheme; + Cell cell = wrapLine [k]; + cell.Attribute = line [k].Attribute; + wrapLine [k] = cell; } wrappedModel.AddLine (lines, wrapLine); @@ -1872,7 +1771,7 @@ public TextModel WrapModel ( return wrappedModel; } - private List GetCurrentLine (int row) { return Model.GetLine (row); } + private List GetCurrentLine (int row) { return Model.GetLine (row); } private class WrappedLine { @@ -1997,7 +1896,7 @@ public TextView () CursorVisibility = CursorVisibility.Default; Used = true; - // By default, disable hotkeys (in case someome sets Title) + // By default, disable hotkeys (in case someone sets Title) HotKeySpecifier = new ('\xffff'); _model.LinesLoaded += Model_LinesLoaded!; @@ -2406,6 +2305,16 @@ public TextView () } ); + AddCommand ( + Command.Open, + () => + { + PromptForColors (); + + return true; + }); + + // Default keybindings for this view KeyBindings.Remove (Key.Space); KeyBindings.Remove (Key.Enter); @@ -2494,6 +2403,8 @@ public TextView () 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); @@ -2609,7 +2520,7 @@ public Point CursorPosition get => new (CurrentColumn, CurrentRow); set { - List line = _model.GetLine (Math.Max (Math.Min (value.Y, _model.Count - 1), 0)); + 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; @@ -2629,11 +2540,11 @@ public Point CursorPosition public bool HasHistoryChanges => _historyText.HasHistoryChanges; /// - /// If and the current is null will inherit from the + /// 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 . + /// this property is automatically sets to . /// - public bool InheritsPreviousColorScheme { get; set; } + public bool InheritsPreviousAttribute { get; set; } /// /// Indicates whatever the text was changed or not. if the text was changed @@ -2641,8 +2552,8 @@ public Point CursorPosition /// public bool IsDirty { - get => _historyText.IsDirty (Text); - set => _historyText.Clear (Text); + get => _historyText.IsDirty (_model.GetAllLines ()); + set => _historyText.Clear (_model.GetAllLines ()); } /// Gets or sets the left column. @@ -2734,6 +2645,22 @@ public bool ReadOnly /// 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 { @@ -2757,7 +2684,7 @@ public int SelectionStartColumn get => _selectionStartColumn; set { - List line = _model.GetLine (_selectionStartRow); + List line = _model.GetLine (_selectionStartRow); _selectionStartColumn = value < 0 ? 0 : value > line.Count ? line.Count : value; @@ -2828,7 +2755,7 @@ public override string Text OnTextChanged (); SetNeedsDisplay (); - _historyText.Clear (Text); + _historyText.Clear (_model.GetAllLines ()); } } @@ -2880,7 +2807,7 @@ public bool WordWrap /// Allows clearing the items updating the original text. - public void ClearHistoryChanges () { _historyText?.Clear (Text); } + public void ClearHistoryChanges () { _historyText?.Clear (_model.GetAllLines ()); } /// Closes the contents of the stream into the . /// true, if stream was closed, false otherwise. @@ -2902,6 +2829,102 @@ public bool CloseFile () /// public event EventHandler? ContentsChanged; + internal void ApplyCellsAttribute (Attribute attribute) + { + if (!ReadOnly && SelectedLength > 0) + { + int startRow = Math.Min (SelectionStartRow, CurrentRow); + int endRow = Math.Max (CurrentRow, SelectionStartRow); + int startCol = SelectionStartRow <= CurrentRow ? SelectionStartColumn : CurrentColumn; + int endCol = CurrentRow >= SelectionStartRow ? CurrentColumn : SelectionStartColumn; + List> selectedCellsOriginal = []; + List> selectedCellsChanged = []; + + for (int r = startRow; r <= endRow; r++) + { + List line = GetLine (r); + + selectedCellsOriginal.Add ([.. line]); + + for (int c = r == startRow ? startCol : 0; + c < (r == endRow ? endCol : line.Count); + c++) + { + Cell cell = line [c]; // Copy value to a new variable + cell.Attribute = attribute; // Modify the copy + line [c] = cell; // Assign the modified copy back + } + + selectedCellsChanged.Add ([..GetLine (r)]); + } + + GetSelectedRegion (); + IsSelecting = false; + + _historyText.Add ( + [.. selectedCellsOriginal], + new (startCol, startRow) + ); + + _historyText.Add ( + [.. selectedCellsChanged], + new (startCol, startRow), + HistoryText.LineStatus.Attribute + ); + } + } + + private Attribute? GetSelectedCellAttribute () + { + List line; + + if (SelectedLength > 0) + { + line = GetLine (SelectionStartRow); + + if (line [Math.Min (SelectionStartColumn, line.Count - 1)].Attribute is { } attributeSel) + { + return new (attributeSel); + } + + return new (ColorScheme!.Focus); + } + + line = GetCurrentLine (); + + if (line [Math.Min (CurrentColumn, line.Count - 1)].Attribute is { } attribute) + { + return new (attribute); + } + + return new (ColorScheme!.Focus); + } + + /// + /// 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 + ); + + ApplyCellsAttribute (attribute); + } + + private string? _copiedText; + private List> _copiedCellsList = []; + /// Copy the selected text to the clipboard contents. public void Copy () { @@ -2909,13 +2932,16 @@ public void Copy () if (IsSelecting) { - SetClipboard (GetRegion ()); + _copiedText = GetRegion (out _copiedCellsList); + SetClipboard (_copiedText); _copyWithoutSelection = false; } else { - List currentLine = GetCurrentLine (); - SetClipboard (TextModel.ToString (currentLine)); + List currentLine = GetCurrentLine (); + _copiedCellsList.Add (currentLine); + _copiedText = Cell.ToString (currentLine); + SetClipboard (_copiedText); _copyWithoutSelection = true; } @@ -2927,14 +2953,15 @@ public void Copy () public void Cut () { SetWrapModel (); - SetClipboard (GetRegion ()); + _copiedText = GetRegion (out _copiedCellsList); + SetClipboard (_copiedText); if (!_isReadOnly) { ClearRegion (); _historyText.Add ( - new () { new (GetCurrentLine ()) }, + [new (GetCurrentLine ())], CursorPosition, HistoryText.LineStatus.Replaced ); @@ -2977,7 +3004,7 @@ public void DeleteCharLeft () ClearSelectedRegion (); - List currentLine = GetCurrentLine (); + List currentLine = GetCurrentLine (); _historyText.Add ( new () { new (currentLine) }, @@ -3021,7 +3048,7 @@ public void DeleteCharRight () ClearSelectedRegion (); - List currentLine = GetCurrentLine (); + List currentLine = GetCurrentLine (); _historyText.Add ( new () { new (currentLine) }, @@ -3050,19 +3077,19 @@ public void DeleteCharRight () } /// Invoked when the normal color is drawn. - public event EventHandler? DrawNormalColor; + public event EventHandler? DrawNormalColor; /// Invoked when the ready only color is drawn. - public event EventHandler? DrawReadOnlyColor; + public event EventHandler? DrawReadOnlyColor; /// Invoked when the selection color is drawn. - public event EventHandler? DrawSelectionColor; + public event EventHandler? DrawSelectionColor; /// /// Invoked when the used color is drawn. The Used Color is used to indicate if the /// was pressed and enabled. /// - public event EventHandler? DrawUsedColor; + public event EventHandler? DrawUsedColor; /// Find the next text based on the match case with the option to replace it. /// The text to find. @@ -3135,19 +3162,19 @@ public bool FindPreviousText ( /// Gets all lines of characters. /// - public List> GetAllLines () { return _model.GetAllLines (); } + 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); } + public List GetCurrentLine () { return _model.GetLine (CurrentRow); } /// Returns the characters on the . /// The intended line. /// - public List GetLine (int line) { return _model.GetLine (line); } + public List GetLine (int line) { return _model.GetLine (line); } /// public override Attribute GetNormalColor () @@ -3202,7 +3229,7 @@ public bool Load (string path) { SetWrapModel (); res = _model.LoadFile (path); - _historyText.Clear (Text); + _historyText.Clear (_model.GetAllLines ()); ResetPosition (); } finally @@ -3224,33 +3251,33 @@ public void Load (Stream stream) { SetWrapModel (); _model.LoadStream (stream); - _historyText.Clear (Text); + _historyText.Clear (_model.GetAllLines ()); ResetPosition (); SetNeedsDisplay (); UpdateWrapModel (); } - /// Loads the contents of the list into the . + /// Loads the contents of the list into the . /// Rune cells list to load the contents from. - public void Load (List cells) + public void Load (List cells) { SetWrapModel (); - _model.LoadRuneCells (cells, ColorScheme); - _historyText.Clear (Text); + _model.LoadCells (cells, ColorScheme?.Focus); + _historyText.Clear (_model.GetAllLines ()); ResetPosition (); SetNeedsDisplay (); UpdateWrapModel (); - InheritsPreviousColorScheme = true; + InheritsPreviousAttribute = true; } - /// Loads the contents of the list of list into the . + /// 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) + public void Load (List> cellsList) { SetWrapModel (); - InheritsPreviousColorScheme = true; - _model.LoadListRuneCells (cellsList, ColorScheme); - _historyText.Clear (Text); + InheritsPreviousAttribute = true; + _model.LoadListCells (cellsList, ColorScheme?.Focus); + _historyText.Clear (_model.GetAllLines ()); ResetPosition (); SetNeedsDisplay (); UpdateWrapModel (); @@ -3346,7 +3373,7 @@ protected internal override bool OnMouseEvent (MouseEvent ev) } else if (ev.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) { - ProcessMouseClick (ev, out List line); + ProcessMouseClick (ev, out List line); PositionCursor (); if (_model.Count > 0 && _shiftSelecting && IsSelecting) @@ -3445,7 +3472,7 @@ protected internal override bool OnMouseEvent (MouseEvent ev) StopSelecting (); } - ProcessMouseClick (ev, out List line); + ProcessMouseClick (ev, out List line); (int col, int row)? newPos; if (CurrentColumn == line.Count @@ -3482,7 +3509,7 @@ protected internal override bool OnMouseEvent (MouseEvent ev) StopSelecting (); } - ProcessMouseClick (ev, out List line); + ProcessMouseClick (ev, out List line); CurrentColumn = 0; if (!IsSelecting) @@ -3508,7 +3535,7 @@ protected internal override bool OnMouseEvent (MouseEvent ev) public void MoveEnd () { CurrentRow = _model.Count - 1; - List line = GetCurrentLine (); + List line = GetCurrentLine (); CurrentColumn = line.Count; TrackColumn (); PositionCursor (); @@ -3553,7 +3580,7 @@ public override void OnDrawContent (Rectangle viewport) for (int idxRow = _topRow; idxRow < _model.Count; idxRow++) { - List line = _model.GetLine (idxRow); + List line = _model.GetLine (idxRow); int lineRuneCount = line.Count; var col = 0; @@ -3601,6 +3628,8 @@ public override void OnDrawContent (Rectangle viewport) else { AddRune (col, row, rune); + // 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.Right, cols)) @@ -3721,17 +3750,17 @@ public void Paste () SetWrapModel (); string? contents = Clipboard.Contents; - if (_copyWithoutSelection && contents.FirstOrDefault (x => x == '\n' || x == '\r') == 0) + if (_copyWithoutSelection && contents.FirstOrDefault (x => x is '\n' or '\r') == 0) { - List runeList = contents is null ? new () : TextModel.ToRuneCellList (contents); - List currentLine = GetCurrentLine (); + List runeList = contents is null ? [] : Cell.ToCellList (contents); + List currentLine = GetCurrentLine (); - _historyText.Add (new () { new (currentLine) }, CursorPosition); + _historyText.Add ([new (currentLine)], CursorPosition); - List> addedLine = new () { new (currentLine), runeList }; + List> addedLine = [new (currentLine), runeList]; _historyText.Add ( - new (addedLine), + [..addedLine], CursorPosition, HistoryText.LineStatus.Added ); @@ -3740,7 +3769,7 @@ public void Paste () CurrentRow++; _historyText.Add ( - new () { new (GetCurrentLine ()) }, + [new (GetCurrentLine ())], CursorPosition, HistoryText.LineStatus.Replaced ); @@ -3756,12 +3785,12 @@ public void Paste () } _copyWithoutSelection = false; - InsertAllText (contents); + InsertAllText (contents, true); if (IsSelecting) { _historyText.ReplaceLast ( - new () { new (GetCurrentLine ()) }, + [new (GetCurrentLine ())], CursorPosition, HistoryText.LineStatus.Original ); @@ -3794,7 +3823,7 @@ public void Paste () SetNeedsDisplay (); } - List line = _model.GetLine (CurrentRow); + List line = _model.GetLine (CurrentRow); var col = 0; if (line.Count > 0) @@ -3812,6 +3841,11 @@ public void Paste () { 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)) { @@ -3948,16 +3982,16 @@ public void Undo () /// The line. /// The col index. /// The row index. - protected virtual void OnDrawNormalColor (List line, int idxCol, int idxRow) + protected virtual void OnDrawNormalColor (List line, int idxCol, int idxRow) { (int Row, int Col) unwrappedPos = GetUnwrappedPosition (idxRow, idxCol); - var ev = new RuneCellEventArgs (line, idxCol, unwrappedPos); + var ev = new CellEventArgs (line, idxCol, unwrappedPos); DrawNormalColor?.Invoke (this, ev); - if (line [idxCol].ColorScheme is { }) + if (line [idxCol].Attribute is { }) { - ColorScheme? colorScheme = line [idxCol].ColorScheme; - Driver.SetAttribute (Enabled ? colorScheme!.Focus : colorScheme!.Disabled); + Attribute? attribute = line [idxCol].Attribute; + Driver.SetAttribute ((Attribute)attribute!); } else { @@ -3974,22 +4008,22 @@ protected virtual void OnDrawNormalColor (List line, int idxCol, int i /// The col index. /// /// /// The row index. - protected virtual void OnDrawReadOnlyColor (List line, int idxCol, int idxRow) + protected virtual void OnDrawReadOnlyColor (List line, int idxCol, int idxRow) { (int Row, int Col) unwrappedPos = GetUnwrappedPosition (idxRow, idxCol); - var ev = new RuneCellEventArgs (line, idxCol, unwrappedPos); + var ev = new CellEventArgs (line, idxCol, unwrappedPos); DrawReadOnlyColor?.Invoke (this, ev); - ColorScheme? colorScheme = line [idxCol].ColorScheme is { } ? line [idxCol].ColorScheme : ColorScheme; + Attribute? cellAttribute = line [idxCol].Attribute is { } ? line [idxCol].Attribute : ColorScheme?.Disabled; Attribute attribute; - if (colorScheme!.Disabled.Foreground == colorScheme.Focus.Background) + if (cellAttribute!.Value.Foreground == cellAttribute.Value.Background) { - attribute = new (colorScheme.Focus.Foreground, colorScheme.Focus.Background); + attribute = new (cellAttribute.Value.Foreground, cellAttribute.Value.Background); } else { - attribute = new (colorScheme.Disabled.Foreground, colorScheme.Focus.Background); + attribute = new (cellAttribute.Value.Foreground, ColorScheme!.Focus.Background); } Driver.SetAttribute (attribute); @@ -4004,18 +4038,18 @@ protected virtual void OnDrawReadOnlyColor (List line, int idxCol, int /// The col index. /// /// /// The row index. - protected virtual void OnDrawSelectionColor (List line, int idxCol, int idxRow) + protected virtual void OnDrawSelectionColor (List line, int idxCol, int idxRow) { (int Row, int Col) unwrappedPos = GetUnwrappedPosition (idxRow, idxCol); - var ev = new RuneCellEventArgs (line, idxCol, unwrappedPos); + var ev = new CellEventArgs (line, idxCol, unwrappedPos); DrawSelectionColor?.Invoke (this, ev); - if (line [idxCol].ColorScheme is { }) + if (line [idxCol].Attribute is { }) { - ColorScheme? colorScheme = line [idxCol].ColorScheme; + Attribute? attribute = line [idxCol].Attribute; Driver.SetAttribute ( - new (colorScheme!.Focus.Background, colorScheme.Focus.Foreground) + new (attribute!.Value.Background, attribute.Value.Foreground) ); } else @@ -4038,20 +4072,20 @@ protected virtual void OnDrawSelectionColor (List line, int idxCol, in /// The col index. /// /// /// The row index. - protected virtual void OnDrawUsedColor (List line, int idxCol, int idxRow) + protected virtual void OnDrawUsedColor (List line, int idxCol, int idxRow) { (int Row, int Col) unwrappedPos = GetUnwrappedPosition (idxRow, idxCol); - var ev = new RuneCellEventArgs (line, idxCol, unwrappedPos); + var ev = new CellEventArgs (line, idxCol, unwrappedPos); DrawUsedColor?.Invoke (this, ev); - if (line [idxCol].ColorScheme is { }) + if (line [idxCol].Attribute is { }) { - ColorScheme? colorScheme = line [idxCol].ColorScheme; - SetValidUsedColor (colorScheme!); + Attribute? attribute = line [idxCol].Attribute; + SetValidUsedColor (attribute!); } else { - SetValidUsedColor (ColorScheme); + SetValidUsedColor (ColorScheme?.Focus); } } @@ -4064,7 +4098,7 @@ protected virtual void OnDrawUsedColor (List line, int idxCol, int idx private void Adjust () { (int width, int height) offB = OffSetBackground (); - List line = GetCurrentLine (); + List line = GetCurrentLine (); bool need = NeedsDisplay || _wrapNeeded || !Used; (int size, int length) tSize = TextModel.DisplaySize (line, -1, -1, false, TabWidth); (int size, int length) dSize = TextModel.DisplaySize (line, _leftColumn, CurrentColumn, true, TabWidth); @@ -4194,6 +4228,14 @@ private void Adjust () null, null, (KeyCode)KeyBindings.GetKeyFromCommands (Command.Redo) + ), + new ( + Strings.ctxColors, + "", + () => PromptForColors (), + null, + null, + (KeyCode)KeyBindings.GetKeyFromCommands (Command.Open) ) } ); @@ -4226,11 +4268,11 @@ private void ClearRegion () var maxrow = (int)(end >> 32); var startCol = (int)(start & 0xffffffff); var endCol = (int)(end & 0xffffffff); - List line = _model.GetLine (startRow); + List line = _model.GetLine (startRow); _historyText.Add (new () { new (line) }, new (startCol, startRow)); - List> removedLines = new (); + List> removedLines = new (); if (startRow == maxrow) { @@ -4265,7 +4307,7 @@ private void ClearRegion () removedLines.Add (new (line)); line.RemoveRange (startCol, line.Count - startCol); - List line2 = _model.GetLine (maxrow); + List line2 = _model.GetLine (maxrow); line.AddRange (line2.Skip (endCol)); for (int row = startRow + 1; row <= maxrow; row++) @@ -4316,7 +4358,7 @@ private bool DeleteTextBackwards () if (CurrentColumn > 0) { // Delete backwards - List currentLine = GetCurrentLine (); + List currentLine = GetCurrentLine (); _historyText.Add (new () { new (currentLine) }, CursorPosition); @@ -4356,11 +4398,11 @@ private bool DeleteTextBackwards () } int prowIdx = CurrentRow - 1; - List prevRow = _model.GetLine (prowIdx); + List prevRow = _model.GetLine (prowIdx); _historyText.Add (new () { new (prevRow) }, CursorPosition); - List> removedLines = new () { new (prevRow) }; + List> removedLines = new () { new (prevRow) }; removedLines.Add (new (GetCurrentLine ())); @@ -4400,7 +4442,7 @@ private bool DeleteTextForwards () { SetWrapModel (); - List currentLine = GetCurrentLine (); + List currentLine = GetCurrentLine (); if (CurrentColumn == currentLine.Count) { @@ -4413,9 +4455,9 @@ private bool DeleteTextForwards () _historyText.Add (new () { new (currentLine) }, CursorPosition); - List> removedLines = new () { new (currentLine) }; + List> removedLines = new () { new (currentLine) }; - List nextLine = _model.GetLine (CurrentRow + 1); + List nextLine = _model.GetLine (CurrentRow + 1); removedLines.Add (new (nextLine)); @@ -4495,7 +4537,7 @@ private void DoSetNeedsDisplay (Rectangle rect) } } - private IEnumerable<(int col, int row, RuneCell rune)> ForwardIterator (int col, int row) + private IEnumerable<(int col, int row, Cell rune)> ForwardIterator (int col, int row) { if (col < 0 || row < 0) { @@ -4507,7 +4549,7 @@ private void DoSetNeedsDisplay (Rectangle rect) yield break; } - List line = GetCurrentLine (); + List line = GetCurrentLine (); if (col >= line.Count) { @@ -4529,7 +4571,7 @@ private void DoSetNeedsDisplay (Rectangle rect) private void GenerateSuggestions () { - List currentLine = GetCurrentLine (); + List currentLine = GetCurrentLine (); int cursorPosition = Math.Min (CurrentColumn, currentLine.Count); Autocomplete.Context = new ( @@ -4585,7 +4627,8 @@ private void GetEncodedRegionBounds ( // Returns a string with the text in the selected // region. // - private string GetRegion ( + internal string GetRegion ( + out List> cellsList, int? sRow = null, int? sCol = null, int? cRow = null, @@ -4593,8 +4636,9 @@ private string GetRegion ( TextModel? model = null ) { - long start, end; - GetEncodedRegionBounds (out start, out end, sRow, sCol, cRow, cCol); + GetEncodedRegionBounds (out long start, out long end, sRow, sCol, cRow, cCol); + + cellsList = []; if (start == end) { @@ -4602,31 +4646,38 @@ private string GetRegion ( } var startRow = (int)(start >> 32); - var maxrow = (int)(end >> 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 line = model is null ? _model.GetLine (startRow) : model.GetLine (startRow); + List cells; - if (startRow == maxrow) + if (startRow == maxRow) { - return StringFromRunes (line.GetRange (startCol, endCol - startCol)); + cells = line.GetRange (startCol, endCol - startCol); + cellsList.Add (cells); + return StringFromRunes (cells); } - string res = StringFromRunes (line.GetRange (startCol, line.Count - startCol)); + cells = line.GetRange (startCol, line.Count - startCol); + cellsList.Add (cells); + string res = StringFromRunes (cells); - for (int row = startRow + 1; row < maxrow; row++) + 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 - + StringFromRunes ( - model == null - ? _model.GetLine (row) - : model.GetLine (row) - ); + + StringFromRunes (cells); } - line = model is null ? _model.GetLine (maxrow) : model.GetLine (maxrow); - res = res + Environment.NewLine + StringFromRunes (line.GetRange (0, endCol)); + line = model is null ? _model.GetLine (maxRow) : model.GetLine (maxRow); + cellsList.AddRange ([]); + cells = line.GetRange (0, endCol); + cellsList.Add (cells); + res = res + Environment.NewLine + StringFromRunes (cells); return res; } @@ -4652,7 +4703,7 @@ private string GetSelectedRegion () OnUnwrappedCursorPosition (cRow, cCol); - return GetRegion (startRow, startCol, cRow, cCol, model); + return GetRegion (out _, sRow: startRow, sCol: startCol, cRow: cRow, cCol: cCol, model: model); } private (int Row, int Col) GetUnwrappedPosition (int line, int col) @@ -4704,12 +4755,12 @@ private void HistoryText_ChangeText (object sender, HistoryText.HistoryTextItemE for (var i = 0; i < obj.Lines.Count; i++) { - if (i == 0) + if (i == 0 || obj.LineStatus == HistoryText.LineStatus.Original || obj.LineStatus == HistoryText.LineStatus.Attribute) { _model.ReplaceLine (startLine, obj.Lines [i]); } - else if ((obj.IsUndoing && obj.LineStatus == HistoryText.LineStatus.Removed) - || (!obj.IsUndoing && obj.LineStatus == HistoryText.LineStatus.Added)) + else if (obj is { IsUndoing: true, LineStatus: HistoryText.LineStatus.Removed } + or { IsUndoing: false, LineStatus: HistoryText.LineStatus.Added }) { _model.AddLine (startLine, obj.Lines [i]); } @@ -4730,9 +4781,9 @@ private void HistoryText_ChangeText (object sender, HistoryText.HistoryTextItemE OnContentsChanged (); } - private void Insert (RuneCell cell) + private void Insert (Cell cell) { - List line = GetCurrentLine (); + List line = GetCurrentLine (); if (Used) { @@ -4758,14 +4809,25 @@ private void Insert (RuneCell cell) } } - private void InsertAllText (string text) + private void InsertAllText (string text, bool fromClipboard = false) { if (string.IsNullOrEmpty (text)) { return; } - List> lines = TextModel.StringToLinesOfRuneCells (text); + List> lines; + + if (fromClipboard && text == _copiedText) + { + lines = _copiedCellsList; + } + else + { + // Get selected attribute + Attribute? attribute = GetSelectedAttribute (CurrentRow, CurrentColumn); + lines = Cell.StringToLinesOfCells (text, attribute); + } if (lines.Count == 0) { @@ -4774,9 +4836,9 @@ private void InsertAllText (string text) SetWrapModel (); - List line = GetCurrentLine (); + List line = GetCurrentLine (); - _historyText.Add (new () { new (line) }, CursorPosition); + _historyText.Add ([new (line)], CursorPosition); // Optimize single line if (lines.Count == 1) @@ -4785,7 +4847,7 @@ private void InsertAllText (string text) CurrentColumn += lines [0].Count; _historyText.Add ( - new () { new (line) }, + [new (line)], CursorPosition, HistoryText.LineStatus.Replaced ); @@ -4813,8 +4875,8 @@ private void InsertAllText (string text) return; } - List? rest = null; - var lastp = 0; + List? rest = null; + var lastPosition = 0; if (_model.Count > 0 && line.Count > 0 && !_copyWithoutSelection) { @@ -4829,19 +4891,19 @@ private void InsertAllText (string text) //model.AddLine (currentRow, lines [0]); - List> addedLines = new () { new (line) }; + List> addedLines = [new (line)]; for (var i = 1; i < lines.Count; i++) { _model.AddLine (CurrentRow + i, lines [i]); - addedLines.Add (new (lines [i])); + addedLines.Add ([..lines [i]]); } if (rest is { }) { - List last = _model.GetLine (CurrentRow + lines.Count - 1); - lastp = last.Count; + List last = _model.GetLine (CurrentRow + lines.Count - 1); + lastPosition = last.Count; last.InsertRange (last.Count, rest); addedLines.Last ().InsertRange (addedLines.Last ().Count, rest); @@ -4851,11 +4913,11 @@ private void InsertAllText (string text) // Now adjust column and row positions CurrentRow += lines.Count - 1; - CurrentColumn = rest is { } ? lastp : lines [lines.Count - 1].Count; + CurrentColumn = rest is { } ? lastPosition : lines [^1].Count; Adjust (); _historyText.Add ( - new () { new (line) }, + [new (line)], CursorPosition, HistoryText.LineStatus.Replaced ); @@ -4864,7 +4926,7 @@ private void InsertAllText (string text) OnContentsChanged (); } - private bool InsertText (Key a, ColorScheme? colorScheme = null) + private bool InsertText (Key a, Attribute? attribute = null) { //So that special keys like tab can be processed if (_isReadOnly) @@ -4874,7 +4936,7 @@ private bool InsertText (Key a, ColorScheme? colorScheme = null) SetWrapModel (); - _historyText.Add (new () { new (GetCurrentLine ()) }, CursorPosition); + _historyText.Add ([new (GetCurrentLine ())], CursorPosition); if (IsSelecting) { @@ -4883,7 +4945,7 @@ private bool InsertText (Key a, ColorScheme? colorScheme = null) if ((uint)a.KeyCode == '\n') { - _model.AddLine (CurrentRow + 1, new ()); + _model.AddLine (CurrentRow + 1, []); CurrentRow++; CurrentColumn = 0; } @@ -4895,7 +4957,7 @@ private bool InsertText (Key a, ColorScheme? colorScheme = null) { if (Used) { - Insert (new () { Rune = a.AsRune, ColorScheme = colorScheme }); + Insert (new () { Rune = a.AsRune, Attribute = attribute }); CurrentColumn++; if (CurrentColumn >= _leftColumn + Viewport.Width) @@ -4906,13 +4968,13 @@ private bool InsertText (Key a, ColorScheme? colorScheme = null) } else { - Insert (new () { Rune = a.AsRune, ColorScheme = colorScheme }); + Insert (new () { Rune = a.AsRune, Attribute = attribute }); CurrentColumn++; } } _historyText.Add ( - new () { new (GetCurrentLine ()) }, + [new (GetCurrentLine ())], CursorPosition, HistoryText.LineStatus.Replaced ); @@ -4938,7 +5000,7 @@ private void KillToEndOfLine () SetWrapModel (); - List currentLine = GetCurrentLine (); + List currentLine = GetCurrentLine (); var setLastWasKill = true; if (currentLine.Count > 0 && CurrentColumn == currentLine.Count) @@ -4956,7 +5018,7 @@ private void KillToEndOfLine () { if (CurrentRow < _model.Count - 1) { - List> removedLines = new () { new (currentLine) }; + List> removedLines = new () { new (currentLine) }; _model.RemoveLine (CurrentRow); @@ -4992,7 +5054,7 @@ private void KillToEndOfLine () else { int restCount = currentLine.Count - CurrentColumn; - List rest = currentLine.GetRange (CurrentColumn, restCount); + List rest = currentLine.GetRange (CurrentColumn, restCount); var val = string.Empty; val += StringFromRunes (rest); @@ -5037,7 +5099,7 @@ private void KillToLeftStart () SetWrapModel (); - List currentLine = GetCurrentLine (); + List currentLine = GetCurrentLine (); var setLastWasKill = true; if (currentLine.Count > 0 && CurrentColumn == 0) @@ -5080,7 +5142,7 @@ private void KillToLeftStart () CurrentRow--; currentLine = _model.GetLine (CurrentRow); - List> removedLine = + List> removedLine = [ [..currentLine], [] @@ -5098,7 +5160,7 @@ private void KillToLeftStart () else { int restCount = CurrentColumn; - List rest = currentLine.GetRange (0, restCount); + List rest = currentLine.GetRange (0, restCount); var val = string.Empty; val += StringFromRunes (rest); @@ -5138,7 +5200,7 @@ private void KillWordBackward () SetWrapModel (); - List currentLine = GetCurrentLine (); + List currentLine = GetCurrentLine (); _historyText.Add ([ [.. GetCurrentLine ()]], CursorPosition); @@ -5206,7 +5268,7 @@ private void KillWordForward () SetWrapModel (); - List currentLine = GetCurrentLine (); + List currentLine = GetCurrentLine (); _historyText.Add ([ [.. GetCurrentLine ()]], CursorPosition); @@ -5327,7 +5389,7 @@ private bool MoveDown () private void MoveEndOfLine () { - List currentLine = GetCurrentLine (); + List currentLine = GetCurrentLine (); CurrentColumn = Math.Max (currentLine.Count - (ReadOnly ? 1 : 0), 0); Adjust (); DoNeededAction (); @@ -5351,7 +5413,7 @@ private bool MoveLeft () SetNeedsDisplay (); } - List currentLine = GetCurrentLine (); + List currentLine = GetCurrentLine (); CurrentColumn = Math.Max (currentLine.Count - (ReadOnly ? 1 : 0), 0); } else @@ -5424,7 +5486,7 @@ private void MovePageUp () private bool MoveRight () { - List currentLine = GetCurrentLine (); + List currentLine = GetCurrentLine (); if ((ReadOnly ? CurrentColumn + 1 : CurrentColumn) < currentLine.Count) { @@ -5620,7 +5682,7 @@ private bool ProcessBackTab () { SetWrapModel (); - List currentLine = GetCurrentLine (); + List currentLine = GetCurrentLine (); if (currentLine.Count > 0 && currentLine [CurrentColumn - 1].Rune.Value == '\t') { @@ -5670,18 +5732,46 @@ private void ProcessDeleteCharRight () DeleteCharRight (); } + private Attribute? GetSelectedAttribute (int row, int col) + { + if (!InheritsPreviousAttribute || (Lines == 1 && GetLine (Lines).Count == 0)) + { + return null; + } + + List line = GetLine (row); + int foundRow = row; + + while (line.Count == 0) + { + if (foundRow == 0 && line.Count == 0) + { + return null; + } + + foundRow--; + line = GetLine (foundRow); + } + + int foundCol = foundRow < row ? line.Count - 1 : Math.Min (col, line.Count - 1); + + Cell cell = line [foundCol]; + + return cell.Attribute; + } + // If InheritsPreviousColorScheme is enabled this method will check if the rune cell on // the row and col location and around has a not null color scheme. If it's null will set it with // the very most previous valid color scheme. private void ProcessInheritsPreviousColorScheme (int row, int col) { - if (!InheritsPreviousColorScheme || (Lines == 1 && GetLine (Lines).Count == 0)) + if (!InheritsPreviousAttribute || (Lines == 1 && GetLine (Lines).Count == 0)) { return; } - List line = GetLine (row); - List lineToSet = line; + List line = GetLine (row); + List lineToSet = line; while (line.Count == 0) { @@ -5696,20 +5786,25 @@ private void ProcessInheritsPreviousColorScheme (int row, int col) } int colWithColor = Math.Max (Math.Min (col - 2, line.Count - 1), 0); - RuneCell cell = line [colWithColor]; + Cell cell = line [colWithColor]; int colWithoutColor = Math.Max (col - 1, 0); - if (cell.ColorScheme is { } && colWithColor == 0 && lineToSet [colWithoutColor].ColorScheme is { }) + 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); + List l = GetLine (r); for (int c = l.Count - 1; c > -1; c--) { - if (l [c].ColorScheme is null) + Cell cell1 = l [c]; + + if (cell1.Attribute is null) { - l [c].ColorScheme = cell.ColorScheme; + cell1.Attribute = cell.Attribute; + l [c] = cell1; } else { @@ -5721,18 +5816,18 @@ private void ProcessInheritsPreviousColorScheme (int row, int col) return; } - if (cell.ColorScheme is null) + if (cell.Attribute is null) { for (int r = row; r > -1; r--) { - List l = GetLine (r); + List l = GetLine (r); colWithColor = l.FindLastIndex ( colWithColor > -1 ? colWithColor : l.Count - 1, - rc => rc.ColorScheme != null + c => c.Attribute != null ); - if (colWithColor > -1 && l [colWithColor].ColorScheme is { }) + if (colWithColor > -1 && l [colWithColor].Attribute is { }) { cell = l [colWithColor]; @@ -5744,9 +5839,9 @@ private void ProcessInheritsPreviousColorScheme (int row, int col) { int cRow = row; - while (cell.ColorScheme is null) + while (cell.Attribute is null) { - if ((colWithColor == 0 || cell.ColorScheme is null) && cRow > 0) + if ((colWithColor == 0 || cell.Attribute is null) && cRow > 0) { line = GetLine (--cRow); colWithColor = line.Count - 1; @@ -5759,11 +5854,12 @@ private void ProcessInheritsPreviousColorScheme (int row, int col) } } - if (cell.ColorScheme is { } && colWithColor > -1 && colWithoutColor < lineToSet.Count && lineToSet [colWithoutColor].ColorScheme is null) + if (cell.Attribute is { } && colWithColor > -1 && colWithoutColor < lineToSet.Count && lineTo.Attribute is null) { - while (lineToSet [colWithoutColor].ColorScheme is null) + while (lineTo.Attribute is null) { - lineToSet [colWithoutColor].ColorScheme = cell.ColorScheme; + lineTo.Attribute = cell.Attribute; + lineToSet [colWithoutColor] = lineTo; colWithoutColor--; if (colWithoutColor == -1 && row > 0) @@ -5787,9 +5883,9 @@ private void ProcessKillWordForward () KillWordForward (); } - private void ProcessMouseClick (MouseEvent ev, out List line) + private void ProcessMouseClick (MouseEvent ev, out List line) { - List? r = null; + List? r = null; if (_model.Count > 0) { @@ -6061,7 +6157,7 @@ private bool ProcessEnterKey (CommandContext ctx) SetWrapModel (); - List currentLine = GetCurrentLine (); + List currentLine = GetCurrentLine (); _historyText.Add (new () { new (currentLine) }, CursorPosition); @@ -6072,10 +6168,10 @@ private bool ProcessEnterKey (CommandContext ctx) } int restCount = currentLine.Count - CurrentColumn; - List rest = currentLine.GetRange (CurrentColumn, restCount); + List rest = currentLine.GetRange (CurrentColumn, restCount); currentLine.RemoveRange (CurrentColumn, restCount); - List> addedLines = new () { new (currentLine) }; + List> addedLines = new () { new (currentLine) }; _model.AddLine (CurrentRow + 1, rest); @@ -6261,11 +6357,11 @@ private void SetOverwrite (bool overwrite) DoNeededAction (); } - private static void SetValidUsedColor (ColorScheme? colorScheme) + private static void SetValidUsedColor (Attribute? attribute) { // BUGBUG: (v2 truecolor) This code depends on 8-bit color names; disabling for now //if ((colorScheme!.HotNormal.Foreground & colorScheme.Focus.Background) == colorScheme.Focus.Foreground) { - Driver.SetAttribute (new (colorScheme!.Focus.Background, colorScheme!.Focus.Foreground)); + Driver.SetAttribute (new (attribute!.Value.Background, attribute!.Value.Foreground)); } /// Restore from original model. @@ -6320,7 +6416,7 @@ private void StopSelecting () _isButtonShift = false; } - private string StringFromRunes (List cells) + private string StringFromRunes (List cells) { if (cells is null) { @@ -6329,7 +6425,7 @@ private string StringFromRunes (List cells) var size = 0; - foreach (RuneCell cell in cells) + foreach (Cell cell in cells) { size += cell.Rune.GetEncodingLength (); } @@ -6337,7 +6433,7 @@ private string StringFromRunes (List cells) var encoded = new byte [size]; var offset = 0; - foreach (RuneCell cell in cells) + foreach (Cell cell in cells) { offset += cell.Rune.Encode (encoded, offset); } @@ -6381,7 +6477,7 @@ private void ToggleSelecting () private void TrackColumn () { // Now track the column - List line = GetCurrentLine (); + List line = GetCurrentLine (); if (line.Count < _columnTrack) { diff --git a/Terminal.Gui/Views/TreeView/Branch.cs b/Terminal.Gui/Views/TreeView/Branch.cs index 0b37314cdd..2b9f637d8d 100644 --- a/Terminal.Gui/Views/TreeView/Branch.cs +++ b/Terminal.Gui/Views/TreeView/Branch.cs @@ -75,7 +75,7 @@ public bool CanExpand () /// public virtual void Draw (ConsoleDriver driver, ColorScheme colorScheme, int y, int availableWidth) { - List cells = new (); + List cells = new (); int? indexOfExpandCollapseSymbol = null; int indexOfModelText; @@ -106,7 +106,7 @@ public virtual void Draw (ConsoleDriver driver, ColorScheme colorScheme, int y, } else { - cells.Add (NewRuneCell (attr, r)); + cells.Add (NewCell (attr, r)); availableWidth -= r.GetColumns (); } } @@ -148,7 +148,7 @@ public virtual void Draw (ConsoleDriver driver, ColorScheme colorScheme, int y, else { indexOfExpandCollapseSymbol = cells.Count; - cells.Add (NewRuneCell (attr, expansion)); + cells.Add (NewCell (attr, expansion)); availableWidth -= expansion.GetColumns (); } @@ -211,7 +211,7 @@ public virtual void Draw (ConsoleDriver driver, ColorScheme colorScheme, int y, } attr = modelColor; - cells.AddRange (lineBody.Select (r => NewRuneCell (attr, new Rune (r)))); + cells.AddRange (lineBody.Select (r => NewCell (attr, new Rune (r)))); if (availableWidth > 0) { @@ -219,7 +219,7 @@ public virtual void Draw (ConsoleDriver driver, ColorScheme colorScheme, int y, cells.AddRange ( Enumerable.Repeat ( - NewRuneCell (attr, new Rune (' ')), + NewCell (attr, new Rune (' ')), availableWidth ) ); @@ -229,7 +229,7 @@ public virtual void Draw (ConsoleDriver driver, ColorScheme colorScheme, int y, { Model = Model, Y = y, - RuneCells = cells, + Cells = cells, Tree = tree, IndexOfExpandCollapseSymbol = indexOfExpandCollapseSymbol, @@ -239,9 +239,9 @@ public virtual void Draw (ConsoleDriver driver, ColorScheme colorScheme, int y, if (!e.Handled) { - foreach (RuneCell cell in cells) + foreach (Cell cell in cells) { - driver.SetAttribute (cell.ColorScheme.Normal); + driver.SetAttribute ((Attribute)cell.Attribute!); driver.AddRune (cell.Rune); } } @@ -529,5 +529,5 @@ private bool IsLast () return Parent.ChildBranches.Values.LastOrDefault () == this; } - private static RuneCell NewRuneCell (Attribute attr, Rune r) { return new RuneCell { Rune = r, ColorScheme = new ColorScheme (attr) }; } + private static Cell NewCell (Attribute attr, Rune r) { return new Cell { Rune = r, Attribute = new (attr) }; } } diff --git a/Terminal.Gui/Views/TreeView/DrawTreeViewLineEventArgs.cs b/Terminal.Gui/Views/TreeView/DrawTreeViewLineEventArgs.cs index 1454abff7e..6cd03747d1 100644 --- a/Terminal.Gui/Views/TreeView/DrawTreeViewLineEventArgs.cs +++ b/Terminal.Gui/Views/TreeView/DrawTreeViewLineEventArgs.cs @@ -12,16 +12,16 @@ public class DrawTreeViewLineEventArgs where T : class public bool Handled { get; set; } /// - /// If line contains a branch that can be expanded/collapsed then this is the index in at + /// If line contains a branch that can be expanded/collapsed then this is the index in at /// which the symbol is (or null for leaf elements). /// public int? IndexOfExpandCollapseSymbol { get; init; } /// - /// The notional index in which contains the first character of the + /// The notional index in which contains the first character of the /// text (i.e. after all branch lines and expansion/collapse symbols). /// - /// May be negative or outside of bounds of if the view has been scrolled horizontally. + /// May be negative or outside of bounds of if the view has been scrolled horizontally. public int IndexOfModelText { get; init; } /// The object at this line in the tree @@ -32,7 +32,7 @@ public class DrawTreeViewLineEventArgs where T : class /// respected. You can modify these to change what is rendered. /// /// Changing the length of this collection may result in corrupt rendering - public List RuneCells { get; init; } + public List Cells { get; init; } /// The that is performing the rendering. public TreeView Tree { get; init; } diff --git a/UICatalog/Scenarios/Editor.cs b/UICatalog/Scenarios/Editor.cs index 69ab13276a..13e32bf5ec 100644 --- a/UICatalog/Scenarios/Editor.cs +++ b/UICatalog/Scenarios/Editor.cs @@ -202,7 +202,15 @@ public override void Main () CreateWrapChecked (), CreateAutocomplete (), CreateAllowsTabChecked (), - CreateReadOnlyChecked () + CreateReadOnlyChecked (), + new MenuItem ( + "Colors", + "", + () => _textView.PromptForColors (), + null, + null, + KeyCode.CtrlMask | KeyCode.L + ) } ), new ( diff --git a/UICatalog/Scenarios/LineDrawing.cs b/UICatalog/Scenarios/LineDrawing.cs index 71f99f64b6..5b2887c590 100644 --- a/UICatalog/Scenarios/LineDrawing.cs +++ b/UICatalog/Scenarios/LineDrawing.cs @@ -135,13 +135,14 @@ public static bool PromptForColor (string title, Color current, out Color newCol var d = new Dialog { Title = title, - Height = 7 + Width = Application.Force16Colors ? 35 : Dim.Auto (DimAutoStyle.Auto, Dim.Percent (80), Dim.Percent (90)), + Height = 10 }; var btnOk = new Button { X = Pos.Center () - 5, - Y = 4, + Y = Application.Force16Colors ? 6 : 4, Text = "Ok", Width = Dim.Auto (), IsDefault = true @@ -171,21 +172,34 @@ public static bool PromptForColor (string title, Color current, out Color newCol d.Add (btnOk); d.Add (btnCancel); - /* Does not work d.AddButton (btnOk); d.AddButton (btnCancel); - */ - var cp = new ColorPicker + + View cp; + if (Application.Force16Colors) { - SelectedColor = current, - Width = Dim.Fill () - }; + cp = new ColorPicker16 + { + SelectedColor = current.GetClosestNamedColor16 (), + Width = Dim.Fill () + }; + } + else + { + cp = new ColorPicker + { + SelectedColor = current, + Width = Dim.Fill (), + Style = new () { ShowColorName = true, ShowTextFields = true } + }; + ((ColorPicker)cp).ApplyStyleChanges (); + } d.Add (cp); Application.Run (d); d.Dispose (); - newColor = cp.SelectedColor; + newColor = Application.Force16Colors ? ((ColorPicker16)cp).SelectedColor : ((ColorPicker)cp).SelectedColor; return accept; } diff --git a/UICatalog/Scenarios/ProgressBarStyles.cs b/UICatalog/Scenarios/ProgressBarStyles.cs index 3af7e1405b..324e49e435 100644 --- a/UICatalog/Scenarios/ProgressBarStyles.cs +++ b/UICatalog/Scenarios/ProgressBarStyles.cs @@ -35,7 +35,7 @@ public override void Main () var editor = new AdornmentsEditor () { - AutoSelectViewToEdit = true + AutoSelectViewToEdit = false }; app.Add (editor); diff --git a/UICatalog/Scenarios/SyntaxHighlighting.cs b/UICatalog/Scenarios/SyntaxHighlighting.cs index ce4e970a2c..398355e34d 100644 --- a/UICatalog/Scenarios/SyntaxHighlighting.cs +++ b/UICatalog/Scenarios/SyntaxHighlighting.cs @@ -85,13 +85,13 @@ public class SyntaxHighlighting : Scenario "exists" }; - private readonly string _path = "RuneCells.rce"; - private ColorScheme _blue; - private ColorScheme _green; - private ColorScheme _magenta; + private readonly string _path = "Cells.rce"; + private Attribute _blue; + private Attribute _green; + private Attribute _magenta; private MenuItem _miWrap; private TextView _textView; - private ColorScheme _white; + private Attribute _white; /// /// Reads an object instance from an Json file. @@ -155,12 +155,12 @@ public override void Main () new ( "_Load Rune Cells", "", - () => ApplyLoadRuneCells () + () => ApplyLoadCells () ), new ( "_Save Rune Cells", "", - () => SaveRuneCells () + () => SaveCells () ), null, new ("_Quit", "", () => Quit ()) @@ -231,11 +231,11 @@ public override void Main () } } - private void ApplyLoadRuneCells () + private void ApplyLoadCells () { ClearAllEvents (); - List runeCells = new (); + List cells = new (); foreach (KeyValuePair color in Colors.ColorSchemes) { @@ -243,21 +243,21 @@ private void ApplyLoadRuneCells () foreach (Rune rune in csName.EnumerateRunes ()) { - runeCells.Add (new() { Rune = rune, ColorScheme = color.Value }); + cells.Add (new() { Rune = rune, Attribute = color.Value.Normal }); } - runeCells.Add (new() { Rune = (Rune)'\n', ColorScheme = color.Value }); + cells.Add (new() { Rune = (Rune)'\n', Attribute = color.Value.Focus }); } if (File.Exists (_path)) { - //Reading the file - List> cells = ReadFromJsonFile>> (_path); - _textView.Load (cells); + //Reading the file + List> fileCells = ReadFromJsonFile>> (_path); + _textView.Load (fileCells); } else { - _textView.Load (runeCells); + _textView.Load (cells); } _textView.Autocomplete.SuggestionGenerator = new SingleWordSuggestionGenerator (); @@ -267,11 +267,11 @@ private void ApplySyntaxHighlighting () { ClearAllEvents (); - _green = new (new Attribute (Color.Green, Color.Black)); - _blue = new (new Attribute (Color.Blue, Color.Black)); - _magenta = new (new Attribute (Color.Magenta, Color.Black)); - _white = new (new Attribute (Color.White, Color.Black)); - _textView.ColorScheme = _white; + _green = new Attribute (Color.Green, Color.Black); + _blue = new Attribute (Color.Blue, Color.Black); + _magenta = new Attribute (Color.Magenta, Color.Black); + _white = new Attribute (Color.White, Color.Black); + _textView.ColorScheme = new () { Focus = _white }; _textView.Text = "/*Query to select:\nLots of data*/\nSELECT TOP 100 * \nfrom\n MyDb.dbo.Biochemistry where TestCode = 'blah';"; @@ -292,7 +292,7 @@ private void ClearAllEvents () _textView.ClearEventHandlers ("DrawContent"); _textView.ClearEventHandlers ("DrawContentComplete"); - _textView.InheritsPreviousColorScheme = false; + _textView.InheritsPreviousAttribute = false; } private bool ContainsPosition (Match m, int pos) { return pos >= m.Index && pos < m.Index + m.Length; } @@ -317,27 +317,30 @@ private void HighlightTextBasedOnKeywords () for (var y = 0; y < _textView.Lines; y++) { - List line = _textView.GetLine (y); + List line = _textView.GetLine (y); for (var x = 0; x < line.Count; x++) { + Cell cell = line [x]; + if (commentMatches.Any (m => ContainsPosition (m, pos))) { - line [x].ColorScheme = _green; + cell.Attribute = _green; } else if (singleQuoteMatches.Any (m => ContainsPosition (m, pos))) { - line [x].ColorScheme = _magenta; + cell.Attribute = _magenta; } else if (keywordMatches.Any (m => ContainsPosition (m, pos))) { - line [x].ColorScheme = _blue; + cell.Attribute = _blue; } else { - line [x].ColorScheme = _white; + cell.Attribute = _white; } + line [x] = cell; pos++; } @@ -384,10 +387,10 @@ private bool IsKeyword (List line, int idx) private void Quit () { Application.RequestStop (); } - private void SaveRuneCells () + private void SaveCells () { //Writing to file - List> cells = _textView.GetAllLines (); + List> cells = _textView.GetAllLines (); WriteToJsonFile (_path, cells); } diff --git a/UICatalog/Scenarios/TreeViewFileSystem.cs b/UICatalog/Scenarios/TreeViewFileSystem.cs index d2073bd18c..590af81b1c 100644 --- a/UICatalog/Scenarios/TreeViewFileSystem.cs +++ b/UICatalog/Scenarios/TreeViewFileSystem.cs @@ -441,16 +441,14 @@ private void TreeViewFiles_DrawLine (object sender, DrawTreeViewLineEventArgs 0 && e.IndexOfModelText < e.RuneCells.Count) + if (e.IndexOfModelText > 0 && e.IndexOfModelText < e.Cells.Count) { - RuneCell cell = e.RuneCells [e.IndexOfModelText]; - - cell.ColorScheme = new ColorScheme ( - new Attribute ( - Color.BrightYellow, - cell.ColorScheme.Normal.Background - ) - ); + Cell cell = e.Cells [e.IndexOfModelText]; + + cell.Attribute = new Attribute ( + Color.BrightYellow, + cell.Attribute!.Value.Background + ); } } } diff --git a/UnitTests/Configuration/ThemeTests.cs b/UnitTests/Configuration/ThemeTests.cs index ffce969d82..e7d023d100 100644 --- a/UnitTests/Configuration/ThemeTests.cs +++ b/UnitTests/Configuration/ThemeTests.cs @@ -10,6 +10,7 @@ public class ThemeTests Converters = { new AttributeJsonConverter (), new ColorJsonConverter () } }; + [Fact] [AutoInitShutdown (configLocation: ConfigLocations.DefaultOnly)] public void TestApply () { diff --git a/UnitTests/Drawing/CellTests.cs b/UnitTests/Drawing/CellTests.cs new file mode 100644 index 0000000000..a7fc88073b --- /dev/null +++ b/UnitTests/Drawing/CellTests.cs @@ -0,0 +1,52 @@ +using System.Text; +using Xunit.Abstractions; + +namespace Terminal.Gui.DrawingTests; + +public class CellTests (ITestOutputHelper output) +{ + [Fact] + public void Constructor_Defaults () + { + var c = new Cell (); + Assert.True (c is { }); + Assert.Equal (0, c.Rune.Value); + Assert.Null (c.Attribute); + } + + [Fact] + public void Equals_False () + { + var c1 = new Cell (); + + var c2 = new Cell + { + Rune = new ('a'), Attribute = new (Color.Red) + }; + Assert.False (c1.Equals (c2)); + Assert.False (c2.Equals (c1)); + + c1.Rune = new ('a'); + c1.Attribute = new (); + Assert.Equal (c1.Rune, c2.Rune); + Assert.False (c1.Equals (c2)); + Assert.False (c2.Equals (c1)); + } + + [Fact] + public void ToString_Override () + { + var c1 = new Cell (); + + var c2 = new Cell + { + Rune = new ('a'), Attribute = new (Color.Red) + }; + Assert.Equal ("[\0, ]", c1.ToString ()); + + Assert.Equal ( + "[a, [Red,Red]]", + c2.ToString () + ); + } +} diff --git a/UnitTests/Text/AutocompleteTests.cs b/UnitTests/Text/AutocompleteTests.cs index c7d907e1a9..e5b1cf4b93 100644 --- a/UnitTests/Text/AutocompleteTests.cs +++ b/UnitTests/Text/AutocompleteTests.cs @@ -254,7 +254,7 @@ public void Test_GenerateSuggestions_Simple () ac.GenerateSuggestions ( new ( - TextModel.ToRuneCellList (tv.Text), + Cell.ToCellList (tv.Text), 2 ) ); diff --git a/UnitTests/View/Mouse/MouseTests.cs b/UnitTests/View/Mouse/MouseTests.cs index e365f98a8d..8c021e3c4c 100644 --- a/UnitTests/View/Mouse/MouseTests.cs +++ b/UnitTests/View/Mouse/MouseTests.cs @@ -404,6 +404,7 @@ public void WantContinuousButtonPressed_True_And_WantMousePositionReports_True_M me.Handled = false; view.Dispose (); + Application.ResetState (ignoreDisposed: true); } [Theory] diff --git a/UnitTests/Views/RuneCellTests.cs b/UnitTests/Views/RuneCellTests.cs deleted file mode 100644 index 98a075f84a..0000000000 --- a/UnitTests/Views/RuneCellTests.cs +++ /dev/null @@ -1,294 +0,0 @@ -using System.Text; -using Xunit.Abstractions; - -namespace Terminal.Gui.ViewsTests; - -public class RuneCellTests (ITestOutputHelper output) -{ - [Fact] - public void Constructor_Defaults () - { - var rc = new RuneCell (); - Assert.NotNull (rc); - Assert.Equal (0, rc.Rune.Value); - Assert.Null (rc.ColorScheme); - } - - [Fact] - public void Equals_False () - { - var rc1 = new RuneCell (); - - var rc2 = new RuneCell - { - Rune = new ('a'), ColorScheme = new() { Normal = new (Color.Red) } - }; - Assert.False (rc1.Equals (rc2)); - Assert.False (rc2.Equals (rc1)); - - rc1.Rune = new ('a'); - rc1.ColorScheme = new (); - Assert.Equal (rc1.Rune, rc2.Rune); - Assert.False (rc1.Equals (rc2)); - Assert.False (rc2.Equals (rc1)); - } - - [Fact] - public void Equals_True () - { - var rc1 = new RuneCell (); - var rc2 = new RuneCell (); - Assert.True (rc1.Equals (rc2)); - Assert.True (rc2.Equals (rc1)); - - rc1.Rune = new ('a'); - rc1.ColorScheme = new (); - rc2.Rune = new ('a'); - rc2.ColorScheme = new (); - Assert.True (rc1.Equals (rc2)); - Assert.True (rc2.Equals (rc1)); - } - - [Fact] - [AutoInitShutdown (configLocation: ConfigurationManager.ConfigLocations.DefaultOnly)] - public void RuneCell_LoadRuneCells_InheritsPreviousColorScheme () - { - List runeCells = new (); - - foreach (KeyValuePair color in Colors.ColorSchemes) - { - string csName = color.Key; - - foreach (Rune rune in csName.EnumerateRunes ()) - { - runeCells.Add (new() { Rune = rune, ColorScheme = color.Value }); - } - - runeCells.Add (new() { Rune = (Rune)'\n', ColorScheme = color.Value }); - } - - TextView tv = CreateTextView (); - tv.Load (runeCells); - var top = new Toplevel (); - top.Add (tv); - RunState rs = Application.Begin (top); - Assert.True (tv.InheritsPreviousColorScheme); - - var expectedText = @" -TopLevel -Base -Dialog -Menu -Error "; - TestHelpers.AssertDriverContentsWithFrameAre (expectedText, output); - - Attribute [] attributes = - { - // 0 - Colors.ColorSchemes ["TopLevel"].Focus, - - // 1 - Colors.ColorSchemes ["Base"].Focus, - - // 2 - Colors.ColorSchemes ["Dialog"].Focus, - - // 3 - Colors.ColorSchemes ["Menu"].Focus, - - // 4 - Colors.ColorSchemes ["Error"].Focus - }; - - var expectedColor = @" -0000000000 -1111000000 -2222220000 -3333000000 -4444400000"; - TestHelpers.AssertDriverAttributesAre (expectedColor, Application.Driver, attributes); - - tv.WordWrap = true; - Application.Refresh (); - TestHelpers.AssertDriverContentsWithFrameAre (expectedText, output); - TestHelpers.AssertDriverAttributesAre (expectedColor, Application.Driver, attributes); - - tv.CursorPosition = new (6, 2); - tv.SelectionStartColumn = 0; - tv.SelectionStartRow = 0; - Assert.Equal ($"TopLevel{Environment.NewLine}Base{Environment.NewLine}Dialog", tv.SelectedText); - tv.Copy (); - tv.IsSelecting = false; - tv.CursorPosition = new (2, 4); - tv.Paste (); - Application.Refresh (); - - expectedText = @" -TopLevel -Base -Dialog -Menu -ErTopLevel -Base -Dialogror "; - TestHelpers.AssertDriverContentsWithFrameAre (expectedText, output); - - expectedColor = @" -0000000000 -1111000000 -2222220000 -3333000000 -4444444444 -4444000000 -4444444440"; - TestHelpers.AssertDriverAttributesAre (expectedColor, Application.Driver, attributes); - - tv.Undo (); - tv.CursorPosition = new (0, 3); - tv.SelectionStartColumn = 0; - tv.SelectionStartRow = 0; - - Assert.Equal ( - $"TopLevel{Environment.NewLine}Base{Environment.NewLine}Dialog{Environment.NewLine}", - tv.SelectedText - ); - tv.Copy (); - tv.IsSelecting = false; - tv.CursorPosition = new (2, 4); - tv.Paste (); - Application.Refresh (); - - expectedText = @" -TopLevel -Base -Dialog -Menu -ErTopLevel -Base -Dialog -ror "; - TestHelpers.AssertDriverContentsWithFrameAre (expectedText, output); - - expectedColor = @" -0000000000 -1111000000 -2222220000 -3333000000 -4444444444 -4444000000 -4444440000 -4440000000"; - TestHelpers.AssertDriverAttributesAre (expectedColor, Application.Driver, attributes); - - Application.End (rs); - top.Dispose (); - } - - [Fact] - public void RuneCell_LoadRuneCells_Without_ColorScheme_Is_Never_Null () - { - List cells = new () - { - new() { Rune = new ('T') }, - new() { Rune = new ('e') }, - new() { Rune = new ('s') }, - new() { Rune = new ('t') } - }; - TextView tv = CreateTextView (); - var top = new Toplevel (); - top.Add (tv); - tv.Load (cells); - - for (var i = 0; i < tv.Lines; i++) - { - List line = tv.GetLine (i); - - foreach (RuneCell rc in line) - { - Assert.NotNull (rc.ColorScheme); - } - } - } - - [Fact] - [AutoInitShutdown] - public void RuneCellEventArgs_WordWrap_True () - { - var eventCount = 0; - - List> text = new () - { - TextModel.ToRuneCells ( - "This is the first line.".ToRunes () - ), - TextModel.ToRuneCells ( - "This is the second line.".ToRunes () - ) - }; - TextView tv = CreateTextView (); - tv.DrawNormalColor += _textView_DrawColor; - tv.DrawReadOnlyColor += _textView_DrawColor; - tv.DrawSelectionColor += _textView_DrawColor; - tv.DrawUsedColor += _textView_DrawColor; - - void _textView_DrawColor (object sender, RuneCellEventArgs e) - { - Assert.Equal (e.Line [e.Col], text [e.UnwrappedPosition.Row] [e.UnwrappedPosition.Col]); - eventCount++; - } - - tv.Text = $"{TextModel.ToString (text [0])}\n{TextModel.ToString (text [1])}\n"; - Assert.False (tv.WordWrap); - var top = new Toplevel (); - top.Add (tv); - Application.Begin (top); - - TestHelpers.AssertDriverContentsWithFrameAre ( - @" -This is the first line. -This is the second line.", - output - ); - - tv.Width = 10; - tv.Height = 25; - tv.WordWrap = true; - Application.Refresh (); - - TestHelpers.AssertDriverContentsWithFrameAre ( - @" -This is -the -first -line. -This is -the -second -line. ", - output - ); - - Assert.Equal (eventCount, (text [0].Count + text [1].Count) * 2); - top.Dispose (); - } - - [Fact] - public void ToString_Override () - { - var rc1 = new RuneCell (); - - var rc2 = new RuneCell - { - Rune = new ('a'), ColorScheme = new() { Normal = new (Color.Red) } - }; - Assert.Equal ("U+0000 '\0'; null", rc1.ToString ()); - - Assert.Equal ( - "U+0061 'a'; Normal: [Red,Red]; Focus: [White,Black]; HotNormal: [White,Black]; HotFocus: [White,Black]; Disabled: [White,Black]", - rc2.ToString () - ); - } - - // TODO: Move the tests below to View or Color - they test ColorScheme, not RuneCell primitives. - private TextView CreateTextView () { return new() { Width = 30, Height = 10 }; } -} diff --git a/UnitTests/Views/TextFieldTests.cs b/UnitTests/Views/TextFieldTests.cs index 5c81dffcad..4e1fec138b 100644 --- a/UnitTests/Views/TextFieldTests.cs +++ b/UnitTests/Views/TextFieldTests.cs @@ -2108,4 +2108,18 @@ public void Autocomplete_Visible_False_By_Default () Assert.True (t.Visible); Assert.False (t.Autocomplete.Visible); } + + [Fact] + [AutoInitShutdown] + public void Draw_Esc_Rune () + { + var tf = new TextField { Width = 5, Text = "\u001b" }; + tf.BeginInit (); + tf.EndInit (); + tf.Draw (); + + TestHelpers.AssertDriverContentsWithFrameAre ("\u241b", output); + + tf.Dispose (); + } } diff --git a/UnitTests/Views/TextViewTests.cs b/UnitTests/Views/TextViewTests.cs index 0c2bbe12f2..a05af9ef07 100644 --- a/UnitTests/Views/TextViewTests.cs +++ b/UnitTests/Views/TextViewTests.cs @@ -1160,7 +1160,7 @@ public void HistoryText_Exceptions () { Assert.Throws ( () => ht.Add ( - new List> { new () }, + new List> { new () }, Point.Empty, (HistoryText.LineStatus)ls ) @@ -1168,7 +1168,7 @@ public void HistoryText_Exceptions () } } - Assert.Null (Record.Exception (() => ht.Add (new List> { new () }, Point.Empty))); + Assert.Null (Record.Exception (() => ht.Add (new List> { new () }, Point.Empty))); } [Fact] @@ -4713,11 +4713,98 @@ public void HistoryText_Undo_Redo_Two_Line_Selected_Return () Assert.Equal (new Point (0, 1), tv.CursorPosition); } + [Fact] + public void HistoryText_Undo_Redo_ApplyCellsAttribute () + { + var text = "This is the first line.\nThis is the second line.\nThis is the third line."; + var tv = new TextView { Text = text }; + + tv.SelectionStartColumn = 12; + tv.CursorPosition = new Point (18, 1); + + if (Environment.NewLine.Length == 2) + { + Assert.Equal (31, tv.SelectedLength); + } + else + { + Assert.Equal (30, tv.SelectedLength); + } + Assert.Equal ($"first line.{Environment.NewLine}This is the second", tv.SelectedText); + Assert.Equal ($"first line.{Environment.NewLine}This is the second", Cell.ToString (tv.SelectedCellsList)); + Assert.Equal (new Point (18, 1), tv.CursorPosition); + Assert.False (tv.IsDirty); + + AssertNullAttribute (); + + tv.ApplyCellsAttribute (new (Color.Red, Color.Green)); + + AssertRedGreenAttribute (); + + Assert.Equal (0, tv.SelectedLength); + Assert.Equal ("", tv.SelectedText); + Assert.Equal ($"first line.{Environment.NewLine}This is the second", Cell.ToString (tv.SelectedCellsList)); + Assert.Equal (new Point (18, 1), tv.CursorPosition); + Assert.True (tv.IsDirty); + + // Undo + Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl)); + + AssertNullAttribute (); + + Assert.Equal (12, tv.SelectionStartColumn); + Assert.Equal (0, tv.SelectionStartRow); + Assert.Equal (0, tv.SelectedLength); + Assert.Equal ("", tv.SelectedText); + Assert.Empty (tv.SelectedCellsList); + Assert.Equal (new Point (12, 0), tv.CursorPosition); + Assert.False (tv.IsDirty); + + // Redo + Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl)); + + AssertRedGreenAttribute (); + + Assert.Equal (12, tv.SelectionStartColumn); + Assert.Equal (0, tv.SelectionStartRow); + Assert.Equal (0, tv.SelectedLength); + Assert.Equal ("", tv.SelectedText); + Assert.Empty (tv.SelectedCellsList); + Assert.Equal (new Point (12, 0), tv.CursorPosition); + Assert.True (tv.IsDirty); + + void AssertNullAttribute () + { + tv.GetRegion (out List> region, 0, 12, 1, 18); + + foreach (List cells in region) + { + foreach (Cell cell in cells) + { + Assert.Null (cell.Attribute); + } + } + } + + void AssertRedGreenAttribute () + { + tv.GetRegion (out List> region, 0, 12, 1, 18); + + foreach (List cells in region) + { + foreach (Cell cell in cells) + { + Assert.Equal ("[Red,Green]", cell.Attribute.ToString ()); + } + } + } + } + [Fact] public void Internal_Tests () { var txt = "This is a text."; - List txtRunes = TextModel.StringToRuneCells (txt); + List txtRunes = Cell.StringToCells (txt); Assert.Equal (txt.Length, txtRunes.Count); Assert.Equal ('T', txtRunes [0].Rune.Value); Assert.Equal ('h', txtRunes [1].Rune.Value); @@ -4757,8 +4844,8 @@ public void Internal_Tests () Assert.Equal (2, TextModel.CalculateLeftColumn (txtRunes, 0, 9, 8)); var tm = new TextModel (); - tm.AddLine (0, TextModel.StringToRuneCells ("This is first line.")); - tm.AddLine (1, TextModel.StringToRuneCells ("This is last line.")); + tm.AddLine (0, Cell.StringToCells ("This is first line.")); + tm.AddLine (1, Cell.StringToCells ("This is last line.")); Assert.Equal ((new Point (2, 0), true), tm.FindNextText ("is", out bool gaveFullTurn)); Assert.False (gaveFullTurn); Assert.Equal ((new Point (5, 0), true), tm.FindNextText ("is", out gaveFullTurn)); @@ -4782,14 +4869,14 @@ public void Internal_Tests () Assert.True (gaveFullTurn); Assert.Equal ((new Point (9, 1), true), tm.ReplaceAllText ("is", false, false, "really")); - Assert.Equal (TextModel.StringToRuneCells ("Threally really first line."), tm.GetLine (0)); - Assert.Equal (TextModel.StringToRuneCells ("Threally really last line."), tm.GetLine (1)); + Assert.Equal (Cell.StringToCells ("Threally really first line."), tm.GetLine (0)); + Assert.Equal (Cell.StringToCells ("Threally really last line."), tm.GetLine (1)); tm = new TextModel (); - tm.AddLine (0, TextModel.StringToRuneCells ("This is first line.")); - tm.AddLine (1, TextModel.StringToRuneCells ("This is last line.")); + tm.AddLine (0, Cell.StringToCells ("This is first line.")); + tm.AddLine (1, Cell.StringToCells ("This is last line.")); Assert.Equal ((new Point (5, 1), true), tm.ReplaceAllText ("is", false, true, "really")); - Assert.Equal (TextModel.StringToRuneCells ("This really first line."), tm.GetLine (0)); - Assert.Equal (TextModel.StringToRuneCells ("This really last line."), tm.GetLine (1)); + Assert.Equal (Cell.StringToCells ("This really first line."), tm.GetLine (0)); + Assert.Equal (Cell.StringToCells ("This really last line."), tm.GetLine (1)); } [Fact] @@ -6273,6 +6360,7 @@ public void Selected_Text_Shows () // TAB to jump between text fields. TestHelpers.AssertDriverAttributesAre ("0000000", Application.Driver, attributes); + Assert.Empty (_textView.SelectedCellsList); _textView.NewKeyDownEvent (Key.CursorRight.WithCtrl.WithShift); @@ -6282,6 +6370,7 @@ public void Selected_Text_Shows () // TAB to jump between text fields. TestHelpers.AssertDriverAttributesAre ("1111000", Application.Driver, attributes); + Assert.Equal ("TAB ", Cell.ToString (_textView.SelectedCellsList [^1])); top.Dispose (); } @@ -8699,4 +8788,264 @@ public void Autocomplete_Visible_False_By_Default () Assert.True (t.Visible); Assert.False (t.Autocomplete.Visible); } + + [Fact] + [AutoInitShutdown] + public void Draw_Esc_Rune () + { + var tv = new TextView { Width = 5, Height = 1, Text = "\u001b" }; + tv.BeginInit (); + tv.EndInit (); + tv.Draw (); + + TestHelpers.AssertDriverContentsWithFrameAre ("\u241b", _output); + + tv.Dispose (); + } + + [Fact] + public void Equals_True () + { + var c1 = new Cell (); + var c2 = new Cell (); + Assert.True (c1.Equals (c2)); + Assert.True (c2.Equals (c1)); + + c1.Rune = new ('a'); + c1.Attribute = new (); + c2.Rune = new ('a'); + c2.Attribute = new (); + Assert.True (c1.Equals (c2)); + Assert.True (c2.Equals (c1)); + } + + [Fact] + [AutoInitShutdown] + public void CellEventArgs_WordWrap_True () + { + var eventCount = 0; + + List> text = + [ + Cell.ToCells ( + "This is the first line.".ToRunes () + ), + + Cell.ToCells ( + "This is the second line.".ToRunes () + ) + ]; + TextView tv = CreateTextView (); + tv.DrawNormalColor += _textView_DrawColor; + tv.DrawReadOnlyColor += _textView_DrawColor; + tv.DrawSelectionColor += _textView_DrawColor; + tv.DrawUsedColor += _textView_DrawColor; + + void _textView_DrawColor (object sender, CellEventArgs e) + { + Assert.Equal (e.Line [e.Col], text [e.UnwrappedPosition.Row] [e.UnwrappedPosition.Col]); + eventCount++; + } + + tv.Text = $"{Cell.ToString (text [0])}\n{Cell.ToString (text [1])}\n"; + Assert.False (tv.WordWrap); + var top = new Toplevel (); + top.Add (tv); + Application.Begin (top); + + TestHelpers.AssertDriverContentsWithFrameAre ( + @" +This is the first line. +This is the second line.", + _output + ); + + tv.Width = 10; + tv.Height = 25; + tv.WordWrap = true; + Application.Refresh (); + + TestHelpers.AssertDriverContentsWithFrameAre ( + @" +This is +the +first +line. +This is +the +second +line. ", + _output + ); + + Assert.Equal (eventCount, (text [0].Count + text [1].Count) * 2); + top.Dispose (); + } + + [Fact] + [AutoInitShutdown (configLocation: ConfigurationManager.ConfigLocations.DefaultOnly)] + public void Cell_LoadCells_InheritsPreviousAttribute () + { + List cells = []; + + foreach (KeyValuePair color in Colors.ColorSchemes) + { + string csName = color.Key; + + foreach (Rune rune in csName.EnumerateRunes ()) + { + cells.Add (new () { Rune = rune, Attribute = color.Value.Normal }); + } + + cells.Add (new () { Rune = (Rune)'\n', Attribute = color.Value.Focus }); + } + + TextView tv = CreateTextView (); + tv.Load (cells); + var top = new Toplevel (); + top.Add (tv); + RunState rs = Application.Begin (top); + Assert.True (tv.InheritsPreviousAttribute); + + var expectedText = @" +TopLevel +Base +Dialog +Menu +Error "; + TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); + + Attribute [] attributes = + { + // 0 + Colors.ColorSchemes ["TopLevel"].Normal, + + // 1 + Colors.ColorSchemes ["Base"].Normal, + + // 2 + Colors.ColorSchemes ["Dialog"].Normal, + + // 3 + Colors.ColorSchemes ["Menu"].Normal, + + // 4 + Colors.ColorSchemes ["Error"].Normal, + + // 5 + tv.ColorScheme!.Focus + }; + + var expectedColor = @" +0000000055 +1111555555 +2222225555 +3333555555 +4444455555"; + TestHelpers.AssertDriverAttributesAre (expectedColor, Application.Driver, attributes); + + tv.WordWrap = true; + Application.Refresh (); + TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); + TestHelpers.AssertDriverAttributesAre (expectedColor, Application.Driver, attributes); + + tv.CursorPosition = new (6, 2); + tv.SelectionStartColumn = 0; + tv.SelectionStartRow = 0; + Assert.Equal ($"TopLevel{Environment.NewLine}Base{Environment.NewLine}Dialog", tv.SelectedText); + tv.Copy (); + tv.IsSelecting = false; + tv.CursorPosition = new (2, 4); + tv.Paste (); + Application.Refresh (); + + expectedText = @" +TopLevel +Base +Dialog +Menu +ErTopLevel +Base +Dialogror "; + TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); + + expectedColor = @" +0000000055 +1111555555 +2222225555 +3333555555 +4400000000 +1111555555 +2222224445"; + TestHelpers.AssertDriverAttributesAre (expectedColor, Application.Driver, attributes); + + tv.Undo (); + tv.CursorPosition = new (0, 3); + tv.SelectionStartColumn = 0; + tv.SelectionStartRow = 0; + + Assert.Equal ( + $"TopLevel{Environment.NewLine}Base{Environment.NewLine}Dialog{Environment.NewLine}", + tv.SelectedText + ); + tv.Copy (); + tv.IsSelecting = false; + tv.CursorPosition = new (2, 4); + tv.Paste (); + Application.Refresh (); + + expectedText = @" +TopLevel +Base +Dialog +Menu +ErTopLevel +Base +Dialog +ror "; + TestHelpers.AssertDriverContentsWithFrameAre (expectedText, _output); + + expectedColor = @" +0000000055 +1111555555 +2222225555 +3333555555 +4400000000 +1111555555 +2222225555 +4445555555"; + TestHelpers.AssertDriverAttributesAre (expectedColor, Application.Driver, attributes); + + Application.End (rs); + top.Dispose (); + } + + [Fact] + public void Cell_LoadCells_Without_ColorScheme_Is_Never_Null () + { + List cells = new () + { + new() { Rune = new ('T') }, + new() { Rune = new ('e') }, + new() { Rune = new ('s') }, + new() { Rune = new ('t') } + }; + TextView tv = CreateTextView (); + var top = new Toplevel (); + top.Add (tv); + tv.Load (cells); + + for (var i = 0; i < tv.Lines; i++) + { + List line = tv.GetLine (i); + + foreach (Cell c in line) + { + Assert.NotNull (c.Attribute); + } + } + } + + private TextView CreateTextView () { return new () { Width = 30, Height = 10 }; } + } diff --git a/UnitTests/Views/TreeViewTests.cs b/UnitTests/Views/TreeViewTests.cs index 56f3805a0c..507ca35418 100644 --- a/UnitTests/Views/TreeViewTests.cs +++ b/UnitTests/Views/TreeViewTests.cs @@ -970,10 +970,10 @@ public void TestTreeView_DrawLineEvent () Assert.All (eventArgs, ea => Assert.Equal (ea.Tree, tv)); Assert.All (eventArgs, ea => Assert.False (ea.Handled)); - Assert.Equal ("├-root one", eventArgs [0].RuneCells.Aggregate ("", (s, n) => s += n.Rune).TrimEnd ()); - Assert.Equal ("│ ├─leaf 1", eventArgs [1].RuneCells.Aggregate ("", (s, n) => s += n.Rune).TrimEnd ()); - Assert.Equal ("│ └─leaf 2", eventArgs [2].RuneCells.Aggregate ("", (s, n) => s += n.Rune).TrimEnd ()); - Assert.Equal ("└─root two", eventArgs [3].RuneCells.Aggregate ("", (s, n) => s += n.Rune).TrimEnd ()); + Assert.Equal ("├-root one", eventArgs [0].Cells.Aggregate ("", (s, n) => s += n.Rune).TrimEnd ()); + Assert.Equal ("│ ├─leaf 1", eventArgs [1].Cells.Aggregate ("", (s, n) => s += n.Rune).TrimEnd ()); + Assert.Equal ("│ └─leaf 2", eventArgs [2].Cells.Aggregate ("", (s, n) => s += n.Rune).TrimEnd ()); + Assert.Equal ("└─root two", eventArgs [3].Cells.Aggregate ("", (s, n) => s += n.Rune).TrimEnd ()); Assert.Equal (1, eventArgs [0].IndexOfExpandCollapseSymbol); Assert.Equal (3, eventArgs [1].IndexOfExpandCollapseSymbol); @@ -1083,9 +1083,9 @@ oot two Assert.All (eventArgs, ea => Assert.Equal (ea.Tree, tv)); Assert.All (eventArgs, ea => Assert.False (ea.Handled)); - Assert.Equal ("─leaf 1", eventArgs [0].RuneCells.Aggregate ("", (s, n) => s += n.Rune).TrimEnd ()); - Assert.Equal ("─leaf 2", eventArgs [1].RuneCells.Aggregate ("", (s, n) => s += n.Rune).TrimEnd ()); - Assert.Equal ("oot two", eventArgs [2].RuneCells.Aggregate ("", (s, n) => s += n.Rune).TrimEnd ()); + Assert.Equal ("─leaf 1", eventArgs [0].Cells.Aggregate ("", (s, n) => s += n.Rune).TrimEnd ()); + Assert.Equal ("─leaf 2", eventArgs [1].Cells.Aggregate ("", (s, n) => s += n.Rune).TrimEnd ()); + Assert.Equal ("oot two", eventArgs [2].Cells.Aggregate ("", (s, n) => s += n.Rune).TrimEnd ()); Assert.Equal (0, eventArgs [0].IndexOfExpandCollapseSymbol); Assert.Equal (0, eventArgs [1].IndexOfExpandCollapseSymbol);