1 // =================================
   2 // Copyright (c) 2022 Seppo Laakko
   3 // Distributed under the MIT license
   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 = 0dirty = 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(thisInvalidateKind.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(01));
 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() + caretLineline);
 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(caretColcaretLine);
 349             if (cp.x >= 0 && cp.x < TerminalWindowWidth() && cp.y >= 0 && cp.y < TerminalWindowHeight())
 350             {
 351                 SetControlCursorPos(cp);
 352                 SetCursorPos(cp.xcp.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(updateRectargs.GetRect());
 362             }
 363             if (updateRect.IsEmpty()) return;
 364             Clear(updateRectForeColor()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(caretColkey);
 396                     inserted = true;
 397                 }
 398                 else
 399                 {
 400                     line.Append(key);
 401                 }
 402                 Point start = ScreenPoint(caretColcaretLine);
 403                 SetCaretPos(caretLinecaretCol + 1);
 404                 Point end = ScreenPoint(caretColcaretLine + 1);
 405                 if (inserted)
 406                 {
 407                     end = ScreenPoint(cast<int>(line.Length())caretLine + 1);
 408                 }
 409                 updateRect = ScreenRect(startend);
 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(falsetrue);
 442                             break;
 443                         }
 444                         case keyRight:
 445                         {
 446                             args.SetHandled();
 447                             CursorRight(falsetrue);
 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(truetrue);
 533                             break;
 534                         }
 535                         case keyShiftRight:
 536                         {
 537                             args.SetHandled();
 538                             CursorRight(truetrue);
 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 + xupdateRect.location.y + y);
 624                     if (c == '\0')
 625                     {
 626                         break;
 627                     }
 628                     updateLine.Append(c);
 629                 }
 630                 SetCursorPos(updateRect.location.xupdateRect.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 + xupdateRect.location.y + y);
 642                     if (c != '\0')
 643                     {
 644                         int line = 0;
 645                         int col = 0;
 646                         LineCol(updateRect.location.x + xupdateRect.location.y + ylinecol);
 647                         if (LineColInSelection(linecol))
 648                         {
 649                             Terminal.Out() << SetColors(selectionForeColorselectionBackColor);
 650                         }
 651                         else
 652                         {
 653                             Terminal.Out() << SetColors(ForeColor()BackColor());
 654                         }
 655                         SetCursorPos(updateRect.location.x + xupdateRect.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(caretLinecaretCol);
 686                 }
 687                 selectionStartExtensionPoint = ScreenPoint(0caretLine);
 688             }
 689             else
 690             {
 691                 ResetSelection();
 692             }
 693             SetCaretPos(00);
 694             xOffset = 0;
 695             yOffset = 0;
 696             SetCursorPos();
 697             if (extendSelection)
 698             {
 699                 Size sz = GetSize();
 700                 selectionEndExtensionPoint = ScreenPoint(sz.w0);
 701                 selection.SetEnd(caretLinecaretCol);
 702                 Rect selectionUpdateRect = ScreenRect(selectionStartExtensionPointselectionEndExtensionPoint);
 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(caretLinecaretCol);
 715                 }
 716                 selectionStartExtensionPoint = ScreenPoint(0caretLine);
 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.wcaretLine + 1);
 730                 selection.SetEnd(caretLinecaretCol);
 731                 Rect selectionUpdateRect = ScreenRect(selectionStartExtensionPointselectionEndExtensionPoint);
 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(caretLinecaretCol);
 744                 }
 745                 selectionStartExtensionPoint = ScreenPoint(0caretLine);
 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.h0);
 756                 SetCursorPos();
 757             }
 758             else
 759             {
 760                 CursorStartOfText(extendSelection);
 761             }
 762             if (extendSelection)
 763             {
 764                 selectionEndExtensionPoint = ScreenPoint(sz.wcaretLine - sz.h);
 765                 selection.SetEnd(caretLinecaretCol);
 766                 Rect selectionUpdateRect = ScreenRect(selectionStartExtensionPointselectionEndExtensionPoint);
 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(caretLinecaretCol);
 779                 }
 780                 selectionStartExtensionPoint = ScreenPoint(0caretLine);
 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.h0);
 794                 yOffset = yOffset + sz.h;
 795                 SetCursorPos();
 796             }
 797             if (extendSelection)
 798             {
 799                 selectionEndExtensionPoint = ScreenPoint(sz.wcaretLine + sz.h);
 800                 selection.SetEnd(caretLinecaretCol);
 801                 Rect selectionUpdateRect = ScreenRect(selectionStartExtensionPointselectionEndExtensionPoint);
 802                 Invalidate(selectionUpdateRect);
 803             }
 804         }
 805         private void CursorLeft(bool extendSelectionbool invalidate)
 806         {
 807             Point selectionStartExtensionPoint;
 808             Point selectionEndExtensionPoint;
 809             if (extendSelection)
 810             {
 811                 if (selection.IsEmpty())
 812                 {
 813                     selection.SetStart(caretLinecaretCol);
 814                 }
 815                 selectionStartExtensionPoint = ScreenPoint(0caretLine);
 816             }
 817             else
 818             {
 819                 ResetSelection();
 820             }
 821             if (caretCol > 0)
 822             {
 823                 SetCaretPos(caretLinecaretCol - 1);
 824                 SetCursorPos();
 825             }
 826             else
 827             {
 828                 if (caretLine > 0)
 829                 {
 830                     ustring& line = lines[caretLine - 1];
 831                     SetCaretPos(caretLine - 1cast<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.wcaretLine + 1);
 844                 selection.SetEnd(caretLinecaretCol);
 845                 Rect selectionUpdateRect = ScreenRect(selectionStartExtensionPointselectionEndExtensionPoint);
 846                 if (invalidate)
 847                 {
 848                     Invalidate(selectionUpdateRect);
 849                 }
 850             }
 851         }
 852         private void CursorRight(bool extendSelectionbool 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(caretLinecaretCol);
 865                 }
 866                 selectionStartExtensionPoint = ScreenPoint(0caretLine);
 867             }
 868             else
 869             {
 870                 ResetSelection();
 871             }
 872             ustring& line = lines[caretLine];
 873             if (caretCol < line.Length())
 874             {
 875                 SetCaretPos(caretLinecaretCol + 1);
 876             }
 877             else
 878             {
 879                 if (caretLine < lines.Count())
 880                 {
 881                     SetCaretPos(caretLine + 10);
 882                     xOffset = 0;
 883                 }
 884             }
 885             SetXOffset();
 886             SetYOffset();
 887             SetCursorPos();
 888             if (extendSelection)
 889             {
 890                 Size sz = GetSize();
 891                 selectionEndExtensionPoint = ScreenPoint(sz.wcaretLine + 1);
 892                 selection.SetEnd(caretLinecaretCol);
 893                 Rect selectionUpdateRect = ScreenRect(selectionStartExtensionPointselectionEndExtensionPoint);
 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(caretLinecaretCol);
 911                     }
 912                     selectionStartExtensionPoint = ScreenPoint(0caretLine);
 913                 }
 914                 else
 915                 {
 916                     ResetSelection();
 917                 }
 918                 SetCaretPos(caretLine - 1Min(caretColcast<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.wcaretLine - 1);
 930                 selection.SetEnd(caretLinecaretCol);
 931                 Rect selectionUpdateRect = ScreenRect(selectionStartExtensionPointselectionEndExtensionPoint);
 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(caretLinecaretCol);
 946                     }
 947                     selectionStartExtensionPoint = ScreenPoint(0caretLine);
 948                 }
 949                 else
 950                 {
 951                     ResetSelection();
 952                 }
 953                 SetCaretPos(caretLine + 1caretCol);
 954                 SetYOffset();
 955                 if (caretLine < lines.Count())
 956                 {
 957                     ustring& line = lines[caretLine];
 958                     SetCaretPos(caretLineMin(caretColcast<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.wcaretLine + 1);
 970                 selection.SetEnd(caretLinecaretCol);
 971                 Rect selectionUpdateRect = ScreenRect(selectionStartExtensionPointselectionEndExtensionPoint);
 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(caretLinecaretCol);
 984                 }
 985                 selectionStartExtensionPoint = ScreenPoint(0caretLine);
 986             }
 987             else
 988             {
 989                 ResetSelection();
 990             }
 991             CursorLeft(extendSelectionfalse);
 992             uchar c = CharAtCursor();
 993             while ((caretCol != 0 || caretLine != 0) && c == ' ')
 994             {
 995                 CursorLeft(extendSelectionfalse);
 996                 c = CharAtCursor();
 997             }
 998             while ((caretCol != 0 || caretLine != 0) && c != ' ' && c != '\0')
 999             {
1000                 CursorLeft(extendSelectionfalse);
1001                 c = CharAtCursor();
1002             }
1003             if ((caretCol != 0 || caretLine != 0) && c == ' ')
1004             {
1005                 CursorRight(extendSelectionfalse);
1006             }
1007             if (extendSelection)
1008             {
1009                 Size sz = GetSize();
1010                 selectionEndExtensionPoint = ScreenPoint(sz.wcaretLine + 1);
1011                 selection.SetEnd(caretLinecaretCol);
1012                 Rect selectionUpdateRect = ScreenRect(selectionStartExtensionPointselectionEndExtensionPoint);
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(caretLinecaretCol);
1025                 }
1026                 selectionStartExtensionPoint = ScreenPoint(0caretLine);
1027             }
1028             else
1029             {
1030                 ResetSelection();
1031             }
1032             CursorRight(extendSelectionfalse);
1033             uchar c = CharAtCursor();
1034             while (caretLine < lines.Count() && c != ' ' && c != '\0')
1035             {
1036                 CursorRight(extendSelectionfalse);
1037                 c = CharAtCursor();
1038             }
1039             while (caretLine < lines.Count() && c == ' ')
1040             {
1041                 CursorRight(extendSelectionfalse);
1042                 c = CharAtCursor();
1043             }
1044             if (extendSelection)
1045             {
1046                 Size sz = GetSize();
1047                 selectionEndExtensionPoint = ScreenPoint(sz.wcaretLine + 1);
1048                 selection.SetEnd(caretLinecaretCol);
1049                 Rect selectionUpdateRect = ScreenRect(selectionStartExtensionPointselectionEndExtensionPoint);
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(caretLinecaretCol);
1062                 }
1063                 selectionStartExtensionPoint = ScreenPoint(0caretLine);
1064             }
1065             else
1066             {
1067                 ResetSelection();
1068             }
1069             SetCaretPos(caretLine0);
1070             xOffset = 0;
1071             SetCursorPos();
1072             if (extendSelection)
1073             {
1074                 Size sz = GetSize();
1075                 selectionEndExtensionPoint = ScreenPoint(sz.wcaretLine + 1);
1076                 selection.SetEnd(caretLinecaretCol);
1077                 Rect selectionUpdateRect = ScreenRect(selectionStartExtensionPointselectionEndExtensionPoint);
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(caretLinecaretCol);
1092                     }
1093                     selectionStartExtensionPoint = ScreenPoint(0caretLine);
1094                 }
1095                 else
1096                 {
1097                     ResetSelection();
1098                 }
1099                 ustring& line = lines[caretLine];
1100                 SetCaretPos(caretLinecast<int>(line.Length()));
1101                 SetXOffset();
1102                 SetCursorPos();
1103                 if (extendSelection)
1104                 {
1105                     Size sz = GetSize();
1106                     selectionEndExtensionPoint = ScreenPoint(sz.wcaretLine + 1);
1107                     selection.SetEnd(caretLinecaretCol);
1108                     Rect selectionUpdateRect = ScreenRect(selectionStartExtensionPointselectionEndExtensionPoint);
1109                     Invalidate(selectionUpdateRect);
1110                 }
1111             }
1112         }
1113         private void Newline(Rect& updateRect)
1114         {
1115             updateRect.location = ScreenPoint(0caretLine);
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(caretColline.Length() - caretCol);
1137                 if (caretLine < lines.Count())
1138                 {
1139                     lines.Insert(lines.Begin() + caretLine + 1Rvalue(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 + 1ustring(' '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 + 1numSpaces);
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(caretColustring(' 'numSpaces));
1182                 }
1183                 else
1184                 {
1185                     line.Append(ustring(' 'numSpaces));
1186                 }
1187                 Point start = ScreenPoint(caretColcaretLine);
1188                 SetCaretPos(caretLinecaretCol + numSpaces);
1189                 Point end = ScreenPoint(cast<int>(line.Length())caretLine + 1);
1190                 updateRect = ScreenRect(startend);
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(0caretLine);
1211                         Point end = ScreenPoint(cast<int>(line.Length())caretLine + 1);
1212                         updateRect = ScreenRect(startend);
1213                         line.Remove(caretCol - numSpacesnumSpaces);
1214                         SetCaretPos(caretLinecaretCol - 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(0spaces);
1231             }
1232             Point start = ScreenPoint(0canonicalSelection.startLine);
1233             Size sz = GetSize();
1234             Point end = ScreenPoint(sz.wcanonicalSelection.endLine);
1235             updateRect = ScreenRect(startend);
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(0tabSize);
1248                 }
1249             }
1250             Point start = ScreenPoint(0canonicalSelection.startLine);
1251             Size sz = GetSize();
1252             Point end = ScreenPoint(sz.wcanonicalSelection.endLine);
1253             updateRect = ScreenRect(startend);
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(caretCol1);
1266                 Point start = ScreenPoint(caretColcaretLine);
1267                 Point end = ScreenPoint(cast<int>(line.Length() + 1)caretLine + 1);
1268                 updateRect = ScreenRect(startend);
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(0caretLine);
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(falsetrue);
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.startColcanonicalSelection.endCol - canonicalSelection.startCol);
1305                 SetCaretPos(caretLinecanonicalSelection.startCol);
1306                 Point start = ScreenPoint(0canonicalSelection.startLine);
1307                 Size sz = GetSize();
1308                 Point end = ScreenPoint(sz.wcanonicalSelection.startLine + 1);
1309                 Rect selectionUpdateRect = ScreenRect(startend);
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.startColline.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(0canonicalSelection.endCol);
1339                     }
1340                     else
1341                     {
1342                         lines.Remove(lines.Begin() + startLine);
1343                     }
1344                 }
1345                 SetCaretPos(canonicalSelection.startLinecanonicalSelection.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() - 11);
1359             }
1360         }
1361         private Point ScreenPoint(int xint y)
1362         {
1363             Point loc = Location();
1364             int sx = loc.x + x - xOffset;
1365             int sy = loc.y + y - yOffset;
1366             return Point(sxsy);
1367         }
1368         private Rect ScreenRect(const Point& startconst Point& end)
1369         {
1370             int minX = Min(start.xend.x);
1371             int minY = Min(start.yend.y);
1372             int maxX = Max(start.xend.x);
1373             int maxY = Max(start.yend.y);
1374             Point loc(minXminY);
1375             Size sz(maxX - minXmaxY - minY);
1376             return Rect(locsz);
1377         }
1378         private void LineCol(int xint yint& lineint& col)
1379         {
1380             Point loc = Location();
1381             line = yOffset + y - loc.y;
1382             col = xOffset + x - loc.x;
1383         }
1384         private uchar CharAt(int xint 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.startLinecanonicalSelection.endLine);
1419                 Swap(canonicalSelection.startColcanonicalSelection.endCol);
1420             }
1421         }
1422         private bool LineColInSelection(int lineint 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(0canonicalSelection.startLine);
1459             Size sz = GetSize();
1460             Point end = ScreenPoint(sz.wcanonicalSelection.endLine + 1);
1461             Rect updateRect = ScreenRect(startend);
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