Простой текстовый редактор в консоли


Я был следующим по отличным видео серии Гари Бернхардта на тему "Создание текстового редактора с нуля". Это в Ruby, но я хотел сделать это в C#, просто чтобы посмотреть, насколько сильно она отличается.

Реализация мудрый он верен, что он делает. У меня есть вопросы, Является ли код до знака с точки зрения нового C конвенций#, не было нарушено ни одной больной или ушли за борт с помощью LINQ.

Метод SplitLine была головная боль (действительно продемонстрированы по выражению Руби), где его расщепление линий и подвижного код выглядит, и я не мог придумать ничего подобного в C#.

lines[row..row] = [line[0...col],line[col..-1]]

Мой Код C#

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;
        }
    }
}


435
7
задан 8 апреля 2018 в 12:04 Источник Поделиться
Комментарии
1 ответ

Есть несколько простых ошибок:

1)
В Buffer.SplitLine(...) вы замените следующую строку со второй части раскола, а не вставить его после первой части:


linesDeepCopy[row] = newLines[0];    
linesDeepCopy[row + 1] = newLines[1];

вместо этого вы должны сделать что-то вроде этого:

    linesDeepCopy[row] = newLines[0];
linesDeepCopy.Insert(row + 1, newLines[1]);


2)
В Cursor.MoveToCol(int col) вы не используете аргумент коль:


  internal Cursor MoveToCol(int col)
{
return new Cursor(Row, 0);
}

Я предполагаю, что это будет:

  internal Cursor MoveToCol(int col)
{
return new Cursor(Row, col);
}


3)

В Editor.HandleInput() вы должны проверить, если символ является текст чар, прежде чем вставить:


        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)

Эта конструкция


(ConsoleModifiers.Control & character.Modifiers) != 0

потенциально неправильным, потому что значение ConsoleModifiers.Control может на самом деле быть 0. Поэтому вы должны делать так:

(ConsoleModifiers.Control & character.Modifiers) == ConsoleModifiers.Control


Другие вещи

Считаю, если эта проверка входного подходит:


    if ((ConsoleModifiers.Control & character.Modifiers) != 0 && character.Key == ConsoleKey.Q)
{
Environment.Exit(0);
}

Потому что флаг-поведение ConsoleModifiers.Контролировать его будет true, если любая комбинация модификаторов прессуется, которые включают Control (+ Q). Может, было бы более полезно, чтобы сделать его более отчетливым, как:

if (character.Modifiers == ConsoleModifiers.Control && character.Key == ConsoleKey.Q)
{
Environment.Exit(0);
}

Таким образом, вы будете экономить другие комбинации с Control + Q для других инструментов.


Ваше обращение государства является красивым и чистым, и легко поддерживается в том, что вы всегда создаете новый экземпляр, когда-либо буфера или курсор. Но это требует immutable объекты. Курсор не immutable:


class Cursor
{
public int Row { get; set; }
public int Col { get; set; }

....
}


Кто-нибудь (?) может менять эти параметры на объекты, размещенные в стеке истории.

По крайней мере, вы должны ограничить Cursor.Row и Cursor.Col частные задается только.

class Cursor
{
public int Row { get; private set; }
public int Col { get; private set; }

....
}


Учитывать, чтобы Buffer.


  public int LineCount()
{
return _lines.Count();
}

как вместо параметра:

public int LineCount => _lines.Length;

3
ответ дан 8 апреля 2018 в 07:04 Источник Поделиться