1 using System;
  
    2 using System.Collections;
  
    3 using System.IO;
  
    4 
  
    5 public enum State
  
    6 {
  
    7     edit, info, promptFileName, promptSave
  
    8 }
  
    9 
  
   10 class Screen
  
   11 {
  
   12     public nothrow Screen()
  
   13     {
  
   14         RtInitScreen();
  
   15         RtNoEcho();
  
   16         RtRaw();
  
   17         RtKeyPad();
  
   18         GetDimensions();
  
   19     }
  
   20     public ~Screen()
  
   21     {
  
   22         RtDoneScreen();
  
   23     }
  
   24     public nothrow void MoveCursorTo(int row, int col)
  
   25     {
  
   26         RtMove(row, col);
  
   27     }
  
   28     public nothrow void Refresh()
  
   29     {
  
   30         RtRefresh();
  
   31     }
  
   32     public nothrow void GetDimensions()
  
   33     {
  
   34         RtGetMaxYX(&rows, &cols);
  
   35         textRows = rows - 1;
  
   36     }
  
   37     public int rows;
  
   38     public int cols;
  
   39     public int textRows;
  
   40 }
  
   41 
  
   42 class Editor
  
   43 {
  
   44     public nothrow Editor(Screen& screen_) : screen(screen_), rowOffset(0), colOffset(0), cursorRow(0), cursorCol(0), dirty(false), fileName(), state(State.edit)
  
   45     {
  
   46     }
  
   47     public void OpenFile(const string& fileName_)
  
   48     {
  
   49         fileName = fileName_;
  
   50         if (File.Exists(fileName))
  
   51         {
  
   52             lines.Clear();
  
   53             List<string> utf8Lines = File.ReadAllLines(fileName);
  
   54             for (const string& utf8Line : utf8Lines)
  
   55             {
  
   56                 lines.Add(ToUtf32(utf8Line));
  
   57             }
  
   58         }
  
   59         dirty = false;
  
   60         Print();
  
   61     }
  
   62     public void Save()
  
   63     {
  
   64         if (fileName.IsEmpty())
  
   65         {
  
   66             Prompt("Enter filename: ", State.promptFileName);
  
   67         }
  
   68         else
  
   69         {
  
   70             StreamWriter writer = File.CreateText(fileName);
  
   71             for (const ustring& line : lines)
  
   72             {
  
   73                 writer.WriteLine(ToUtf8(line));
  
   74             }
  
   75             dirty = false;
  
   76             Prompt("File '" + fileName + "' saved.", State.info);
  
   77             Sleep(Duration.FromSeconds(3));
  
   78             state = State.edit;
  
   79             RtNoEcho();
  
   80             RtRaw();
  
   81             Print();
  
   82         }
  
   83     }
  
   84     public void Run()
  
   85     {
  
   86         while (true)
  
   87         {
  
   88             int ch = RtGetCh();
  
   89             if (state == State.edit)
  
   90             {
  
   91                 if (ch >= 0 && ch < 32 || ch >= specialKeyStart && ch <= specialKeyEnd)
  
   92                 {
  
   93                     if (ch == keyEnter)
  
   94                     {
  
   95                         Enter();
  
   96                     }
  
   97                     else if (ch == keyBackspace)
  
   98                     {
  
   99                         Backspace();
  
  100                     }
  
  101                     else if (ch == keyDel)
  
  102                     {
  
  103                         Delete();
  
  104                     }
  
  105                     else if (ch == keyControlY)
  
  106                     {
  
  107                         DeleteCurrentLine();
  
  108                     }
  
  109                     else if (ch == keyControlS)
  
  110                     {
  
  111                         Save();
  
  112                     }
  
  113                     else if (ch == keyLeft)
  
  114                     {
  
  115                         CursorLeft();
  
  116                     }
  
  117                     else if (ch == keyRight)
  
  118                     {
  
  119                         CursorRight();
  
  120                     }
  
  121                     else if (ch == keyUp)
  
  122                     {
  
  123                         CursorUp();
  
  124                     }
  
  125                     else if (ch == keyDown)
  
  126                     {
  
  127                         CursorDown();
  
  128                     }
  
  129                     else if (ch == keyPgUp)
  
  130                     {
  
  131                         CursorPageUp();
  
  132                     }
  
  133                     else if (ch == keyPgDown)
  
  134                     {
  
  135                         CursorPageDown();
  
  136                     }
  
  137                     else if (ch == keyHome)
  
  138                     {
  
  139                         CursorToBeginningOfLine();
  
  140                     }
  
  141                     else if (ch == keyEnd)
  
  142                     {
  
  143                         CursorToEndOfLine();
  
  144                     }
  
  145                     else if (ch == keyControlHome || ch == keyF3)
  
  146                     {
  
  147                         CursorToBeginningOfFile();
  
  148                     }
  
  149                     else if (ch == keyControlEnd || ch == keyF4)
  
  150                     {
  
  151                         CursorToEndOfFile();
  
  152                     }
  
  153                     else if (ch == keyResize)
  
  154                     {
  
  155                         screen.GetDimensions();
  
  156                         Print();
  
  157                     }
  
  158                     else if (ch == keyEscape)
  
  159                     {
  
  160                         if (dirty)
  
  161                         {
  
  162                             Prompt("Save changes? (y/n)", State.promptSave);
  
  163                         }
  
  164                         else
  
  165                         {
  
  166                             break;
  
  167                         }
  
  168                     }
  
  169                 }
  
  170                 else if (ch >= 32 && ch < specialKeyStart)
  
  171                 {
  
  172                     InsertChar(cast<uchar>(ch));
  
  173                 }
  
  174             }
  
  175             else
  
  176             {
  
  177                 if (state == State.promptFileName)
  
  178                 {
  
  179                     if (ch == keyEnter)
  
  180                     {
  
  181                         if (fileName.IsEmpty())
  
  182                         {
  
  183                             Prompt("no file name given", State.info);
  
  184                             Sleep(Duration.FromSeconds(3));
  
  185                             state = State.edit;
  
  186                             RtNoEcho();
  
  187                             RtRaw();
  
  188                             Print();
  
  189                         }
  
  190                         else
  
  191                         {
  
  192                             Save();
  
  193                         }
  
  194                     }
  
  195                     else if (ch == keyEscape)
  
  196                     {
  
  197                         state = State.edit;
  
  198                         RtNoEcho();
  
  199                         RtRaw();
  
  200                         Print();
  
  201                     }
  
  202                     else if (ch >= 32 && ch < specialKeyStart)
  
  203                     {
  
  204                         fileName.Append(cast<char>(ch));
  
  205                     }
  
  206                 }
  
  207                 else if (state == State.promptSave)
  
  208                 {
  
  209                     if (ch == keyEscape)
  
  210                     {
  
  211                         state = State.edit;
  
  212                         RtNoEcho();
  
  213                         RtRaw();
  
  214                         Print();
  
  215                     }
  
  216                     else if (ch >= 32 && ch < specialKeyStart)
  
  217                     {
  
  218                         if (cast<char>(ch) == 'y')
  
  219                         {
  
  220                             Save();
  
  221                             if (state == State.edit)
  
  222                             {
  
  223                                 break;
  
  224                             }
  
  225                         }
  
  226                         else if (cast<char>(ch) == 'n')
  
  227                         {
  
  228                             break;
  
  229                         }
  
  230                     }
  
  231                 }
  
  232             }
  
  233         }
  
  234     }
  
  235     private void Prompt(const string& promptText, State state_)
  
  236     {
  
  237         state = state_;
  
  238         screen.MoveCursorTo(screen.rows - 1, 0);
  
  239         for (char c : promptText)
  
  240         {
  
  241             RtAddCh(cast<int>(c));
  
  242         }
  
  243         RtClearToEol();
  
  244         RtEcho();
  
  245         RtNoRaw();
  
  246         RtRefresh();
  
  247     }
  
  248     private void InsertChar(uchar c)
  
  249     {
  
  250         int lineNumber = cursorRow + rowOffset;
  
  251         while (lineNumber >= lines.Count())
  
  252         {
  
  253             lines.Add(ustring());
  
  254         }
  
  255         int charIndex = cursorCol + colOffset;
  
  256         ustring line = lines[lineNumber];
  
  257         if (charIndex < line.Length())
  
  258         {
  
  259             line = line.Substring(0, charIndex) + ustring(c) + line.Substring(charIndex);
  
  260         }
  
  261         else
  
  262         {
  
  263             line.Append(c);
  
  264         }
  
  265         lines[lineNumber] = line;
  
  266         dirty = true;
  
  267         PrintLine(cursorRow, lineNumber);
  
  268         CursorRight();
  
  269         PrintStatus();
  
  270         screen.Refresh();
  
  271     }
  
  272     private void Delete()
  
  273     {
  
  274         bool print = false;
  
  275         int lineNumber = cursorRow + rowOffset;
  
  276         if (lineNumber < lines.Count())
  
  277         {
  
  278             int charIndex = cursorCol + colOffset;
  
  279             ustring line = lines[lineNumber];
  
  280             if (lineNumber < lines.Count() - 1 && charIndex >= line.Length())
  
  281             {
  
  282                 line.Append(lines[lineNumber + 1]);
  
  283                 lines.Remove(lines.Begin() + lineNumber + 1);
  
  284                 print = true;
  
  285             }
  
  286             else if (lineNumber == lines.Count() - 1 && charIndex >= line.Length())
  
  287             {
  
  288                 if (line.IsEmpty())
  
  289                 {
  
  290                     lines.RemoveLast();
  
  291                     Print();
  
  292                 }
  
  293                 return;
  
  294             }
  
  295             else
  
  296             {
  
  297                 line = line.Substring(0, charIndex) + line.Substring(charIndex + 1);
  
  298             }
  
  299             lines[lineNumber] = line;
  
  300             dirty = true;
  
  301             if (print)
  
  302             {
  
  303                 Print();
  
  304             }
  
  305             else
  
  306             {
  
  307                 PrintLine(cursorRow, lineNumber);
  
  308                 PrintStatus();
  
  309                 screen.Refresh();
  
  310             }
  
  311         }
  
  312     }
  
  313     private void DeleteCurrentLine()
  
  314     {
  
  315         int lineNumber = cursorRow + rowOffset;
  
  316         if (lineNumber < lines.Count())
  
  317         {
  
  318             lines.Remove(lines.Begin() + lineNumber);
  
  319             dirty = true;
  
  320             Print();
  
  321         }
  
  322     }
  
  323     private void Backspace()
  
  324     {
  
  325         int lineNumber = cursorRow + rowOffset;
  
  326         int charIndex = cursorCol + colOffset;
  
  327         if (lineNumber > 0 || charIndex > 0)
  
  328         {
  
  329             CursorLeft();
  
  330             Delete();
  
  331             dirty = true;
  
  332             Print();
  
  333         }
  
  334     }
  
  335     private void Enter()
  
  336     {
  
  337         int lineNumber = cursorRow + rowOffset;
  
  338         if (lineNumber < lines.Count())
  
  339         {
  
  340             int charIndex = cursorCol + colOffset;
  
  341             ustring tail = lines[lineNumber].Substring(charIndex);
  
  342             lines[lineNumber] = lines[lineNumber].Substring(0, charIndex);
  
  343             lines.Insert(lines.Begin() + lineNumber + 1, tail);
  
  344             dirty = true;
  
  345             Print();
  
  346         }
  
  347         else
  
  348         {
  
  349             lines.Add(ustring());
  
  350             dirty = true;
  
  351             PrintStatus();
  
  352             screen.Refresh();
  
  353         }
  
  354         CursorToEndOfLine();
  
  355         CursorRight();
  
  356     }
  
  357     private void PrintLine(int row, int lineNumber)
  
  358     {
  
  359         screen.MoveCursorTo(row, 0);
  
  360         for (int col = 0; col < screen.cols; ++col;)
  
  361         {
  
  362             int ch = cast<int>(' ');
  
  363             if (lineNumber < lines.Count())
  
  364             {
  
  365                 const ustring& line = lines[lineNumber];
  
  366                 int charIndex = col + colOffset;
  
  367                 if (charIndex < line.Length())
  
  368                 {
  
  369                     ch = cast<int>(line[charIndex]);
  
  370                 }
  
  371             }
  
  372             RtAddCh(ch);
  
  373         }
  
  374     }
  
  375     private void Print()
  
  376     {
  
  377         if (RtRunningOnWsl())
  
  378         {
  
  379             RtClear();
  
  380         }
  
  381         for (int r = 0; r < screen.textRows; ++r;)
  
  382         {
  
  383             int lineNumber = r + rowOffset;
  
  384             PrintLine(r, lineNumber);
  
  385         }
  
  386         PrintStatus();
  
  387         screen.Refresh();
  
  388     }
  
  389     private void PrintStatus()
  
  390     {
  
  391         screen.MoveCursorTo(screen.rows - 1, 0);
  
  392         for (char c : fileName)
  
  393         {
  
  394             RtAddCh(cast<int>(c));
  
  395         }
  
  396         RtAddCh(cast<int>(' '));
  
  397         if (dirty)
  
  398         {
  
  399             RtAddCh(cast<int>('*'));
  
  400         }
  
  401         else
  
  402         {
  
  403             RtAddCh(cast<int>(' '));
  
  404         }
  
  405         RtClearToEol();
  
  406         string positionStr = "(" + ToString(cursorRow + rowOffset + 1) + ", " + ToString(cursorCol + colOffset + 1) + ")";
  
  407         screen.MoveCursorTo(screen.rows - 1, screen.cols - cast<int>(positionStr.Length()));
  
  408         RtAddStr(positionStr.Chars());
  
  409         screen.MoveCursorTo(cursorRow, cursorCol);
  
  410     }
  
  411     private void CursorRight()
  
  412     {
  
  413         int lineNumber = cursorRow + rowOffset;
  
  414         if (lineNumber < lines.Count())
  
  415         {
  
  416             int charIndex = cursorCol + colOffset;
  
  417             if (charIndex < lines[lineNumber].Length())
  
  418             {
  
  419                 if (cursorCol < screen.cols - 1)
  
  420                 {
  
  421                     ++cursorCol;
  
  422                     screen.MoveCursorTo(cursorRow, cursorCol);
  
  423                     PrintStatus();
  
  424                     screen.Refresh();
  
  425                 }
  
  426                 else
  
  427                 {
  
  428                     ++colOffset;
  
  429                     Print();
  
  430                 }
  
  431             }
  
  432             else
  
  433             {
  
  434                 cursorCol = 0;
  
  435                 colOffset = 0;
  
  436                 CursorDown();
  
  437             }
  
  438         }
  
  439     }
  
  440     private void CursorLeft()
  
  441     {
  
  442         if (cursorCol > 0)
  
  443         {
  
  444             --cursorCol;
  
  445             screen.MoveCursorTo(cursorRow, cursorCol);
  
  446             PrintStatus();
  
  447             screen.Refresh();
  
  448         }
  
  449         else if (colOffset > 0)
  
  450         {
  
  451             --colOffset;
  
  452             Print();
  
  453         }
  
  454         else if (cursorRow > 0 || rowOffset > 0)
  
  455         {
  
  456             CursorUp();
  
  457             CursorToEndOfLine();
  
  458         }
  
  459     }
  
  460     private void CursorDown()
  
  461     {
  
  462         if (cursorRow < screen.textRows - 1)
  
  463         {
  
  464             ++cursorRow;
  
  465             int lineNumber = cursorRow + rowOffset;
  
  466             if (lineNumber < lines.Count())
  
  467             {
  
  468                 int charIndex = cursorCol + colOffset;
  
  469                 int lineLength = cast<int>(lines[lineNumber].Length());
  
  470                 if (charIndex >= lineLength)
  
  471                 {
  
  472                     colOffset = 0;
  
  473                     cursorCol = lineLength;
  
  474                     Print();
  
  475                 }
  
  476                 else
  
  477                 {
  
  478                     screen.MoveCursorTo(cursorRow, cursorCol);
  
  479                     PrintStatus();
  
  480                     screen.Refresh();
  
  481                 }
  
  482             }
  
  483             else
  
  484             {
  
  485                 CursorToEndOfFile();
  
  486             }
  
  487         }
  
  488         else
  
  489         {
  
  490             int lineNumber = cursorRow + rowOffset;
  
  491             if (lineNumber < lines.Count())
  
  492             {
  
  493                 ++rowOffset;
  
  494                 int charIndex = cursorCol + colOffset;
  
  495                 int lineLength = 0;
  
  496                 lineNumber = cursorRow + rowOffset;
  
  497                 if (lineNumber < lines.Count())
  
  498                 {
  
  499                     lineLength = cast<int>(lines[lineNumber].Length());
  
  500                 }
  
  501                 if (charIndex >= lineLength)
  
  502                 {
  
  503                     colOffset = 0;
  
  504                     cursorCol = lineLength;
  
  505                 }
  
  506                 Print();
  
  507             }
  
  508             else
  
  509             {
  
  510                 CursorToEndOfFile();
  
  511             }
  
  512         }
  
  513     }
  
  514     private void CursorUp()
  
  515     {
  
  516         if (cursorRow > 0)
  
  517         {
  
  518             --cursorRow;
  
  519             int lineNumber = cursorRow + rowOffset;
  
  520             int lineLength = cast<int>(lines[lineNumber].Length());
  
  521             int charIndex = cursorCol + colOffset;
  
  522             if (charIndex > lineLength)
  
  523             {
  
  524                 CursorToEndOfLine();
  
  525             }
  
  526             else
  
  527             {
  
  528                 screen.MoveCursorTo(cursorRow, cursorCol);
  
  529                 PrintStatus();
  
  530                 screen.Refresh();
  
  531             }
  
  532         }
  
  533         else if (rowOffset > 0)
  
  534         {
  
  535             --rowOffset;
  
  536             int lineNumber = cursorRow + rowOffset;
  
  537             int lineLength = cast<int>(lines[lineNumber].Length());
  
  538             int charIndex = cursorCol + colOffset;
  
  539             if (charIndex > lineLength)
  
  540             {
  
  541                 CursorToEndOfLine();
  
  542             }
  
  543             else
  
  544             {
  
  545                 Print();
  
  546             }
  
  547         }
  
  548     }
  
  549     private void CursorPageUp()
  
  550     {
  
  551         if (rowOffset >= screen.textRows)
  
  552         {
  
  553             rowOffset = rowOffset - screen.textRows;
  
  554         }
  
  555         else if (cursorRow + rowOffset >= screen.textRows)
  
  556         {
  
  557             rowOffset = 0;
  
  558         }
  
  559         else
  
  560         {
  
  561             rowOffset = 0;
  
  562             cursorRow = 0;
  
  563         }
  
  564         int lineNumber = cursorRow + rowOffset;
  
  565         if (lineNumber == 0)
  
  566         {
  
  567             CursorToBeginningOfFile();
  
  568         }
  
  569         else
  
  570         {
  
  571             int lineLength = cast<int>(lines[lineNumber].Length());
  
  572             int charIndex = cursorCol + colOffset;
  
  573             if (charIndex > lineLength)
  
  574             {
  
  575                 CursorToEndOfLine();
  
  576             }
  
  577             Print();
  
  578         }
  
  579     }
  
  580     private void CursorPageDown()
  
  581     {
  
  582         int lineCount = cast<int>(lines.Count());
  
  583         if (rowOffset + screen.textRows > lineCount)
  
  584         {
  
  585             CursorToEndOfFile();
  
  586         }
  
  587         else
  
  588         {
  
  589             rowOffset = rowOffset + screen.textRows;
  
  590             Print();
  
  591         }
  
  592     }
  
  593     private void CursorToBeginningOfLine()
  
  594     {
  
  595         cursorCol = 0;
  
  596         if (colOffset > 0)
  
  597         {
  
  598             colOffset = 0;
  
  599             Print();
  
  600         }
  
  601         else
  
  602         {
  
  603             PrintStatus();
  
  604             screen.Refresh();
  
  605         }
  
  606     }
  
  607     private void CursorToEndOfLine()
  
  608     {
  
  609         int lineNumber = cursorRow + rowOffset;
  
  610         if (lineNumber < lines.Count())
  
  611         {
  
  612             int lineLength = cast<int>(lines[lineNumber].Length());
  
  613             if (lineLength < screen.cols)
  
  614             {
  
  615                 cursorCol = lineLength;
  
  616                 colOffset = 0;
  
  617                 Print();
  
  618             }
  
  619             else
  
  620             {
  
  621                 cursorCol = screen.cols - 1;
  
  622                 colOffset = lineLength - cursorCol;
  
  623                 Print();
  
  624             }
  
  625         }
  
  626     }
  
  627     private void CursorToBeginningOfFile()
  
  628     {
  
  629         cursorRow = 0;
  
  630         rowOffset = 0;
  
  631         cursorCol = 0;
  
  632         colOffset = 0;
  
  633         Print();
  
  634     }
  
  635     private void CursorToEndOfFile()
  
  636     {
  
  637         int lineCount = cast<int>(lines.Count());
  
  638         if (lineCount < screen.textRows)
  
  639         {
  
  640             rowOffset = 0;
  
  641             cursorRow = lineCount;
  
  642             cursorCol = 0;
  
  643             colOffset = 0;
  
  644         }
  
  645         else
  
  646         {
  
  647             cursorCol = 0;
  
  648             colOffset = 0;
  
  649             cursorRow = screen.textRows - 1;
  
  650             rowOffset = lineCount - cursorRow;
  
  651         }
  
  652         Print();
  
  653     }
  
  654     private Screen& screen;
  
  655     private List<ustring> lines;
  
  656     private int rowOffset;
  
  657     private int colOffset;
  
  658     private int cursorRow;
  
  659     private int cursorCol;
  
  660     private bool dirty;
  
  661     private string fileName;
  
  662     private State state;
  
  663 }
  
  664 
  
  665 int main(int argc, const char** argv)
  
  666 {
  
  667     Screen screen;
  
  668     try
  
  669     {
  
  670         Editor editor(screen);
  
  671         string fileName;
  
  672         if (argc >= 2)
  
  673         {
  
  674             fileName = argv[1];
  
  675         }
  
  676         if (!fileName.IsEmpty())
  
  677         {
  
  678             editor.OpenFile(fileName);
  
  679         }
  
  680         editor.Run();
  
  681     }
  
  682     catch (const Exception& ex)
  
  683     {
  
  684         Console.Error() << ex.Message() << endl();
  
  685         return 1;
  
  686     }
  
  687     return 0;
  
  688 }