Simple console text editor

Clash Royale CLAN TAG#URR8PPP
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;
up vote
7
down vote
favorite
I have been following along Gary Bernhardt'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.
Implementation wise it's faithful to what he does. I do have questions whether the code is up to mark in terms of new C# conventions, haven't violated any biggies or gone overboard with LINQ.
The method SplitLine was a head scratcher (really showcased the terseness of Ruby) where his line splitting and new carriage code looks like and I couldn't come up with anything like it in C#.
lines[row..row] = [line[0...col],line[col..-1]]
My C# Code
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()
while (true)
Render();
HandleInput();
private void HandleInput()
var character = Console.ReadKey();
if ((ConsoleModifiers.Control & character.Modifiers) != 0 &&
character.Key == ConsoleKey.Q)
Environment.Exit(0);
else if ((ConsoleModifiers.Control & character.Modifiers) != 0 &&
character.Key == ConsoleKey.P)
_cursor = _cursor.Up(_buffer);
else if ((ConsoleModifiers.Control & character.Modifiers) != 0 &&
character.Key == ConsoleKey.N)
_cursor = _cursor.Down(_buffer);
else if ((ConsoleModifiers.Control & character.Modifiers) != 0 &&
character.Key == ConsoleKey.B)
_cursor = _cursor.Left(_buffer);
else if ((ConsoleModifiers.Control & character.Modifiers) != 0 &&
character.Key == ConsoleKey.Z)
_cursor = _cursor.Right(_buffer);
else if ((ConsoleModifiers.Control & character.Modifiers) != 0 &&
character.Key == ConsoleKey.U)
RestoreSnapshot();
else if (character.Key == ConsoleKey.Backspace)
if (_cursor.Col > 0)
SaveSnapshot();
_buffer = _buffer.Delete(_cursor.Row, _cursor.Col - 1);
_cursor = _cursor.Left(_buffer);
else if(character.Key == ConsoleKey.Enter)
SaveSnapshot();
_buffer = _buffer.SplitLine(_cursor.Row, _cursor.Col);
_cursor = _cursor.Down(_buffer).MoveToCol(0);
else
SaveSnapshot();
_buffer = _buffer.Insert(character.KeyChar.ToString(), _cursor.Row, _cursor.Col);
_cursor = _cursor.Right(_buffer);
private void Render()
ANSI.ClearScreen();
ANSI.MoveCursor(0, 0);
_buffer.Render();
ANSI.MoveCursor(_cursor.Row, _cursor.Col);
private void SaveSnapshot()
_history.Push(_cursor);
_history.Push(_buffer);
private void RestoreSnapshot()
if( _history.Count > 0 )
_buffer = (Buffer)_history.Pop();
_cursor = (Cursor)_history.Pop();
class Buffer
string _lines;
public Buffer(IEnumerable<string> lines)
_lines = lines.ToArray();
public void Render()
foreach (var line in _lines)
Console.WriteLine(line);
public int LineCount()
return _lines.Count();
public int LineLength(int row)
return _lines[row].Length;
internal Buffer Insert(string character, int row, int col)
var linesDeepCopy = _lines.Select(x => x).ToArray();
linesDeepCopy[row] = linesDeepCopy[row].Insert(col, character);
return new Buffer(linesDeepCopy);
internal Buffer Delete(int row, int col)
var linesDeepCopy = _lines.Select(x => x).ToArray();
linesDeepCopy[row] = linesDeepCopy[row].Remove(col, 1);
return new Buffer(linesDeepCopy);
internal Buffer SplitLine(int row, int col)
var linesDeepCopy = _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) ;
linesDeepCopy[row] = newLines[0];
linesDeepCopy[row + 1] = newLines[1];
return new Buffer(linesDeepCopy);
class Cursor
public int Row get; set;
public int Col get; set;
public Cursor(int row=0, int col=0)
Row = row;
Col = col;
internal Cursor Up(Buffer buffer)
return new Cursor(Row - 1, Col).Clamp(buffer);
internal Cursor Down(Buffer buffer)
return new Cursor(Row + 1, Col).Clamp(buffer);
internal Cursor Left(Buffer buffer)
return new Cursor(Row, Col - 1).Clamp(buffer);
internal Cursor Right(Buffer buffer)
return new Cursor(Row, Col + 1).Clamp(buffer);
private Cursor Clamp(Buffer buffer)
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)
return new Cursor(Row, 0);
class ANSI
public static void ClearScreen()
Console.Clear();
public static void MoveCursor(int row, int col)
Console.CursorTop = row;
Console.CursorLeft = col;
c# console text-editor
add a comment |Â
up vote
7
down vote
favorite
I have been following along Gary Bernhardt'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.
Implementation wise it's faithful to what he does. I do have questions whether the code is up to mark in terms of new C# conventions, haven't violated any biggies or gone overboard with LINQ.
The method SplitLine was a head scratcher (really showcased the terseness of Ruby) where his line splitting and new carriage code looks like and I couldn't come up with anything like it in C#.
lines[row..row] = [line[0...col],line[col..-1]]
My C# Code
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()
while (true)
Render();
HandleInput();
private void HandleInput()
var character = Console.ReadKey();
if ((ConsoleModifiers.Control & character.Modifiers) != 0 &&
character.Key == ConsoleKey.Q)
Environment.Exit(0);
else if ((ConsoleModifiers.Control & character.Modifiers) != 0 &&
character.Key == ConsoleKey.P)
_cursor = _cursor.Up(_buffer);
else if ((ConsoleModifiers.Control & character.Modifiers) != 0 &&
character.Key == ConsoleKey.N)
_cursor = _cursor.Down(_buffer);
else if ((ConsoleModifiers.Control & character.Modifiers) != 0 &&
character.Key == ConsoleKey.B)
_cursor = _cursor.Left(_buffer);
else if ((ConsoleModifiers.Control & character.Modifiers) != 0 &&
character.Key == ConsoleKey.Z)
_cursor = _cursor.Right(_buffer);
else if ((ConsoleModifiers.Control & character.Modifiers) != 0 &&
character.Key == ConsoleKey.U)
RestoreSnapshot();
else if (character.Key == ConsoleKey.Backspace)
if (_cursor.Col > 0)
SaveSnapshot();
_buffer = _buffer.Delete(_cursor.Row, _cursor.Col - 1);
_cursor = _cursor.Left(_buffer);
else if(character.Key == ConsoleKey.Enter)
SaveSnapshot();
_buffer = _buffer.SplitLine(_cursor.Row, _cursor.Col);
_cursor = _cursor.Down(_buffer).MoveToCol(0);
else
SaveSnapshot();
_buffer = _buffer.Insert(character.KeyChar.ToString(), _cursor.Row, _cursor.Col);
_cursor = _cursor.Right(_buffer);
private void Render()
ANSI.ClearScreen();
ANSI.MoveCursor(0, 0);
_buffer.Render();
ANSI.MoveCursor(_cursor.Row, _cursor.Col);
private void SaveSnapshot()
_history.Push(_cursor);
_history.Push(_buffer);
private void RestoreSnapshot()
if( _history.Count > 0 )
_buffer = (Buffer)_history.Pop();
_cursor = (Cursor)_history.Pop();
class Buffer
string _lines;
public Buffer(IEnumerable<string> lines)
_lines = lines.ToArray();
public void Render()
foreach (var line in _lines)
Console.WriteLine(line);
public int LineCount()
return _lines.Count();
public int LineLength(int row)
return _lines[row].Length;
internal Buffer Insert(string character, int row, int col)
var linesDeepCopy = _lines.Select(x => x).ToArray();
linesDeepCopy[row] = linesDeepCopy[row].Insert(col, character);
return new Buffer(linesDeepCopy);
internal Buffer Delete(int row, int col)
var linesDeepCopy = _lines.Select(x => x).ToArray();
linesDeepCopy[row] = linesDeepCopy[row].Remove(col, 1);
return new Buffer(linesDeepCopy);
internal Buffer SplitLine(int row, int col)
var linesDeepCopy = _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) ;
linesDeepCopy[row] = newLines[0];
linesDeepCopy[row + 1] = newLines[1];
return new Buffer(linesDeepCopy);
class Cursor
public int Row get; set;
public int Col get; set;
public Cursor(int row=0, int col=0)
Row = row;
Col = col;
internal Cursor Up(Buffer buffer)
return new Cursor(Row - 1, Col).Clamp(buffer);
internal Cursor Down(Buffer buffer)
return new Cursor(Row + 1, Col).Clamp(buffer);
internal Cursor Left(Buffer buffer)
return new Cursor(Row, Col - 1).Clamp(buffer);
internal Cursor Right(Buffer buffer)
return new Cursor(Row, Col + 1).Clamp(buffer);
private Cursor Clamp(Buffer buffer)
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)
return new Cursor(Row, 0);
class ANSI
public static void ClearScreen()
Console.Clear();
public static void MoveCursor(int row, int col)
Console.CursorTop = row;
Console.CursorLeft = col;
c# console text-editor
add a comment |Â
up vote
7
down vote
favorite
up vote
7
down vote
favorite
I have been following along Gary Bernhardt'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.
Implementation wise it's faithful to what he does. I do have questions whether the code is up to mark in terms of new C# conventions, haven't violated any biggies or gone overboard with LINQ.
The method SplitLine was a head scratcher (really showcased the terseness of Ruby) where his line splitting and new carriage code looks like and I couldn't come up with anything like it in C#.
lines[row..row] = [line[0...col],line[col..-1]]
My C# Code
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()
while (true)
Render();
HandleInput();
private void HandleInput()
var character = Console.ReadKey();
if ((ConsoleModifiers.Control & character.Modifiers) != 0 &&
character.Key == ConsoleKey.Q)
Environment.Exit(0);
else if ((ConsoleModifiers.Control & character.Modifiers) != 0 &&
character.Key == ConsoleKey.P)
_cursor = _cursor.Up(_buffer);
else if ((ConsoleModifiers.Control & character.Modifiers) != 0 &&
character.Key == ConsoleKey.N)
_cursor = _cursor.Down(_buffer);
else if ((ConsoleModifiers.Control & character.Modifiers) != 0 &&
character.Key == ConsoleKey.B)
_cursor = _cursor.Left(_buffer);
else if ((ConsoleModifiers.Control & character.Modifiers) != 0 &&
character.Key == ConsoleKey.Z)
_cursor = _cursor.Right(_buffer);
else if ((ConsoleModifiers.Control & character.Modifiers) != 0 &&
character.Key == ConsoleKey.U)
RestoreSnapshot();
else if (character.Key == ConsoleKey.Backspace)
if (_cursor.Col > 0)
SaveSnapshot();
_buffer = _buffer.Delete(_cursor.Row, _cursor.Col - 1);
_cursor = _cursor.Left(_buffer);
else if(character.Key == ConsoleKey.Enter)
SaveSnapshot();
_buffer = _buffer.SplitLine(_cursor.Row, _cursor.Col);
_cursor = _cursor.Down(_buffer).MoveToCol(0);
else
SaveSnapshot();
_buffer = _buffer.Insert(character.KeyChar.ToString(), _cursor.Row, _cursor.Col);
_cursor = _cursor.Right(_buffer);
private void Render()
ANSI.ClearScreen();
ANSI.MoveCursor(0, 0);
_buffer.Render();
ANSI.MoveCursor(_cursor.Row, _cursor.Col);
private void SaveSnapshot()
_history.Push(_cursor);
_history.Push(_buffer);
private void RestoreSnapshot()
if( _history.Count > 0 )
_buffer = (Buffer)_history.Pop();
_cursor = (Cursor)_history.Pop();
class Buffer
string _lines;
public Buffer(IEnumerable<string> lines)
_lines = lines.ToArray();
public void Render()
foreach (var line in _lines)
Console.WriteLine(line);
public int LineCount()
return _lines.Count();
public int LineLength(int row)
return _lines[row].Length;
internal Buffer Insert(string character, int row, int col)
var linesDeepCopy = _lines.Select(x => x).ToArray();
linesDeepCopy[row] = linesDeepCopy[row].Insert(col, character);
return new Buffer(linesDeepCopy);
internal Buffer Delete(int row, int col)
var linesDeepCopy = _lines.Select(x => x).ToArray();
linesDeepCopy[row] = linesDeepCopy[row].Remove(col, 1);
return new Buffer(linesDeepCopy);
internal Buffer SplitLine(int row, int col)
var linesDeepCopy = _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) ;
linesDeepCopy[row] = newLines[0];
linesDeepCopy[row + 1] = newLines[1];
return new Buffer(linesDeepCopy);
class Cursor
public int Row get; set;
public int Col get; set;
public Cursor(int row=0, int col=0)
Row = row;
Col = col;
internal Cursor Up(Buffer buffer)
return new Cursor(Row - 1, Col).Clamp(buffer);
internal Cursor Down(Buffer buffer)
return new Cursor(Row + 1, Col).Clamp(buffer);
internal Cursor Left(Buffer buffer)
return new Cursor(Row, Col - 1).Clamp(buffer);
internal Cursor Right(Buffer buffer)
return new Cursor(Row, Col + 1).Clamp(buffer);
private Cursor Clamp(Buffer buffer)
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)
return new Cursor(Row, 0);
class ANSI
public static void ClearScreen()
Console.Clear();
public static void MoveCursor(int row, int col)
Console.CursorTop = row;
Console.CursorLeft = col;
c# console text-editor
I have been following along Gary Bernhardt'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.
Implementation wise it's faithful to what he does. I do have questions whether the code is up to mark in terms of new C# conventions, haven't violated any biggies or gone overboard with LINQ.
The method SplitLine was a head scratcher (really showcased the terseness of Ruby) where his line splitting and new carriage code looks like and I couldn't come up with anything like it in C#.
lines[row..row] = [line[0...col],line[col..-1]]
My C# Code
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()
while (true)
Render();
HandleInput();
private void HandleInput()
var character = Console.ReadKey();
if ((ConsoleModifiers.Control & character.Modifiers) != 0 &&
character.Key == ConsoleKey.Q)
Environment.Exit(0);
else if ((ConsoleModifiers.Control & character.Modifiers) != 0 &&
character.Key == ConsoleKey.P)
_cursor = _cursor.Up(_buffer);
else if ((ConsoleModifiers.Control & character.Modifiers) != 0 &&
character.Key == ConsoleKey.N)
_cursor = _cursor.Down(_buffer);
else if ((ConsoleModifiers.Control & character.Modifiers) != 0 &&
character.Key == ConsoleKey.B)
_cursor = _cursor.Left(_buffer);
else if ((ConsoleModifiers.Control & character.Modifiers) != 0 &&
character.Key == ConsoleKey.Z)
_cursor = _cursor.Right(_buffer);
else if ((ConsoleModifiers.Control & character.Modifiers) != 0 &&
character.Key == ConsoleKey.U)
RestoreSnapshot();
else if (character.Key == ConsoleKey.Backspace)
if (_cursor.Col > 0)
SaveSnapshot();
_buffer = _buffer.Delete(_cursor.Row, _cursor.Col - 1);
_cursor = _cursor.Left(_buffer);
else if(character.Key == ConsoleKey.Enter)
SaveSnapshot();
_buffer = _buffer.SplitLine(_cursor.Row, _cursor.Col);
_cursor = _cursor.Down(_buffer).MoveToCol(0);
else
SaveSnapshot();
_buffer = _buffer.Insert(character.KeyChar.ToString(), _cursor.Row, _cursor.Col);
_cursor = _cursor.Right(_buffer);
private void Render()
ANSI.ClearScreen();
ANSI.MoveCursor(0, 0);
_buffer.Render();
ANSI.MoveCursor(_cursor.Row, _cursor.Col);
private void SaveSnapshot()
_history.Push(_cursor);
_history.Push(_buffer);
private void RestoreSnapshot()
if( _history.Count > 0 )
_buffer = (Buffer)_history.Pop();
_cursor = (Cursor)_history.Pop();
class Buffer
string _lines;
public Buffer(IEnumerable<string> lines)
_lines = lines.ToArray();
public void Render()
foreach (var line in _lines)
Console.WriteLine(line);
public int LineCount()
return _lines.Count();
public int LineLength(int row)
return _lines[row].Length;
internal Buffer Insert(string character, int row, int col)
var linesDeepCopy = _lines.Select(x => x).ToArray();
linesDeepCopy[row] = linesDeepCopy[row].Insert(col, character);
return new Buffer(linesDeepCopy);
internal Buffer Delete(int row, int col)
var linesDeepCopy = _lines.Select(x => x).ToArray();
linesDeepCopy[row] = linesDeepCopy[row].Remove(col, 1);
return new Buffer(linesDeepCopy);
internal Buffer SplitLine(int row, int col)
var linesDeepCopy = _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) ;
linesDeepCopy[row] = newLines[0];
linesDeepCopy[row + 1] = newLines[1];
return new Buffer(linesDeepCopy);
class Cursor
public int Row get; set;
public int Col get; set;
public Cursor(int row=0, int col=0)
Row = row;
Col = col;
internal Cursor Up(Buffer buffer)
return new Cursor(Row - 1, Col).Clamp(buffer);
internal Cursor Down(Buffer buffer)
return new Cursor(Row + 1, Col).Clamp(buffer);
internal Cursor Left(Buffer buffer)
return new Cursor(Row, Col - 1).Clamp(buffer);
internal Cursor Right(Buffer buffer)
return new Cursor(Row, Col + 1).Clamp(buffer);
private Cursor Clamp(Buffer buffer)
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)
return new Cursor(Row, 0);
class ANSI
public static void ClearScreen()
Console.Clear();
public static void MoveCursor(int row, int col)
Console.CursorTop = row;
Console.CursorLeft = col;
c# console text-editor
edited Apr 8 at 7:09
t3chb0t
32k54195
32k54195
asked Apr 8 at 0:59
rustyocean
382
382
add a comment |Â
add a comment |Â
1 Answer
1
active
oldest
votes
up vote
3
down vote
accepted
There are a couple of simple errors:
1)
In Buffer.SplitLine(...) you replace the next line with the second part of the split instead of inserting it after the first part:
linesDeepCopy[row] = newLines[0];
linesDeepCopy[row + 1] = newLines[1];
instead you should do something like this:
linesDeepCopy[row] = newLines[0];
linesDeepCopy.Insert(row + 1, newLines[1]);
2)
In Cursor.MoveToCol(int col) you don't use the argument col:
internal Cursor MoveToCol(int col)
return new Cursor(Row, 0);
I suppose it to be:
internal Cursor MoveToCol(int col)
return new Cursor(Row, col);
3)
In Editor.HandleInput() you should check if a character is a text char before you insert:
else if (IsTextChar(character))
SaveSnapshot();
_buffer = _buffer.Insert(character.KeyChar.ToString(), _cursor.Row, _cursor.Col);
_cursor = _cursor.Right(_buffer);
....
private bool IsTextChar(ConsoleKeyInfo character)
return !Char.IsControl(character.KeyChar);
4)
This construct
(ConsoleModifiers.Control & character.Modifiers) != 0
is potentially wrong because the value of ConsoleModifiers.Control could actually be 0. Therefore you should do like this:
(ConsoleModifiers.Control & character.Modifiers) == ConsoleModifiers.Control
Other things
Consider if this input check is suitable:
if ((ConsoleModifiers.Control & character.Modifiers) != 0 && character.Key == ConsoleKey.Q)
Environment.Exit(0);
Because of the flag-behavior of ConsoleModifiers.Control it will be true if any combination of Modifiers are pressed that involve Control (+ Q). It would maybe be more useful to make it more distinct like:
if (character.Modifiers == ConsoleModifiers.Control && character.Key == ConsoleKey.Q)
Environment.Exit(0);
In this way you'll save the other combinations with Control + Q to other tools.
Your handling of state is nice and clean and easily maintained in that you always create a new instance when ever Buffer or Cursor changes. But it requires immutable objects. Cursor is not immutable:
class Cursor
public int Row get; set;
public int Col get; set;
....
Anyone (?) could change these parameters on objects placed on the history stack.
At least you should restrict Cursor.Row and Cursor.Col to be private settable only.
class Cursor
public int Row get; private set;
public int Col get; private set;
....
Consider to make the Buffer.
public int LineCount()
return _lines.Count();
as a parameter instead:
public int LineCount => _lines.Length;
add a comment |Â
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
3
down vote
accepted
There are a couple of simple errors:
1)
In Buffer.SplitLine(...) you replace the next line with the second part of the split instead of inserting it after the first part:
linesDeepCopy[row] = newLines[0];
linesDeepCopy[row + 1] = newLines[1];
instead you should do something like this:
linesDeepCopy[row] = newLines[0];
linesDeepCopy.Insert(row + 1, newLines[1]);
2)
In Cursor.MoveToCol(int col) you don't use the argument col:
internal Cursor MoveToCol(int col)
return new Cursor(Row, 0);
I suppose it to be:
internal Cursor MoveToCol(int col)
return new Cursor(Row, col);
3)
In Editor.HandleInput() you should check if a character is a text char before you insert:
else if (IsTextChar(character))
SaveSnapshot();
_buffer = _buffer.Insert(character.KeyChar.ToString(), _cursor.Row, _cursor.Col);
_cursor = _cursor.Right(_buffer);
....
private bool IsTextChar(ConsoleKeyInfo character)
return !Char.IsControl(character.KeyChar);
4)
This construct
(ConsoleModifiers.Control & character.Modifiers) != 0
is potentially wrong because the value of ConsoleModifiers.Control could actually be 0. Therefore you should do like this:
(ConsoleModifiers.Control & character.Modifiers) == ConsoleModifiers.Control
Other things
Consider if this input check is suitable:
if ((ConsoleModifiers.Control & character.Modifiers) != 0 && character.Key == ConsoleKey.Q)
Environment.Exit(0);
Because of the flag-behavior of ConsoleModifiers.Control it will be true if any combination of Modifiers are pressed that involve Control (+ Q). It would maybe be more useful to make it more distinct like:
if (character.Modifiers == ConsoleModifiers.Control && character.Key == ConsoleKey.Q)
Environment.Exit(0);
In this way you'll save the other combinations with Control + Q to other tools.
Your handling of state is nice and clean and easily maintained in that you always create a new instance when ever Buffer or Cursor changes. But it requires immutable objects. Cursor is not immutable:
class Cursor
public int Row get; set;
public int Col get; set;
....
Anyone (?) could change these parameters on objects placed on the history stack.
At least you should restrict Cursor.Row and Cursor.Col to be private settable only.
class Cursor
public int Row get; private set;
public int Col get; private set;
....
Consider to make the Buffer.
public int LineCount()
return _lines.Count();
as a parameter instead:
public int LineCount => _lines.Length;
add a comment |Â
up vote
3
down vote
accepted
There are a couple of simple errors:
1)
In Buffer.SplitLine(...) you replace the next line with the second part of the split instead of inserting it after the first part:
linesDeepCopy[row] = newLines[0];
linesDeepCopy[row + 1] = newLines[1];
instead you should do something like this:
linesDeepCopy[row] = newLines[0];
linesDeepCopy.Insert(row + 1, newLines[1]);
2)
In Cursor.MoveToCol(int col) you don't use the argument col:
internal Cursor MoveToCol(int col)
return new Cursor(Row, 0);
I suppose it to be:
internal Cursor MoveToCol(int col)
return new Cursor(Row, col);
3)
In Editor.HandleInput() you should check if a character is a text char before you insert:
else if (IsTextChar(character))
SaveSnapshot();
_buffer = _buffer.Insert(character.KeyChar.ToString(), _cursor.Row, _cursor.Col);
_cursor = _cursor.Right(_buffer);
....
private bool IsTextChar(ConsoleKeyInfo character)
return !Char.IsControl(character.KeyChar);
4)
This construct
(ConsoleModifiers.Control & character.Modifiers) != 0
is potentially wrong because the value of ConsoleModifiers.Control could actually be 0. Therefore you should do like this:
(ConsoleModifiers.Control & character.Modifiers) == ConsoleModifiers.Control
Other things
Consider if this input check is suitable:
if ((ConsoleModifiers.Control & character.Modifiers) != 0 && character.Key == ConsoleKey.Q)
Environment.Exit(0);
Because of the flag-behavior of ConsoleModifiers.Control it will be true if any combination of Modifiers are pressed that involve Control (+ Q). It would maybe be more useful to make it more distinct like:
if (character.Modifiers == ConsoleModifiers.Control && character.Key == ConsoleKey.Q)
Environment.Exit(0);
In this way you'll save the other combinations with Control + Q to other tools.
Your handling of state is nice and clean and easily maintained in that you always create a new instance when ever Buffer or Cursor changes. But it requires immutable objects. Cursor is not immutable:
class Cursor
public int Row get; set;
public int Col get; set;
....
Anyone (?) could change these parameters on objects placed on the history stack.
At least you should restrict Cursor.Row and Cursor.Col to be private settable only.
class Cursor
public int Row get; private set;
public int Col get; private set;
....
Consider to make the Buffer.
public int LineCount()
return _lines.Count();
as a parameter instead:
public int LineCount => _lines.Length;
add a comment |Â
up vote
3
down vote
accepted
up vote
3
down vote
accepted
There are a couple of simple errors:
1)
In Buffer.SplitLine(...) you replace the next line with the second part of the split instead of inserting it after the first part:
linesDeepCopy[row] = newLines[0];
linesDeepCopy[row + 1] = newLines[1];
instead you should do something like this:
linesDeepCopy[row] = newLines[0];
linesDeepCopy.Insert(row + 1, newLines[1]);
2)
In Cursor.MoveToCol(int col) you don't use the argument col:
internal Cursor MoveToCol(int col)
return new Cursor(Row, 0);
I suppose it to be:
internal Cursor MoveToCol(int col)
return new Cursor(Row, col);
3)
In Editor.HandleInput() you should check if a character is a text char before you insert:
else if (IsTextChar(character))
SaveSnapshot();
_buffer = _buffer.Insert(character.KeyChar.ToString(), _cursor.Row, _cursor.Col);
_cursor = _cursor.Right(_buffer);
....
private bool IsTextChar(ConsoleKeyInfo character)
return !Char.IsControl(character.KeyChar);
4)
This construct
(ConsoleModifiers.Control & character.Modifiers) != 0
is potentially wrong because the value of ConsoleModifiers.Control could actually be 0. Therefore you should do like this:
(ConsoleModifiers.Control & character.Modifiers) == ConsoleModifiers.Control
Other things
Consider if this input check is suitable:
if ((ConsoleModifiers.Control & character.Modifiers) != 0 && character.Key == ConsoleKey.Q)
Environment.Exit(0);
Because of the flag-behavior of ConsoleModifiers.Control it will be true if any combination of Modifiers are pressed that involve Control (+ Q). It would maybe be more useful to make it more distinct like:
if (character.Modifiers == ConsoleModifiers.Control && character.Key == ConsoleKey.Q)
Environment.Exit(0);
In this way you'll save the other combinations with Control + Q to other tools.
Your handling of state is nice and clean and easily maintained in that you always create a new instance when ever Buffer or Cursor changes. But it requires immutable objects. Cursor is not immutable:
class Cursor
public int Row get; set;
public int Col get; set;
....
Anyone (?) could change these parameters on objects placed on the history stack.
At least you should restrict Cursor.Row and Cursor.Col to be private settable only.
class Cursor
public int Row get; private set;
public int Col get; private set;
....
Consider to make the Buffer.
public int LineCount()
return _lines.Count();
as a parameter instead:
public int LineCount => _lines.Length;
There are a couple of simple errors:
1)
In Buffer.SplitLine(...) you replace the next line with the second part of the split instead of inserting it after the first part:
linesDeepCopy[row] = newLines[0];
linesDeepCopy[row + 1] = newLines[1];
instead you should do something like this:
linesDeepCopy[row] = newLines[0];
linesDeepCopy.Insert(row + 1, newLines[1]);
2)
In Cursor.MoveToCol(int col) you don't use the argument col:
internal Cursor MoveToCol(int col)
return new Cursor(Row, 0);
I suppose it to be:
internal Cursor MoveToCol(int col)
return new Cursor(Row, col);
3)
In Editor.HandleInput() you should check if a character is a text char before you insert:
else if (IsTextChar(character))
SaveSnapshot();
_buffer = _buffer.Insert(character.KeyChar.ToString(), _cursor.Row, _cursor.Col);
_cursor = _cursor.Right(_buffer);
....
private bool IsTextChar(ConsoleKeyInfo character)
return !Char.IsControl(character.KeyChar);
4)
This construct
(ConsoleModifiers.Control & character.Modifiers) != 0
is potentially wrong because the value of ConsoleModifiers.Control could actually be 0. Therefore you should do like this:
(ConsoleModifiers.Control & character.Modifiers) == ConsoleModifiers.Control
Other things
Consider if this input check is suitable:
if ((ConsoleModifiers.Control & character.Modifiers) != 0 && character.Key == ConsoleKey.Q)
Environment.Exit(0);
Because of the flag-behavior of ConsoleModifiers.Control it will be true if any combination of Modifiers are pressed that involve Control (+ Q). It would maybe be more useful to make it more distinct like:
if (character.Modifiers == ConsoleModifiers.Control && character.Key == ConsoleKey.Q)
Environment.Exit(0);
In this way you'll save the other combinations with Control + Q to other tools.
Your handling of state is nice and clean and easily maintained in that you always create a new instance when ever Buffer or Cursor changes. But it requires immutable objects. Cursor is not immutable:
class Cursor
public int Row get; set;
public int Col get; set;
....
Anyone (?) could change these parameters on objects placed on the history stack.
At least you should restrict Cursor.Row and Cursor.Col to be private settable only.
class Cursor
public int Row get; private set;
public int Col get; private set;
....
Consider to make the Buffer.
public int LineCount()
return _lines.Count();
as a parameter instead:
public int LineCount => _lines.Length;
edited Apr 8 at 9:08
answered Apr 8 at 7:55
Henrik Hansen
3,8581417
3,8581417
add a comment |Â
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f191503%2fsimple-console-text-editor%23new-answer', 'question_page');
);
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password