From 30602b1f4621388a4093ea336289fa88833c9ec7 Mon Sep 17 00:00:00 2001 From: Miguel de Icaza Date: Fri, 23 Mar 2018 23:46:01 -0400 Subject: [PATCH] Forward word, backward word, some small code changes --- Terminal.Gui/Core.cs | 11 +- Terminal.Gui/Dialogs/MessageBox.cs | 2 +- Terminal.Gui/Driver.cs | 4 +- Terminal.Gui/MonoCurses/mainloop.cs | 6 +- Terminal.Gui/Views/Button.cs | 10 +- Terminal.Gui/Views/Checkbox.cs | 6 +- Terminal.Gui/Views/Clipboard.cs | 2 +- Terminal.Gui/Views/Label.cs | 4 +- Terminal.Gui/Views/ListView.cs | 2 +- Terminal.Gui/Views/Menu.cs | 2 +- Terminal.Gui/Views/TextView.cs | 268 ++++++++++++++++++++-------- demo.cs | 2 +- 12 files changed, 218 insertions(+), 101 deletions(-) diff --git a/Terminal.Gui/Core.cs b/Terminal.Gui/Core.cs index 50cfce26d..71a1408a8 100644 --- a/Terminal.Gui/Core.cs +++ b/Terminal.Gui/Core.cs @@ -1176,7 +1176,7 @@ namespace Terminal.Gui { /// to the mainloop, allowing user code to use async/await. /// /// - public class Application { + public static class Application { /// /// The current Console Driver in use. /// @@ -1316,7 +1316,7 @@ namespace Terminal.Gui { protected virtual void Dispose (bool disposing) { if (Toplevel != null) { - Application.End (Toplevel); + End (Toplevel); Toplevel = null; } } @@ -1509,8 +1509,9 @@ namespace Terminal.Gui { toplevels.Pop (); if (toplevels.Count == 0) Shutdown (); - else { - Current = toplevels.Peek () as Toplevel; + else + { + Current = toplevels.Peek(); Refresh (); } } @@ -1605,8 +1606,6 @@ namespace Terminal.Gui { /// public static void RequestStop () { - var ct = Current as Toplevel; - Current.Running = false; } diff --git a/Terminal.Gui/Dialogs/MessageBox.cs b/Terminal.Gui/Dialogs/MessageBox.cs index 485b92a60..5fa9edb4d 100644 --- a/Terminal.Gui/Dialogs/MessageBox.cs +++ b/Terminal.Gui/Dialogs/MessageBox.cs @@ -21,7 +21,7 @@ namespace Terminal.Gui { /// /// /// - public class MessageBox { + public static class MessageBox { /// /// Presents a message with the specified title and message and a list of buttons to show to the user. /// diff --git a/Terminal.Gui/Driver.cs b/Terminal.Gui/Driver.cs index 34042ed18..674173a97 100644 --- a/Terminal.Gui/Driver.cs +++ b/Terminal.Gui/Driver.cs @@ -583,6 +583,8 @@ namespace Terminal.Gui { { int wch; var code = Curses.get_wch (out wch); + if (code == Curses.ERR) + return; if (code == Curses.KEY_CODE_YES) { if (wch == Curses.KeyResize) { if (Curses.CheckWinChange ()) { @@ -602,7 +604,7 @@ namespace Terminal.Gui { // Special handling for ESC, we want to try to catch ESC+letter to simulate alt-letter as well as Alt-Fkey if (wch == 27) { - Curses.timeout (100); + Curses.timeout (200); code = Curses.get_wch (out wch); if (code == Curses.KEY_CODE_YES) diff --git a/Terminal.Gui/MonoCurses/mainloop.cs b/Terminal.Gui/MonoCurses/mainloop.cs index 633e9a702..919f706ab 100644 --- a/Terminal.Gui/MonoCurses/mainloop.cs +++ b/Terminal.Gui/MonoCurses/mainloop.cs @@ -407,8 +407,10 @@ namespace Mono.Terminal { } } - if (idleHandlers.Count > 0) - RunIdle (); + lock (idleHandlers){ + if (idleHandlers.Count > 0) + RunIdle(); + } } /// diff --git a/Terminal.Gui/Views/Button.cs b/Terminal.Gui/Views/Button.cs index 75394d309..cf1630b2e 100644 --- a/Terminal.Gui/Views/Button.cs +++ b/Terminal.Gui/Views/Button.cs @@ -27,7 +27,7 @@ namespace Terminal.Gui { public class Button : View { ustring text; ustring shown_text; - char hot_key; + Rune hot_key; int hot_pos = -1; bool is_default; @@ -112,10 +112,10 @@ namespace Terminal.Gui { shown_text = "[ " + text + " ]"; hot_pos = -1; - hot_key = (char)0; + hot_key = (Rune)0; int i = 0; - foreach (char c in shown_text) { - if (Char.IsUpper (c)) { + foreach (Rune c in shown_text) { + if (Rune.IsUpper (c)) { hot_key = c; hot_pos = i; break; @@ -197,7 +197,7 @@ namespace Terminal.Gui { public override bool ProcessKey (KeyEvent kb) { var c = kb.KeyValue; - if (c == '\n' || c == ' ' || Char.ToUpper ((char)c) == hot_key) { + if (c == '\n' || c == ' ' || Rune.ToUpper ((Rune)c) == hot_key) { if (Clicked != null) Clicked (); return true; diff --git a/Terminal.Gui/Views/Checkbox.cs b/Terminal.Gui/Views/Checkbox.cs index d9b5f3996..4e672200f 100644 --- a/Terminal.Gui/Views/Checkbox.cs +++ b/Terminal.Gui/Views/Checkbox.cs @@ -15,7 +15,7 @@ namespace Terminal.Gui { public class CheckBox : View { ustring text; int hot_pos = -1; - char hot_key; + Rune hot_key; /// /// Toggled event, raised when the CheckButton is toggled. @@ -74,8 +74,8 @@ namespace Terminal.Gui { int i = 0; hot_pos = -1; hot_key = (char)0; - foreach (char c in text) { - if (Char.IsUpper (c)) { + foreach (Rune c in text) { + if (Rune.IsUpper (c)) { hot_key = c; hot_pos = i; break; diff --git a/Terminal.Gui/Views/Clipboard.cs b/Terminal.Gui/Views/Clipboard.cs index be6000269..eb1860f10 100644 --- a/Terminal.Gui/Views/Clipboard.cs +++ b/Terminal.Gui/Views/Clipboard.cs @@ -2,7 +2,7 @@ using NStack; namespace Terminal.Gui { - public class Clipboard { + public static class Clipboard { public static ustring Contents { get; set; } } } diff --git a/Terminal.Gui/Views/Label.cs b/Terminal.Gui/Views/Label.cs index 433334c97..0c276b9ad 100644 --- a/Terminal.Gui/Views/Label.cs +++ b/Terminal.Gui/Views/Label.cs @@ -91,7 +91,7 @@ namespace Terminal.Gui { if (talign == TextAlignment.Justified) { // TODO: ustring needs this var words = str.ToString ().Split (whitespace, StringSplitOptions.RemoveEmptyEntries); - int textCount = words.Sum ((arg) => arg.Length); + int textCount = words.Sum (arg => arg.Length); var spaces = (width- textCount) / (words.Length - 1); var extras = (width - textCount) % words.Length; @@ -186,7 +186,7 @@ namespace Terminal.Gui { { var result = new List (); Recalc (text, result, width, TextAlignment.Left); - return result.Count (); + return result.Count; } /// diff --git a/Terminal.Gui/Views/ListView.cs b/Terminal.Gui/Views/ListView.cs index 5f67e6d62..f58e1a07f 100644 --- a/Terminal.Gui/Views/ListView.cs +++ b/Terminal.Gui/Views/ListView.cs @@ -137,7 +137,7 @@ namespace Terminal.Gui { } else if (t is string) { RenderUstr (t as string, col, line, width); } else - RenderUstr (((string)t).ToString (), col, line, width); + RenderUstr (t.ToString (), col, line, width); } public bool IsMarked (int item) diff --git a/Terminal.Gui/Views/Menu.cs b/Terminal.Gui/Views/Menu.cs index a5cdc34bc..109548a5c 100644 --- a/Terminal.Gui/Views/Menu.cs +++ b/Terminal.Gui/Views/Menu.cs @@ -30,7 +30,7 @@ namespace Terminal.Gui { Help = help ?? ""; Action = action; bool nextIsHot = false; - foreach (var x in title) { + foreach (var x in Title) { if (x == '_') nextIsHot = true; else { diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs index b5ca54651..3946a4b26 100644 --- a/Terminal.Gui/Views/TextView.cs +++ b/Terminal.Gui/Views/TextView.cs @@ -16,6 +16,11 @@ // public API to insert, remove ranges // Add word forward/word backwards commands // Save buffer API +// Mouse +// +// Desirable: +// Move all the text manipulation into the TextModel + using System; using System.Collections.Generic; @@ -34,8 +39,6 @@ namespace Terminal.Gui { throw new ArgumentNullException (nameof (file)); try { var stream = File.OpenRead (file); - if (stream == null) - return false; } catch { return false; } @@ -115,15 +118,32 @@ namespace Terminal.Gui { return sb.ToString (); } + /// + /// The number of text lines in the model + /// public int Count => lines.Count; + /// + /// Returns the specified line as a List of Rune + /// + /// The line. + /// Line number to retrieve. public List GetLine (int line) => lines [line]; + /// + /// Adds a line to the model at the specified position. + /// + /// Line number where the line will be inserted. + /// The line of text, as a List of Rune. public void AddLine (int pos, List runes) { lines.Insert (pos, runes); } + /// + /// Removes the line at the specified position + /// + /// Position. public void RemoveLine (int pos) { lines.RemoveAt (pos); @@ -306,15 +326,14 @@ namespace Terminal.Gui { /// Loads the contents of the stream into the TextView. /// /// true, if stream was loaded, false otherwise. - /// Stream. - public bool LoadStream (Stream stream) + /// Stream to load the contents from. + public void LoadStream (Stream stream) { if (stream == null) throw new ArgumentNullException (nameof (stream)); ResetPosition (); - var res = model.LoadFile (path); + model.LoadStream(stream); SetNeedsDisplay (); - return res; } /// @@ -406,10 +425,10 @@ namespace Terminal.Gui { ustring res = StringFromRunes (line.GetRange (startCol, line.Count - startCol)); for (int row = startRow+1; row < maxrow; row++) { - res = res + ustring.Make (10) + StringFromRunes (model.GetLine (row)); + res = res + ustring.Make ((Rune)10) + StringFromRunes (model.GetLine (row)); } line = model.GetLine (maxrow); - res = res + ustring.Make (10) + StringFromRunes (line.GetRange (0, endCol)); + res = res + ustring.Make ((Rune)10) + StringFromRunes (line.GetRange (0, endCol)); return res; } @@ -592,15 +611,32 @@ namespace Terminal.Gui { currentColumn = columnTrack; else if (currentColumn > line.Count) currentColumn = line.Count; - + Adjust (); + } + + void Adjust () + { + bool need = false; if (currentColumn < leftColumn) { - leftColumn = currentColumn; - SetNeedsDisplay (); + currentColumn = leftColumn; + need = true; } if (currentColumn - leftColumn > Frame.Width) { leftColumn = currentColumn - Frame.Width + 1; - SetNeedsDisplay (); + need = true; } + if (currentRow < topRow) { + topRow = currentRow; + need = true; + } + if (currentRow - topRow > Frame.Height) { + topRow = currentRow - Frame.Height + 1; + need = true; + } + if (need) + SetNeedsDisplay (); + else + PositionCursor (); } bool lastWasKill; @@ -786,7 +822,7 @@ namespace Terminal.Gui { currentLine = GetCurrentLine (); if (currentLine.Count == 0) { model.RemoveLine (currentRow); - var val = ustring.Make ('\n'); + var val = ustring.Make ((Rune)'\n'); if (lastWasKill) AppendClipboard (val); else @@ -816,7 +852,7 @@ namespace Terminal.Gui { selectionStartRow = currentRow; break; - case (Key)((int)'w' + Key.AltMask): + case ((int)'w' + Key.AltMask): SetClipboard (GetRegion ()); selecting = false; break; @@ -828,9 +864,22 @@ namespace Terminal.Gui { break; case (Key)((int)'b' + Key.AltMask): + var newPos = WordBackward (currentColumn, currentRow); + if (newPos.HasValue) { + currentColumn = newPos.Value.col; + currentRow = newPos.Value.row; + } + Adjust (); + break; - case (Key)((int)'f' + Key.AltMask): + case (Key)((int)'f' + Key.AltMask): + newPos = WordForward (currentColumn, currentRow); + if (newPos.HasValue) { + currentColumn = newPos.Value.col; + currentRow = newPos.Value.row; + } + Adjust (); break; case Key.Enter: @@ -874,69 +923,134 @@ namespace Terminal.Gui { return true; } + IEnumerable<(int col, int row, Rune rune)> ForwardIterator (int col, int row) + { + if (col < 0 || row < 0) + yield break; + if (row >= model.Count) + yield break; + var line = GetCurrentLine (); + if (col >= line.Count) + yield break; + + while (row < model.Count) { + for (int c = col; c < line.Count; c++) { + yield return (c, row, line [c]); + } + col = 0; + row++; + line = GetCurrentLine (); + } + } + + Rune RuneAt (int col, int row) => model.GetLine (row) [col]; + + bool MoveNext (ref int col, ref int row, out Rune rune) + { + var line = model.GetLine (row); + if (col + 1 < line.Count) { + col++; + rune = line [col]; + return true; + } + while (row + 1 < model.Count){ + col = 0; + row++; + line = model.GetLine (row); + if (line.Count > 0) { + rune = line [0]; + return true; + } + } + rune = 0; + return false; + } + + bool MovePrev (ref int col, ref int row, out Rune rune) + { + var line = model.GetLine (row); + + if (col > 0) { + col--; + rune = line [col]; + return true; + } + if (row == 0) { + rune = 0; + return false; + } + while (row > 0) { + row--; + line = model.GetLine (row); + col = line.Count - 1; + if (col >= 0) { + rune = line [col]; + return true; + } + } + rune = 0; + return false; + } + + (int col, int row)? WordForward (int fromCol, int fromRow) + { + var col = fromCol; + var row = fromRow; + var line = GetCurrentLine (); + 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; + } + 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; + } + } + if (fromCol != col || fromRow != row) + return (col, row); + return null; + } + + (int col, int row)? WordBackward (int fromCol, int fromRow) + { + if (fromRow == 0 && fromCol == 0) + return null; + + var col = fromCol; + var row = fromRow; + var line = GetCurrentLine (); + var rune = RuneAt (col, row); + + if (Rune.IsPunctuation (rune) || Rune.IsSymbol (rune) || Rune.IsWhiteSpace (rune)) { + while (MovePrev (ref col, ref row, out rune)){ + if (Rune.IsLetterOrDigit (rune)) + break; + } + while (MovePrev (ref col, ref row, out rune)){ + if (!Rune.IsLetterOrDigit (rune)) + break; + } + } else { + while (MovePrev (ref col, ref row, out rune)) { + if (!Rune.IsLetterOrDigit (rune)) + break; + } + } + if (fromCol != col || fromRow != row) + return (col, row); + return null; + } + #if false - int WordForward (int p) - { - if (p >= text.Length) - return -1; - - int i = p; - if (Rune.IsPunctuation (text [p]) || Rune.IsWhiteSpace (text [p])) { - for (; i < text.Length; i++) { - var r = text [i]; - if (Rune.IsLetterOrDigit (r)) - break; - } - for (; i < text.Length; i++) { - var r = text [i]; - if (!Rune.IsLetterOrDigit (r)) - break; - } - } else { - for (; i < text.Length; i++) { - var r = text [i]; - if (!Rune.IsLetterOrDigit (r)) - break; - } - } - if (i != p) - return i; - return -1; - } - - int WordBackward (int p) - { - if (p == 0) - return -1; - - int i = p - 1; - if (i == 0) - return 0; - - var ti = text [i]; - if (Rune.IsPunctuation (ti) || Rune.IsSymbol (ti) || Rune.IsWhiteSpace (ti)) { - for (; i >= 0; i--) { - if (Rune.IsLetterOrDigit (text [i])) - break; - } - for (; i >= 0; i--) { - if (!Rune.IsLetterOrDigit (text [i])) - break; - } - } else { - for (; i >= 0; i--) { - if (!Rune.IsLetterOrDigit (text [i])) - break; - } - } - i++; - - if (i != p) - return i; - - return -1; - } - public override bool MouseEvent (MouseEvent ev) { if (!ev.Flags.HasFlag (MouseFlags.Button1Clicked)) diff --git a/demo.cs b/demo.cs index 90c95bbae..33cfef9c3 100644 --- a/demo.cs +++ b/demo.cs @@ -2,7 +2,7 @@ using Terminal.Gui; using System; using Mono.Terminal; -class Demo { +static class Demo { class Box10x : View { public Box10x (int x, int y) : base (new Rect (x, y, 10, 10)) {