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 }