Editor works for most stuff now

This commit is contained in:
Miguel de Icaza
2018-03-21 02:46:31 -04:00
parent 874c685f9e
commit c0761d84bd

View File

@@ -7,11 +7,11 @@
// //
// TODO: // TODO:
// Attributed text on spans // Attributed text on spans
// Cursor target track
// Kill-ring, paste
// Render selection // Render selection
// Mark/Delete/Cut commands // Mark/Delete/Cut commands
// Replace insertion with Insert method
// String accumulation (Control-k, control-k is not preserving the last new line, see StringToRunes
//
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@@ -22,7 +22,6 @@ using NStack;
namespace Terminal.Gui { namespace Terminal.Gui {
class TextModel { class TextModel {
List<List<Rune>> lines; List<List<Rune>> lines;
List<int> lineLength;
public bool LoadFile (string file) public bool LoadFile (string file)
{ {
@@ -39,7 +38,9 @@ namespace Terminal.Gui {
return true; return true;
} }
List<Rune> ToRunes (ustring str) // Turns the ustring into runes, this does not split the
// contents on a newline if it is present.
static List<Rune> ToRunes (ustring str)
{ {
List<Rune> runes = new List<Rune> (); List<Rune> runes = new List<Rune> ();
foreach (var x in str.ToRunes ()) { foreach (var x in str.ToRunes ()) {
@@ -48,6 +49,25 @@ namespace Terminal.Gui {
return runes; return runes;
} }
// Splits a string into a List that contains a List<Rune> for each line
public static List<List<Rune>> StringToRunes (ustring content)
{
var lines = new List<List<Rune>> ();
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]));
return lines;
}
void Append (List<byte> line) void Append (List<byte> line)
{ {
var str = ustring.Make (line.ToArray ()); var str = ustring.Make (line.ToArray ());
@@ -77,19 +97,7 @@ namespace Terminal.Gui {
public void LoadString (ustring content) public void LoadString (ustring content)
{ {
lines = new List<List<Rune>> (); lines = StringToRunes (content);
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 () public override string ToString ()
@@ -201,6 +209,10 @@ namespace Terminal.Gui {
} }
} }
/// <summary>
/// Redraw the text editor region
/// </summary>
/// <param name="region">The region to redraw.</param>
public override void Redraw (Rect region) public override void Redraw (Rect region)
{ {
Driver.SetAttribute (ColorScheme.Focus); Driver.SetAttribute (ColorScheme.Focus);
@@ -241,26 +253,146 @@ namespace Terminal.Gui {
Clipboard.Contents = text; Clipboard.Contents = text;
} }
void AppendClipboard (ustring text)
{
Clipboard.Contents = Clipboard.Contents + text;
}
void Insert (Rune rune) void Insert (Rune rune)
{ {
var line = model.GetLine (currentRow); var line = GetCurrentLine ();
line.Insert (currentColumn, rune); line.Insert (currentColumn, rune);
var prow = currentRow - topRow; var prow = currentRow - topRow;
SetNeedsDisplay (new Rect (0, prow, Frame.Width, prow + 1)); SetNeedsDisplay (new Rect (0, prow, Frame.Width, prow + 1));
} }
ustring StringFromRunes (List<Rune> runes)
{
if (runes == null)
throw new ArgumentNullException (nameof (runes));
int size = 0;
foreach (var rune in runes) {
size += Utf8.RuneLen (rune);
}
var encoded = new byte [size];
int offset = 0;
foreach (var rune in runes) {
offset += Utf8.EncodeRune (rune, encoded, offset);
}
return ustring.Make (encoded);
}
List<Rune> GetCurrentLine () => model.GetLine (currentRow);
void InsertText (ustring text)
{
var lines = TextModel.StringToRunes (text);
if (lines.Count == 0)
return;
var line = GetCurrentLine ();
// Optmize single line
if (lines.Count == 1) {
line.InsertRange (currentColumn, lines [0]);
currentColumn += lines [0].Count;
if (currentColumn - leftColumn > Frame.Width)
leftColumn = currentColumn - Frame.Width + 1;
SetNeedsDisplay (new Rect (0, currentRow - topRow, Frame.Width, currentRow - topRow + 1));
return;
}
// Keep a copy of the rest of the line
var restCount = line.Count - currentColumn;
var rest = line.GetRange (currentColumn, restCount);
line.RemoveRange (currentColumn, restCount);
// First line is inserted at the current location, the rest is appended
line.InsertRange (currentColumn, lines [0]);
for (int i = 1; i < lines.Count; i++)
model.AddLine (currentRow + i, lines [i]);
var last = model.GetLine (currentRow + lines.Count-1);
var lastp = last.Count;
last.InsertRange (last.Count, rest);
// Now adjjust column and row positions
currentRow += lines.Count-1;
currentColumn = lastp;
if (currentRow - topRow > Frame.Height) {
topRow = currentRow - Frame.Height + 1;
if (topRow < 0)
topRow = 0;
}
if (currentColumn < leftColumn)
leftColumn = currentColumn;
if (currentColumn-leftColumn >= Frame.Width)
leftColumn = currentColumn - Frame.Width + 1;
SetNeedsDisplay ();
}
// The column we are tracking, or -1 if we are not tracking any column
int columnTrack = -1;
// Tries to snap the cursor to the tracking column
void TrackColumn ()
{
// Now track the column
var line = GetCurrentLine ();
if (line.Count < columnTrack)
currentColumn = line.Count;
else if (columnTrack != -1)
currentColumn = columnTrack;
else if (currentColumn > line.Count)
currentColumn = line.Count;
if (currentColumn < leftColumn) {
leftColumn = currentColumn;
SetNeedsDisplay ();
}
if (currentColumn - leftColumn > Frame.Width) {
leftColumn = currentColumn - Frame.Width + 1;
SetNeedsDisplay ();
}
}
bool lastWasKill;
public override bool ProcessKey (KeyEvent kb) public override bool ProcessKey (KeyEvent kb)
{ {
int restCount;
List<Rune> rest;
switch (kb.Key) {
case Key.ControlN:
case Key.CursorDown:
case Key.ControlP:
case Key.CursorUp:
lastWasKill = false;
break;
case Key.ControlK:
break;
default:
lastWasKill = false;
columnTrack = -1;
break;
}
switch (kb.Key) { switch (kb.Key) {
case Key.ControlN: case Key.ControlN:
case Key.CursorDown: case Key.CursorDown:
if (currentRow + 1 < model.Count) { if (currentRow + 1 < model.Count) {
if (columnTrack == -1)
columnTrack = currentColumn;
currentRow++; currentRow++;
if (currentRow >= topRow + Frame.Height) { if (currentRow >= topRow + Frame.Height) {
topRow++; topRow++;
SetNeedsDisplay (); SetNeedsDisplay ();
} }
TrackColumn ();
PositionCursor (); PositionCursor ();
} }
break; break;
@@ -268,18 +400,21 @@ namespace Terminal.Gui {
case Key.ControlP: case Key.ControlP:
case Key.CursorUp: case Key.CursorUp:
if (currentRow > 0) { if (currentRow > 0) {
if (columnTrack == -1)
columnTrack = currentColumn;
currentRow--; currentRow--;
if (currentRow < topRow) { if (currentRow < topRow) {
topRow--; topRow--;
SetNeedsDisplay (); SetNeedsDisplay ();
} }
TrackColumn ();
PositionCursor (); PositionCursor ();
} }
break; break;
case Key.ControlF: case Key.ControlF:
case Key.CursorRight: case Key.CursorRight:
var currentLine = model.GetLine (currentRow); var currentLine = GetCurrentLine ();
if (currentColumn < currentLine.Count) { if (currentColumn < currentLine.Count) {
currentColumn++; currentColumn++;
if (currentColumn >= leftColumn + Frame.Width) { if (currentColumn >= leftColumn + Frame.Width) {
@@ -318,7 +453,7 @@ namespace Terminal.Gui {
topRow--; topRow--;
} }
currentLine = model.GetLine (currentRow); currentLine = GetCurrentLine ();
currentColumn = currentLine.Count; currentColumn = currentLine.Count;
int prev = leftColumn; int prev = leftColumn;
leftColumn = currentColumn - Frame.Width + 1; leftColumn = currentColumn - Frame.Width + 1;
@@ -334,7 +469,8 @@ namespace Terminal.Gui {
case Key.Delete: case Key.Delete:
case Key.Backspace: case Key.Backspace:
if (currentColumn > 0) { if (currentColumn > 0) {
currentLine = model.GetLine (currentRow); // Delete backwards
currentLine = GetCurrentLine ();
currentLine.RemoveAt (currentColumn - 1); currentLine.RemoveAt (currentColumn - 1);
currentColumn--; currentColumn--;
if (currentColumn < leftColumn) { if (currentColumn < leftColumn) {
@@ -349,7 +485,7 @@ namespace Terminal.Gui {
var prowIdx = currentRow - 1; var prowIdx = currentRow - 1;
var prevRow = model.GetLine (prowIdx); var prevRow = model.GetLine (prowIdx);
var prevCount = prevRow.Count; var prevCount = prevRow.Count;
model.GetLine (prowIdx).AddRange (model.GetLine (currentRow)); model.GetLine (prowIdx).AddRange (GetCurrentLine ());
currentRow--; currentRow--;
currentColumn = prevCount; currentColumn = prevCount;
leftColumn = currentColumn - Frame.Width + 1; leftColumn = currentColumn - Frame.Width + 1;
@@ -371,7 +507,7 @@ namespace Terminal.Gui {
break; break;
case Key.ControlD: // Delete case Key.ControlD: // Delete
currentLine = model.GetLine (currentRow); currentLine = GetCurrentLine ();
if (currentColumn == currentLine.Count) { if (currentColumn == currentLine.Count) {
if (currentRow + 1 == model.Count) if (currentRow + 1 == model.Count)
break; break;
@@ -388,7 +524,7 @@ namespace Terminal.Gui {
break; break;
case Key.ControlE: // End case Key.ControlE: // End
currentLine = model.GetLine (currentRow); currentLine = GetCurrentLine ();
currentColumn = currentLine.Count; currentColumn = currentLine.Count;
int pcol = leftColumn; int pcol = leftColumn;
leftColumn = currentColumn - Frame.Width + 1; leftColumn = currentColumn - Frame.Width + 1;
@@ -400,9 +536,31 @@ namespace Terminal.Gui {
break; break;
case Key.ControlK: // kill-to-end case Key.ControlK: // kill-to-end
currentLine = GetCurrentLine ();
if (currentLine.Count == 0) {
model.RemoveLine (currentRow);
var val = ustring.Make ('\n');
if (lastWasKill)
AppendClipboard (val);
else
SetClipboard (val);
} else {
restCount = currentLine.Count - currentColumn;
rest = currentLine.GetRange (currentColumn, restCount);
var val = StringFromRunes (rest);
if (lastWasKill)
AppendClipboard (val);
else
SetClipboard (val);
currentLine.RemoveRange (currentColumn, restCount);
}
SetNeedsDisplay (new Rect (0, currentRow - topRow, Frame.Width, Frame.Height));
lastWasKill = true;
break; break;
case Key.ControlY: // Control-y, yank case Key.ControlY: // Control-y, yank
InsertText (Clipboard.Contents);
break;
case (Key)((int)'b' + Key.AltMask): case (Key)((int)'b' + Key.AltMask):
break; break;
@@ -412,9 +570,9 @@ namespace Terminal.Gui {
case Key.Enter: case Key.Enter:
var orow = currentRow; var orow = currentRow;
currentLine = model.GetLine (currentRow); currentLine = GetCurrentLine ();
var restCount = currentLine.Count - currentColumn; restCount = currentLine.Count - currentColumn;
var rest = currentLine.GetRange (currentColumn, restCount); rest = currentLine.GetRange (currentColumn, restCount);
currentLine.RemoveRange (currentColumn, restCount); currentLine.RemoveRange (currentColumn, restCount);
model.AddLine (currentRow + 1, rest); model.AddLine (currentRow + 1, rest);
currentRow++; currentRow++;
@@ -434,6 +592,7 @@ namespace Terminal.Gui {
else else
SetNeedsDisplay (new Rect (0, currentRow - topRow, 0, Frame.Height)); SetNeedsDisplay (new Rect (0, currentRow - topRow, 0, Frame.Height));
break; break;
default: default:
// Ignore control characters and other special keys // Ignore control characters and other special keys
if (kb.Key < Key.Space || kb.Key > Key.CharMask) if (kb.Key < Key.Space || kb.Key > Key.CharMask)