From e7847650943afb4dbb47a43ab2b67e6f34cf7a61 Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 13 Apr 2021 21:37:13 +0100 Subject: [PATCH] Fixes #1179. TextView does not copy to the clipboard on deleting. (#1180) * Fixes #1133. Flaw in LayoutSubviews/TopologicalSort. * Toplevel can't be used on Pos/Dim but only his subviews. Was not caught before because the LayoutSubviews method never gone so deep before. * Fixed the error that is triggered when the Pos/Dim is the current Application.Top. * Application.Top is the only exception in the TopologicalSort method check. * Fixes #1179. TextView does not copy to the clipboard on deleting. * Added Button DoubleClick and fixed WordForward/WordBackward issues. * Prevents a negative height. * Fixes the enter key line feed. * Fixes #1187. Prevents WordBackward throwing an exception if point is greater than the text length. * Fixes #1189. Prevents negative index. * Fixes #1193. A non auto size default Button now preserves his width and thus the text alignment now work. * Fixing the Width and Height checks of the Dim class with AutoSize dependence. * Fixes #1197. Prevents width negative value if added directly to the Application.Top * Fixes #1199. Normalize views constructors and did some typo fixing. * Fixing the Application.Top Pos/Dim settings. * Always uses inverted color for selected text to avoid same colors. * Prevents throw an exception if the clipboard content is null. * Added Find and Replace (next/previous). Replace All and Select All. A non modal dialog box. * Keeps tracking the selected replaced text. * Fixes #1202. CheckBox now deals with a functional '_' underscore hotkey. * The selected text should be maintained when losing focus. * Fixes an extra line on page down. * Fixes the WordBackward if it text has more than one whitespaces or when has only one digit or letter. * Fixes WordForward/WordBackward on text with more than one whitespace or with only one digit or letter. * Forgot to replace the hacking. * Added unit tests for the TextField view. Fixed some more bugs. * Redraw should only show the selected text if it is focused. * Fixes cursor position on double click and ensures the setting of the selected text. * Added match whole word checking. * Added missing parameters documentation. * Ensures the SelectedLength property to be always with positive value. * Fixes the WordBackward when at the end of the text has a character between two whitespace. * Added unit tests to the TextView, Used property and fixed some more bugs. * Fixed Used to only show if it has focus. * Fixed ReplaceAll and prevents Debug.Assert from showing. --- Terminal.Gui/Core/PosDim.cs | 10 +- Terminal.Gui/Core/TextFormatter.cs | 3 +- Terminal.Gui/Core/Toplevel.cs | 4 +- Terminal.Gui/Core/View.cs | 83 ++- Terminal.Gui/Views/Button.cs | 7 +- Terminal.Gui/Views/TextView.cs | 697 +++++++++++++++++++++--- UICatalog/Scenarios/Dialogs.cs | 9 +- UICatalog/Scenarios/Editor.cs | 369 ++++++++++++- UICatalog/Scenarios/MessageBoxes.cs | 9 +- UnitTests/DimTests.cs | 84 ++- UnitTests/PosTests.cs | 86 ++- UnitTests/TextViewTests.cs | 797 ++++++++++++++++++++++++++++ 12 files changed, 2046 insertions(+), 112 deletions(-) create mode 100644 UnitTests/TextViewTests.cs diff --git a/Terminal.Gui/Core/PosDim.cs b/Terminal.Gui/Core/PosDim.cs index 86c43ebf5..40abbb6ca 100644 --- a/Terminal.Gui/Core/PosDim.cs +++ b/Terminal.Gui/Core/PosDim.cs @@ -36,7 +36,7 @@ namespace Terminal.Gui { return 0; } - class PosFactor : Pos { + internal class PosFactor : Pos { float factor; public PosFactor (float n) @@ -82,7 +82,7 @@ namespace Terminal.Gui { static PosAnchorEnd endNoMargin; - class PosAnchorEnd : Pos { + internal class PosAnchorEnd : Pos { int n; public PosAnchorEnd (int n) @@ -205,8 +205,8 @@ namespace Terminal.Gui { return new PosAbsolute (n); } - class PosCombine : Pos { - Pos left, right; + internal class PosCombine : Pos { + internal Pos left, right; bool add; public PosCombine (bool add, Pos left, Pos right) { @@ -500,7 +500,7 @@ namespace Terminal.Gui { } internal class DimCombine : Dim { - Dim left, right; + internal Dim left, right; bool add; public DimCombine (bool add, Dim left, Dim right) { diff --git a/Terminal.Gui/Core/TextFormatter.cs b/Terminal.Gui/Core/TextFormatter.cs index 384544534..f904f2099 100644 --- a/Terminal.Gui/Core/TextFormatter.cs +++ b/Terminal.Gui/Core/TextFormatter.cs @@ -625,7 +625,8 @@ namespace Terminal.Gui { Application.Driver?.AddRune (rune); } col += Rune.ColumnWidth (rune); - if (idx + 1 < runes.Length && col + Rune.ColumnWidth (runes [idx + 1]) > bounds.Width) { + if (idx + 1 > - 1 && idx + 1 < runes.Length && col + + Rune.ColumnWidth (runes [idx + 1]) > bounds.Width) { break; } } diff --git a/Terminal.Gui/Core/Toplevel.cs b/Terminal.Gui/Core/Toplevel.cs index 69f75e3a3..b3c0d208f 100644 --- a/Terminal.Gui/Core/Toplevel.cs +++ b/Terminal.Gui/Core/Toplevel.cs @@ -370,7 +370,7 @@ namespace Terminal.Gui { l = SuperView.Frame.Width; } nx = nx + top.Frame.Width > l ? Math.Max (l - top.Frame.Width, 0) : nx; - SetWidth (top.Frame.Width, out int rWidth); + SetWidth (top.Frame.Width, out int rWidth, out _); if (rWidth < 0 && nx >= top.Frame.X) { nx = Math.Max (top.Frame.Right - 2, 0); } @@ -399,7 +399,7 @@ namespace Terminal.Gui { } ny = Math.Min (ny, l); ny = ny + top.Frame.Height > l ? Math.Max (l - top.Frame.Height, m ? 1 : 0) : ny; - SetHeight (top.Frame.Height, out int rHeight); + SetHeight (top.Frame.Height, out int rHeight, out _); if (rHeight < 0 && ny >= top.Frame.Y) { ny = Math.Max (top.Frame.Bottom - 2, 0); } diff --git a/Terminal.Gui/Core/View.cs b/Terminal.Gui/Core/View.cs index 85610276a..53a95ed1c 100644 --- a/Terminal.Gui/Core/View.cs +++ b/Terminal.Gui/Core/View.cs @@ -1753,7 +1753,7 @@ namespace Terminal.Gui { var result = new List (); // Set of all nodes with no incoming edges - var S = new HashSet (nodes.Where (n => edges.All (e => e.To.Equals (n) == false))); + var S = new HashSet (nodes.Where (n => edges.All (e => !e.To.Equals (n)))); while (S.Any ()) { // remove a node n from S @@ -1772,15 +1772,15 @@ namespace Terminal.Gui { edges.Remove (e); // if m has no other incoming edges then - if (edges.All (me => me.To.Equals (m) == false) && m != this?.SuperView) { + if (edges.All (me => !me.To.Equals (m)) && m != this?.SuperView) { // insert m into S S.Add (m); } } } - if (edges.Any ()) { - if (!object.ReferenceEquals (edges.First ().From, edges.First ().To)) { + if (edges.Any () && edges.First ().From != Application.Top) { + if (!ReferenceEquals (edges.First ().From, edges.First ().To)) { throw new InvalidOperationException ($"TopologicalSort (for Pos/Dim) cannot find {edges.First ().From}. Did you forget to add it to {this}?"); } else { throw new InvalidOperationException ("TopologicalSort encountered a recursive cycle in the relative Pos/Dim in the views of " + this); @@ -1863,20 +1863,60 @@ namespace Terminal.Gui { var nodes = new HashSet (); var edges = new HashSet<(View, View)> (); - foreach (var v in InternalSubviews) { - nodes.Add (v); - if (v.LayoutStyle == LayoutStyle.Computed) { - if (v.X is Pos.PosView vX) - edges.Add ((vX.Target, v)); - if (v.Y is Pos.PosView vY) - edges.Add ((vY.Target, v)); - if (v.Width is Dim.DimView vWidth) - edges.Add ((vWidth.Target, v)); - if (v.Height is Dim.DimView vHeight) - edges.Add ((vHeight.Target, v)); + void CollectPos (Pos pos, View from, ref HashSet nNodes, ref HashSet<(View, View)> nEdges) + { + if (pos is Pos.PosView pv) { + if (pv.Target != this) { + nEdges.Add ((pv.Target, from)); + } + foreach (var v in from.InternalSubviews) { + CollectAll (v, ref nNodes, ref nEdges); + } + return; + } + if (pos is Pos.PosCombine pc) { + foreach (var v in from.InternalSubviews) { + CollectPos (pc.left, from, ref nNodes, ref nEdges); + CollectPos (pc.right, from, ref nNodes, ref nEdges); + } } } + void CollectDim (Dim dim, View from, ref HashSet nNodes, ref HashSet<(View, View)> nEdges) + { + if (dim is Dim.DimView dv) { + if (dv.Target != this) { + nEdges.Add ((dv.Target, from)); + } + foreach (var v in from.InternalSubviews) { + CollectAll (v, ref nNodes, ref nEdges); + } + return; + } + if (dim is Dim.DimCombine dc) { + foreach (var v in from.InternalSubviews) { + CollectDim (dc.left, from, ref nNodes, ref nEdges); + CollectDim (dc.right, from, ref nNodes, ref nEdges); + } + } + } + + void CollectAll (View from, ref HashSet nNodes, ref HashSet<(View, View)> nEdges) + { + foreach (var v in from.InternalSubviews) { + nNodes.Add (v); + if (v.layoutStyle != LayoutStyle.Computed) { + continue; + } + CollectPos (v.X, v, ref nNodes, ref nEdges); + CollectPos (v.Y, v, ref nNodes, ref nEdges); + CollectDim (v.Width, v, ref nNodes, ref nEdges); + CollectDim (v.Height, v, ref nNodes, ref nEdges); + } + } + + CollectAll (this, ref nodes, ref edges); + var ordered = TopologicalSort (nodes, edges); foreach (var v in ordered) { @@ -1886,7 +1926,6 @@ namespace Terminal.Gui { v.LayoutSubviews (); v.LayoutNeeded = false; - } if (SuperView == Application.Top && LayoutNeeded && ordered.Count == 0 && LayoutStyle == LayoutStyle.Computed) { @@ -2154,14 +2193,17 @@ namespace Terminal.Gui { /// /// The desired width. /// The real result width. + /// The real current width. /// True if the width can be directly assigned, false otherwise. - public bool SetWidth (int desiredWidth, out int resultWidth) + public bool SetWidth (int desiredWidth, out int resultWidth, out int currentWidth) { int w = desiredWidth; + currentWidth = Width != null ? Width.Anchor (0) : 0; bool canSetWidth; if (Width is Dim.DimCombine || Width is Dim.DimView || Width is Dim.DimFill) { // It's a Dim.DimCombine and so can't be assigned. Let it have it's width anchored. w = Width.Anchor (w); + currentWidth = Width.Anchor (w); canSetWidth = false; } else if (Width is Dim.DimFactor factor) { // Tries to get the SuperView width otherwise the view width. @@ -2170,6 +2212,7 @@ namespace Terminal.Gui { sw -= Frame.X; } w = Width.Anchor (sw); + currentWidth = Width.Anchor (sw); canSetWidth = false; } else { canSetWidth = true; @@ -2184,14 +2227,17 @@ namespace Terminal.Gui { /// /// The desired height. /// The real result height. + /// The real current height. /// True if the height can be directly assigned, false otherwise. - public bool SetHeight (int desiredHeight, out int resultHeight) + public bool SetHeight (int desiredHeight, out int resultHeight, out int currentHeight) { int h = desiredHeight; + currentHeight = Height != null ? Height.Anchor (0) : 0; bool canSetHeight; if (Height is Dim.DimCombine || Height is Dim.DimView || Height is Dim.DimFill) { // It's a Dim.DimCombine and so can't be assigned. Let it have it's height anchored. h = Height.Anchor (h); + currentHeight = Height.Anchor (h); canSetHeight = false; } else if (Height is Dim.DimFactor factor) { // Tries to get the SuperView height otherwise the view height. @@ -2200,6 +2246,7 @@ namespace Terminal.Gui { sh -= Frame.Y; } h = Height.Anchor (sh); + currentHeight = Height.Anchor (sh); canSetHeight = false; } else { canSetHeight = true; diff --git a/Terminal.Gui/Views/Button.cs b/Terminal.Gui/Views/Button.cs index 7d71e778f..f0685f636 100644 --- a/Terminal.Gui/Views/Button.cs +++ b/Terminal.Gui/Views/Button.cs @@ -146,10 +146,13 @@ namespace Terminal.Gui { base.Text = ustring.Make (_leftBracket) + " " + text + " " + ustring.Make (_rightBracket); int w = base.Text.RuneCount - (base.Text.Contains (HotKeySpecifier) ? 1 : 0); - if (SetWidth (w, out int rWidth)) { + var canSetWidth = SetWidth (w, out int rWidth, out int cWidth); + if (canSetWidth && (cWidth < rWidth || AutoSize)) { Width = rWidth; + w = rWidth; + } else if (!canSetWidth || !AutoSize) { + w = cWidth; } - w = rWidth; var layout = LayoutStyle; bool layoutChanged = false; if (!(Height is Dim.DimAbsolute)) { diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs index f2dfc96b7..85c170f34 100644 --- a/Terminal.Gui/Views/TextView.cs +++ b/Terminal.Gui/Views/TextView.cs @@ -279,6 +279,184 @@ namespace Terminal.Gui { } return col; } + + (Point startPointToFind, Point currentPointToFind, bool found) toFind; + + internal (Point current, bool found) FindNextText (ustring text, out bool gaveFullTurn, bool matchCase = false, bool matchWholeWord = false) + { + if (text == null || lines.Count == 0) { + gaveFullTurn = false; + return (Point.Empty, false); + } + + if (toFind.found) { + toFind.currentPointToFind.X++; + } + var foundPos = GetFoundNextTextPoint (text, lines.Count, matchCase, matchWholeWord, toFind.currentPointToFind); + if (!foundPos.found && toFind.currentPointToFind != toFind.startPointToFind) { + foundPos = GetFoundNextTextPoint (text, toFind.startPointToFind.Y + 1, matchCase, matchWholeWord, Point.Empty); + } + gaveFullTurn = ApplyToFind (foundPos); + + return foundPos; + } + + internal (Point current, bool found) FindPreviousText (ustring text, out bool gaveFullTurn, bool matchCase = false, bool matchWholeWord = false) + { + if (text == null || lines.Count == 0) { + gaveFullTurn = false; + return (Point.Empty, false); + } + + if (toFind.found) { + toFind.currentPointToFind.X++; + } + var foundPos = GetFoundPreviousTextPoint (text, toFind.currentPointToFind.Y, matchCase, matchWholeWord, toFind.currentPointToFind); + if (!foundPos.found && toFind.currentPointToFind != toFind.startPointToFind) { + foundPos = GetFoundPreviousTextPoint (text, lines.Count - 1, matchCase, matchWholeWord, + new Point (lines [lines.Count - 1].Count, lines.Count)); + } + gaveFullTurn = ApplyToFind (foundPos); + + return foundPos; + } + + internal (Point current, bool found) ReplaceAllText (ustring text, bool matchCase = false, bool matchWholeWord = false, ustring textToReplace = null) + { + bool found = false; + Point pos = Point.Empty; + + for (int i = 0; i < lines.Count; i++) { + var x = lines [i]; + var txt = ustring.Make (x).ToString (); + if (!matchCase) { + txt = txt.ToUpper (); + } + var matchText = !matchCase ? text.ToUpper ().ToString () : text.ToString (); + var col = txt.IndexOf (matchText); + if (col > -1 && matchWholeWord && !MatchWholeWord (txt, matchText, col)) { + continue; + } + if (col > -1) { + if (!found) { + found = true; + } + pos = new Point (col, i); + lines [i] = ReplaceText (x, textToReplace, matchText, col).ToRuneList (); + i--; + } + } + + return (pos, found); + } + + ustring ReplaceText (List source, ustring textToReplace, string matchText, int col) + { + var origTxt = ustring.Make (source); + (int _, int len) = TextModel.DisplaySize (source, 0, col, false); + (var _, var len2) = TextModel.DisplaySize (source, col, col + matchText.Length, false); + (var _, var len3) = TextModel.DisplaySize (source, col + matchText.Length, origTxt.RuneCount, false); + + return origTxt [0, len] + + textToReplace.ToString () + + origTxt [len + len2, len + len2 + len3]; + } + + bool ApplyToFind ((Point current, bool found) foundPos) + { + bool gaveFullTurn = false; + if (foundPos.found) { + toFind.currentPointToFind = foundPos.current; + if (toFind.found && toFind.currentPointToFind == toFind.startPointToFind) { + gaveFullTurn = true; + } + if (!toFind.found) { + toFind.startPointToFind = toFind.currentPointToFind = foundPos.current; + toFind.found = foundPos.found; + } + } + + return gaveFullTurn; + } + + (Point current, bool found) GetFoundNextTextPoint (ustring text, int linesCount, bool matchCase, bool matchWholeWord, Point start) + { + for (int i = start.Y; i < linesCount; i++) { + var x = lines [i]; + var txt = ustring.Make (x).ToString (); + if (!matchCase) { + txt = txt.ToUpper (); + } + var matchText = !matchCase ? text.ToUpper ().ToString () : text.ToString (); + var col = txt.IndexOf (matchText, Math.Min (start.X, txt.Length)); + if (col > -1 && matchWholeWord && !MatchWholeWord (txt, matchText, col)) { + continue; + } + if (col > -1 && ((i == start.Y && col >= start.X) + || i > start.Y) + && txt.Contains (matchText)) { + return (new Point (col, i), true); + } else if (col == -1 && start.X > 0) { + start.X = 0; + } + } + + return (Point.Empty, false); + } + + (Point current, bool found) GetFoundPreviousTextPoint (ustring text, int linesCount, bool matchCase, bool matchWholeWord, Point start) + { + for (int i = linesCount; i >= 0; i--) { + var x = lines [i]; + var txt = ustring.Make (x).ToString (); + if (!matchCase) { + txt = txt.ToUpper (); + } + if (start.Y != i) { + start.X = Math.Max (x.Count - 1, 0); + } + var matchText = !matchCase ? text.ToUpper ().ToString () : text.ToString (); + var col = txt.LastIndexOf (matchText, start.X); + if (col > -1 && matchWholeWord && !MatchWholeWord (txt, matchText, col)) { + continue; + } + if (col > -1 && ((i == linesCount && col <= start.X) + || i < start.Y) + && txt.Contains (matchText)) { + return (new Point (col, i), true); + } + } + + return (Point.Empty, false); + } + + bool MatchWholeWord (string source, string matchText, int index = 0) + { + if (string.IsNullOrEmpty (source) || string.IsNullOrEmpty (matchText)) { + return false; + } + + var txt = matchText.Trim (); + var start = index > 0 ? index - 1 : 0; + var end = index + txt.Length; + + if ((start == 0 || Rune.IsWhiteSpace (source [start])) + && (end == source.Length || Rune.IsWhiteSpace (source [end]))) { + return true; + } + + return false; + } + + /// + /// Redefine column and line tracking. + /// + /// Contains the column and line. + internal void ResetContinuousFind (Point point) + { + toFind.startPointToFind = toFind.currentPointToFind = point; + toFind.found = false; + } } class WordWrapManager { @@ -645,9 +823,9 @@ namespace Terminal.Gui { int currentColumn; int selectionStartColumn, selectionStartRow; bool selecting; - //bool used; bool wordWrap; WordWrapManager wrapManager; + bool continuousFind; /// /// Raised when the of the changes. @@ -671,7 +849,7 @@ namespace Terminal.Gui { /// public TextView (Rect frame) : base (frame) { - CanFocus = true; + Initialize (); } /// @@ -680,9 +858,21 @@ namespace Terminal.Gui { /// public TextView () : base () { - CanFocus = true; + Initialize (); } + void Initialize () + { + CanFocus = true; + Used = true; + } + + /// + /// Tracks whether the text view should be considered "used", that is, that the user has moved in the entry, + /// so new input should be appended at the cursor position, rather than clearing the entry + /// + public bool Used { get; set; } + void ResetPosition () { topRow = leftColumn = currentRow = currentColumn = 0; @@ -763,6 +953,79 @@ namespace Terminal.Gui { /// public int Lines => model.Count; + /// + /// Sets or gets the current cursor position. + /// + public Point CursorPosition { + get => new Point (currentColumn, currentRow); + set { + var line = model.GetLine (Math.Max (Math.Min (value.Y, model.Count - 1), 0)); + currentColumn = value.X < 0 ? 0 : value.X > line.Count ? line.Count : value.X; + currentRow = value.Y < 0 ? 0 : value.Y > model.Count - 1 + ? Math.Max (model.Count - 1, 0) : value.Y; + SetNeedsDisplay (); + Adjust (); + } + } + + /// + /// Start column position of the selected text. + /// + public int SelectionStartColumn { + get => selectionStartColumn; + set { + var line = model.GetLine (currentRow); + selectionStartColumn = value < 0 ? 0 : value > line.Count ? line.Count : value; + selecting = true; + SetNeedsDisplay (); + Adjust (); + } + } + + /// + /// Start row position of the selected text. + /// + public int SelectionStartRow { + get => selectionStartRow; + set { + selectionStartRow = value < 0 ? 0 : value > model.Count - 1 + ? Math.Max (model.Count - 1, 0) : value; + selecting = true; + SetNeedsDisplay (); + Adjust (); + } + } + + /// + /// The selected text. + /// + public ustring SelectedText { + get { + if (!selecting || (model.Count == 1 && model.GetLine (0).Count == 0)) { + return ustring.Empty; + } + + SetWrapModel (); + var sel = GetRegion (); + UpdateWrapModel (); + Adjust (); + + return sel; + } + } + + /// + /// Length of the selected text. + /// + public int SelectedLength => GetSelectedLength (); + + /// + /// Get or sets the selecting. + /// + public bool Selecting { + get => selecting; + set => selecting = value; + } /// /// Allows word wrap the to fit the available container width. /// @@ -796,6 +1059,11 @@ namespace Terminal.Gui { /// public int RightOffset { get; set; } + int GetSelectedLength () + { + return SelectedText.Length; + } + CursorVisibility savedCursorVisibility = CursorVisibility.Default; void SaveCursorVisibility () @@ -884,7 +1152,7 @@ namespace Terminal.Gui { var col = 0; if (line.Count > 0) { retreat = Math.Max (SpecialRune (line [Math.Min (Math.Max (currentColumn - leftColumn - 1, 0), line.Count - 1)]) - ? 1 : 0, 0); + ? 1 : 0, 0); for (int idx = leftColumn < 0 ? 0 : leftColumn; idx < line.Count; idx++) { if (idx == currentColumn) break; @@ -892,7 +1160,7 @@ namespace Terminal.Gui { col += cols - 1; } } - var ccol = currentColumn - leftColumn - retreat + col; + var ccol = currentColumn - leftColumn + retreat + col; if (leftColumn <= currentColumn && ccol < Frame.Width && topRow <= currentRow && currentRow - topRow < Frame.Height) { ResetCursorVisibility (); @@ -921,6 +1189,11 @@ namespace Terminal.Gui { Driver.SetAttribute (ColorScheme.Focus); } + void ColorUsed () + { + Driver.SetAttribute (ColorScheme.HotFocus); + } + bool isReadOnly = false; /// @@ -1050,6 +1323,145 @@ namespace Terminal.Gui { SetNeedsDisplay (); } + /// + /// Select all text. + /// + public void SelectAll () + { + if (model.Count == 0) { + return; + } + + StartSelecting (); + selectionStartColumn = 0; + selectionStartRow = 0; + currentColumn = model.GetLine (model.Count - 1).Count; + currentRow = model.Count - 1; + SetNeedsDisplay (); + } + + /// + /// Find the next text based on the match case with the option to replace it. + /// + /// The text to find. + /// trueIf all the text was forward searched.falseotherwise. + /// The match case setting. + /// The match whole word setting. + /// The text to replace. + /// trueIf is replacing.falseotherwise. + /// trueIf the text was found.falseotherwise. + public bool FindNextText (ustring textToFind, out bool gaveFullTurn, bool matchCase = false, + bool matchWholeWord = false, ustring textToReplace = null, bool replace = false) + { + if (model.Count == 0) { + gaveFullTurn = false; + return false; + } + + SetWrapModel (); + ResetContinuousFind (); + var foundPos = model.FindNextText (textToFind, out gaveFullTurn, matchCase, matchWholeWord); + + return SetFoundText (textToFind, foundPos, textToReplace, replace); + } + + /// + /// Find the previous text based on the match case with the option to replace it. + /// + /// The text to find. + /// trueIf all the text was backward searched.falseotherwise. + /// The match case setting. + /// The match whole word setting. + /// The text to replace. + /// trueIf the text was found.falseotherwise. + /// trueIf the text was found.falseotherwise. + public bool FindPreviousText (ustring textToFind, out bool gaveFullTurn, bool matchCase = false, + bool matchWholeWord = false, ustring textToReplace = null, bool replace = false) + { + if (model.Count == 0) { + gaveFullTurn = false; + return false; + } + + SetWrapModel (); + ResetContinuousFind (); + var foundPos = model.FindPreviousText (textToFind, out gaveFullTurn, matchCase, matchWholeWord); + + return SetFoundText (textToFind, foundPos, textToReplace, replace); + } + + /// + /// Reset the flag to stop continuous find. + /// + public void FindTextChanged () + { + continuousFind = false; + } + + /// + /// Replaces all the text based on the match case. + /// + /// The text to find. + /// The match case setting. + /// The match whole word setting. + /// The text to replace. + /// trueIf the text was found.falseotherwise. + public bool ReplaceAllText (ustring textToFind, bool matchCase = false, bool matchWholeWord = false, + ustring textToReplace = null) + { + if (isReadOnly || model.Count == 0) { + return false; + } + + SetWrapModel (); + ResetContinuousFind (); + var foundPos = model.ReplaceAllText (textToFind, matchCase, matchWholeWord, textToReplace); + + return SetFoundText (textToFind, foundPos, textToReplace, false, true); + } + + bool SetFoundText (ustring text, (Point current, bool found) foundPos, + ustring textToReplace = null, bool replace = false, bool replaceAll = false) + { + if (foundPos.found) { + StartSelecting (); + selectionStartColumn = foundPos.current.X; + selectionStartRow = foundPos.current.Y; + if (!replaceAll) { + currentColumn = selectionStartColumn + text.RuneCount; + } else { + currentColumn = selectionStartColumn + textToReplace.RuneCount; + } + currentRow = foundPos.current.Y; + if (!isReadOnly && replace) { + Adjust (); + ClearSelectedRegion (); + InsertText (textToReplace); + StartSelecting (); + selectionStartColumn = currentColumn - textToReplace.RuneCount; + } else { + UpdateWrapModel (); + SetNeedsDisplay (); + Adjust (); + } + continuousFind = true; + return foundPos.found; + } + UpdateWrapModel (); + continuousFind = false; + + return foundPos.found; + } + + void ResetContinuousFind () + { + if (!continuousFind) { + var col = selecting ? selectionStartColumn : currentColumn; + var row = selecting ? selectionStartRow : currentRow; + model.ResetContinuousFind (new Point (col, row)); + } + } + /// /// Restore from original model. /// @@ -1088,7 +1500,7 @@ namespace Terminal.Gui { ColorNormal (); int bottom = bounds.Bottom; - int right = bounds.Right + 1; + int right = bounds.Right; for (int row = bounds.Top; row < bottom; row++) { int textLine = topRow + row; if (textLine >= model.Count) { @@ -1111,14 +1523,27 @@ namespace Terminal.Gui { var cols = Rune.ColumnWidth (rune); if (lineCol < line.Count && selecting && PointInSelection (idx + leftColumn, row + topRow)) { ColorSelection (); + } else if (lineCol == currentColumn && textLine == currentRow && !selecting && !Used + && HasFocus && lineCol < lineRuneCount) { + ColorUsed (); } else { ColorNormal (); } if (!SpecialRune (rune)) { AddRune (col, row, rune); + } else { + col++; } col = TextModel.SetCol (col, bounds.Right, cols); + if (idx + 1 < lineRuneCount && col + Rune.ColumnWidth (line [idx + 1]) > right) { + break; + } else if (idx == lineRuneCount - 1) { + ColorNormal (); + for (int i = col; i < right; i++) { + Driver.AddRune (' '); + } + } } } PositionCursor (); @@ -1156,9 +1581,21 @@ namespace Terminal.Gui { void Insert (Rune rune) { var line = GetCurrentLine (); - line.Insert (Math.Min (currentColumn, line.Count), rune); + if (Used) { + line.Insert (Math.Min (currentColumn, line.Count), rune); + } else { + if (currentColumn < line.Count) { + line.RemoveAt (currentColumn); + } + line.Insert (Math.Min (currentColumn, line.Count), rune); + } if (wordWrap) { - wrapNeeded = wrapManager.Insert (currentRow, currentColumn, rune); + if (Used) { + wrapNeeded = wrapManager.Insert (currentRow, currentColumn, rune); + } else { + wrapNeeded = wrapManager.RemoveAt (currentRow, currentColumn); + wrapNeeded = wrapManager.Insert (currentRow, currentColumn, rune); + } if (wrapNeeded) { SetNeedsDisplay (); } @@ -1211,7 +1648,7 @@ namespace Terminal.Gui { if (wordWrap) { SetNeedsDisplay (); } else { - SetNeedsDisplay (new Rect (0, currentRow - topRow, Frame.Width, currentRow - topRow + 1)); + SetNeedsDisplay (new Rect (0, currentRow - topRow, Frame.Width, Math.Max (currentRow - topRow + 1, 0))); } return; } @@ -1348,9 +1785,35 @@ namespace Terminal.Gui { case Key.P | Key.CtrlMask: case Key.CursorUp: lastWasKill = false; + continuousFind = false; break; case Key.K | Key.CtrlMask: break; + case Key.F | Key.CtrlMask: + case Key.B | Key.CtrlMask: + case (Key)((int)'B' + Key.AltMask): + case Key.A | Key.CtrlMask: + case Key.E | Key.CtrlMask: + case Key.CursorRight: + case Key.CursorLeft: + case Key.CursorRight | Key.CtrlMask: + case Key.CursorLeft | Key.CtrlMask: + case Key.CursorRight | Key.ShiftMask: + case Key.CursorLeft | Key.ShiftMask: + case Key.CursorRight | Key.CtrlMask | Key.ShiftMask: + case Key.CursorLeft | Key.CtrlMask | Key.ShiftMask: + case Key.Home: + case Key.Home | Key.CtrlMask: + case Key.Home | Key.ShiftMask: + case Key.Home | Key.CtrlMask | Key.ShiftMask: + case Key.End: + case Key.End | Key.CtrlMask: + case Key.End | Key.ShiftMask: + case Key.End | Key.CtrlMask | Key.ShiftMask: + lastWasKill = false; + columnTrack = -1; + continuousFind = false; + break; default: lastWasKill = false; columnTrack = -1; @@ -1371,7 +1834,9 @@ namespace Terminal.Gui { if (currentRow < model.Count) { if (columnTrack == -1) columnTrack = currentColumn; - currentRow = (currentRow + nPageDnShift) > model.Count ? model.Count : currentRow + nPageDnShift; + currentRow = (currentRow + nPageDnShift) > model.Count + ? model.Count > 0 ? model.Count - 1 : 0 + : currentRow + nPageDnShift; if (topRow < currentRow - nPageDnShift) { topRow = currentRow >= model.Count ? currentRow - nPageDnShift : topRow + nPageDnShift; SetNeedsDisplay (); @@ -1478,7 +1943,7 @@ namespace Terminal.Gui { if (isReadOnly) break; if (selecting) { - Cut (); + ClearSelectedRegion (); return true; } if (currentColumn > 0) { @@ -1533,7 +1998,7 @@ namespace Terminal.Gui { if (isReadOnly) break; if (selecting) { - Cut (); + ClearSelectedRegion (); return true; } currentLine = GetCurrentLine (); @@ -1733,6 +2198,15 @@ namespace Terminal.Gui { MoveHome (); break; + case Key.T | Key.CtrlMask: + SelectAll (); + break; + + case Key.InsertChar: + Used = !Used; + SetNeedsDisplay (); + break; + default: // Ignore control characters and other special keys if (kb.Key < Key.Space || kb.Key > Key.CharMask) @@ -1741,13 +2215,18 @@ namespace Terminal.Gui { if (isReadOnly) return true; if (selecting) { - Cut (); + ClearSelectedRegion (); } - Insert ((uint)kb.Key); - currentColumn++; - if (currentColumn >= leftColumn + Frame.Width) { - leftColumn++; - SetNeedsDisplay (); + if (Used) { + Insert ((uint)kb.Key); + currentColumn++; + if (currentColumn >= leftColumn + Frame.Width) { + leftColumn++; + SetNeedsDisplay (); + } + } else { + Insert ((uint)kb.Key); + currentColumn++; } break; } @@ -1838,6 +2317,17 @@ namespace Terminal.Gui { selecting = false; } + void ClearSelectedRegion () + { + SetWrapModel (); + if (!isReadOnly) { + ClearRegion (); + } + UpdateWrapModel (); + selecting = false; + DoNeededAction (); + } + void MoveUp () { if (currentRow > 0) { @@ -1895,7 +2385,11 @@ namespace Terminal.Gui { Rune RuneAt (int col, int row) { var line = model.GetLine (row); - return line [col > line.Count - 1 ? line.Count - 1 : col]; + if (line.Count > 0) { + return line [col > line.Count - 1 ? line.Count - 1 : col]; + } else { + return 0; + } } /// @@ -1927,6 +2421,9 @@ namespace Terminal.Gui { if (col + 1 < line.Count) { col++; rune = line [col]; + if (col + 1 == line.Count) { + col++; + } return true; } while (row + 1 < model.Count) { @@ -1975,27 +2472,45 @@ namespace Terminal.Gui { try { var rune = RuneAt (col, row); - var srow = row; - if (Rune.IsPunctuation (rune) || Rune.IsWhiteSpace (rune)) { - while (MoveNext (ref col, ref row, out rune)) { - if (Rune.IsLetterOrDigit (rune)) - break; - } - if (row != fromRow && Rune.IsLetterOrDigit (rune)) { - return (col, row); - } - while (MoveNext (ref col, ref row, out rune)) { - if (!Rune.IsLetterOrDigit (rune)) - break; - } - } else { - while (MoveNext (ref col, ref row, out rune)) { - if (!Rune.IsLetterOrDigit (rune)) - break; + void ProcMoveNext (ref int nCol, ref int nRow, Rune nRune) + { + if (Rune.IsSymbol (nRune) || Rune.IsWhiteSpace (nRune)) { + while (MoveNext (ref nCol, ref nRow, out nRune)) { + if (Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune)) + return; + } + if (nRow != fromRow && (Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune))) { + return; + } + while (MoveNext (ref nCol, ref nRow, out nRune)) { + if (!Rune.IsLetterOrDigit (nRune) && !Rune.IsPunctuation (nRune)) + break; + } + } else { + if (!MoveNext (ref nCol, ref nRow, out nRune)) { + return; + } + + var line = model.GetLine (fromRow); + if ((nRow != fromRow && fromCol < line.Count) + || (nRow == fromRow && nCol == line.Count - 1)) { + nCol = line.Count; + nRow = fromRow; + return; + } else if (nRow != fromRow && fromCol == line.Count) { + line = model.GetLine (nRow); + if (Rune.IsLetterOrDigit (line [nCol]) || Rune.IsPunctuation (line [nCol])) { + return; + } + } + ProcMoveNext (ref nCol, ref nRow, nRune); } } + + ProcMoveNext (ref col, ref row, rune); + if (fromCol != col || fromRow != row) - return (col + 1, row); + return (col, row); return null; } catch (Exception) { return null; @@ -2011,36 +2526,54 @@ namespace Terminal.Gui { var row = fromRow; try { var rune = RuneAt (col, row); + int lastValidCol = Rune.IsLetterOrDigit (rune) || Rune.IsPunctuation (rune) ? col : -1; - if (Rune.IsPunctuation (rune) || Rune.IsSymbol (rune) || Rune.IsWhiteSpace (rune)) { - while (MovePrev (ref col, ref row, out rune)) { - if (Rune.IsLetterOrDigit (rune)) - break; - } - int lastValidCol = -1; - while (MovePrev (ref col, ref row, out rune)) { - if (col == 0 && Rune.IsLetterOrDigit (rune)) { - return (col, row); - } else if (col == 0 && !Rune.IsLetterOrDigit (rune) && lastValidCol > -1) { - col = lastValidCol; - return (col, row); + void ProcMovePrev (ref int nCol, ref int nRow, Rune nRune) + { + if (Rune.IsSymbol (nRune) || Rune.IsWhiteSpace (nRune)) { + while (MovePrev (ref nCol, ref nRow, out nRune)) { + if (Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune)) { + lastValidCol = nCol; + break; + } } - if (!Rune.IsLetterOrDigit (rune)) { - break; + if (nRow != fromRow && (Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune))) { + return; } - lastValidCol = Rune.IsLetterOrDigit (rune) ? col : -1; - } - } else { - while (MovePrev (ref col, ref row, out rune)) { - if (!Rune.IsLetterOrDigit (rune)) - break; + while (MovePrev (ref nCol, ref nRow, out nRune)) { + if (!Rune.IsLetterOrDigit (nRune) && !Rune.IsPunctuation (nRune)) + break; + lastValidCol = nCol; + } + if (lastValidCol > -1) { + nCol = lastValidCol; + } + } else { + if (!MovePrev (ref nCol, ref nRow, out nRune)) { + return; + } + + var line = model.GetLine (nRow); + if (nCol == 0 && nRow == fromRow && (Rune.IsLetterOrDigit (line [0]) || Rune.IsPunctuation (line [0]))) { + return; + } + lastValidCol = Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) ? nCol : lastValidCol; + if (lastValidCol > -1 && (Rune.IsSymbol (nRune) || Rune.IsWhiteSpace (nRune))) { + nCol = lastValidCol; + return; + } + if (fromRow != nRow) { + nCol = line.Count; + return; + } + ProcMovePrev (ref nCol, ref nRow, nRune); } } - if (fromCol != col && fromRow == row) { - return (col == 0 ? col : col + 1, row); - } else if (fromCol != col && fromRow != row) { - return (col + 1, row); - } + + ProcMovePrev (ref col, ref row, rune); + + if (fromCol != col || fromRow != row) + return (col, row); return null; } catch (Exception) { return null; @@ -2054,7 +2587,9 @@ namespace Terminal.Gui { && !ev.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) && !ev.Flags.HasFlag (MouseFlags.Button1Released) && !ev.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ButtonShift) - && !ev.Flags.HasFlag (MouseFlags.WheeledDown) && !ev.Flags.HasFlag (MouseFlags.WheeledUp)) { + && !ev.Flags.HasFlag (MouseFlags.WheeledDown) && !ev.Flags.HasFlag (MouseFlags.WheeledUp) + && !ev.Flags.HasFlag (MouseFlags.Button1DoubleClicked) + && !ev.Flags.HasFlag (MouseFlags.Button1DoubleClicked | MouseFlags.ButtonShift)) { return false; } @@ -2066,6 +2601,8 @@ namespace Terminal.Gui { SetFocus (); } + continuousFind = false; + if (ev.Flags == MouseFlags.Button1Clicked) { if (shiftSelecting) { shiftSelecting = false; @@ -2143,6 +2680,40 @@ namespace Terminal.Gui { } } else if (ev.Flags.HasFlag (MouseFlags.Button1Released)) { Application.UngrabMouse (); + } else if (ev.Flags.HasFlag (MouseFlags.Button1DoubleClicked)) { + if (ev.Flags.HasFlag (MouseFlags.ButtonShift)) { + if (!selecting) { + StartSelecting (); + } + } else if (selecting) { + StopSelecting (); + } + ProcessMouseClick (ev, out List line); + (int col, int row)? newPos = null; + if (currentColumn > 0 && line [currentColumn - 1] != ' ') { + newPos = WordBackward (currentColumn, currentRow); + if (newPos.HasValue) { + currentColumn = newPos.Value.col; + currentRow = newPos.Value.row; + } + } + if (!selecting) { + StartSelecting (); + } + if (currentRow < selectionStartRow || currentRow == selectionStartRow && currentColumn < selectionStartColumn) { + if (currentColumn > 0 && line [currentColumn - 1] != ' ') { + newPos = WordBackward (currentColumn, currentRow); + } + } else { + newPos = WordForward (currentColumn, currentRow); + } + if (newPos != null && newPos.HasValue) { + currentColumn = newPos.Value.col; + currentRow = newPos.Value.row; + } + PositionCursor (); + lastWasKill = false; + columnTrack = currentColumn; } return true; diff --git a/UICatalog/Scenarios/Dialogs.cs b/UICatalog/Scenarios/Dialogs.cs index a4790c840..31907cbac 100644 --- a/UICatalog/Scenarios/Dialogs.cs +++ b/UICatalog/Scenarios/Dialogs.cs @@ -93,13 +93,8 @@ namespace UICatalog { }; frame.Add (numButtonsEdit); - void Top_Loaded () - { - frame.Height = Dim.Height (widthEdit) + Dim.Height (heightEdit) + Dim.Height (titleEdit) - + Dim.Height (numButtonsEdit) + 2; - Top.Loaded -= Top_Loaded; - } - Top.Loaded += Top_Loaded; + frame.Height = Dim.Height (widthEdit) + Dim.Height (heightEdit) + Dim.Height (titleEdit) + + Dim.Height (numButtonsEdit) + 2; label = new Label ("Button Pressed:") { X = Pos.Center (), diff --git a/UICatalog/Scenarios/Editor.cs b/UICatalog/Scenarios/Editor.cs index 7e72df0b0..f71ab4c59 100644 --- a/UICatalog/Scenarios/Editor.cs +++ b/UICatalog/Scenarios/Editor.cs @@ -15,6 +15,11 @@ namespace UICatalog { private bool _saved = true; private ScrollBarView _scrollBar; private byte [] _originalText; + private string _textToFind; + private string _textToReplace; + private bool _matchCase; + private bool _matchWholeWord; + Window winDialog; public override void Init (Toplevel top, ColorScheme colorScheme) { @@ -36,7 +41,17 @@ namespace UICatalog { new MenuBarItem ("_Edit", new MenuItem [] { new MenuItem ("_Copy", "", () => Copy(),null,null, Key.CtrlMask | Key.C), new MenuItem ("C_ut", "", () => Cut(),null,null, Key.CtrlMask | Key.W), - new MenuItem ("_Paste", "", () => Paste(),null,null, Key.CtrlMask | Key.Y) + new MenuItem ("_Paste", "", () => Paste(),null,null, Key.CtrlMask | Key.Y), + null, + new MenuItem ("_Find", "", () => Find(),null,null, Key.CtrlMask | Key.S), + new MenuItem ("Find _Next", "", () => FindNext(),null,null, Key.CtrlMask | Key.ShiftMask | Key.S), + new MenuItem ("Find P_revious", "", () => FindPrevious(),null,null, Key.CtrlMask | Key.ShiftMask | Key.AltMask | Key.S), + new MenuItem ("_Replace", "", () => Replace(),null,null, Key.CtrlMask | Key.R), + new MenuItem ("Replace Ne_xt", "", () => ReplaceNext(),null,null, Key.CtrlMask | Key.ShiftMask | Key.R), + new MenuItem ("Replace Pre_vious", "", () => ReplacePrevious(),null,null, Key.CtrlMask | Key.ShiftMask | Key.AltMask | Key.R), + new MenuItem ("Replace _All", "", () => ReplaceAll(),null,null, Key.CtrlMask | Key.ShiftMask | Key.AltMask | Key.A), + null, + new MenuItem ("_Select All", "", () => SelectAll(),null,null, Key.CtrlMask | Key.T) }), new MenuBarItem ("_ScrollBarView", CreateKeepChecked ()), new MenuBarItem ("_Cursor", new MenuItem [] { @@ -115,6 +130,20 @@ namespace UICatalog { _scrollBar.LayoutSubviews (); _scrollBar.Refresh (); }; + + Win.KeyPress += (e) => { + if (winDialog != null && (e.KeyEvent.Key == Key.Esc + || e.KeyEvent.Key.HasFlag (Key.Q | Key.CtrlMask))) { + DisposeWinDialog (); + } + }; + } + + private void DisposeWinDialog () + { + winDialog.Dispose (); + Win.Remove (winDialog); + winDialog = null; } public override void Setup () @@ -166,6 +195,91 @@ namespace UICatalog { } } + private void SelectAll () + { + _textView.SelectAll (); + } + + private void Find () + { + CreateFindReplace (); + } + + private void FindNext () + { + ContinueFind (); + } + + private void FindPrevious () + { + ContinueFind (false); + } + + private void ContinueFind (bool next = true, bool replace = false) + { + if (!replace && string.IsNullOrEmpty (_textToFind)) { + Find (); + return; + } else if (replace && (string.IsNullOrEmpty (_textToFind) + || (winDialog == null && string.IsNullOrEmpty (_textToReplace)))) { + Replace (); + return; + } + + bool found; + bool gaveFullTurn; + + if (next) { + if (!replace) { + found = _textView.FindNextText (_textToFind, out gaveFullTurn, _matchCase, _matchWholeWord); + } else { + found = _textView.FindNextText (_textToFind, out gaveFullTurn, _matchCase, _matchWholeWord, + _textToReplace, true); + } + } else { + if (!replace) { + found = _textView.FindPreviousText (_textToFind, out gaveFullTurn, _matchCase, _matchWholeWord); + } else { + found = _textView.FindPreviousText (_textToFind, out gaveFullTurn, _matchCase, _matchWholeWord, + _textToReplace, true); + } + } + if (!found) { + MessageBox.Query ("Find", $"The following specified text was not found: '{_textToFind}'", "Ok"); + } else if (gaveFullTurn) { + MessageBox.Query ("Find", $"No more occurrences were found for the following specified text: '{_textToFind}'", "Ok"); + } + } + + private void Replace () + { + CreateFindReplace (false); + } + + private void ReplaceNext () + { + ContinueFind (true, true); + } + + private void ReplacePrevious () + { + ContinueFind (false, true); + } + + private void ReplaceAll () + { + if (string.IsNullOrEmpty (_textToFind) || (string.IsNullOrEmpty (_textToReplace) && winDialog == null)) { + Replace (); + return; + } + + if (_textView.ReplaceAllText (_textToFind, _matchCase, _matchWholeWord, _textToReplace)) { + MessageBox.Query ("Replace All", $"All occurrences were replaced for the following specified text: '{_textToReplace}'", "Ok"); + } else { + MessageBox.Query ("Replace All", $"None of the following specified text was found: '{_textToFind}'", "Ok"); + } + } + private void SetCursor (CursorVisibility visibility) { _textView.DesiredCursorVisibility = visibility; @@ -307,6 +421,259 @@ namespace UICatalog { return new MenuItem [] { item }; } + private void CreateFindReplace (bool isFind = true) + { + winDialog = new Window (isFind ? "Find" : "Replace") { + X = Win.Bounds.Width / 2 - 30, + Y = Win.Bounds.Height / 2 - 10, + ColorScheme = Colors.Menu + }; + + var tabView = new TabView () { + X = 0, + Y = 0, + Width = Dim.Fill (), + Height = Dim.Fill () + }; + + tabView.AddTab (new Tab ("Find", FindTab ()), isFind); + var replace = ReplaceTab (); + tabView.AddTab (new Tab ("Replace", replace), !isFind); + tabView.SelectedTabChanged += (s, e) => tabView.SelectedTab.View.FocusFirst (); + winDialog.Add (tabView); + + Win.Add (winDialog); + + winDialog.Width = replace.Width + 4; + winDialog.Height = replace.Height + 4; + + winDialog.SuperView.BringSubviewToFront (winDialog); + winDialog.SetFocus (); + } + + private void SetFindText () + { + _textToFind = !_textView.SelectedText.IsEmpty + ? _textView.SelectedText.ToString () + : string.IsNullOrEmpty (_textToFind) ? "" : _textToFind; + + _textToReplace = string.IsNullOrEmpty (_textToReplace) ? "" : _textToReplace; + } + + private View FindTab () + { + var d = new View () { + X = 0, + Y = 0, + Width = Dim.Fill (), + Height = Dim.Fill () + }; + d.DrawContent += (e) => { + foreach (var v in d.Subviews) { + v.SetNeedsDisplay (); + } + }; + + var lblWidth = "Replace:".Length; + + var label = new Label (0, 1, "Find:") { + Width = lblWidth, + TextAlignment = TextAlignment.Right, + LayoutStyle = LayoutStyle.Computed + }; + d.Add (label); + + SetFindText (); + var txtToFind = new TextField (_textToFind) { + X = Pos.Right (label) + 1, + Y = Pos.Top (label), + Width = 20 + }; + txtToFind.Enter += (_) => txtToFind.Text = _textToFind; + d.Add (txtToFind); + + var btnFindNext = new Button ("Find _Next") { + X = Pos.Right (txtToFind) + 1, + Y = Pos.Top (label), + Width = 20, + CanFocus = !txtToFind.Text.IsEmpty, + TextAlignment = TextAlignment.Centered, + IsDefault = true + }; + btnFindNext.Clicked += () => FindNext (); + d.Add (btnFindNext); + + var btnFindPrevious = new Button ("Find _Previous") { + X = Pos.Right (txtToFind) + 1, + Y = Pos.Top (btnFindNext) + 1, + Width = 20, + CanFocus = !txtToFind.Text.IsEmpty, + TextAlignment = TextAlignment.Centered + }; + btnFindPrevious.Clicked += () => FindPrevious (); + d.Add (btnFindPrevious); + + txtToFind.TextChanged += (e) => { + _textToFind = txtToFind.Text.ToString (); + _textView.FindTextChanged (); + btnFindNext.CanFocus = !txtToFind.Text.IsEmpty; + btnFindPrevious.CanFocus = !txtToFind.Text.IsEmpty; + }; + + var btnCancel = new Button ("Cancel") { + X = Pos.Right (txtToFind) + 1, + Y = Pos.Top (btnFindPrevious) + 2, + Width = 20, + TextAlignment = TextAlignment.Centered + }; + btnCancel.Clicked += () => { + DisposeWinDialog (); + }; + d.Add (btnCancel); + + var ckbMatchCase = new CheckBox ("Match c_ase") { + X = 0, + Y = Pos.Top (txtToFind) + 2, + Checked = _matchCase + }; + ckbMatchCase.Toggled += (e) => _matchCase = ckbMatchCase.Checked; + d.Add (ckbMatchCase); + + var ckbMatchWholeWord = new CheckBox ("Match _whole word") { + X = 0, + Y = Pos.Top (ckbMatchCase) + 1, + Checked = _matchWholeWord + }; + ckbMatchWholeWord.Toggled += (e) => _matchWholeWord = ckbMatchWholeWord.Checked; + d.Add (ckbMatchWholeWord); + + d.Width = label.Width + txtToFind.Width + btnFindNext.Width + 2; + d.Height = btnFindNext.Height + btnFindPrevious.Height + btnCancel.Height + 4; + + return d; + } + + private View ReplaceTab () + { + var d = new View () { + X = 0, + Y = 0, + Width = Dim.Fill (), + Height = Dim.Fill () + }; + d.DrawContent += (e) => { + foreach (var v in d.Subviews) { + v.SetNeedsDisplay (); + } + }; + + var lblWidth = "Replace:".Length; + + var label = new Label (0, 1, "Find:") { + Width = lblWidth, + TextAlignment = TextAlignment.Right, + LayoutStyle = LayoutStyle.Computed + }; + d.Add (label); + + SetFindText (); + var txtToFind = new TextField (_textToFind) { + X = Pos.Right (label) + 1, + Y = Pos.Top (label), + Width = 20 + }; + txtToFind.Enter += (_) => txtToFind.Text = _textToFind; + d.Add (txtToFind); + + var btnFindNext = new Button ("Replace _Next") { + X = Pos.Right (txtToFind) + 1, + Y = Pos.Top (label), + Width = 20, + CanFocus = !txtToFind.Text.IsEmpty, + TextAlignment = TextAlignment.Centered, + IsDefault = true + }; + btnFindNext.Clicked += () => ReplaceNext (); + d.Add (btnFindNext); + + label = new Label ("Replace:") { + X = Pos.Left (label), + Y = Pos.Top (label) + 1, + Width = lblWidth, + TextAlignment = TextAlignment.Right + }; + d.Add (label); + + SetFindText (); + var txtToReplace = new TextField (_textToReplace) { + X = Pos.Right (label) + 1, + Y = Pos.Top (label), + Width = 20 + }; + txtToReplace.TextChanged += (e) => _textToReplace = txtToReplace.Text.ToString (); + d.Add (txtToReplace); + + var btnFindPrevious = new Button ("Replace _Previous") { + X = Pos.Right (txtToFind) + 1, + Y = Pos.Top (btnFindNext) + 1, + Width = 20, + CanFocus = !txtToFind.Text.IsEmpty, + TextAlignment = TextAlignment.Centered + }; + btnFindPrevious.Clicked += () => ReplacePrevious (); + d.Add (btnFindPrevious); + + var btnReplaceAll = new Button ("Replace _All") { + X = Pos.Right (txtToFind) + 1, + Y = Pos.Top (btnFindPrevious) + 1, + Width = 20, + CanFocus = !txtToFind.Text.IsEmpty, + TextAlignment = TextAlignment.Centered + }; + btnReplaceAll.Clicked += () => ReplaceAll (); + d.Add (btnReplaceAll); + + txtToFind.TextChanged += (e) => { + _textToFind = txtToFind.Text.ToString (); + _textView.FindTextChanged (); + btnFindNext.CanFocus = !txtToFind.Text.IsEmpty; + btnFindPrevious.CanFocus = !txtToFind.Text.IsEmpty; + btnReplaceAll.CanFocus = !txtToFind.Text.IsEmpty; + }; + + var btnCancel = new Button ("Cancel") { + X = Pos.Right (txtToFind) + 1, + Y = Pos.Top (btnReplaceAll) + 1, + Width = 20, + TextAlignment = TextAlignment.Centered + }; + btnCancel.Clicked += () => { + DisposeWinDialog (); + }; + d.Add (btnCancel); + + var ckbMatchCase = new CheckBox ("Match c_ase") { + X = 0, + Y = Pos.Top (txtToFind) + 2, + Checked = _matchCase + }; + ckbMatchCase.Toggled += (e) => _matchCase = ckbMatchCase.Checked; + d.Add (ckbMatchCase); + + var ckbMatchWholeWord = new CheckBox ("Match _whole word") { + X = 0, + Y = Pos.Top (ckbMatchCase) + 1, + Checked = _matchWholeWord + }; + ckbMatchWholeWord.Toggled += (e) => _matchWholeWord = ckbMatchWholeWord.Checked; + d.Add (ckbMatchWholeWord); + + d.Width = lblWidth + txtToFind.Width + btnFindNext.Width + 2; + d.Height = btnFindNext.Height + btnFindPrevious.Height + btnCancel.Height + 4; + + return d; + } + public override void Run () { base.Run (); diff --git a/UICatalog/Scenarios/MessageBoxes.cs b/UICatalog/Scenarios/MessageBoxes.cs index 3a71ccb2d..a157e6d1b 100644 --- a/UICatalog/Scenarios/MessageBoxes.cs +++ b/UICatalog/Scenarios/MessageBoxes.cs @@ -140,13 +140,8 @@ namespace UICatalog { }; frame.Add (styleRadioGroup); - void Top_Loaded () - { - frame.Height = Dim.Height (widthEdit) + Dim.Height (heightEdit) + Dim.Height (titleEdit) + Dim.Height (messageEdit) - + Dim.Height (numButtonsEdit) + Dim.Height (defaultButtonEdit) + Dim.Height (styleRadioGroup) + 2; - Top.Loaded -= Top_Loaded; - } - Top.Loaded += Top_Loaded; + frame.Height = Dim.Height (widthEdit) + Dim.Height (heightEdit) + Dim.Height (titleEdit) + Dim.Height (messageEdit) + + Dim.Height (numButtonsEdit) + Dim.Height(defaultButtonEdit) + Dim.Height (styleRadioGroup) + 2; label = new Label ("Button Pressed:") { X = Pos.Center (), diff --git a/UnitTests/DimTests.cs b/UnitTests/DimTests.cs index 1b8f9b7fa..9bbe6b7f3 100644 --- a/UnitTests/DimTests.cs +++ b/UnitTests/DimTests.cs @@ -483,6 +483,10 @@ namespace Terminal.Gui { Assert.Equal (1, v3.Frame.Height); // 1 because is Dim.DimAbsolute v4.Text = "Button4"; + v4.AutoSize = false; + Assert.Equal ("Dim.Absolute(50)", v4.Width.ToString ()); + Assert.Equal ("Dim.Absolute(1)", v4.Height.ToString ()); + v4.AutoSize = true; Assert.Equal ("Dim.Absolute(11)", v4.Width.ToString ()); Assert.Equal ("Dim.Absolute(1)", v4.Height.ToString ()); Assert.Equal (11, v4.Frame.Width); // 11 is the text length and because is Dim.DimAbsolute @@ -507,6 +511,84 @@ namespace Terminal.Gui { Application.Shutdown (); } - // TODO: Test operators + // DONE: Test operators + [Fact] + public void DimCombine_Do_Not_Throws () + { + Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true))); + + var t = Application.Top; + + var w = new Window ("w") { + Width = Dim.Width (t) - 2, + Height = Dim.Height (t) - 2 + }; + var f = new FrameView ("f"); + var v1 = new View ("v1") { + Width = Dim.Width (w) - 2, + Height = Dim.Height (w) - 2 + }; + var v2 = new View ("v2") { + Width = Dim.Width (v1) - 2, + Height = Dim.Height (v1) - 2 + }; + + f.Add (v1, v2); + w.Add (f); + t.Add (w); + + f.Width = Dim.Width (t) - Dim.Width (v2); + f.Height = Dim.Height (t) - Dim.Height (v2); + + t.Ready += () => { + Assert.Equal (80, t.Frame.Width); + Assert.Equal (25, t.Frame.Height); + Assert.Equal (78, w.Frame.Width); + Assert.Equal (23, w.Frame.Height); + Assert.Equal (6, f.Frame.Width); + Assert.Equal (6, f.Frame.Height); + Assert.Equal (76, v1.Frame.Width); + Assert.Equal (21, v1.Frame.Height); + Assert.Equal (74, v2.Frame.Width); + Assert.Equal (19, v2.Frame.Height); + }; + + Application.Iteration += () => Application.RequestStop (); + + Application.Run (); + Application.Shutdown (); + } + + [Fact] + public void PosCombine_Will_Throws () + { + Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true))); + + var t = Application.Top; + + var w = new Window ("w") { + Width = Dim.Width (t) - 2, + Height = Dim.Height (t) - 2 + }; + var f = new FrameView ("f"); + var v1 = new View ("v1") { + Width = Dim.Width (w) - 2, + Height = Dim.Height (w) - 2 + }; + var v2 = new View ("v2") { + Width = Dim.Width (v1) - 2, + Height = Dim.Height (v1) - 2 + }; + + f.Add (v1); // v2 not added + w.Add (f); + t.Add (w); + + f.Width = Dim.Width (t) - Dim.Width (v2); + f.Height = Dim.Height (t) - Dim.Height (v2); + + Assert.Throws (() => Application.Run ()); + Application.Shutdown (); + } } } diff --git a/UnitTests/PosTests.cs b/UnitTests/PosTests.cs index 3c7a2793b..f1ab89b01 100644 --- a/UnitTests/PosTests.cs +++ b/UnitTests/PosTests.cs @@ -79,7 +79,7 @@ namespace Terminal.Gui { Assert.Equal (pos1, pos2); } - [Fact] + [Fact] public void SetSide_Null_Throws () { var pos = Pos.Left (null); @@ -91,7 +91,7 @@ namespace Terminal.Gui { pos = Pos.Top (null); Assert.Throws (() => pos.ToString ()); - pos = Pos.Y(null); + pos = Pos.Y (null); Assert.Throws (() => pos.ToString ()); pos = Pos.Bottom (null); @@ -112,7 +112,7 @@ namespace Terminal.Gui { string side; // used in format string var testRect = Rect.Empty; var testInt = 0; - Pos pos; + Pos pos; // Pos.Left side = "x"; @@ -456,9 +456,85 @@ namespace Terminal.Gui { Application.Shutdown (); } - // TODO: Test PosCombine + // DONE: Test PosCombine + // DONE: Test operators + [Fact] + public void PosCombine_Do_Not_Throws () + { + Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true))); + var t = Application.Top; - // TODO: Test operators + var w = new Window ("w") { + X = Pos.Left (t) + 2, + Y = Pos.Top (t) + 2 + }; + var f = new FrameView ("f"); + var v1 = new View ("v1") { + X = Pos.Left (w) + 2, + Y = Pos.Top (w) + 2 + }; + var v2 = new View ("v2") { + X = Pos.Left (v1) + 2, + Y = Pos.Top (v1) + 2 + }; + + f.Add (v1, v2); + w.Add (f); + t.Add (w); + + f.X = Pos.X (t) + Pos.X (v2) - Pos.X (v1); + f.Y = Pos.Y (t) + Pos.Y (v2) - Pos.Y (v1); + + t.Ready += () => { + Assert.Equal (0, t.Frame.X); + Assert.Equal (0, t.Frame.Y); + Assert.Equal (2, w.Frame.X); + Assert.Equal (2, w.Frame.Y); + Assert.Equal (2, f.Frame.X); + Assert.Equal (2, f.Frame.Y); + Assert.Equal (4, v1.Frame.X); + Assert.Equal (4, v1.Frame.Y); + Assert.Equal (6, v2.Frame.X); + Assert.Equal (6, v2.Frame.Y); + }; + + Application.Iteration += () => Application.RequestStop (); + + Application.Run (); + Application.Shutdown (); + } + + [Fact] + public void PosCombine_Will_Throws () + { + Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true))); + + var t = Application.Top; + + var w = new Window ("w") { + X = Pos.Left (t) + 2, + Y = Pos.Top (t) + 2 + }; + var f = new FrameView ("f"); + var v1 = new View ("v1") { + X = Pos.Left (w) + 2, + Y = Pos.Top (w) + 2 + }; + var v2 = new View ("v2") { + X = Pos.Left (v1) + 2, + Y = Pos.Top (v1) + 2 + }; + + f.Add (v1); // v2 not added + w.Add (f); + t.Add (w); + + f.X = Pos.X (v2) - Pos.X (v1); + f.Y = Pos.Y (v2) - Pos.Y (v1); + + Assert.Throws (() => Application.Run ()); + Application.Shutdown (); + } } } diff --git a/UnitTests/TextViewTests.cs b/UnitTests/TextViewTests.cs new file mode 100644 index 000000000..6055ff81c --- /dev/null +++ b/UnitTests/TextViewTests.cs @@ -0,0 +1,797 @@ +using Xunit; + +namespace Terminal.Gui { + public class TextViewTests { + private TextView _textView; + + public TextViewTests () + { + Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true))); + + // 1 2 3 + // 01234567890123456789012345678901=32 (Length) + var txt = "TAB to jump between text fields."; + var buff = new byte [txt.Length]; + for (int i = 0; i < txt.Length; i++) { + buff [i] = (byte)txt [i]; + } + var ms = new System.IO.MemoryStream (buff).ToArray (); + _textView = new TextView () { Width = 30, Height = 10 }; + _textView.Text = ms; + } + + [Fact] + public void Changing_Selection_Or_CursorPosition_Update_SelectedLength_And_SelectedText () + { + _textView.SelectionStartColumn = 2; + _textView.SelectionStartRow = 0; + Assert.Equal (0, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (2, _textView.SelectedLength); + Assert.Equal ("TA", _textView.SelectedText); + _textView.CursorPosition = new Point (20, 0); + Assert.Equal (2, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (18, _textView.SelectedLength); + Assert.Equal ("B to jump between ", _textView.SelectedText); + } + + [Fact] + public void Selection_With_Value_Less_Than_Zero_Changes_To_Zero () + { + _textView.SelectionStartColumn = -2; + _textView.SelectionStartRow = -2; + Assert.Equal (0, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (0, _textView.SelectedLength); + Assert.Equal ("", _textView.SelectedText); + } + + [Fact] + public void Selection_With_Value_Greater_Than_Text_Length_Changes_To_Text_Length () + { + _textView.CursorPosition = new Point (2, 0); + _textView.SelectionStartColumn = 33; + _textView.SelectionStartRow = 1; + Assert.Equal (32, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (30, _textView.SelectedLength); + Assert.Equal ("B to jump between text fields.", _textView.SelectedText); + } + + [Fact] + public void Selection_With_Empty_Text () + { + _textView = new TextView (); + _textView.CursorPosition = new Point (2, 0); + _textView.SelectionStartColumn = 33; + _textView.SelectionStartRow = 1; + Assert.Equal (0, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (0, _textView.SelectedLength); + Assert.Equal ("", _textView.SelectedText); + } + + [Fact] + public void Selection_And_CursorPosition_With_Value_Greater_Than_Text_Length_Changes_Both_To_Text_Length () + { + _textView.CursorPosition = new Point (33, 2); + _textView.SelectionStartColumn = 33; + _textView.SelectionStartRow = 33; + Assert.Equal (32, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (32, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (0, _textView.SelectedLength); + Assert.Equal ("", _textView.SelectedText); + } + + [Fact] + public void CursorPosition_With_Value_Less_Than_Zero_Changes_To_Zero () + { + _textView.CursorPosition = new Point (-1, -1); + Assert.Equal (0, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (0, _textView.SelectedLength); + Assert.Equal ("", _textView.SelectedText); + } + + [Fact] + public void CursorPosition_With_Value_Greater_Than_Text_Length_Changes_To_Text_Length () + { + _textView.CursorPosition = new Point (33, 1); + Assert.Equal (32, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (0, _textView.SelectedLength); + Assert.Equal ("", _textView.SelectedText); + } + + [Fact] + public void WordForward_With_No_Selection () + { + _textView.CursorPosition = new Point (0, 0); + var iteration = 0; + + while (_textView.CursorPosition.X < _textView.Text.Length) { + _textView.ProcessKey (new KeyEvent (Key.CursorRight | Key.CtrlMask, new KeyModifiers ())); + switch (iteration) { + case 0: + Assert.Equal (4, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (0, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (0, _textView.SelectedLength); + Assert.Equal ("", _textView.SelectedText); + break; + case 1: + Assert.Equal (7, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (0, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (0, _textView.SelectedLength); + Assert.Equal ("", _textView.SelectedText); + break; + case 2: + Assert.Equal (12, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (0, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (0, _textView.SelectedLength); + Assert.Equal ("", _textView.SelectedText); + break; + case 3: + Assert.Equal (20, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (0, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (0, _textView.SelectedLength); + Assert.Equal ("", _textView.SelectedText); + break; + case 4: + Assert.Equal (25, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (0, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (0, _textView.SelectedLength); + Assert.Equal ("", _textView.SelectedText); + break; + case 5: + Assert.Equal (32, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (0, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (0, _textView.SelectedLength); + Assert.Equal ("", _textView.SelectedText); + break; + } + iteration++; + } + } + + [Fact] + public void WordBackward_With_No_Selection () + { + _textView.CursorPosition = new Point (_textView.Text.Length, 0); + var iteration = 0; + + while (_textView.CursorPosition.X > 0) { + _textView.ProcessKey (new KeyEvent (Key.CursorLeft | Key.CtrlMask, new KeyModifiers ())); + switch (iteration) { + case 0: + Assert.Equal (25, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (0, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (0, _textView.SelectedLength); + Assert.Equal ("", _textView.SelectedText); + break; + case 1: + Assert.Equal (20, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (0, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (0, _textView.SelectedLength); + Assert.Equal ("", _textView.SelectedText); + break; + case 2: + Assert.Equal (12, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (0, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (0, _textView.SelectedLength); + Assert.Equal ("", _textView.SelectedText); + break; + case 3: + Assert.Equal (7, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (0, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (0, _textView.SelectedLength); + Assert.Equal ("", _textView.SelectedText); + break; + case 4: + Assert.Equal (4, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (0, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (0, _textView.SelectedLength); + Assert.Equal ("", _textView.SelectedText); + break; + case 5: + Assert.Equal (0, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (0, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (0, _textView.SelectedLength); + Assert.Equal ("", _textView.SelectedText); + break; + } + iteration++; + } + } + + [Fact] + public void WordForward_With_Selection () + { + _textView.CursorPosition = new Point (0, 0); + _textView.SelectionStartColumn = 0; + _textView.SelectionStartRow = 0; + var iteration = 0; + + while (_textView.CursorPosition.X < _textView.Text.Length) { + _textView.ProcessKey (new KeyEvent (Key.CursorRight | Key.CtrlMask | Key.ShiftMask, new KeyModifiers ())); + switch (iteration) { + case 0: + Assert.Equal (4, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (0, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (4, _textView.SelectedLength); + Assert.Equal ("TAB ", _textView.SelectedText); + break; + case 1: + Assert.Equal (7, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (0, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (7, _textView.SelectedLength); + Assert.Equal ("TAB to ", _textView.SelectedText); + break; + case 2: + Assert.Equal (12, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (0, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (12, _textView.SelectedLength); + Assert.Equal ("TAB to jump ", _textView.SelectedText); + break; + case 3: + Assert.Equal (20, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (0, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (20, _textView.SelectedLength); + Assert.Equal ("TAB to jump between ", _textView.SelectedText); + break; + case 4: + Assert.Equal (25, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (0, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (25, _textView.SelectedLength); + Assert.Equal ("TAB to jump between text ", _textView.SelectedText); + break; + case 5: + Assert.Equal (32, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (0, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (32, _textView.SelectedLength); + Assert.Equal ("TAB to jump between text fields.", _textView.SelectedText); + break; + } + iteration++; + } + } + + [Fact] + public void WordBackward_With_Selection () + { + _textView.CursorPosition = new Point (_textView.Text.Length, 0); + _textView.SelectionStartColumn = _textView.Text.Length; + _textView.SelectionStartRow = 0; + var iteration = 0; + + while (_textView.CursorPosition.X > 0) { + _textView.ProcessKey (new KeyEvent (Key.CursorLeft | Key.CtrlMask | Key.ShiftMask, new KeyModifiers ())); + switch (iteration) { + case 0: + Assert.Equal (25, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (32, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (7, _textView.SelectedLength); + Assert.Equal ("fields.", _textView.SelectedText); + break; + case 1: + Assert.Equal (20, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (32, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (12, _textView.SelectedLength); + Assert.Equal ("text fields.", _textView.SelectedText); + break; + case 2: + Assert.Equal (12, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (32, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (20, _textView.SelectedLength); + Assert.Equal ("between text fields.", _textView.SelectedText); + break; + case 3: + Assert.Equal (7, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (32, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (25, _textView.SelectedLength); + Assert.Equal ("jump between text fields.", _textView.SelectedText); + break; + case 4: + Assert.Equal (4, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (32, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (28, _textView.SelectedLength); + Assert.Equal ("to jump between text fields.", _textView.SelectedText); + break; + case 5: + Assert.Equal (0, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (32, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (32, _textView.SelectedLength); + Assert.Equal ("TAB to jump between text fields.", _textView.SelectedText); + break; + } + iteration++; + } + } + + [Fact] + public void WordForward_With_The_Same_Values_For_SelectedStart_And_CursorPosition_And_Not_Starting_At_Beginning_Of_The_Text () + { + _textView.CursorPosition = new Point (10, 0); + _textView.SelectionStartColumn = 10; + _textView.SelectionStartRow = 0; + var iteration = 0; + + while (_textView.CursorPosition.X < _textView.Text.Length) { + _textView.ProcessKey (new KeyEvent (Key.CursorRight | Key.CtrlMask | Key.ShiftMask, new KeyModifiers ())); + switch (iteration) { + case 0: + Assert.Equal (12, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (10, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (2, _textView.SelectedLength); + Assert.Equal ("p ", _textView.SelectedText); + break; + case 1: + Assert.Equal (20, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (10, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (10, _textView.SelectedLength); + Assert.Equal ("p between ", _textView.SelectedText); + break; + case 2: + Assert.Equal (25, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (10, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (15, _textView.SelectedLength); + Assert.Equal ("p between text ", _textView.SelectedText); + break; + case 3: + Assert.Equal (32, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (10, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (22, _textView.SelectedLength); + Assert.Equal ("p between text fields.", _textView.SelectedText); + break; + } + iteration++; + } + } + + [Fact] + public void WordBackward_With_The_Same_Values_For_SelectedStart_And_CursorPosition_And_Not_Starting_At_Beginning_Of_The_Text () + { + _textView.CursorPosition = new Point (10, 0); + _textView.SelectionStartColumn = 10; + _textView.SelectionStartRow = 0; + var iteration = 0; + + while (_textView.CursorPosition.X > 0) { + _textView.ProcessKey (new KeyEvent (Key.CursorLeft | Key.CtrlMask | Key.ShiftMask, new KeyModifiers ())); + switch (iteration) { + case 0: + Assert.Equal (7, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (10, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (3, _textView.SelectedLength); + Assert.Equal ("jum", _textView.SelectedText); + break; + case 1: + Assert.Equal (4, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (10, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (6, _textView.SelectedLength); + Assert.Equal ("to jum", _textView.SelectedText); + break; + case 2: + Assert.Equal (0, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (10, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (10, _textView.SelectedLength); + Assert.Equal ("TAB to jum", _textView.SelectedText); + break; + } + iteration++; + } + } + + [Fact] + public void WordForward_With_No_Selection_And_With_More_Than_Only_One_Whitespace_And_With_Only_One_Letter () + { + // 1 2 3 4 5 + // 0123456789012345678901234567890123456789012345678901234=55 (Length) + _textView.Text = "TAB t o jump b etween t ext f ields ."; + _textView.CursorPosition = new Point (0, 0); + var iteration = 0; + + while (_textView.CursorPosition.X < _textView.Text.Length) { + _textView.ProcessKey (new KeyEvent (Key.CursorRight | Key.CtrlMask, new KeyModifiers ())); + switch (iteration) { + case 0: + Assert.Equal (6, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (0, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (0, _textView.SelectedLength); + Assert.Equal ("", _textView.SelectedText); + break; + case 1: + Assert.Equal (9, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (0, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (0, _textView.SelectedLength); + Assert.Equal ("", _textView.SelectedText); + break; + case 2: + Assert.Equal (12, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (0, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (0, _textView.SelectedLength); + Assert.Equal ("", _textView.SelectedText); + break; + case 3: + Assert.Equal (25, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (0, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (0, _textView.SelectedLength); + Assert.Equal ("", _textView.SelectedText); + break; + case 4: + Assert.Equal (28, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (0, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (0, _textView.SelectedLength); + Assert.Equal ("", _textView.SelectedText); + break; + case 5: + Assert.Equal (38, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (0, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (0, _textView.SelectedLength); + Assert.Equal ("", _textView.SelectedText); + break; + case 6: + Assert.Equal (40, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (0, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (0, _textView.SelectedLength); + Assert.Equal ("", _textView.SelectedText); + break; + case 7: + Assert.Equal (46, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (0, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (0, _textView.SelectedLength); + Assert.Equal ("", _textView.SelectedText); + break; + case 8: + Assert.Equal (48, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (0, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (0, _textView.SelectedLength); + Assert.Equal ("", _textView.SelectedText); + break; + case 9: + Assert.Equal (55, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (0, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (0, _textView.SelectedLength); + Assert.Equal ("", _textView.SelectedText); + break; + } + iteration++; + } + } + + [Fact] + public void WordBackward_With_No_Selection_And_With_More_Than_Only_One_Whitespace_And_With_Only_One_Letter () + { + // 1 2 3 4 5 + // 0123456789012345678901234567890123456789012345678901234=55 (Length) + _textView.Text = "TAB t o jump b etween t ext f ields ."; + _textView.CursorPosition = new Point (_textView.Text.Length, 0); + var iteration = 0; + + while (_textView.CursorPosition.X > 0) { + _textView.ProcessKey (new KeyEvent (Key.CursorLeft | Key.CtrlMask, new KeyModifiers ())); + switch (iteration) { + case 0: + Assert.Equal (54, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (0, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (0, _textView.SelectedLength); + Assert.Equal ("", _textView.SelectedText); + break; + case 1: + Assert.Equal (48, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (0, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (0, _textView.SelectedLength); + Assert.Equal ("", _textView.SelectedText); + break; + case 2: + Assert.Equal (46, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (0, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (0, _textView.SelectedLength); + Assert.Equal ("", _textView.SelectedText); + break; + case 3: + Assert.Equal (40, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (0, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (0, _textView.SelectedLength); + Assert.Equal ("", _textView.SelectedText); + break; + case 4: + Assert.Equal (38, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (0, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (0, _textView.SelectedLength); + Assert.Equal ("", _textView.SelectedText); + break; + case 5: + Assert.Equal (28, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (0, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (0, _textView.SelectedLength); + Assert.Equal ("", _textView.SelectedText); + break; + case 6: + Assert.Equal (25, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (0, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (0, _textView.SelectedLength); + Assert.Equal ("", _textView.SelectedText); + break; + case 7: + Assert.Equal (12, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (0, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (0, _textView.SelectedLength); + Assert.Equal ("", _textView.SelectedText); + break; + case 8: + Assert.Equal (9, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (0, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (0, _textView.SelectedLength); + Assert.Equal ("", _textView.SelectedText); + break; + case 9: + Assert.Equal (6, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (0, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (0, _textView.SelectedLength); + Assert.Equal ("", _textView.SelectedText); + break; + case 10: + Assert.Equal (0, _textView.CursorPosition.X); + Assert.Equal (0, _textView.CursorPosition.Y); + Assert.Equal (0, _textView.SelectionStartColumn); + Assert.Equal (0, _textView.SelectionStartRow); + Assert.Equal (0, _textView.SelectedLength); + Assert.Equal ("", _textView.SelectedText); + break; + } + iteration++; + } + } + + [Fact] + public void Copy_Or_Cut_Null_If_No_Selection () + { + _textView.SelectionStartColumn = 0; + _textView.SelectionStartRow = 0; + _textView.Copy (); + Assert.Equal ("", _textView.SelectedText); + _textView.Cut (); + Assert.Equal ("", _textView.SelectedText); + } + + [Fact] + public void Copy_Or_Cut_Not_Null_If_Has_Selection () + { + _textView.SelectionStartColumn = 20; + _textView.SelectionStartRow = 0; + _textView.CursorPosition = new Point (24, 0); + _textView.Copy (); + Assert.Equal ("text", _textView.SelectedText); + _textView.Cut (); + Assert.Equal ("", _textView.SelectedText); + } + + [Fact] + public void Copy_Or_Cut_And_Paste_With_Selection () + { + _textView.SelectionStartColumn = 20; + _textView.SelectionStartRow = 0; + _textView.CursorPosition = new Point (24, 0); + _textView.Copy (); + Assert.Equal ("text", _textView.SelectedText); + Assert.Equal ("TAB to jump between text fields.", _textView.Text); + _textView.Paste (); + Assert.Equal ("TAB to jump between text fields.", _textView.Text); + _textView.SelectionStartColumn = 20; + _textView.SelectionStartRow = 0; + _textView.Cut (); + _textView.Paste (); + Assert.Equal ("TAB to jump between text fields.", _textView.Text); + } + + [Fact] + public void Copy_Or_Cut_And_Paste_With_No_Selection () + { + _textView.SelectionStartColumn = 20; + _textView.SelectionStartRow = 0; + _textView.CursorPosition = new Point (24, 0); + _textView.Copy (); + Assert.Equal ("text", _textView.SelectedText); + Assert.Equal ("TAB to jump between text fields.", _textView.Text); + _textView.SelectionStartColumn = 0; + _textView.SelectionStartRow = 0; + Assert.True (_textView.Selecting); + _textView.Selecting = false; + _textView.Paste (); + Assert.Equal ("TAB to jump between texttext fields.", _textView.Text); + _textView.SelectionStartColumn = 24; + _textView.SelectionStartRow = 0; + _textView.Cut (); + Assert.Equal ("", _textView.SelectedText); + Assert.Equal ("TAB to jump between text fields.", _textView.Text); + _textView.SelectionStartColumn = 0; + _textView.SelectionStartRow = 0; + Assert.True (_textView.Selecting); + _textView.Selecting = false; + _textView.Paste (); + Assert.Equal ("TAB to jump between texttext fields.", _textView.Text); + } + + [Fact] + public void Cut_Not_Allowed_If_ReadOnly_Is_True () + { + _textView.ReadOnly = true; + _textView.SelectionStartColumn = 20; + _textView.SelectionStartRow = 0; + _textView.CursorPosition = new Point (24, 0); + _textView.Copy (); + Assert.Equal ("text", _textView.SelectedText); + _textView.Cut (); // Selecting is set to false after Cut. + Assert.Equal ("", _textView.SelectedText); + _textView.ReadOnly = false; + Assert.False (_textView.Selecting); + _textView.Selecting = true; // Needed to set Selecting to true. + _textView.Copy (); + Assert.Equal ("text", _textView.SelectedText); + _textView.Cut (); + Assert.Equal ("", _textView.SelectedText); + } + + [Fact] + public void Paste_Always_Clear_The_SelectedText () + { + _textView.SelectionStartColumn = 20; + _textView.SelectionStartRow = 0; + _textView.CursorPosition = new Point (24, 0); + _textView.Copy (); + Assert.Equal ("text", _textView.SelectedText); + _textView.Paste (); + Assert.Equal ("", _textView.SelectedText); + } + + [Fact] + public void TextChanged_Event () + { + _textView.TextChanged += () => { + if (_textView.Text == "changing") { + Assert.Equal ("changing", _textView.Text); + _textView.Text = "changed"; + } + }; + + _textView.Text = "changing"; + Assert.Equal ("changed", _textView.Text); + } + + [Fact] + public void Used_Is_True_By_Default () + { + _textView.CursorPosition = new Point (10, 0); + Assert.Equal ("TAB to jump between text fields.", _textView.Text); + _textView.ProcessKey (new KeyEvent ((Key)0x75, new KeyModifiers ())); // u + Assert.Equal ("TAB to jumup between text fields.", _textView.Text); + _textView.ProcessKey (new KeyEvent ((Key)0x73, new KeyModifiers ())); // s + Assert.Equal ("TAB to jumusp between text fields.", _textView.Text); + _textView.ProcessKey (new KeyEvent ((Key)0x65, new KeyModifiers ())); // e + Assert.Equal ("TAB to jumusep between text fields.", _textView.Text); + _textView.ProcessKey (new KeyEvent ((Key)0x64, new KeyModifiers ())); // d + Assert.Equal ("TAB to jumusedp between text fields.", _textView.Text); + } + + [Fact] + public void Used_Is_False () + { + _textView.Used = false; + _textView.CursorPosition = new Point (10, 0); + Assert.Equal ("TAB to jump between text fields.", _textView.Text); + _textView.ProcessKey (new KeyEvent ((Key)0x75, new KeyModifiers ())); // u + Assert.Equal ("TAB to jumu between text fields.", _textView.Text); + _textView.ProcessKey (new KeyEvent ((Key)0x73, new KeyModifiers ())); // s + Assert.Equal ("TAB to jumusbetween text fields.", _textView.Text); + _textView.ProcessKey (new KeyEvent ((Key)0x65, new KeyModifiers ())); // e + Assert.Equal ("TAB to jumuseetween text fields.", _textView.Text); + _textView.ProcessKey (new KeyEvent ((Key)0x64, new KeyModifiers ())); // d + Assert.Equal ("TAB to jumusedtween text fields.", _textView.Text); + } + } +}