From fc07e5f3bde97b880996583518bf98c963e69850 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 10 Aug 2022 21:59:39 +0100 Subject: [PATCH] Fixes #1948. It isn't possible to get unwrapped cursor position when word wrap is enabled on TextView. --- Terminal.Gui/Views/TextView.cs | 21 ++++++++ UICatalog/Scenarios/Editor.cs | 61 +++++++++++++---------- UnitTests/TextViewTests.cs | 91 ++++++++++++++++++++++++++++++++++ 3 files changed, 146 insertions(+), 27 deletions(-) diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs index f03de2ec8b..0df674f5b4 100644 --- a/Terminal.Gui/Views/TextView.cs +++ b/Terminal.Gui/Views/TextView.cs @@ -1176,6 +1176,11 @@ public class TextView : View { /// public event Action TextChanged; + /// + /// Invoked with the unwrapped . + /// + public event Action UnwrappedCursorPosition; + /// /// Provides autocomplete context menu based on suggestions at the current cursor /// position. Populate to enable this feature @@ -2320,6 +2325,20 @@ void UpdateWrapModel ([CallerMemberName] string caller = null) throw new InvalidOperationException ($"WordWrap settings was changed after the {currentCaller} call."); } + /// + /// Invoke the event with the unwrapped . + /// + public virtual void OnUnwrappedCursorPosition () + { + var row = currentRow; + var col = currentColumn; + if (wordWrap) { + row = wrapManager.GetModelLineFromWrappedLines (currentRow); + col = wrapManager.GetModelColFromWrappedLines (currentRow, currentColumn); + } + UnwrappedCursorPosition?.Invoke (new Point (col, row)); + } + /// public override void Redraw (Rect bounds) { @@ -2617,6 +2636,8 @@ void Adjust () } else { PositionCursor (); } + + OnUnwrappedCursorPosition (); } (int width, int height) OffSetBackground () diff --git a/UICatalog/Scenarios/Editor.cs b/UICatalog/Scenarios/Editor.cs index bfc18fd9ef..633158ee57 100644 --- a/UICatalog/Scenarios/Editor.cs +++ b/UICatalog/Scenarios/Editor.cs @@ -26,11 +26,11 @@ public class Editor : Scenario { private string _textToReplace; private bool _matchCase; private bool _matchWholeWord; - private Window winDialog; + private Window _winDialog; private TabView _tabView; - private MenuItem miForceMinimumPosToZero; - private bool forceMinimumPosToZero = true; - private readonly List cultureInfos = Application.SupportedCultures; + private MenuItem _miForceMinimumPosToZero; + private bool _forceMinimumPosToZero = true; + private readonly List _cultureInfos = Application.SupportedCultures; public override void Init (Toplevel top, ColorScheme colorScheme) { @@ -60,6 +60,12 @@ public override void Init (Toplevel top, ColorScheme colorScheme) CreateDemoFile (_fileName); + var siCursorPosition = new StatusItem (Key.Null, "", null); + + _textView.UnwrappedCursorPosition += (e) => { + siCursorPosition.Title = $"Ln {e.Y + 1}, Col {e.X + 1}"; + }; + LoadFile (); Win.Add (_textView); @@ -103,16 +109,17 @@ public override void Init (Toplevel top, ColorScheme colorScheme) CreateVisibleChecked () }), new MenuBarItem ("Conte_xtMenu", new MenuItem [] { - miForceMinimumPosToZero = new MenuItem ("ForceMinimumPosTo_Zero", "", () => { - miForceMinimumPosToZero.Checked = forceMinimumPosToZero = !forceMinimumPosToZero; - _textView.ContextMenu.ForceMinimumPosToZero = forceMinimumPosToZero; - }) { CheckType = MenuItemCheckStyle.Checked, Checked = forceMinimumPosToZero }, + _miForceMinimumPosToZero = new MenuItem ("ForceMinimumPosTo_Zero", "", () => { + _miForceMinimumPosToZero.Checked = _forceMinimumPosToZero = !_forceMinimumPosToZero; + _textView.ContextMenu.ForceMinimumPosToZero = _forceMinimumPosToZero; + }) { CheckType = MenuItemCheckStyle.Checked, Checked = _forceMinimumPosToZero }, new MenuBarItem ("_Languages", GetSupportedCultures ()) }) }); Top.Add (menu); var statusBar = new StatusBar (new StatusItem [] { + siCursorPosition, new StatusItem(Key.F2, "~F2~ Open", () => Open()), new StatusItem(Key.F3, "~F3~ Save", () => Save()), new StatusItem(Key.F4, "~F4~ Save As", () => SaveAs()), @@ -168,20 +175,20 @@ public override void Init (Toplevel top, ColorScheme colorScheme) Win.KeyPress += (e) => { var keys = ShortcutHelper.GetModifiersKey (e.KeyEvent); - if (winDialog != null && (e.KeyEvent.Key == Key.Esc + if (_winDialog != null && (e.KeyEvent.Key == Key.Esc || e.KeyEvent.Key == (Key.Q | Key.CtrlMask))) { DisposeWinDialog (); } else if (e.KeyEvent.Key == (Key.Q | Key.CtrlMask)) { Quit (); e.Handled = true; - } else if (winDialog != null && keys == (Key.Tab | Key.CtrlMask)) { + } else if (_winDialog != null && keys == (Key.Tab | Key.CtrlMask)) { if (_tabView.SelectedTab == _tabView.Tabs.ElementAt (_tabView.Tabs.Count - 1)) { _tabView.SelectedTab = _tabView.Tabs.ElementAt (0); } else { _tabView.SwitchTabBy (1); } e.Handled = true; - } else if (winDialog != null && keys == (Key.Tab | Key.CtrlMask | Key.ShiftMask)) { + } else if (_winDialog != null && keys == (Key.Tab | Key.CtrlMask | Key.ShiftMask)) { if (_tabView.SelectedTab == _tabView.Tabs.ElementAt (0)) { _tabView.SelectedTab = _tabView.Tabs.ElementAt (_tabView.Tabs.Count - 1); } else { @@ -196,9 +203,9 @@ public override void Init (Toplevel top, ColorScheme colorScheme) private void DisposeWinDialog () { - winDialog.Dispose (); - Win.Remove (winDialog); - winDialog = null; + _winDialog.Dispose (); + Win.Remove (_winDialog); + _winDialog = null; } public override void Setup () @@ -276,7 +283,7 @@ private void ContinueFind (bool next = true, bool replace = false) Find (); return; } else if (replace && (string.IsNullOrEmpty (_textToFind) - || (winDialog == null && string.IsNullOrEmpty (_textToReplace)))) { + || (_winDialog == null && string.IsNullOrEmpty (_textToReplace)))) { Replace (); return; } @@ -323,7 +330,7 @@ private void ReplacePrevious () private void ReplaceAll () { - if (string.IsNullOrEmpty (_textToFind) || (string.IsNullOrEmpty (_textToReplace) && winDialog == null)) { + if (string.IsNullOrEmpty (_textToFind) || (string.IsNullOrEmpty (_textToReplace) && _winDialog == null)) { Replace (); return; } @@ -468,7 +475,7 @@ private MenuItem [] GetSupportedCultures () List supportedCultures = new List (); var index = -1; - foreach (var c in cultureInfos) { + foreach (var c in _cultureInfos) { var culture = new MenuItem { CheckType = MenuItemCheckStyle.Checked }; @@ -714,17 +721,17 @@ void SetCursor (CursorVisibility visibility) private void CreateFindReplace (bool isFind = true) { - if (winDialog != null) { - winDialog.SetFocus (); + if (_winDialog != null) { + _winDialog.SetFocus (); return; } - winDialog = new Window (isFind ? "Find" : "Replace") { + _winDialog = new Window (isFind ? "Find" : "Replace") { X = Win.Bounds.Width / 2 - 30, Y = Win.Bounds.Height / 2 - 10, ColorScheme = Colors.TopLevel }; - winDialog.Border.Effect3D = true; + _winDialog.Border.Effect3D = true; _tabView = new TabView () { X = 0, @@ -737,15 +744,15 @@ private void CreateFindReplace (bool isFind = true) var replace = ReplaceTab (); _tabView.AddTab (new TabView.Tab ("Replace", replace), !isFind); _tabView.SelectedTabChanged += (s, e) => _tabView.SelectedTab.View.FocusFirst (); - winDialog.Add (_tabView); + _winDialog.Add (_tabView); - Win.Add (winDialog); + Win.Add (_winDialog); - winDialog.Width = replace.Width + 4; - winDialog.Height = replace.Height + 4; + _winDialog.Width = replace.Width + 4; + _winDialog.Height = replace.Height + 4; - winDialog.SuperView.BringSubviewToFront (winDialog); - winDialog.SetFocus (); + _winDialog.SuperView.BringSubviewToFront (_winDialog); + _winDialog.SetFocus (); } private void SetFindText () diff --git a/UnitTests/TextViewTests.cs b/UnitTests/TextViewTests.cs index bb7dfbb5d1..6154e8c532 100644 --- a/UnitTests/TextViewTests.cs +++ b/UnitTests/TextViewTests.cs @@ -5934,5 +5934,96 @@ public void Mouse_Button_Shift_Preserves_Selection () Assert.True (_textView.Selecting); Assert.Equal ("", _textView.SelectedText); } + + [Fact, AutoInitShutdown] + public void UnwrappedCursorPosition_Event () + { + var cp = Point.Empty; + var tv = new TextView () { + Width = Dim.Fill (), + Height = Dim.Fill (), + Text = "This is the first line.\nThis is the second line.\n" + }; + tv.UnwrappedCursorPosition += (e) => { + cp = e; + }; + Application.Top.Add (tv); + Application.Begin (Application.Top); + + Assert.False (tv.WordWrap); + Assert.Equal (Point.Empty, tv.CursorPosition); + Assert.Equal (Point.Empty, cp); + GraphViewTests.AssertDriverContentsWithFrameAre (@" +This is the first line. +This is the second line. +", output); + + tv.WordWrap = true; + tv.CursorPosition = new Point (12, 0); + tv.Redraw (tv.Bounds); + Assert.Equal (new Point (12, 0), tv.CursorPosition); + Assert.Equal (new Point (12, 0), cp); + GraphViewTests.AssertDriverContentsWithFrameAre (@" +This is the first line. +This is the second line. +", output); + + ((FakeDriver)Application.Driver).SetBufferSize (6, 25); + tv.Redraw (tv.Bounds); + Assert.Equal (new Point (4, 2), tv.CursorPosition); + Assert.Equal (new Point (12, 0), cp); + GraphViewTests.AssertDriverContentsWithFrameAre (@" +This +is +the +first + +line. +This +is +the +secon +d +line. +", output); + + Assert.True (tv.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ()))); + tv.Redraw (tv.Bounds); + Assert.Equal (new Point (0, 3), tv.CursorPosition); + Assert.Equal (new Point (12, 0), cp); + GraphViewTests.AssertDriverContentsWithFrameAre (@" +This +is +the +first + +line. +This +is +the +secon +d +line. +", output); + + Assert.True (tv.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ()))); + tv.Redraw (tv.Bounds); + Assert.Equal (new Point (1, 3), tv.CursorPosition); + Assert.Equal (new Point (13, 0), cp); + GraphViewTests.AssertDriverContentsWithFrameAre (@" +This +is +the +first + +line. +This +is +the +secon +d +line. +", output); + } } } \ No newline at end of file