1 using System;
  2 using System.Collections;
  3 using System.IO;
  4 
  5 public enum State
  6 {
  7     editinfopromptFileNamepromptSave
  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 rowint col)
 25     {
 26         RtMove(rowcol);
 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& promptTextState state_)
236     {
237         state = state_;
238         screen.MoveCursorTo(screen.rows - 10);
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(0charIndex) + ustring(c) + line.Substring(charIndex);
260         }
261         else
262         {
263             line.Append(c);
264         }
265         lines[lineNumber] = line;
266         dirty = true;
267         PrintLine(cursorRowlineNumber);
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(0charIndex) + line.Substring(charIndex + 1);
298             }
299             lines[lineNumber] = line;
300             dirty = true;
301             if (print)
302             {
303                 Print();
304             }
305             else
306             {
307                 PrintLine(cursorRowlineNumber);
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(0charIndex);
343             lines.Insert(lines.Begin() + lineNumber + 1tail);
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 rowint lineNumber)
358     {
359         screen.MoveCursorTo(row0);
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(rlineNumber);
385         }
386         PrintStatus();
387         screen.Refresh();
388     }
389     private void PrintStatus()
390     {
391         screen.MoveCursorTo(screen.rows - 10);
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 - 1screen.cols - cast<int>(positionStr.Length()));
408         RtAddStr(positionStr.Chars());
409         screen.MoveCursorTo(cursorRowcursorCol);
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(cursorRowcursorCol);
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(cursorRowcursorCol);
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(cursorRowcursorCol);
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(cursorRowcursorCol);
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 argcconst 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 }