I have been following along Gary BernhardtsBernhardt's excellent video series on "Building a Text Editor From Scratch". It's in Ruby but I wanted to do it in C# just to see how much it differs.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace TextEditor
{
class Program
{
static void Main(string[] args)
{
new Editor().Run();
}
}
class Editor
{
Buffer _buffer;
Cursor _cursor;
Stack<object> _history;
public Editor()
{
var lines = File.ReadAllLines("foo.txt")
.Where(x => x != Environment.NewLine);
_buffer = new Buffer(lines);
_cursor = new Cursor();
_history = new Stack<object>();
}
public void Run()Program
{
whilestatic void Main(truestring[] args)
{
Rendernew Editor();
HandleInput.Run();
}
}
private voidclass HandleInput()Editor
{
varBuffer character_buffer;
= Console.ReadKey(); Cursor _cursor;
Stack<object> _history;
ifpublic (Editor(ConsoleModifiers.Control & character.Modifiers) != 0 &&
character.Key == ConsoleKey.Q)
{
Environmentvar lines = File.ExitReadAllLines(0"foo.txt");
} .Where(x => x != Environment.NewLine);
else if ((ConsoleModifiers.Control & character.Modifiers)_buffer != 0new &&Buffer(lines);
_cursor character.Key= ==new ConsoleKey.PCursor()
{;
_cursor_history = _cursor.Upnew Stack<object>(_buffer);
}
elsepublic ifvoid (Run(ConsoleModifiers.Control & character.Modifiers) != 0 &&
character.Key == ConsoleKey.N)
{
_cursorwhile =(true)
_cursor.Down {
Render(_buffer);
HandleInput();
}
}
elseprivate ifvoid (HandleInput(ConsoleModifiers.Control & character.Modifiers) != 0 &&
character.Key == ConsoleKey.B)
{
_cursorvar character = _cursorConsole.LeftReadKey(_buffer);
}
else if ((ConsoleModifiers.Control & character.Modifiers) != 0 &&
character.Key == ConsoleKey.ZQ)
{
_cursor = _cursor Environment.RightExit(_buffer0);
}
else if ((ConsoleModifiers.Control & character.Modifiers) != 0 &&
character.Key == ConsoleKey.UP)
{
RestoreSnapshot _cursor = _cursor.Up(_buffer);
}
else if (character(ConsoleModifiers.KeyControl ==& ConsoleKeycharacter.BackspaceModifiers)
!= 0 &&
{
if (_cursorcharacter.ColKey >== 0ConsoleKey.N)
{
SaveSnapshot_cursor = _cursor.Down(_buffer);
}
_buffer = _buffer.Delete else if (_cursor(ConsoleModifiers.Row,Control _cursor& character.ColModifiers) -!= 10 &&
character.Key == ConsoleKey.B);
{
_cursor = _cursor.Left(_buffer);
}
}
else if (character(ConsoleModifiers.KeyControl ==& ConsoleKeycharacter.EnterModifiers) != 0 &&
{
character.Key == SaveSnapshot(ConsoleKey.Z);
_buffer{
= _buffer.SplitLine(_cursor.Row, _cursor.Col);
_cursor = _cursor.DownRight(_buffer).MoveToCol(0);
}
else
else if ((ConsoleModifiers.Control & {character.Modifiers) != 0 &&
SaveSnapshot();
character.Key == ConsoleKey.U)
_buffer = _buffer.Insert(character.KeyChar.ToString(), _cursor.Row, _cursor.Col); {
_cursor = _cursor.Right RestoreSnapshot(_buffer);
}
else if (character.Key == ConsoleKey.Backspace)
{
if (_cursor.Col > 0)
{
SaveSnapshot();
_buffer = _buffer.Delete(_cursor.Row, _cursor.Col - 1);
_cursor = _cursor.Left(_buffer);
}
}
private void Render else if(character.Key == ConsoleKey.Enter)
{
ANSI.ClearScreen SaveSnapshot();
ANSI.MoveCursor(0, 0);
_buffer = _buffer.RenderSplitLine(_cursor.Row, _cursor.Col);
ANSI.MoveCursor( _cursor.Row, = _cursor.ColDown(_buffer).MoveToCol(0);
} }
private void else
{
SaveSnapshot();
{
_history _buffer = _buffer.PushInsert(character.KeyChar.ToString(), _cursor.Row, _cursor.Col);
_history _cursor = _cursor.PushRight(_buffer);
}
private void RestoreSnapshot()
{
if( _history.Count > 0 )
{
_buffer = (Buffer)_history.Pop();
_cursor = (Cursor)_history.Pop();
}
}
}
class Buffer private void Render()
{
string[] _lines; ANSI.ClearScreen();
ANSI.MoveCursor(0, 0);
_buffer.Render();
ANSI.MoveCursor(_cursor.Row, _cursor.Col);
}
public Buffer(IEnumerable<string> lines private void SaveSnapshot()
{
_lines = lines _history.ToArrayPush(_cursor);
_history.Push(_buffer);
}
public void Render()
{
private foreachvoid RestoreSnapshot(var line in _lines)
{
Consoleif( _history.WriteLineCount > 0 )
{
_buffer = (lineBuffer)_history.Pop();
_cursor = (Cursor)_history.Pop();
}
}
}
public intclass LineCount()Buffer
{
return _lines.Count();
string[] }_lines;
public int LineLength public Buffer(intIEnumerable<string> rowlines)
{
return _lines[row] _lines = lines.Length;ToArray();
}
internal Buffer Insert(string character, int row,public intvoid colRender()
{
var linesDeepCopy = _lines.Select(x =>foreach x).ToArray(var line in _lines);
linesDeepCopy[row] = linesDeepCopy[row].Insert(col, character); {
return new Buffer Console.WriteLine(linesDeepCopyline);
}
}
internal Buffer Delete(int row, public int colLineCount()
{
var linesDeepCopy ={
_lines.Select(x => x).ToArray();
linesDeepCopy[row] =return linesDeepCopy[row]_lines.RemoveCount(col, 1);
return new Buffer(linesDeepCopy);
}
internal Buffer SplitLine(int row, public int colLineLength(int row)
{
var linesDeepCopy = _lines.Select(x =>return x)_lines[row].ToList();Length;
}
internal Buffer Insert(string character, int row, int col)
{
var linelinesDeepCopy = linesDeepCopy[row];_lines.Select(x => x).ToArray();
linesDeepCopy[row] = linesDeepCopy[row].Insert(col, character);
return new Buffer(linesDeepCopy);
}
varinternal newLinesBuffer =Delete(int newrow, []int col)
{
line var linesDeepCopy = _lines.SubstringSelect(0,x col),=> linex).SubstringToArray(col,);
line.Length - line linesDeepCopy[row] = linesDeepCopy[row].SubstringRemove(0col, col1).Length;
return new Buffer(linesDeepCopy);
};
linesDeepCopy[row]internal =Buffer newLines[0];SplitLine(int row, int col)
linesDeepCopy[row{
+ 1] var linesDeepCopy = newLines[1];_lines.Select(x => x).ToList();
var line = linesDeepCopy[row];
var newLines = new [] { line.Substring(0, col), line.Substring(col, line.Length - line.Substring(0, col).Length) };
return new Buffer(linesDeepCopy);
linesDeepCopy[row] = }newLines[0];
} linesDeepCopy[row + 1] = newLines[1];
class Cursor
{
public int Row { get; set; }
public int Col { get; set; }
public Cursor(int row=0, int col=0)
{
Rowreturn =new row;Buffer(linesDeepCopy);
Col = col;}
}
internalclass Cursor Up(Buffer buffer)
{
returnpublic newint Cursor(Row -{ 1,get; Col).Clamp(buffer);set; }
public int Col { get; set; }
internal Cursor Down public Cursor(Bufferint bufferrow=0, int col=0)
{
return new Cursor( Row += 1,row;
Col).Clamp(buffer); = col;
}
}
internal Cursor LeftUp(Buffer buffer)
{
return new Cursor(Row, Col - 1, Col).Clamp(buffer);
}
internal Cursor RightDown(Buffer buffer)
{
return new Cursor(Row, Col + 1, Col).Clamp(buffer);
}
private internal Cursor ClampLeft(Buffer buffer)
{
Row = Math.Min(buffer.LineCount() - 1return ,new Math.MaxCursor(Row, 0));
Col =- Math.Min(buffer.LineLength(Row1), Math.MaxClamp(Col, 0)buffer);
return new Cursor(Row, Col);
}
internal Cursor MoveToColRight(intBuffer colbuffer)
{
return new Cursor(Row, 0Col + 1).Clamp(buffer);
}
}
class ANSI private Cursor Clamp(Buffer buffer)
{
public static void ClearScreen Row = Math.Min(buffer.LineCount() - 1 , Math.Max(Row, 0));
Col = Math.Min(buffer.LineLength(Row), Math.Max(Col, 0));
return new Cursor(Row, Col);
}
internal Cursor MoveToCol(int col)
{
Console.Clear return new Cursor(Row, 0);
}
}
public static void MoveCursor(int row, intclass col)ANSI
{
public static void ClearScreen()
{
Console.Clear();
}
public static void MoveCursor(int row, int col)
{
Console.CursorTop = row;
Console.CursorLeft = col;
}
}
}
}