diff --git a/Terminal.Gui/Core.cs b/Terminal.Gui/Core.cs
index 688848d5e..5f66afc33 100644
--- a/Terminal.Gui/Core.cs
+++ b/Terminal.Gui/Core.cs
@@ -1536,7 +1536,8 @@ namespace Terminal.Gui {
DrawBounds (state.Toplevel);
state.Toplevel.PositionCursor ();
Driver.Refresh ();
- }
+ } else
+ Driver.UpdateCursor ();
}
}
diff --git a/Terminal.Gui/Driver.cs b/Terminal.Gui/Driver.cs
index bc337eda5..34042ed18 100644
--- a/Terminal.Gui/Driver.cs
+++ b/Terminal.Gui/Driver.cs
@@ -264,6 +264,11 @@ namespace Terminal.Gui {
///
public abstract void Refresh ();
+ ///
+ /// Updates the location of the cursor position
+ ///
+ public abstract void UpdateCursor ();
+
///
/// Ends the execution of the console driver.
///
@@ -495,6 +500,7 @@ namespace Terminal.Gui {
}
public override void Refresh () => Curses.refresh ();
+ public override void UpdateCursor () => Curses.refresh ();
public override void End () => Curses.endwin ();
public override void UpdateScreen () => window.redrawwin ();
public override void SetAttribute (Attribute c) => Curses.attrset (c.value);
@@ -1017,6 +1023,11 @@ namespace Terminal.Gui {
Console.CursorLeft = savedCol;
}
+ public override void UpdateCursor ()
+ {
+ //
+ }
+
public override void StartReportingMouseMoves()
{
}
diff --git a/Terminal.Gui/Event.cs b/Terminal.Gui/Event.cs
index 640576c9a..a0ce2ef6a 100644
--- a/Terminal.Gui/Event.cs
+++ b/Terminal.Gui/Event.cs
@@ -25,6 +25,11 @@ namespace Terminal.Gui {
///
///
public enum Key : uint {
+ ///
+ /// Mask that indictes that this is a character value, values outside this range
+ /// indicate special characters like Alt-key combinations or special keys on the
+ /// keyboard like function keys, arrows keys and so on.
+ ///
CharMask = 0xfffff,
///
diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs
new file mode 100644
index 000000000..0f01bb206
--- /dev/null
+++ b/Terminal.Gui/Views/TextView.cs
@@ -0,0 +1,459 @@
+//
+// TextView.cs: multi-line text editing
+//
+// Authors:
+// Miguel de Icaza (miguel@gnome.org)
+//
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using NStack;
+
+namespace Terminal.Gui {
+ class TextModel {
+ List> lines;
+ List lineLength;
+
+ public bool LoadFile (string file)
+ {
+ if (file == null)
+ throw new ArgumentNullException (nameof (file));
+ try {
+ var stream = File.OpenRead (file);
+ if (stream == null)
+ return false;
+ } catch {
+ return false;
+ }
+ LoadStream (File.OpenRead (file));
+ return true;
+ }
+
+ List ToRunes (ustring str)
+ {
+ List runes = new List ();
+ foreach (var x in str.ToRunes ()) {
+ runes.Add (x);
+ }
+ return runes;
+ }
+
+ void Append (List line)
+ {
+ var str = ustring.Make (line.ToArray ());
+ lines.Add (ToRunes (str));
+ }
+
+ public void LoadStream (Stream input)
+ {
+ if (input == null)
+ throw new ArgumentNullException (nameof (input));
+
+ lines = new List> ();
+ var buff = new BufferedStream (input);
+ int v;
+ var line = new List ();
+ while ((v = buff.ReadByte ()) != -1) {
+ if (v == 10) {
+ Append (line);
+ line.Clear ();
+ continue;
+ }
+ line.Add ((byte)v);
+ }
+ if (line.Count > 0)
+ Append (line);
+ }
+
+ public void LoadString (ustring content)
+ {
+ lines = new List> ();
+ int start = 0, i = 0;
+ for (; i < content.Length; i++) {
+ if (content [i] == 10) {
+ if (i - start > 0)
+ lines.Add (ToRunes (content [start, i]));
+ else
+ lines.Add (ToRunes (ustring.Empty));
+ start = i + 1;
+ }
+ }
+ if (i - start > 0)
+ lines.Add (ToRunes (content [start, null]));
+ }
+
+ public override string ToString ()
+ {
+ var sb = new StringBuilder ();
+ foreach (var line in lines) {
+ sb.Append (line);
+ sb.AppendLine ();
+ }
+ return sb.ToString ();
+ }
+
+ public int Count => lines.Count;
+
+ public List GetLine (int line) => lines [line];
+ }
+
+ ///
+ /// Text data entry widget
+ ///
+ ///
+ /// The Entry widget provides Emacs-like editing
+ /// functionality, and mouse support.
+ ///
+ public class TextView : View {
+ TextModel model = new TextModel ();
+ int topRow;
+ int leftColumn;
+ int currentRow;
+ int currentColumn;
+ bool used;
+
+ ///
+ /// Changed event, raised when the text has clicked.
+ ///
+ ///
+ /// Client code can hook up to this event, it is
+ /// raised when the text in the entry changes.
+ ///
+ public event EventHandler Changed;
+
+ ///
+ /// Public constructor.
+ ///
+ ///
+ ///
+ public TextView (Rect frame) : base (frame)
+ {
+ CanFocus = true;
+ }
+
+ void ResetPosition ()
+ {
+ topRow = leftColumn = currentRow = currentColumn = 0;
+ }
+
+ ///
+ /// Sets or gets the text in the entry.
+ ///
+ ///
+ ///
+ public ustring Text {
+ get {
+ return model.ToString ();
+ }
+
+ set {
+ ResetPosition ();
+ model.LoadString (value);
+ SetNeedsDisplay ();
+ }
+ }
+
+ ///
+ /// The current cursor row.
+ ///
+ public int CurrentRow => currentRow;
+
+ ///
+ /// Gets the cursor column.
+ ///
+ /// The cursor column.
+ public int CurrentColumn => currentColumn;
+
+ ///
+ /// Sets the cursor position.
+ ///
+ public override void PositionCursor ()
+ {
+ Move (CurrentColumn - leftColumn, CurrentRow - topRow);
+ }
+
+ void ClearRegion (int left, int top, int right, int bottom)
+ {
+ for (int row = top; row < bottom; row++) {
+ Move (left, row);
+ for (int col = left; col < right; col++)
+ AddRune (col, row, ' ');
+ }
+ }
+
+ public override void Redraw (Rect region)
+ {
+ Driver.SetAttribute (ColorScheme.Focus);
+ Move (0, 0);
+
+ int bottom = region.Bottom;
+ int right = region.Right;
+ for (int row = region.Top; row < bottom; row++) {
+ int textLine = topRow + row;
+ if (textLine >= model.Count) {
+ ClearRegion (region.Left, row, region.Right, row + 1);
+ continue;
+ }
+ var line = model.GetLine (textLine);
+ int lineRuneCount = line.Count;
+ if (line.Count < region.Left){
+ ClearRegion (region.Left, row, region.Right, row + 1);
+ continue;
+ }
+
+ Move (region.Left, row);
+ for (int col = region.Left; col < right; col++) {
+ var lineCol = leftColumn + col;
+ var rune = lineCol >= lineRuneCount ? ' ' : line [lineCol];
+ AddRune (col, row, rune);
+ }
+ }
+ PositionCursor ();
+ }
+
+ public override bool CanFocus {
+ get => true;
+ set { base.CanFocus = value; }
+ }
+
+ void SetClipboard (ustring text)
+ {
+ Clipboard.Contents = text;
+ }
+
+ public void Insert (Rune rune)
+ {
+ var line = model.GetLine (currentRow);
+ line.Insert (currentColumn, rune);
+ var prow = currentRow - topRow;
+
+ SetNeedsDisplay (new Rect (0, prow, Frame.Width, prow + 1));
+ }
+
+ public override bool ProcessKey (KeyEvent kb)
+ {
+ switch (kb.Key) {
+ case Key.ControlN:
+ case Key.CursorDown:
+ if (currentRow + 1 < model.Count) {
+ currentRow++;
+ if (currentRow >= topRow + Frame.Height) {
+ topRow++;
+ SetNeedsDisplay ();
+ }
+ PositionCursor ();
+ }
+ break;
+
+ case Key.ControlP:
+ case Key.CursorUp:
+ if (currentRow > 0) {
+ currentRow--;
+ if (currentRow < topRow) {
+ topRow--;
+ SetNeedsDisplay ();
+ }
+ PositionCursor ();
+ }
+ break;
+
+ case Key.ControlF:
+ case Key.CursorRight:
+ var currentLine = model.GetLine (currentRow);
+ if (currentColumn < currentLine.Count) {
+ currentColumn++;
+ if (currentColumn >= leftColumn + Frame.Width) {
+ leftColumn++;
+ SetNeedsDisplay ();
+ }
+ PositionCursor ();
+ } else {
+ if (currentRow + 1 < model.Count) {
+ currentRow++;
+ currentColumn = 0;
+ leftColumn = 0;
+ if (currentRow >= topRow + Frame.Height) {
+ topRow++;
+ }
+ SetNeedsDisplay ();
+ PositionCursor ();
+ }
+ break;
+ }
+ break;
+
+ case Key.ControlB:
+ case Key.CursorLeft:
+ if (currentColumn > 0) {
+ currentColumn--;
+ if (currentColumn < leftColumn) {
+ leftColumn--;
+ SetNeedsDisplay ();
+ }
+ PositionCursor ();
+ } else {
+ if (currentRow > 0) {
+ currentRow--;
+ if (currentRow < topRow) {
+ topRow--;
+
+ }
+ currentLine = model.GetLine (currentRow);
+ currentColumn = currentLine.Count;
+ int prev = leftColumn;
+ leftColumn = currentColumn - Frame.Width + 1;
+ if (leftColumn < 0)
+ leftColumn = 0;
+ if (prev != leftColumn)
+ SetNeedsDisplay ();
+ PositionCursor ();
+ }
+ }
+ break;
+
+ case Key.Delete:
+ case Key.Backspace:
+ break;
+
+ // Home, C-A
+ case Key.Home:
+ case Key.ControlA:
+ currentColumn = 0;
+ if (currentColumn < leftColumn) {
+ leftColumn = 0;
+ SetNeedsDisplay ();
+ } else
+ PositionCursor ();
+ break;
+
+ case Key.ControlD: // Delete
+ break;
+
+ case Key.ControlE: // End
+ currentLine = model.GetLine (currentRow);
+ currentColumn = currentLine.Count;
+ int pcol = leftColumn;
+ leftColumn = currentColumn - Frame.Width + 1;
+ if (leftColumn < 0)
+ leftColumn = 0;
+ if (pcol != leftColumn)
+ SetNeedsDisplay ();
+ PositionCursor ();
+ break;
+
+ case Key.ControlK: // kill-to-end
+ break;
+
+ case Key.ControlY: // Control-y, yank
+
+ case (Key)((int)'b' + Key.AltMask):
+ break;
+
+ case (Key)((int)'f' + Key.AltMask):
+ break;
+
+ default:
+ // Ignore control characters and other special keys
+ if (kb.Key < Key.Space || kb.Key > Key.CharMask)
+ return false;
+ Insert ((uint)kb.Key);
+ currentColumn++;
+ if (currentColumn >= leftColumn + Frame.Width) {
+ leftColumn++;
+ SetNeedsDisplay ();
+ }
+ PositionCursor ();
+ return true;
+ }
+ return true;
+ }
+
+#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))
+ return false;
+
+ if (!HasFocus)
+ SuperView.SetFocus (this);
+
+ // We could also set the cursor position.
+ point = first + ev.X;
+ if (point > text.Length)
+ point = text.Length;
+ if (point < first)
+ point = 0;
+
+ SetNeedsDisplay ();
+ return true;
+ }
+ #endif
+ }
+
+}
+
diff --git a/demo.cs b/demo.cs
index 139b061e8..13d73ba40 100644
--- a/demo.cs
+++ b/demo.cs
@@ -169,7 +169,11 @@ class Demo {
})
});
- ShowEntries (win);
+ //ShowEntries (win);
+ var text = new TextView (new Rect (1, 1, 60, 14));
+ text.Text = System.IO.File.ReadAllText ("/etc/passwd");
+ win.Add (text);
+
int count = 0;
ml = new Label (new Rect (3, 17, 47, 1), "Mouse: ");
Application.RootMouseEvent += delegate (MouseEvent me) {