From 6c340230880e25628efb6c61564a741955328771 Mon Sep 17 00:00:00 2001 From: Jeff Greene Date: Wed, 24 Aug 2022 18:22:26 -0700 Subject: [PATCH 1/2] Fixed double delete when wordwrap is enabled --- Terminal.Gui/Views/TextView.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs index b357308e4e..f33def2b9e 100644 --- a/Terminal.Gui/Views/TextView.cs +++ b/Terminal.Gui/Views/TextView.cs @@ -3760,9 +3760,12 @@ bool DeleteTextBackwards () historyText.Add (new List> () { new List (currentLine) }, CursorPosition); - currentLine.RemoveAt (currentColumn - 1); - if (wordWrap && wrapManager.RemoveAt (currentRow, currentColumn - 1)) { - wrapNeeded = true; + if (wordWrap) { + if (wrapManager.RemoveAt (currentRow, currentColumn - 1)) { + wrapNeeded = true; + } + } else { + currentLine.RemoveAt (currentColumn - 1); } currentColumn--; From ca95daad02c149b31b353e6239f18de90a561ee4 Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 25 Aug 2022 18:09:10 +0100 Subject: [PATCH 2/2] Fixes #1963. Deletes multiple characters editing a TextView with WordWrap enabled. --- Terminal.Gui/Views/TextView.cs | 21 ++- UnitTests/TextViewTests.cs | 226 +++++++++++++++++++++++++++++++++ 2 files changed, 234 insertions(+), 13 deletions(-) diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs index b5bc051730..d57152572f 100644 --- a/Terminal.Gui/Views/TextView.cs +++ b/Terminal.Gui/Views/TextView.cs @@ -3178,14 +3178,14 @@ void KillWordBackward () if (newPos.HasValue && currentRow == newPos.Value.row) { var restCount = currentColumn - newPos.Value.col; currentLine.RemoveRange (newPos.Value.col, restCount); - if (wordWrap && wrapManager.RemoveRange (currentRow, newPos.Value.col, restCount)) { + if (wordWrap) { wrapNeeded = true; } currentColumn = newPos.Value.col; } else if (newPos.HasValue) { var restCount = currentLine.Count - currentColumn; currentLine.RemoveRange (currentColumn, restCount); - if (wordWrap && wrapManager.RemoveRange (currentRow, currentColumn, restCount)) { + if (wordWrap) { wrapNeeded = true; } currentColumn = newPos.Value.col; @@ -3235,7 +3235,7 @@ void KillWordForward () restCount = currentLine.Count - currentColumn; currentLine.RemoveRange (currentColumn, restCount); } - if (wordWrap && wrapManager.RemoveRange (currentRow, currentColumn, restCount)) { + if (wordWrap) { wrapNeeded = true; } @@ -3717,7 +3717,7 @@ bool DeleteTextForwards () historyText.Add (new List> () { new List (currentLine) }, CursorPosition, HistoryText.LineStatus.Replaced); - if (wordWrap && wrapManager.RemoveLine (currentRow, currentColumn, out _)) { + if (wordWrap) { wrapNeeded = true; } if (wrapNeeded) { @@ -3734,7 +3734,7 @@ bool DeleteTextForwards () historyText.Add (new List> () { new List (currentLine) }, CursorPosition, HistoryText.LineStatus.Replaced); - if (wordWrap && wrapManager.RemoveAt (currentRow, currentColumn)) { + if (wordWrap) { wrapNeeded = true; } @@ -3762,7 +3762,7 @@ bool DeleteTextBackwards () historyText.Add (new List> () { new List (currentLine) }, CursorPosition); currentLine.RemoveAt (currentColumn - 1); - if (wordWrap && wrapManager.RemoveAt (currentRow, currentColumn - 1)) { + if (wordWrap) { wrapNeeded = true; } currentColumn--; @@ -3794,8 +3794,7 @@ bool DeleteTextBackwards () var prevCount = prevRow.Count; model.GetLine (prowIdx).AddRange (GetCurrentLine ()); model.RemoveLine (currentRow); - bool lineRemoved = false; - if (wordWrap && wrapManager.RemoveLine (currentRow, currentColumn, out lineRemoved, false)) { + if (wordWrap) { wrapNeeded = true; } currentRow--; @@ -3803,11 +3802,7 @@ bool DeleteTextBackwards () historyText.Add (new List> () { GetCurrentLine () }, new Point (currentColumn, prowIdx), HistoryText.LineStatus.Replaced); - if (wrapNeeded && !lineRemoved) { - currentColumn = Math.Max (prevCount - 1, 0); - } else { - currentColumn = prevCount; - } + currentColumn = prevCount; SetNeedsDisplay (); } diff --git a/UnitTests/TextViewTests.cs b/UnitTests/TextViewTests.cs index 2ad914c9e7..d6a5376a72 100644 --- a/UnitTests/TextViewTests.cs +++ b/UnitTests/TextViewTests.cs @@ -6044,5 +6044,231 @@ This is the second line. line. ", output); } + + [Fact] + [AutoInitShutdown] + public void DeleteTextBackwards_WordWrap_False_Return_Undo () + { + const string text = "This is the first line.\nThis is the second line.\n"; + var tv = new TextView () { + Width = Dim.Fill (), + Height = Dim.Fill (), + Text = text + }; + var envText = tv.Text; + Application.Top.Add (tv); + Application.Begin (Application.Top); + + Assert.False (tv.WordWrap); + Assert.Equal (Point.Empty, tv.CursorPosition); + GraphViewTests.AssertDriverContentsWithFrameAre (@" +This is the first line. +This is the second line. +", output); + + tv.CursorPosition = new Point (3, 0); + Assert.Equal (new Point (3, 0), tv.CursorPosition); + Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ()))); + tv.Redraw (tv.Bounds); + Assert.Equal (new Point (2, 0), tv.CursorPosition); + GraphViewTests.AssertDriverContentsWithFrameAre (@" +Ths is the first line. +This is the second line. +", output); + + tv.CursorPosition = new Point (0, 1); + Assert.Equal (new Point (0, 1), tv.CursorPosition); + Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ()))); + tv.Redraw (tv.Bounds); + Assert.Equal (new Point (22, 0), tv.CursorPosition); + GraphViewTests.AssertDriverContentsWithFrameAre (@" +Ths is the first line.This is the second line. +", output); + + Assert.True (tv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ()))); + tv.Redraw (tv.Bounds); + Assert.Equal (new Point (0, 1), tv.CursorPosition); + GraphViewTests.AssertDriverContentsWithFrameAre (@" +Ths is the first line. +This is the second line. +", output); + + while (tv.Text != envText) { + Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ()))); + } + Assert.Equal (envText, tv.Text); + Assert.Equal (new Point (3, 0), tv.CursorPosition); + Assert.False (tv.IsDirty); + } + + [Fact] + [AutoInitShutdown] + public void DeleteTextBackwards_WordWrap_True_Return_Undo () + { + const string text = "This is the first line.\nThis is the second line.\n"; + var tv = new TextView () { + Width = Dim.Fill (), + Height = Dim.Fill (), + Text = text, + WordWrap = true + }; + var envText = tv.Text; + Application.Top.Add (tv); + Application.Begin (Application.Top); + + Assert.True (tv.WordWrap); + Assert.Equal (Point.Empty, tv.CursorPosition); + GraphViewTests.AssertDriverContentsWithFrameAre (@" +This is the first line. +This is the second line. +", output); + + tv.CursorPosition = new Point (3, 0); + Assert.Equal (new Point (3, 0), tv.CursorPosition); + Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ()))); + tv.Redraw (tv.Bounds); + Assert.Equal (new Point (2, 0), tv.CursorPosition); + GraphViewTests.AssertDriverContentsWithFrameAre (@" +Ths is the first line. +This is the second line. +", output); + + tv.CursorPosition = new Point (0, 1); + Assert.Equal (new Point (0, 1), tv.CursorPosition); + Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ()))); + tv.Redraw (tv.Bounds); + Assert.Equal (new Point (22, 0), tv.CursorPosition); + GraphViewTests.AssertDriverContentsWithFrameAre (@" +Ths is the first line.This is the second line. +", output); + + Assert.True (tv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ()))); + tv.Redraw (tv.Bounds); + Assert.Equal (new Point (0, 1), tv.CursorPosition); + GraphViewTests.AssertDriverContentsWithFrameAre (@" +Ths is the first line. +This is the second line. +", output); + + while (tv.Text != envText) { + Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ()))); + } + Assert.Equal (envText, tv.Text); + Assert.Equal (new Point (3, 0), tv.CursorPosition); + Assert.False (tv.IsDirty); + } + + [Fact] + [AutoInitShutdown] + public void DeleteTextForwards_WordWrap_False_Return_Undo () + { + const string text = "This is the first line.\nThis is the second line.\n"; + var tv = new TextView () { + Width = Dim.Fill (), + Height = Dim.Fill (), + Text = text + }; + var envText = tv.Text; + Application.Top.Add (tv); + Application.Begin (Application.Top); + + Assert.False (tv.WordWrap); + Assert.Equal (Point.Empty, tv.CursorPosition); + GraphViewTests.AssertDriverContentsWithFrameAre (@" +This is the first line. +This is the second line. +", output); + + tv.CursorPosition = new Point (2, 0); + Assert.Equal (new Point (2, 0), tv.CursorPosition); + Assert.True (tv.ProcessKey (new KeyEvent (Key.DeleteChar, new KeyModifiers ()))); + tv.Redraw (tv.Bounds); + Assert.Equal (new Point (2, 0), tv.CursorPosition); + GraphViewTests.AssertDriverContentsWithFrameAre (@" +Ths is the first line. +This is the second line. +", output); + + tv.CursorPosition = new Point (22, 0); + Assert.Equal (new Point (22, 0), tv.CursorPosition); + Assert.True (tv.ProcessKey (new KeyEvent (Key.DeleteChar, new KeyModifiers ()))); + tv.Redraw (tv.Bounds); + Assert.Equal (new Point (22, 0), tv.CursorPosition); + GraphViewTests.AssertDriverContentsWithFrameAre (@" +Ths is the first line.This is the second line. +", output); + + Assert.True (tv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ()))); + tv.Redraw (tv.Bounds); + Assert.Equal (new Point (0, 1), tv.CursorPosition); + GraphViewTests.AssertDriverContentsWithFrameAre (@" +Ths is the first line. +This is the second line. +", output); + + while (tv.Text != envText) { + Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ()))); + } + Assert.Equal (envText, tv.Text); + Assert.Equal (new Point (2, 0), tv.CursorPosition); + Assert.False (tv.IsDirty); + } + + [Fact] + [AutoInitShutdown] + public void DeleteTextForwards_WordWrap_True_Return_Undo () + { + const string text = "This is the first line.\nThis is the second line.\n"; + var tv = new TextView () { + Width = Dim.Fill (), + Height = Dim.Fill (), + Text = text, + WordWrap = true + }; + var envText = tv.Text; + Application.Top.Add (tv); + Application.Begin (Application.Top); + + Assert.True (tv.WordWrap); + Assert.Equal (Point.Empty, tv.CursorPosition); + GraphViewTests.AssertDriverContentsWithFrameAre (@" +This is the first line. +This is the second line. +", output); + + tv.CursorPosition = new Point (2, 0); + Assert.Equal (new Point (2, 0), tv.CursorPosition); + Assert.True (tv.ProcessKey (new KeyEvent (Key.DeleteChar, new KeyModifiers ()))); + tv.Redraw (tv.Bounds); + Assert.Equal (new Point (2, 0), tv.CursorPosition); + GraphViewTests.AssertDriverContentsWithFrameAre (@" +Ths is the first line. +This is the second line. +", output); + + tv.CursorPosition = new Point (22, 0); + Assert.Equal (new Point (22, 0), tv.CursorPosition); + Assert.True (tv.ProcessKey (new KeyEvent (Key.DeleteChar, new KeyModifiers ()))); + tv.Redraw (tv.Bounds); + Assert.Equal (new Point (22, 0), tv.CursorPosition); + GraphViewTests.AssertDriverContentsWithFrameAre (@" +Ths is the first line.This is the second line. +", output); + + Assert.True (tv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ()))); + tv.Redraw (tv.Bounds); + Assert.Equal (new Point (0, 1), tv.CursorPosition); + GraphViewTests.AssertDriverContentsWithFrameAre (@" +Ths is the first line. +This is the second line. +", output); + + while (tv.Text != envText) { + Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ()))); + } + Assert.Equal (envText, tv.Text); + Assert.Equal (new Point (2, 0), tv.CursorPosition); + Assert.False (tv.IsDirty); + } } } \ No newline at end of file