1
2
3
4
5
6 using System;
7 using System.Collections;
8
9 namespace System.Screen
10 {
11 const int tabSize = 4;
12
13 public class Selection
14 {
15 public nothrow Selection() : startLine(-1), startCol(-1), endLine(-1), endCol(-1)
16 {
17 }
18 public nothrow bool IsEmpty() const
19 {
20 return startLine == -1 && startCol == -1 && endLine == -1 && endCol == -1;
21 }
22 public nothrow void Reset()
23 {
24 startLine = -1;
25 startCol = -1;
26 endLine = -1;
27 endCol = -1;
28 }
29 public nothrow void SetStart(int startLine_, int startCol_)
30 {
31 startLine = startLine_;
32 startCol = startCol_;
33 }
34 public nothrow void SetEnd(int endLine_, int endCol_)
35 {
36 endLine = endLine_;
37 endCol = endCol_;
38 }
39 public nothrow string ToString() const
40 {
41 return "[(" + ToString(startLine) + ", " + ToString(startCol) + "), (" + ToString(endLine) + ", " + ToString(endCol) + ")]";
42 }
43 public int startLine;
44 public int startCol;
45 public int endLine;
46 public int endCol;
47 }
48
49 public class SelectionData
50 {
51 public nothrow SelectionData()
52 {
53 }
54 public nothrow bool IsEmpty() const
55 {
56 return lines.IsEmpty();
57 }
58 public void SetLines(const List<ustring>& lines_)
59 {
60 lines = lines_;
61 }
62 public nothrow const List<ustring>& Lines() const
63 {
64 return lines;
65 }
66 private List<ustring> lines;
67 }
68
69 public class Clipboard
70 {
71 static Clipboard() : instance(new Clipboard())
72 {
73 }
74 private Clipboard()
75 {
76 }
77 public static Clipboard& Instance()
78 {
79 return *instance;
80 }
81 public nothrow bool IsEmpty() const
82 {
83 return selectionData.IsEmpty();
84 }
85 public nothrow SelectionData& GetSelectionData()
86 {
87 return selectionData;
88 }
89 private static UniquePtr<Clipboard> instance;
90 private SelectionData selectionData;
91 }
92
93 public class EditorCreateParams
94 {
95 public nothrow EditorCreateParams() :
96 controlCreateParams(),
97 selectionForeColor(ConsoleColor.defaultColor),
98 selectionBackColor(ConsoleColor.defaultColor)
99 {
100 }
101 public nothrow EditorCreateParams& Defaults()
102 {
103 return *this;
104 }
105 public nothrow EditorCreateParams& SelectionForeColor(ConsoleColor selectionForeColor_)
106 {
107 selectionForeColor = selectionForeColor_;
108 return *this;
109 }
110 public nothrow EditorCreateParams& SelectionBackColor(ConsoleColor selectionBackColor_)
111 {
112 selectionBackColor = selectionBackColor_;
113 return *this;
114 }
115 public ControlCreateParams controlCreateParams;
116 public ConsoleColor selectionForeColor;
117 public ConsoleColor selectionBackColor;
118 }
119
120 public enum EditorFlags
121 {
122 none = 0, dirty = 1 << 0
123 }
124
125 public class Editor : Control
126 {
127 public nothrow Editor(EditorCreateParams& createParams) :
128 base(createParams.controlCreateParams), flags(EditorFlags.none), xOffset(0), yOffset(0), caretLine(0), caretCol(0)
129 {
130 InvalidateGuard invalidateGuard(this, InvalidateKind.dontInvalidate);
131 if (ForeColor() == ConsoleColor.defaultColor)
132 {
133 SetForeColor(ConsoleColor.gray);
134 }
135 if (BackColor() == ConsoleColor.defaultColor)
136 {
137 SetBackColor(ConsoleColor.darkBlue);
138 }
139 if (createParams.selectionForeColor == ConsoleColor.defaultColor)
140 {
141 selectionForeColor = ConsoleColor.black;
142 }
143 else
144 {
145 selectionForeColor = createParams.selectionForeColor;
146 }
147 if (createParams.selectionBackColor == ConsoleColor.defaultColor)
148 {
149 selectionBackColor = ConsoleColor.cyan;
150 }
151 else
152 {
153 selectionBackColor = createParams.selectionBackColor;
154 }
155 if (Location().IsDefault())
156 {
157 SetLocation(Point(0, 1));
158 }
159 if (GetSize().IsDefault())
160 {
161 SetSize(Size(TerminalWindowWidth(), TerminalWindowHeight() - 2));
162 }
163 }
164 public nothrow bool IsDirty() const
165 {
166 return (flags & EditorFlags.dirty) != EditorFlags.none;
167 }
168 public nothrow void SetDirty()
169 {
170 if (!IsDirty())
171 {
172 flags = cast<EditorFlags>(flags | EditorFlags.dirty);
173 dirtyChangedEvent.Fire();
174 }
175 }
176 public nothrow void ResetDirty()
177 {
178 if (IsDirty())
179 {
180 flags = cast<EditorFlags>(flags & ~EditorFlags.dirty);
181 dirtyChangedEvent.Fire();
182 }
183 }
184 public void SetLines(const List<string>& lines_)
185 {
186 lines.Clear();
187 for (const string& line : lines_)
188 {
189 lines.Add(ToUtf32(line));
190 }
191 CursorStartOfText(false);
192 ResetDirty();
193 }
194 public void AddLine(const string& line)
195 {
196 lines.Add(ToUtf32(line));
197 Invalidate(GetRect());
198 }
199 public nothrow void SetFilePath(const string& filePath_)
200 {
201 if (filePath != filePath_)
202 {
203 filePath = filePath_;
204 filePathChangedEvent.Fire();
205 }
206 }
207 public nothrow const string& FilePath() const
208 {
209 return filePath;
210 }
211 public void SetCaretPos(int caretLine_, int caretCol_)
212 {
213 if (caretLine != caretLine_ || caretCol != caretCol_)
214 {
215 caretLine = caretLine_;
216 caretCol = caretCol_;
217 caretPosChangedEvent.Fire();
218 }
219 }
220 public nothrow int CaretLine() const
221 {
222 return caretLine;
223 }
224 public nothrow int CaretCol() const
225 {
226 return caretCol;
227 }
228 public void Clear()
229 {
230 lines.Clear();
231 CursorStartOfText(false);
232 SetStatusText("text cleared");
233 ResetDirty();
234 }
235 public void Save()
236 {
237 try
238 {
239 if (filePath.IsEmpty()) return;
240 StreamWriter writer = File.CreateText(filePath);
241 for (const ustring& line : lines)
242 {
243 writer.WriteLine(line);
244 }
245 SetStatusText("text saved");
246 ResetDirty();
247 }
248 catch (const Exception& ex)
249 {
250 UniquePtr<MessageBox> messageBox = new MessageBox(MessageBoxCreateParams().Caption("Error").Text(ex.ToString()));
251 messageBox->ShowDialog();
252 SetFocus();
253 }
254 }
255 public void Load()
256 {
257 try
258 {
259 if (filePath.IsEmpty()) return;
260 List<string> lines_ = File.ReadAllLines(filePath);
261 SetLines(lines_);
262 SetStatusText("text loaded");
263 }
264 catch (const Exception& ex)
265 {
266 UniquePtr<MessageBox> messageBox = new MessageBox(MessageBoxCreateParams().Caption("Error").Text(ex.ToString()));
267 messageBox->ShowDialog();
268 SetFocus();
269 }
270 }
271 public void Copy()
272 {
273 if (selection.IsEmpty()) return;
274 MakeCanonicalSelection();
275 List<ustring> selectionLines;
276 for (int i = canonicalSelection.startLine; i <= canonicalSelection.endLine; ++i;)
277 {
278 if (i >= 0 && i < lines.Count())
279 {
280 selectionLines.Add(lines[i]);
281 }
282 }
283 Clipboard.Instance().GetSelectionData().SetLines(selectionLines);
284 SetStatusText("selection copied");
285 }
286 public void Cut()
287 {
288 if (selection.IsEmpty()) return;
289 Copy();
290 DeleteSelection();
291 SetStatusText("selection was cut");
292 }
293 public void Paste()
294 {
295 if (Clipboard.Instance().IsEmpty()) return;
296 const List<ustring>& selectionLines = Clipboard.Instance().GetSelectionData().Lines();
297 for (const ustring& line : selectionLines)
298 {
299 if (caretLine < lines.Count())
300 {
301 lines.Insert(lines.Begin() + caretLine, line);
302 ++caretLine;
303 }
304 else
305 {
306 lines.Add(line);
307 ++caretLine;
308 }
309 }
310 Invalidate(GetRect());
311 SetDirty();
312 }
313 public nothrow Event<ChangedEventHandler>& FilePathChangedEvent()
314 {
315 return filePathChangedEvent;
316 }
317 public nothrow Event<ChangedEventHandler>& CaretPosChangedEvent()
318 {
319 return caretPosChangedEvent;
320 }
321 public nothrow Event<ChangedEventHandler>& StatusTextChangedEvent()
322 {
323 return statusTextChangedEvent;
324 }
325 public nothrow Event<ChangedEventHandler>& DirtyChangedEvent()
326 {
327 return dirtyChangedEvent;
328 }
329 public nothrow void SetStatusText(const string& statusText_)
330 {
331 if (statusText != statusText_)
332 {
333 statusText = statusText_;
334 statusTextChangedEvent.Fire();
335 }
336 }
337 public nothrow const string& StatusText() const
338 {
339 return statusText;
340 }
341 public override void SetFocus()
342 {
343 base->SetFocus();
344 SetCursorPos();
345 }
346 public void SetCursorPos()
347 {
348 Point cp = ScreenPoint(caretCol, caretLine);
349 if (cp.x >= 0 && cp.x < TerminalWindowWidth() && cp.y >= 0 && cp.y < TerminalWindowHeight())
350 {
351 SetControlCursorPos(cp);
352 SetCursorPos(cp.x, cp.y);
353 }
354 }
355 public override void OnWriteScreen(WriteScreenEventArgs& args)
356 {
357 base->OnWriteScreen(args);
358 Rect updateRect = GetRect();
359 if (!args.GetRect().IsDefault())
360 {
361 updateRect = Rect.Intersection(updateRect, args.GetRect());
362 }
363 if (updateRect.IsEmpty()) return;
364 Clear(updateRect, ForeColor(), BackColor());
365 if (selection.IsEmpty())
366 {
367 WriteScreenNoSelection(updateRect);
368 }
369 else
370 {
371 WriteScreenWithSelection(updateRect);
372 }
373 SetCursorPos();
374 }
375
376 public override void OnKeyPressed(KeyEventArgs& args)
377 {
378 if (args.Handled()) return;
379 Rect updateRect;
380 int prevXOffset = xOffset;
381 int prevYOffset = yOffset;
382 uchar key = args.Key();
383 if (key >= ' ' && key < specialKeyStart)
384 {
385 args.SetHandled();
386 DeleteSelection();
387 while (caretLine >= lines.Count())
388 {
389 lines.Add(ustring());
390 }
391 ustring& line = lines[caretLine];
392 bool inserted = false;
393 if (caretCol < line.Length())
394 {
395 line.Insert(caretCol, key);
396 inserted = true;
397 }
398 else
399 {
400 line.Append(key);
401 }
402 Point start = ScreenPoint(caretCol, caretLine);
403 SetCaretPos(caretLine, caretCol + 1);
404 Point end = ScreenPoint(caretCol, caretLine + 1);
405 if (inserted)
406 {
407 end = ScreenPoint(cast<int>(line.Length()), caretLine + 1);
408 }
409 updateRect = ScreenRect(start, end);
410 SetXOffset();
411 SetYOffset();
412 SetDirty();
413 }
414 else
415 {
416 if (key < specialKeyStart)
417 {
418 if (key == keyNewline)
419 {
420 args.SetHandled();
421 Newline(updateRect);
422 }
423 else if (key == keyTab)
424 {
425 args.SetHandled();
426 Tab(updateRect);
427 }
428 else if (key == keyBackspace)
429 {
430 args.SetHandled();
431 Backspace(updateRect);
432 }
433 }
434 else
435 {
436 switch (key)
437 {
438 case keyLeft:
439 {
440 args.SetHandled();
441 CursorLeft(false, true);
442 break;
443 }
444 case keyRight:
445 {
446 args.SetHandled();
447 CursorRight(false, true);
448 break;
449 }
450 case keyUp:
451 {
452 args.SetHandled();
453 CursorUp(false);
454 break;
455 }
456 case keyDown:
457 {
458 args.SetHandled();
459 CursorDown(false);
460 break;
461 }
462 case keyControlLeft:
463 {
464 args.SetHandled();
465 CursorPrevWord(false);
466 break;
467 }
468 case keyControlRight:
469 {
470 args.SetHandled();
471 CursorNextWord(false);
472 break;
473 }
474 case keyHome:
475 {
476 args.SetHandled();
477 CursorStartOfLine(false);
478 break;
479 }
480 case keyEnd:
481 {
482 args.SetHandled();
483 CursorEndOfLine(false);
484 break;
485 }
486 case keyPgUp:
487 {
488 args.SetHandled();
489 CursorPrevPage(false);
490 break;
491 }
492 case keyPgDown:
493 {
494 args.SetHandled();
495 CursorNextPage(false);
496 break;
497 }
498 case keyControlHome:
499 {
500 args.SetHandled();
501 CursorStartOfText(false);
502 break;
503 }
504 case keyControlEnd:
505 {
506 args.SetHandled();
507 CursorEndOfText(false);
508 break;
509 }
510 case keyShiftTab:
511 {
512 args.SetHandled();
513 Untab(updateRect);
514 break;
515 }
516 case keyDel:
517 {
518 args.SetHandled();
519 if (selection.IsEmpty())
520 {
521 DeleteChar(updateRect);
522 }
523 else
524 {
525 DeleteSelection();
526 }
527 break;
528 }
529 case keyShiftLeft:
530 {
531 args.SetHandled();
532 CursorLeft(true, true);
533 break;
534 }
535 case keyShiftRight:
536 {
537 args.SetHandled();
538 CursorRight(true, true);
539 break;
540 }
541 case keyShiftDown:
542 {
543 args.SetHandled();
544 CursorDown(true);
545 break;
546 }
547 case keyShiftUp:
548 {
549 args.SetHandled();
550 CursorUp(true);
551 break;
552 }
553 case keyShiftHome:
554 {
555 args.SetHandled();
556 CursorStartOfLine(true);
557 break;
558 }
559 case keyShiftEnd:
560 {
561 args.SetHandled();
562 CursorEndOfLine(true);
563 break;
564 }
565 case keyShiftPgUp:
566 {
567 args.SetHandled();
568 CursorPrevPage(true);
569 break;
570 }
571 case keyShiftPgDown:
572 {
573 args.SetHandled();
574 CursorNextPage(true);
575 break;
576 }
577 case keyControlShiftLeft:
578 {
579 args.SetHandled();
580 CursorPrevWord(true);
581 break;
582 }
583 case keyControlShiftRight:
584 {
585 args.SetHandled();
586 CursorNextWord(true);
587 break;
588 }
589 case keyControlShiftHome:
590 {
591 args.SetHandled();
592 CursorStartOfText(true);
593 break;
594 }
595 case keyControlShiftEnd:
596 {
597 args.SetHandled();
598 CursorEndOfText(true);
599 break;
600 }
601 }
602 }
603 }
604 if (xOffset != prevXOffset || yOffset != prevYOffset)
605 {
606 Invalidate(GetRect());
607 SetCursorPos();
608 }
609 else
610 {
611 Invalidate(updateRect);
612 SetCursorPos();
613 }
614 }
615 private void WriteScreenNoSelection(const Rect& updateRect)
616 {
617 Terminal.Out() << SetColors(ForeColor(), BackColor());
618 for (int y = 0; y < updateRect.size.h; ++y;)
619 {
620 ustring updateLine;
621 for (int x = 0; x < updateRect.size.w; ++x;)
622 {
623 uchar c = CharAt(updateRect.location.x + x, updateRect.location.y + y);
624 if (c == '\0')
625 {
626 break;
627 }
628 updateLine.Append(c);
629 }
630 SetCursorPos(updateRect.location.x, updateRect.location.y + y);
631 Terminal.Out() << updateLine;
632 }
633 }
634 private void WriteScreenWithSelection(const Rect& updateRect)
635 {
636 MakeCanonicalSelection();
637 for (int y = 0; y < updateRect.size.h; ++y;)
638 {
639 for (int x = 0; x < updateRect.size.w; ++x;)
640 {
641 uchar c = CharAt(updateRect.location.x + x, updateRect.location.y + y);
642 if (c != '\0')
643 {
644 int line = 0;
645 int col = 0;
646 LineCol(updateRect.location.x + x, updateRect.location.y + y, line, col);
647 if (LineColInSelection(line, col))
648 {
649 Terminal.Out() << SetColors(selectionForeColor, selectionBackColor);
650 }
651 else
652 {
653 Terminal.Out() << SetColors(ForeColor(), BackColor());
654 }
655 SetCursorPos(updateRect.location.x + x, updateRect.location.y + y);
656 Terminal.Out() << c;
657 }
658 }
659 }
660 }
661 private void SetXOffset()
662 {
663 Size sz = GetSize();
664 while (caretCol - xOffset >= sz.w)
665 {
666 ++xOffset;
667 }
668 }
669 private void SetYOffset()
670 {
671 Size sz = GetSize();
672 while (caretLine - yOffset >= sz.h)
673 {
674 ++yOffset;
675 }
676 }
677 private void CursorStartOfText(bool extendSelection)
678 {
679 Point selectionStartExtensionPoint;
680 Point selectionEndExtensionPoint;
681 if (extendSelection)
682 {
683 if (selection.IsEmpty())
684 {
685 selection.SetStart(caretLine, caretCol);
686 }
687 selectionStartExtensionPoint = ScreenPoint(0, caretLine);
688 }
689 else
690 {
691 ResetSelection();
692 }
693 SetCaretPos(0, 0);
694 xOffset = 0;
695 yOffset = 0;
696 SetCursorPos();
697 if (extendSelection)
698 {
699 Size sz = GetSize();
700 selectionEndExtensionPoint = ScreenPoint(sz.w, 0);
701 selection.SetEnd(caretLine, caretCol);
702 Rect selectionUpdateRect = ScreenRect(selectionStartExtensionPoint, selectionEndExtensionPoint);
703 Invalidate(selectionUpdateRect);
704 }
705 }
706 private void CursorEndOfText(bool extendSelection)
707 {
708 Point selectionStartExtensionPoint;
709 Point selectionEndExtensionPoint;
710 if (extendSelection)
711 {
712 if (selection.IsEmpty())
713 {
714 selection.SetStart(caretLine, caretCol);
715 }
716 selectionStartExtensionPoint = ScreenPoint(0, caretLine);
717 }
718 else
719 {
720 ResetSelection();
721 }
722 SetCaretPos(cast<int>(lines.Count()), 0);
723 SetXOffset();
724 SetYOffset();
725 SetCursorPos();
726 if (extendSelection)
727 {
728 Size sz = GetSize();
729 selectionEndExtensionPoint = ScreenPoint(sz.w, caretLine + 1);
730 selection.SetEnd(caretLine, caretCol);
731 Rect selectionUpdateRect = ScreenRect(selectionStartExtensionPoint, selectionEndExtensionPoint);
732 Invalidate(selectionUpdateRect);
733 }
734 }
735 private void CursorPrevPage(bool extendSelection)
736 {
737 Point selectionStartExtensionPoint;
738 Point selectionEndExtensionPoint;
739 if (extendSelection)
740 {
741 if (selection.IsEmpty())
742 {
743 selection.SetStart(caretLine, caretCol);
744 }
745 selectionStartExtensionPoint = ScreenPoint(0, caretLine);
746 }
747 else
748 {
749 ResetSelection();
750 }
751 Size sz = GetSize();
752 if (yOffset >= sz.h)
753 {
754 yOffset = yOffset - sz.h;
755 SetCaretPos(caretLine - sz.h, 0);
756 SetCursorPos();
757 }
758 else
759 {
760 CursorStartOfText(extendSelection);
761 }
762 if (extendSelection)
763 {
764 selectionEndExtensionPoint = ScreenPoint(sz.w, caretLine - sz.h);
765 selection.SetEnd(caretLine, caretCol);
766 Rect selectionUpdateRect = ScreenRect(selectionStartExtensionPoint, selectionEndExtensionPoint);
767 Invalidate(selectionUpdateRect);
768 }
769 }
770 private void CursorNextPage(bool extendSelection)
771 {
772 Point selectionStartExtensionPoint;
773 Point selectionEndExtensionPoint;
774 if (extendSelection)
775 {
776 if (selection.IsEmpty())
777 {
778 selection.SetStart(caretLine, caretCol);
779 }
780 selectionStartExtensionPoint = ScreenPoint(0, caretLine);
781 }
782 else
783 {
784 ResetSelection();
785 }
786 Size sz = GetSize();
787 if (caretLine + sz.h >= lines.Count())
788 {
789 CursorEndOfText(extendSelection);
790 }
791 else
792 {
793 SetCaretPos(caretLine + sz.h, 0);
794 yOffset = yOffset + sz.h;
795 SetCursorPos();
796 }
797 if (extendSelection)
798 {
799 selectionEndExtensionPoint = ScreenPoint(sz.w, caretLine + sz.h);
800 selection.SetEnd(caretLine, caretCol);
801 Rect selectionUpdateRect = ScreenRect(selectionStartExtensionPoint, selectionEndExtensionPoint);
802 Invalidate(selectionUpdateRect);
803 }
804 }
805 private void CursorLeft(bool extendSelection, bool invalidate)
806 {
807 Point selectionStartExtensionPoint;
808 Point selectionEndExtensionPoint;
809 if (extendSelection)
810 {
811 if (selection.IsEmpty())
812 {
813 selection.SetStart(caretLine, caretCol);
814 }
815 selectionStartExtensionPoint = ScreenPoint(0, caretLine);
816 }
817 else
818 {
819 ResetSelection();
820 }
821 if (caretCol > 0)
822 {
823 SetCaretPos(caretLine, caretCol - 1);
824 SetCursorPos();
825 }
826 else
827 {
828 if (caretLine > 0)
829 {
830 ustring& line = lines[caretLine - 1];
831 SetCaretPos(caretLine - 1, cast<int>(line.Length()));
832 SetXOffset();
833 SetCursorPos();
834 }
835 }
836 if (caretCol < xOffset)
837 {
838 --xOffset;
839 }
840 if (extendSelection)
841 {
842 Size sz = GetSize();
843 selectionEndExtensionPoint = ScreenPoint(sz.w, caretLine + 1);
844 selection.SetEnd(caretLine, caretCol);
845 Rect selectionUpdateRect = ScreenRect(selectionStartExtensionPoint, selectionEndExtensionPoint);
846 if (invalidate)
847 {
848 Invalidate(selectionUpdateRect);
849 }
850 }
851 }
852 private void CursorRight(bool extendSelection, bool invalidate)
853 {
854 Point selectionStartExtensionPoint;
855 Point selectionEndExtensionPoint;
856 if (caretLine >= lines.Count())
857 {
858 return;
859 }
860 if (extendSelection)
861 {
862 if (selection.IsEmpty())
863 {
864 selection.SetStart(caretLine, caretCol);
865 }
866 selectionStartExtensionPoint = ScreenPoint(0, caretLine);
867 }
868 else
869 {
870 ResetSelection();
871 }
872 ustring& line = lines[caretLine];
873 if (caretCol < line.Length())
874 {
875 SetCaretPos(caretLine, caretCol + 1);
876 }
877 else
878 {
879 if (caretLine < lines.Count())
880 {
881 SetCaretPos(caretLine + 1, 0);
882 xOffset = 0;
883 }
884 }
885 SetXOffset();
886 SetYOffset();
887 SetCursorPos();
888 if (extendSelection)
889 {
890 Size sz = GetSize();
891 selectionEndExtensionPoint = ScreenPoint(sz.w, caretLine + 1);
892 selection.SetEnd(caretLine, caretCol);
893 Rect selectionUpdateRect = ScreenRect(selectionStartExtensionPoint, selectionEndExtensionPoint);
894 if (invalidate)
895 {
896 Invalidate(selectionUpdateRect);
897 }
898 }
899 }
900 private void CursorUp(bool extendSelection)
901 {
902 Point selectionStartExtensionPoint;
903 Point selectionEndExtensionPoint;
904 if (caretLine > 0)
905 {
906 if (extendSelection)
907 {
908 if (selection.IsEmpty())
909 {
910 selection.SetStart(caretLine, caretCol);
911 }
912 selectionStartExtensionPoint = ScreenPoint(0, caretLine);
913 }
914 else
915 {
916 ResetSelection();
917 }
918 SetCaretPos(caretLine - 1, Min(caretCol, cast<int>(line.Length())));
919 ustring& line = lines[caretLine];
920 SetCursorPos();
921 }
922 if (caretLine < yOffset)
923 {
924 --yOffset;
925 }
926 if (extendSelection)
927 {
928 Size sz = GetSize();
929 selectionEndExtensionPoint = ScreenPoint(sz.w, caretLine - 1);
930 selection.SetEnd(caretLine, caretCol);
931 Rect selectionUpdateRect = ScreenRect(selectionStartExtensionPoint, selectionEndExtensionPoint);
932 Invalidate(selectionUpdateRect);
933 }
934 }
935 private void CursorDown(bool extendSelection)
936 {
937 Point selectionStartExtensionPoint;
938 Point selectionEndExtensionPoint;
939 if (caretLine < lines.Count())
940 {
941 if (extendSelection)
942 {
943 if (selection.IsEmpty())
944 {
945 selection.SetStart(caretLine, caretCol);
946 }
947 selectionStartExtensionPoint = ScreenPoint(0, caretLine);
948 }
949 else
950 {
951 ResetSelection();
952 }
953 SetCaretPos(caretLine + 1, caretCol);
954 SetYOffset();
955 if (caretLine < lines.Count())
956 {
957 ustring& line = lines[caretLine];
958 SetCaretPos(caretLine, Min(caretCol, cast<int>(line.Length())));
959 SetCursorPos();
960 }
961 else
962 {
963 CursorEndOfText(extendSelection);
964 }
965 }
966 if (extendSelection)
967 {
968 Size sz = GetSize();
969 selectionEndExtensionPoint = ScreenPoint(sz.w, caretLine + 1);
970 selection.SetEnd(caretLine, caretCol);
971 Rect selectionUpdateRect = ScreenRect(selectionStartExtensionPoint, selectionEndExtensionPoint);
972 Invalidate(selectionUpdateRect);
973 }
974 }
975 private void CursorPrevWord(bool extendSelection)
976 {
977 Point selectionStartExtensionPoint;
978 Point selectionEndExtensionPoint;
979 if (extendSelection)
980 {
981 if (selection.IsEmpty())
982 {
983 selection.SetStart(caretLine, caretCol);
984 }
985 selectionStartExtensionPoint = ScreenPoint(0, caretLine);
986 }
987 else
988 {
989 ResetSelection();
990 }
991 CursorLeft(extendSelection, false);
992 uchar c = CharAtCursor();
993 while ((caretCol != 0 || caretLine != 0) && c == ' ')
994 {
995 CursorLeft(extendSelection, false);
996 c = CharAtCursor();
997 }
998 while ((caretCol != 0 || caretLine != 0) && c != ' ' && c != '\0')
999 {
1000 CursorLeft(extendSelection, false);
1001 c = CharAtCursor();
1002 }
1003 if ((caretCol != 0 || caretLine != 0) && c == ' ')
1004 {
1005 CursorRight(extendSelection, false);
1006 }
1007 if (extendSelection)
1008 {
1009 Size sz = GetSize();
1010 selectionEndExtensionPoint = ScreenPoint(sz.w, caretLine + 1);
1011 selection.SetEnd(caretLine, caretCol);
1012 Rect selectionUpdateRect = ScreenRect(selectionStartExtensionPoint, selectionEndExtensionPoint);
1013 Invalidate(selectionUpdateRect);
1014 }
1015 }
1016 private void CursorNextWord(bool extendSelection)
1017 {
1018 Point selectionStartExtensionPoint;
1019 Point selectionEndExtensionPoint;
1020 if (extendSelection)
1021 {
1022 if (selection.IsEmpty())
1023 {
1024 selection.SetStart(caretLine, caretCol);
1025 }
1026 selectionStartExtensionPoint = ScreenPoint(0, caretLine);
1027 }
1028 else
1029 {
1030 ResetSelection();
1031 }
1032 CursorRight(extendSelection, false);
1033 uchar c = CharAtCursor();
1034 while (caretLine < lines.Count() && c != ' ' && c != '\0')
1035 {
1036 CursorRight(extendSelection, false);
1037 c = CharAtCursor();
1038 }
1039 while (caretLine < lines.Count() && c == ' ')
1040 {
1041 CursorRight(extendSelection, false);
1042 c = CharAtCursor();
1043 }
1044 if (extendSelection)
1045 {
1046 Size sz = GetSize();
1047 selectionEndExtensionPoint = ScreenPoint(sz.w, caretLine + 1);
1048 selection.SetEnd(caretLine, caretCol);
1049 Rect selectionUpdateRect = ScreenRect(selectionStartExtensionPoint, selectionEndExtensionPoint);
1050 Invalidate(selectionUpdateRect);
1051 }
1052 }
1053 private void CursorStartOfLine(bool extendSelection)
1054 {
1055 Point selectionStartExtensionPoint;
1056 Point selectionEndExtensionPoint;
1057 if (extendSelection)
1058 {
1059 if (selection.IsEmpty())
1060 {
1061 selection.SetStart(caretLine, caretCol);
1062 }
1063 selectionStartExtensionPoint = ScreenPoint(0, caretLine);
1064 }
1065 else
1066 {
1067 ResetSelection();
1068 }
1069 SetCaretPos(caretLine, 0);
1070 xOffset = 0;
1071 SetCursorPos();
1072 if (extendSelection)
1073 {
1074 Size sz = GetSize();
1075 selectionEndExtensionPoint = ScreenPoint(sz.w, caretLine + 1);
1076 selection.SetEnd(caretLine, caretCol);
1077 Rect selectionUpdateRect = ScreenRect(selectionStartExtensionPoint, selectionEndExtensionPoint);
1078 Invalidate(selectionUpdateRect);
1079 }
1080 }
1081 private void CursorEndOfLine(bool extendSelection)
1082 {
1083 Point selectionStartExtensionPoint;
1084 Point selectionEndExtensionPoint;
1085 if (caretLine < lines.Count())
1086 {
1087 if (extendSelection)
1088 {
1089 if (selection.IsEmpty())
1090 {
1091 selection.SetStart(caretLine, caretCol);
1092 }
1093 selectionStartExtensionPoint = ScreenPoint(0, caretLine);
1094 }
1095 else
1096 {
1097 ResetSelection();
1098 }
1099 ustring& line = lines[caretLine];
1100 SetCaretPos(caretLine, cast<int>(line.Length()));
1101 SetXOffset();
1102 SetCursorPos();
1103 if (extendSelection)
1104 {
1105 Size sz = GetSize();
1106 selectionEndExtensionPoint = ScreenPoint(sz.w, caretLine + 1);
1107 selection.SetEnd(caretLine, caretCol);
1108 Rect selectionUpdateRect = ScreenRect(selectionStartExtensionPoint, selectionEndExtensionPoint);
1109 Invalidate(selectionUpdateRect);
1110 }
1111 }
1112 }
1113 private void Newline(Rect& updateRect)
1114 {
1115 updateRect.location = ScreenPoint(0, caretLine);
1116 while (caretLine >= lines.Count())
1117 {
1118 lines.Add(ustring());
1119 }
1120 ustring& line = lines[caretLine];
1121 int numSpaces = 0;
1122 for (uchar c : line)
1123 {
1124 if (c == ' ')
1125 {
1126 ++numSpaces;
1127 }
1128 else
1129 {
1130 break;
1131 }
1132 }
1133 if (caretCol < line.Length())
1134 {
1135 ustring splitLine = ustring(' ', numSpaces) + line.Substring(caretCol);
1136 line.Remove(caretCol, line.Length() - caretCol);
1137 if (caretLine < lines.Count())
1138 {
1139 lines.Insert(lines.Begin() + caretLine + 1, Rvalue(splitLine));
1140 }
1141 else
1142 {
1143 lines.Add(Rvalue(splitLine));
1144 }
1145 }
1146 else
1147 {
1148 if (caretLine < lines.Count())
1149 {
1150 lines.Insert(lines.Begin() + caretLine + 1, ustring(' ', numSpaces));
1151 }
1152 else
1153 {
1154 lines.Add(ustring(' ', numSpaces));
1155 }
1156 }
1157 Size sz = GetSize();
1158 updateRect.size.w = sz.w + xOffset;
1159 updateRect.size.h = sz.h - (caretLine - yOffset);
1160 SetCaretPos(caretLine + 1, numSpaces);
1161 xOffset = 0;
1162 SetYOffset();
1163 SetDirty();
1164 }
1165 private void Tab(Rect& updateRect)
1166 {
1167 if (!selection.IsEmpty())
1168 {
1169 IndentSelection(updateRect);
1170 }
1171 else
1172 {
1173 while (caretLine >= lines.Count())
1174 {
1175 lines.Add(ustring());
1176 }
1177 ustring& line = lines[caretLine];
1178 int numSpaces = tabSize - (caretCol % tabSize);
1179 if (caretCol < line.Length())
1180 {
1181 line.Insert(caretCol, ustring(' ', numSpaces));
1182 }
1183 else
1184 {
1185 line.Append(ustring(' ', numSpaces));
1186 }
1187 Point start = ScreenPoint(caretCol, caretLine);
1188 SetCaretPos(caretLine, caretCol + numSpaces);
1189 Point end = ScreenPoint(cast<int>(line.Length()), caretLine + 1);
1190 updateRect = ScreenRect(start, end);
1191 SetXOffset();
1192 SetYOffset();
1193 SetDirty();
1194 }
1195 }
1196 private void Untab(Rect& updateRect)
1197 {
1198 if (!selection.IsEmpty())
1199 {
1200 UnindentSelection(updateRect);
1201 }
1202 else
1203 {
1204 if (caretLine < lines.Count())
1205 {
1206 if (caretCol >= tabSize)
1207 {
1208 ustring& line = lines[caretLine];
1209 int numSpaces = tabSize - (caretCol % tabSize);
1210 Point start = ScreenPoint(0, caretLine);
1211 Point end = ScreenPoint(cast<int>(line.Length()), caretLine + 1);
1212 updateRect = ScreenRect(start, end);
1213 line.Remove(caretCol - numSpaces, numSpaces);
1214 SetCaretPos(caretLine, caretCol - numSpaces);
1215 SetXOffset();
1216 SetYOffset();
1217 SetCursorPos();
1218 SetDirty();
1219 }
1220 }
1221 }
1222 }
1223 private void IndentSelection(Rect& updateRect)
1224 {
1225 MakeCanonicalSelection();
1226 ustring spaces(' ', tabSize);
1227 for (int i = canonicalSelection.startLine; i < canonicalSelection.endLine; ++i;)
1228 {
1229 ustring& line = lines[i];
1230 line.Insert(0, spaces);
1231 }
1232 Point start = ScreenPoint(0, canonicalSelection.startLine);
1233 Size sz = GetSize();
1234 Point end = ScreenPoint(sz.w, canonicalSelection.endLine);
1235 updateRect = ScreenRect(start, end);
1236 SetDirty();
1237 }
1238 private void UnindentSelection(Rect& updateRect)
1239 {
1240 MakeCanonicalSelection();
1241 ustring spaces(' ', tabSize);
1242 for (int i = canonicalSelection.startLine; i < canonicalSelection.endLine; ++i;)
1243 {
1244 ustring& line = lines[i];
1245 if (line.StartsWith(spaces))
1246 {
1247 line.Remove(0, tabSize);
1248 }
1249 }
1250 Point start = ScreenPoint(0, canonicalSelection.startLine);
1251 Size sz = GetSize();
1252 Point end = ScreenPoint(sz.w, canonicalSelection.endLine);
1253 updateRect = ScreenRect(start, end);
1254 SetDirty();
1255 }
1256 private void DeleteChar(Rect& updateRect)
1257 {
1258 if (caretLine >= lines.Count())
1259 {
1260 return;
1261 }
1262 ustring& line = lines[caretLine];
1263 if (caretCol < line.Length())
1264 {
1265 line.Remove(caretCol, 1);
1266 Point start = ScreenPoint(caretCol, caretLine);
1267 Point end = ScreenPoint(cast<int>(line.Length() + 1), caretLine + 1);
1268 updateRect = ScreenRect(start, end);
1269 }
1270 else if (caretLine < lines.Count() - 1)
1271 {
1272 line.Append(lines[caretLine + 1]);
1273 TrimEnd(line);
1274 lines.Remove(lines.Begin() + caretLine + 1);
1275 updateRect.location = ScreenPoint(0, caretLine);
1276 Size sz = GetSize();
1277 updateRect.size.w = sz.w + xOffset;
1278 updateRect.size.h = sz.h - (caretLine - yOffset);
1279 }
1280 if (caretLine < yOffset)
1281 {
1282 --yOffset;
1283 }
1284 SetXOffset();
1285 SetYOffset();
1286 SetCursorPos();
1287 SetDirty();
1288 }
1289 private void Backspace(Rect& updateRect)
1290 {
1291 if (caretCol != 0 || caretLine != 0)
1292 {
1293 CursorLeft(false, true);
1294 DeleteChar(updateRect);
1295 }
1296 }
1297 private void DeleteSelection()
1298 {
1299 if (selection.IsEmpty()) return;
1300 MakeCanonicalSelection();
1301 if (canonicalSelection.startLine == canonicalSelection.endLine)
1302 {
1303 ustring& line = lines[canonicalSelection.startLine];
1304 line.Remove(canonicalSelection.startCol, canonicalSelection.endCol - canonicalSelection.startCol);
1305 SetCaretPos(caretLine, canonicalSelection.startCol);
1306 Point start = ScreenPoint(0, canonicalSelection.startLine);
1307 Size sz = GetSize();
1308 Point end = ScreenPoint(sz.w, canonicalSelection.startLine + 1);
1309 Rect selectionUpdateRect = ScreenRect(start, end);
1310 Invalidate(selectionUpdateRect);
1311 SetCursorPos();
1312 selection.Reset();
1313 }
1314 else
1315 {
1316 int startLine = canonicalSelection.startLine;
1317 int numLines = Max(cast<int>(0), canonicalSelection.endLine - startLine);
1318 if (numLines > 0)
1319 {
1320 if (canonicalSelection.startCol != 0)
1321 {
1322 ustring& line = lines[startLine];
1323 line.Remove(canonicalSelection.startCol, line.Length() - canonicalSelection.startCol);
1324 ++startLine;
1325 --numLines;
1326 }
1327 }
1328 while (numLines > 1)
1329 {
1330 lines.Remove(lines.Begin() + startLine);
1331 --numLines;
1332 }
1333 if (numLines > 0)
1334 {
1335 if (canonicalSelection.endCol != 0)
1336 {
1337 ustring& line = lines[startLine];
1338 line.Remove(0, canonicalSelection.endCol);
1339 }
1340 else
1341 {
1342 lines.Remove(lines.Begin() + startLine);
1343 }
1344 }
1345 SetCaretPos(canonicalSelection.startLine, canonicalSelection.startCol);
1346 SetXOffset();
1347 SetYOffset();
1348 SetCursorPos();
1349 Invalidate(GetRect());
1350 SetDirty();
1351 selection.Reset();
1352 }
1353 }
1354 private void TrimEnd(ustring& line)
1355 {
1356 while (!line.IsEmpty() && line[line.Length() - 1] == ' ')
1357 {
1358 line.Remove(line.Length() - 1, 1);
1359 }
1360 }
1361 private Point ScreenPoint(int x, int y)
1362 {
1363 Point loc = Location();
1364 int sx = loc.x + x - xOffset;
1365 int sy = loc.y + y - yOffset;
1366 return Point(sx, sy);
1367 }
1368 private Rect ScreenRect(const Point& start, const Point& end)
1369 {
1370 int minX = Min(start.x, end.x);
1371 int minY = Min(start.y, end.y);
1372 int maxX = Max(start.x, end.x);
1373 int maxY = Max(start.y, end.y);
1374 Point loc(minX, minY);
1375 Size sz(maxX - minX, maxY - minY);
1376 return Rect(loc, sz);
1377 }
1378 private void LineCol(int x, int y, int& line, int& col)
1379 {
1380 Point loc = Location();
1381 line = yOffset + y - loc.y;
1382 col = xOffset + x - loc.x;
1383 }
1384 private uchar CharAt(int x, int y)
1385 {
1386 Point loc = Location();
1387 int line = yOffset + y - loc.y;
1388 int col = xOffset + x - loc.x;
1389 if (line < lines.Count())
1390 {
1391 const ustring& ln = lines[line];
1392 if (col < ln.Length())
1393 {
1394 return ln[col];
1395 }
1396 }
1397 return uchar('\0');
1398 }
1399 private uchar CharAtCursor()
1400 {
1401 if (caretLine < lines.Count())
1402 {
1403 const ustring& line = lines[caretLine];
1404 if (caretCol < line.Length())
1405 {
1406 return line[caretCol];
1407 }
1408 }
1409 return uchar('\0');
1410 }
1411 private void MakeCanonicalSelection()
1412 {
1413 canonicalSelection = selection;
1414 if (canonicalSelection.startLine > canonicalSelection.endLine ||
1415 canonicalSelection.startLine == canonicalSelection.endLine &&
1416 canonicalSelection.startCol > canonicalSelection.endCol)
1417 {
1418 Swap(canonicalSelection.startLine, canonicalSelection.endLine);
1419 Swap(canonicalSelection.startCol, canonicalSelection.endCol);
1420 }
1421 }
1422 private bool LineColInSelection(int line, int col)
1423 {
1424 if (line > canonicalSelection.startLine && line < canonicalSelection.endLine)
1425 {
1426 return true;
1427 }
1428 else if (line == canonicalSelection.startLine)
1429 {
1430 if (canonicalSelection.endLine == canonicalSelection.startLine)
1431 {
1432 if (col >= canonicalSelection.startCol && col < canonicalSelection.endCol)
1433 {
1434 return true;
1435 }
1436 }
1437 else if (col >= canonicalSelection.startCol)
1438 {
1439 return true;
1440 }
1441 }
1442 else if (line == canonicalSelection.endLine)
1443 {
1444 if (col < canonicalSelection.endCol)
1445 {
1446 return true;
1447 }
1448 }
1449 return false;
1450 }
1451 private void ResetSelection()
1452 {
1453 if (selection.IsEmpty())
1454 {
1455 return;
1456 }
1457 MakeCanonicalSelection();
1458 Point start = ScreenPoint(0, canonicalSelection.startLine);
1459 Size sz = GetSize();
1460 Point end = ScreenPoint(sz.w, canonicalSelection.endLine + 1);
1461 Rect updateRect = ScreenRect(start, end);
1462 Invalidate(updateRect);
1463 selection.Reset();
1464 }
1465 private EditorFlags flags;
1466 private int xOffset;
1467 private int yOffset;
1468 private List<ustring> lines;
1469 private string filePath;
1470 private int caretLine;
1471 private int caretCol;
1472 private string statusText;
1473 private Selection selection;
1474 private Selection canonicalSelection;
1475 private Rect selectionScreenRect;
1476 private ConsoleColor selectionForeColor;
1477 private ConsoleColor selectionBackColor;
1478 private Event<ChangedEventHandler> filePathChangedEvent;
1479 private Event<ChangedEventHandler> caretPosChangedEvent;
1480 private Event<ChangedEventHandler> statusTextChangedEvent;
1481 private Event<ChangedEventHandler> dirtyChangedEvent;
1482 }
1483 }
1484