Simple console text editor

The name of the pictureThe name of the pictureThe name of the pictureClash 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;









share|improve this question



























    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;









    share|improve this question























      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;









      share|improve this question













      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;











      share|improve this question












      share|improve this question




      share|improve this question








      edited Apr 8 at 7:09









      t3chb0t

      32k54195




      32k54195









      asked Apr 8 at 0:59









      rustyocean

      382




      382




















          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;





          share|improve this answer























            Your Answer




            StackExchange.ifUsing("editor", function ()
            return StackExchange.using("mathjaxEditing", function ()
            StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
            StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
            );
            );
            , "mathjax-editing");

            StackExchange.ifUsing("editor", function ()
            StackExchange.using("externalEditor", function ()
            StackExchange.using("snippets", function ()
            StackExchange.snippets.init();
            );
            );
            , "code-snippets");

            StackExchange.ready(function()
            var channelOptions =
            tags: "".split(" "),
            id: "196"
            ;
            initTagRenderer("".split(" "), "".split(" "), channelOptions);

            StackExchange.using("externalEditor", function()
            // Have to fire editor after snippets, if snippets enabled
            if (StackExchange.settings.snippets.snippetsEnabled)
            StackExchange.using("snippets", function()
            createEditor();
            );

            else
            createEditor();

            );

            function createEditor()
            StackExchange.prepareEditor(
            heartbeatType: 'answer',
            convertImagesToLinks: false,
            noModals: false,
            showLowRepImageUploadWarning: true,
            reputationToPostImages: null,
            bindNavPrevention: true,
            postfix: "",
            onDemand: true,
            discardSelector: ".discard-answer"
            ,immediatelyShowMarkdownHelp:true
            );



            );








             

            draft saved


            draft discarded


















            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






























            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;





            share|improve this answer



























              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;





              share|improve this answer

























                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;





                share|improve this answer















                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;






                share|improve this answer















                share|improve this answer



                share|improve this answer








                edited Apr 8 at 9:08


























                answered Apr 8 at 7:55









                Henrik Hansen

                3,8581417




                3,8581417






















                     

                    draft saved


                    draft discarded


























                     


                    draft saved


                    draft discarded














                    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













































































                    Popular posts from this blog

                    Python Lists

                    Aion

                    JavaScript Array Iteration Methods