mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 15:57:56 +01:00
Editor works for most stuff now
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user