ecere/gui/EditBox: (#1034) Fixed 5w. being wrongly highlighted as a number
[sdk] / ecere / src / gui / controls / EditBox.ec
1 namespace gui::controls;
2
3 /*
4 selectionForeground = white;
5 disabled: defaultTextColor = Color { 85, 85, 85 };
6 */
7
8 import "Window"
9 import "ReplaceDialog"
10 import "FindDialog"
11 import "GoToDialog"
12 import "Array"
13
14 public class SyntaxColorScheme
15 {
16 public:
17    Color commentColor;
18    Color charLiteralColor;
19    Color stringLiteralColor;
20    Color preprocessorColor;
21    Color numberColor;
22    private Array<Color> keywordColors { };
23
24    public property Container<Color> keywordColors
25    {
26       set { keywordColors.Copy((void *)value); }
27       get { return keywordColors; }
28    }
29 };
30
31 #include <stdarg.h>
32
33 #define MAXWORDLEN   64
34
35 #define XOFFSET   (3 + (/*style.lineNumbers?6 * this.space.w:*/0))
36 // #define YOFFSET   1
37 #define YOFFSET   (style.multiLine ? 1 : ((clientSize.h + 1 - space.h) / 2))
38
39 #define IS_ALUNDER(ch) (/*(ch) == '_' || */CharMatchCategories((ch), letters|numbers|marks|connector))
40
41 #define UTF8_IS_FIRST(x)   (__extension__({ byte b = x; (!(b) || !((b) & 0x80) || ((b) & 0x40)); }))
42 #define UTF8_NUM_BYTES(x)  (__extension__({ byte b = x; (b & 0x80 && b & 0x40) ? ((b & 0x20) ? ((b & 0x10) ? 4 : 3) : 2) : 1; }))
43 #define UTF8_GET_CHAR(string, numBytes) \
44    __extension__( \
45    { \
46       unichar ch; \
47       byte b = (string)[0]; \
48       int i; \
49       byte mask = 0x7F; \
50       numBytes = b ? 1 : 0; \
51       ch = 0; \
52       if(b & 0x80) \
53       { \
54          if(b & 0x40) \
55          { \
56             mask >>= 2; \
57             numBytes++; \
58             if(b & 0x20) \
59             { \
60                numBytes++; \
61                mask >>= 1; \
62                if(b & 0x10) \
63                { \
64                   if(b & 0x08) { numBytes = 0; } \
65                   numBytes++; \
66                   mask >>= 1; \
67                } \
68             } \
69          } \
70          else \
71             numBytes = 0; \
72       } \
73       for(i = 0; i<numBytes; i++) \
74       { \
75          ch <<= 6; \
76          ch |= (b = (string)[i]) & mask; \
77          mask = 0x3F; \
78          if(i > 1 && (!(b & 0x80) || (b & 0x40))) \
79          { \
80             numBytes = 0; \
81             ch = 0; \
82          } \
83       } \
84       if(i < numBytes || \
85          ch > 0x10FFFF || (ch >= 0xD800 && ch <= 0xDFFF) || \
86         (ch < 0x80 && numBytes > 1) || \
87         (ch < 0x800 && numBytes > 2) || \
88         (ch < 0x10000 && numBytes > 3))\
89       { \
90          ch = 0; \
91          numBytes = 0; \
92       } \
93       ch; \
94    })
95
96 class EditBoxBits
97 {
98    bool autoEmpty:1, readOnly:1, multiLine:1, stuckCaret:1, freeCaret:1, select:1, hScroll:1, vScroll:1, smartHome:1;
99    bool noCaret:1, noSelect:1, tabKey:1, useTab:1, tabSel:1, allCaps:1, syntax:1, wrap:1;
100
101    // Syntax States
102    bool inMultiLineComment:1, inPrep:1, escaped:1, continuedSingleLineComment:1, wasInMultiLine:1;
103
104    bool recomputeSyntax:1;
105    bool cursorFollowsView:1;
106
107    // bool lineNumbers:1;
108    bool autoSize:1;
109 };
110
111 /* TODO:
112 void UnregisterClass_EditBox()
113 {
114    int g;
115    for(g = 0; g<NUM_KEYWORD_GROUPS; g++)
116    {
117       if(keyLen[g])
118          free(keyLen[g]);
119    }
120 }
121 */
122
123 default:
124 extern int __ecereVMethodID_class_OnFree;
125 private:
126
127 static class ArrayImpl
128 {
129    Class type;
130    uint size;
131    byte * array;
132 };
133
134 public class OldArray
135 {
136    ~OldArray()
137    {
138       int c;
139       void ** array = (void **)((ArrayImpl)this).array;
140       if(type.type == normalClass || type.type == noHeadClass)
141       {
142          for(c = 0; c<size; c++)
143             ((void (*)(void *, void *))(void *)type._vTbl[__ecereVMethodID_class_OnFree])(type, array[c]);
144       }
145       // TODO: Call OnFree for structClass
146       delete ((ArrayImpl)this).array;
147    }
148
149 public:
150    Class type;
151    property uint size
152    {
153       set
154       {
155          if(((ArrayImpl)this).array)
156          {
157             if(value == size)
158                return;
159             ((ArrayImpl)this).array = renew0 ((ArrayImpl)this).array byte[type.typeSize * value];
160          }
161          else if(value)
162             ((ArrayImpl)this).array = new0 byte[value * type.typeSize];
163          size = value;
164       }
165       get { return size; }
166    }
167    property void * data
168    {
169       set
170       {
171          memcpy(((ArrayImpl)this).array, value, type.typeSize * size);
172       }
173    }
174 private:
175    uint size;
176 };
177
178 public class UndoAction : struct
179 {
180 public:
181    subclass(UndoAction) type;
182    virtual void Undo(void * data) { type.Undo(this, data); }
183    virtual void Redo(void * data) { type.Redo(this, data); }
184 #ifdef _DEBUG
185    virtual void Print(void * data)  { type.Print(this, data); }
186 #endif
187    ~UndoAction()
188    {
189       if(((Class)type).Destructor)
190          ((void (*)(void *))((Class)type).Destructor)(this);
191    }
192 };
193
194 public class UndoBuffer
195 {
196    Array<UndoAction> actions { size = 8 };
197 public:
198    int count;
199    int curAction;
200    void * data;
201    int dontRecord;
202    bool insideRedo;
203
204    dontRecord = 0;
205
206    ~UndoBuffer()
207    {
208       actions.Free();
209    }
210
211    void Undo()
212    {
213       dontRecord++;
214       if(curAction > 0)
215       {
216          UndoAction action = actions[--curAction];
217 #ifdef _DEBUG
218          /*Print("Undoing: ");
219          action.Print(data);*/
220 #endif
221          action.Undo(data);
222       }
223       dontRecord--;
224    }
225
226    void Redo()
227    {
228       dontRecord++;
229       insideRedo = true;
230       if(curAction < count)
231       {
232          UndoAction action = actions[curAction];
233          curAction++;
234 #ifdef _DEBUG
235          /*Print("Redoing: ");
236          action.Print(data);*/
237 #endif
238          action.Redo(data);
239       }
240       insideRedo = false;
241       dontRecord--;
242    }
243
244    void Record(UndoAction action)
245    {
246       if(!dontRecord && !insideRedo)
247       {
248          if(curAction < count)
249          {
250             int c;
251             for(c = curAction; c < count; c++)
252                delete actions[c];
253          }
254
255          count = curAction;
256
257          if(count >= actions.size)
258             actions.size += actions.size / 2;
259
260 #ifdef _DEBUG
261          /*Print("Recording: ");
262          action.Print(data);*/
263 #endif
264          actions[count++] = action;
265          curAction = count;
266
267          if(actions.size > count + count / 2 && count + count / 2 >= 8)
268             actions.size = count + count / 2;
269       }
270       else
271          delete action;
272    }
273 };
274
275 static class AddCharAction : UndoAction
276 {
277    int y, x;
278    unichar ch;
279    int addedSpaces, addedTabs;
280    type = class(AddCharAction);
281
282    void Undo(EditBox editBox)
283    {
284       editBox.GoToPosition(null, (ch == '\n') ? (y + 1) : y, (ch == '\n') ? 0 : (x + 1));
285       editBox.BackSpace();
286       if(addedTabs || addedSpaces)
287          editBox.DelCh(editBox.line, y, x - (addedSpaces + addedTabs), editBox.line, y, x, false);
288       editBox.UpdateDirty();
289    }
290
291    void Redo(EditBox editBox)
292    {
293       editBox.GoToPosition(null, y, x);
294       editBox.PutCh(ch);
295       editBox.UpdateDirty();
296    }
297 #ifdef _DEBUG
298    void Print(EditBox editBox)
299    {
300       PrintLn("AddChar: y = ", y, "x = ", x, ", ch = ", ch, ", addedSpaces = ", addedSpaces, ", addedTabs = ", addedTabs);
301    }
302 #endif
303 };
304
305 static class AddTextAction : UndoAction
306 {
307    int y1, x1, y2, x2;
308    char * string;
309    int addedSpaces, addedTabs;
310    type = class(AddTextAction);
311
312 #ifdef _DEBUG
313    void Print(EditBox editBox)
314    {
315       PrintLn("AddText: y1 = ", y1, "x1 = ", x1, ", y2 = ", y2, ", x2 = ", x2, ", string = ", string, ", addedSpaces = ", addedSpaces, ", addedTabs = ", addedTabs);
316    }
317 #endif
318    ~AddTextAction()
319    {
320       delete string;
321    }
322
323    void Undo(EditBox editBox)
324    {
325       EditLine l1, l2;
326       int c;
327
328       editBox.GoToPosition(null, y1, x1);
329       l1 = editBox.line;
330       l2 = editBox.lines.first;
331       for(c = 0; c < y2 && l2; c++, l2 = l2.next);
332
333       editBox.DelCh(l1, y1, x1, l2, y2, x2, true);
334       if(addedTabs || addedSpaces)
335          editBox.DelCh(editBox.line, y1, x1 - (addedSpaces + addedTabs), editBox.line, y1, x1, false);
336
337       editBox.SetViewToCursor(true);
338       editBox.Modified();
339    }
340
341    void Redo(EditBox editBox)
342    {
343       editBox.GoToPosition(null, y1, x1);
344       editBox.PutS(string);
345    }
346 };
347
348 static class DelTextAction : UndoAction
349 {
350    int y1, x1, y2, x2;
351    char * string;
352    bool placeAfter;
353    int addedSpaces;
354    type = class(DelTextAction);
355
356 #ifdef _DEBUG
357    void Print(EditBox editBox)
358    {
359       PrintLn("DelText: y1 = ", y1, "x1 = ", x1, ", y2 = ", y2, ", x2 = ", x2, ", string = ", string, ", addedSpaces = ", addedSpaces, ", placeAfter = ", placeAfter);
360    }
361 #endif
362    void Undo(EditBox editBox)
363    {
364       editBox.GoToPosition(null, y1, x1);
365       editBox.PutS(string);
366
367       if(!placeAfter)
368       {
369          editBox.GoToPosition(null, y1, x1);
370          editBox.selY = y2;
371          editBox.selX = x2;
372          { int c; editBox.selLine = editBox.lines.first; for(c = 0; c < editBox.selY && editBox.selLine; c++, editBox.selLine = editBox.selLine.next); }
373          //editBox.SetViewToCursor(true);
374
375          if(addedSpaces)
376             editBox.DelCh(editBox.line, y1, x1 - addedSpaces, editBox.line, y1, x1, false);
377       }
378       else
379       {
380          editBox.selY = y1;
381          editBox.selX = x1;
382          { int c; editBox.selLine = editBox.lines.first; for(c = 0; c < editBox.selY && editBox.selLine; c++, editBox.selLine = editBox.selLine.next); }
383          //editBox.SetViewToCursor(true);
384
385          if(addedSpaces)
386             editBox.DelCh(editBox.selLine, y1, x1 - addedSpaces, editBox.selLine, y1, x1, false);
387       }
388    }
389
390    void Redo(EditBox editBox)
391    {
392       EditLine l1, l2;
393       int c;
394
395       editBox.GoToPosition(null, y1, x1);
396       l1 = editBox.line;
397
398       l2 = editBox.lines.first;
399       for(c = 0; c < y2 && l2; c++, l2 = l2.next);
400
401       editBox.DelCh(l1, y1, x1, l2, y2, x2, true);
402       editBox.SetViewToCursor(true);
403       editBox.Modified();
404    }
405
406    ~DelTextAction()
407    {
408       delete string;
409    }
410 };
411
412 static class ReplaceTextAction : UndoAction
413 {
414    int y1, x1, y2, x2, y3, x3;
415    char * oldString;
416    char * newString;
417    bool placeAfter;
418    int addedSpaces, addedTabs;
419
420    type = class(ReplaceTextAction);
421
422 #ifdef _DEBUG
423    void Print(EditBox editBox)
424    {
425       PrintLn("ReplaceText: y1 = ", y1, "x1 = ", x1, ", y2 = ", y2, ", x2 = ", x2, ", y3 = ", y3, ", x3 = ", x3, ", oldString = ", oldString, ", newString = ", newString, ", addedSpaces = ", addedSpaces, ", addedTabs = ", addedTabs, ", placeAfter = ", placeAfter);
426    }
427 #endif
428    void Undo(EditBox editBox)
429    {
430       EditLine l1, l3;
431       int c;
432
433       editBox.GoToPosition(null, y1, x1);
434       l1 = editBox.line;
435       l3 = editBox.lines.first;
436       for(c = 0; c < y3 && l3; c++, l3 = l3.next);
437
438       editBox.DelCh(l1, y1, x1, l3, y3, x3, true);
439
440       editBox.PutS(oldString);
441       if(addedSpaces || addedTabs)
442          editBox.DelCh(l1, y1, x1 - (addedSpaces + addedTabs), l1, y1, x1, false);
443
444       //editBox.PutS(oldString);
445       if(!placeAfter)
446       {
447          editBox.GoToPosition(null, y1, x1);
448          editBox.selY = y2;
449          editBox.selX = x2;
450          { int c; editBox.selLine = editBox.lines.first; for(c = 0; c < editBox.selY && editBox.selLine; c++, editBox.selLine = editBox.selLine.next); }
451       }
452       else
453       {
454          editBox.selY = y1;
455          editBox.selX = x1;
456          { int c; editBox.selLine = editBox.lines.first; for(c = 0; c < editBox.selY && editBox.selLine; c++, editBox.selLine = editBox.selLine.next); }
457       }
458    }
459
460    void Redo(EditBox editBox)
461    {
462       EditLine l1, l2;
463       int c;
464
465       editBox.GoToPosition(null, y1, x1);
466       l1 = editBox.line;
467       l2 = editBox.lines.first;
468       for(c = 0; c < y2 && l2; c++, l2 = l2.next);
469
470       editBox.DelCh(l1, y1, x1, l2, y2, x2, true);
471
472       editBox.PutS(newString);
473    }
474
475    ~ReplaceTextAction()
476    {
477       delete oldString;
478       delete newString;
479    }
480 };
481 /*
482 static class MoveTextAction : UndoAction
483 {
484    int fy1, fx1, fy2, fx2;
485    int ty, tx;
486    type = class(MoveTextAction);
487
488    void Undo(EditBox editBox)
489    {
490
491    }
492
493    void Redo(EditBox editBox)
494    {
495
496    }
497 };
498 */
499 public class EditLine : struct
500 {
501    EditLine prev, next;
502    char * buffer;
503    uint size, count;
504    int length;
505    EditBox editBox;
506 public:
507    property char * text
508    {
509       set
510       {
511          // Only works for single line edit for now...
512          EditBox editBox = this.editBox;
513          editBox.Clear();
514          editBox.PutS(text);
515       }
516       get { return this ? buffer : null; }
517    };
518    property EditLine prev { get { return this ? prev : null; } };
519    property EditLine next { get { return this ? next : null; } };
520    property int count { get { return this ? count : 0; } };
521 private:
522    void Free()
523    {
524       delete buffer;
525    }
526
527    // This makes sure the buffer always contains at least count characters
528    // Keeps a count/2 pad for avoiding always reallocating memory.
529    bool AdjustBuffer(int count)
530    {
531       char * buffer;
532       int newSize;
533
534       // Adds '\0' byte
535       count = count+1;
536
537       newSize =  (count + (count >> 1));
538
539       // Shrink down the buffer
540       if(size > newSize)
541       {
542          buffer = new char[newSize];
543          if(!buffer) return false;
544
545          if(this.buffer)
546          {
547             CopyBytes(buffer, this.buffer, count);
548             delete this.buffer;
549          }
550          this.buffer = buffer;
551          size = newSize;
552          return true;
553       }
554       // Increase the buffer
555       else if(size < count)
556       {
557           buffer = new char[newSize];
558           if(!buffer) return false;
559
560           if(this.buffer)
561           {
562              CopyBytes(buffer, this.buffer, this.count + 1); // size);
563              delete this.buffer;
564           }
565           this.buffer = buffer;
566           size = newSize;
567           return true;
568       }
569       else
570          return true;
571    }
572 };
573
574 public struct BufferLocation
575 {
576    EditLine line;
577    int y, x;
578
579    void AdjustDelete(BufferLocation start, BufferLocation end)
580    {
581       // Location is before, nothing to do
582       if(y < start.y || (y == start.y && x < start.x))
583          return;
584       // Location is inside deleted bytes, point to the start
585       if((y >= start.y && (y > start.y || x >= start.x)) &&
586          (y >= end.y && (y > end.y || x >= end.x)))
587       {
588          // Location is after
589          if(y >= end.y)
590          {
591             // Location is on another line
592             if(y > end.y)
593                y -= end.y - start.y;
594             // Location is the last touched line
595             else
596             {
597                if(x >= end.x)
598                {
599                   line = start.line;
600                   y = start.y;
601                   //if(start.line == end.line)
602                      x -= end.x - start.x;
603                }
604             }
605          }
606       }
607       else
608          this = start;
609    }
610
611    // Assuming no carriage return before first character ???? fixed?
612    void AdjustAdd(BufferLocation start, BufferLocation end)
613    {
614       int numLines = end.y - start.y;
615       if(y >= start.y)
616       {
617          if(y > start.y)
618             y += numLines;
619          else
620          {
621             if(x > start.x || (x == start.x /*&& (numLines ? true : false)*/))
622             {
623                int c;
624                for(c = 0, line = start.line; c<numLines; c++)
625                   line = line.next;
626                y += numLines;
627                //x += numLines ? end.x : (end.x - start.x);
628                x += end.x - start.x;
629             }
630          }
631       }
632    }
633 };
634
635 public enum EditBoxFindResult { notFound, found, wrapped };
636
637 static char * keyWords1[] =
638 {
639    // C
640    "return","break","continue","default","switch","case","if","else","for","while", "do","long","short",
641    "void", "char","int","float","double","unsigned","static", "extern", "struct", "union", "typedef","enum",
642    "const",   "sizeof",
643    "#include", "#define", "#pragma", "#if", "#else", "#elif", "#ifdef", "#ifndef", "#endif", "#undef", "#line",
644    "__attribute__", "__stdcall", "_stdcall",
645    "__declspec", "goto",
646     "inline", "__inline__", "_inline", "__inline", "__typeof","__extension__",
647    "asm", "__asm", "_asm", "volatile", "#cpu", "__stdcall__",
648
649    // eC
650    "class", "private", "public",
651    "property","import",
652    "delete", "new", "new0", "renew", "renew0", "define",
653    "get", "set",
654    "remote",
655    "dllexport", "dllimport", "stdcall",
656    "subclass", "__on_register_module", "namespace", "using",
657    "typed_object", "any_object", "incref", "register", "watch", "stopwatching", "firewatchers", "watchable", "class_designer",
658    "class_fixed", "class_no_expansion", "isset", "class_default_property", "property_category", "class_data", "class_property",
659    "virtual", "thisclass","unichar", "dbtable", "dbindex", "database_open", "dbfield",
660
661    // Types
662    "uint", "uint32", "uint16", "uint64", "bool", "byte", "int64", "uintptr", "intptr", "intsize", "uintsize",
663
664    // Values
665    "this", "true", "false", "null", "value",
666
667
668    // C++
669    "protected",
670    /* "defined" */
671    null
672 };
673
674 static char * keyWords2[] =
675 {
676    "defined", "warning", null
677 };
678
679 static char ** keyWords[] = { keyWords1, keyWords2 };
680 #define NUM_KEYWORD_GROUPS (sizeof(keyWords) / sizeof(char **))
681 //static int * keyLen[NUM_KEYWORD_GROUPS];
682 static int keyLen[NUM_KEYWORD_GROUPS][sizeof(keyWords1)];
683
684 static char searchString[1025], replaceString[1025];
685 static bool matchCase = false, wholeWord = false, searchUp = false;
686
687 static GoToDialog goToDialog
688 {
689    autoCreate = false, isModal = true, text = $"Go To"
690 };
691
692 public class EditBox : CommonControl
693 {
694    class_property(icon) = "<:ecere>controls/editBox.png";
695 public:
696
697    // Notifications
698    virtual bool Window::NotifyModified(EditBox editBox);
699    virtual void Window::NotifyCaretMove(EditBox editBox, int line, int charPos);
700    virtual void Window::NotifyUpdate(EditBox editBox);
701    virtual bool Window::NotifyDoubleClick(EditBox editBox, EditLine line, Modifiers mods);
702    virtual void Window::NotifyOvrToggle(EditBox editBox, bool overwrite);
703    virtual bool Window::NotifyKeyDown(EditBox editBox, Key key, unichar ch);
704
705    virtual bool Window::NotifyCharsAdded(EditBox editBox, BufferLocation before, BufferLocation after, bool pasteOperation);
706    virtual bool Window::NotifyCharsDeleted(EditBox editBox, BufferLocation beforeLoc, BufferLocation after, bool pasteOperation);
707    virtual bool Window::NotifyDropped(EditBox editBox, int x, int y);
708
709    virtual bool Window::NotifyUnsetModified(EditBox editBox);
710
711    // Why was this commented out?
712    //     It is required otherwise updating font property from property sheet doesn't immediately reflect in form designer,
713    // and the scrollArea property isn't compared properly either.
714    watch(font)
715    {
716       font = fontObject;
717       if(font) ComputeFont();
718       SetInitSize(initSize);
719    };
720
721    watch(hasVertScroll)
722    {
723       if(hasVertScroll)
724          style.vScroll = true;
725    };
726
727    watch(hasHorzScroll)
728    {
729       if(hasHorzScroll)
730          style.hScroll = true;
731    };
732
733    // Properties
734    property bool textHorzScroll { property_category $"Behavior" set { style.hScroll = value; } get { return style.hScroll; } };      // Should cut the text on set to false
735    property bool textVertScroll { property_category $"Behavior" set { style.vScroll = value; } get { return style.vScroll; } };
736    property bool readOnly
737    {
738       property_category $"Behavior"
739       set
740       {
741          style.readOnly = value;
742          itemEditCut.disabled = value || !selection;
743          itemEditDelete.disabled = value || !selection;
744          itemEditPaste.disabled = value;
745       }
746       get { return style.readOnly; }
747    };
748    property bool multiLine { property_category $"Behavior" set { style.multiLine = value; } get { return style.multiLine; } };
749    property bool freeCaret { property_category $"Behavior" set { style.freeCaret = value; } get { return style.freeCaret; } };
750    property bool tabKey { property_category $"Behavior" set { style.tabKey = value; } get { return style.tabKey; } };
751    property int tabSize { property_category $"Behavior" set { tabSize = value; } get { return tabSize; } };
752    property bool tabSelection { property_category $"Behavior" set { style.tabSel = value; if(value) style.tabKey = true; } get { return style.tabSel; } };
753    property bool smartHome { property_category $"Behavior" set { style.smartHome = value; } get { return style.smartHome; } };
754    property bool autoEmpty { property_category $"Behavior" set { style.autoEmpty = value; } get { return style.autoEmpty; } };
755    property bool noCaret { property_category $"Behavior" set { style.noCaret = value; if(value) { style.readOnly = true; style.stuckCaret = true; } } get { return style.noCaret; } };
756    property int maxLineSize { property_category $"Behavior" set { maxLineSize = value; } get { return maxLineSize; } };
757    property int maxNumLines { property_category $"Behavior" set { maxLines = value; } get { return maxLines; } };
758    property bool useTab { property_category $"Behavior" set { style.useTab = value; itemEditInsertTab.checked = value; } get { return style.useTab; } };
759    property bool syntaxHighlighting { property_category $"Appearance" set { style.syntax = value; } get { return style.syntax; } };
760    property bool noSelect { property_category $"Behavior" set { style.noSelect = value; } get { return style.noSelect; } };
761    property bool allCaps { property_category $"Behavior" set { style.allCaps = value; } get { return style.allCaps; } };
762    property bool autoSize { property_category $"Behavior" set { style.autoSize = value; } get { return style.autoSize; } };
763    property bool wrap { set { style.wrap = value; Update(null); } get { return style.wrap; } };
764    //property bool lineNumbers { set { style.lineNumbers = value; } get { return style.lineNumbers; } };
765    property int numLines { get { return this ? lineCount : 0; } };
766    property int lineNumber { get { return y; } };  // TODO: Change to property of EditLine    this.line.number
767    property int column { get { return col; } };   // TODO: Add Set
768    property int charPos { get { return x; } };   // TODO: Add Set
769    property EditLine firstLine { get { return lines.first; } };      // Change these to a List<EditLine>... (this.lines[10].text)
770    property EditLine lastLine  { get { return lines.last; } };
771    property EditLine line { get { return this.line; } }; // TODO: Add Set   this.line = this.lines[10]
772    property char * contents
773    {
774       property_category $"Data"
775       set
776       {
777          if(this)
778          {
779             undoBuffer.dontRecord++;
780             Deselect();
781             DelCh(this.lines.first, 0, 0, this.lines.last, this.lineCount-1, ((EditLine)(this.lines.last)).count, true);
782             if(value)
783                AddS(value);
784             //SetViewToCursor(true);
785             UpdateDirty();
786             Home();
787             undoBuffer.dontRecord--;
788          }
789       }
790
791       get
792       {
793          char * buffer = null;
794          if(this)
795          {
796             /* Can't implement this right now because of memory leak... Need string reference counting...
797             if(style.multiLine)
798             {
799
800                EditLine line;
801                int len = 0;
802
803                for(line = lines.first; line; line = line.next)
804                   len += strlen(line.buffer);
805
806                buffer = new char[len+1];
807                len = 0;
808                for(line = lines.first; line; line = line.next)
809                {
810                   int lineLen = strlen(line.buffer);
811                   memcpy(buffer + len, line.buffer, lineLen);
812                   len += lineLen;
813                }
814                buffer[len] = '\0';
815             else
816                */
817                buffer = this.line ? this.line.buffer : null;
818          }
819          return buffer;
820       }
821    };
822    property bool overwrite { get { return overwrite; } };
823    property bool caretFollowsScrolling { get { return style.cursorFollowsView; } set { style.cursorFollowsView = value; } }
824
825    property char * multiLineContents
826    {
827       get
828       {
829          char * buffer = null;
830          if(style.multiLine)
831          {
832
833             EditLine line;
834             int len = 0;
835
836             for(line = lines.first; line; line = line.next)
837                len += strlen(line.buffer)+1;
838
839             buffer = new char[len+1];
840             len = 0;
841             for(line = lines.first; line; line = line.next)
842             {
843                int lineLen = strlen(line.buffer);
844                memcpy(buffer + len, line.buffer, lineLen);
845                len += lineLen;
846                if(line.next) buffer[len++] = '\n';
847             }
848             buffer[len] = '\0';
849          }
850          return buffer;
851       }
852    }
853
854    /*
855    char * GetLineText()
856    {
857       if(this)
858       {
859          return this.line.buffer;
860       }
861       return null;
862    }
863
864    void SetLineText(char * text)
865    {
866       if(this)
867       {
868          EditLine_SetText(this.line, text);
869       }
870    }
871    */
872    property Color selectionColor { set { selectionColor = value; } get { return selectionColor; } isset { return selectionColor ? true : false; } };
873    property Color selectionText  { set { selectionText = value; } get { return selectionText; } isset { return selectionText ? true : false; } };
874    property SyntaxColorScheme syntaxColorScheme { set { delete colorScheme; colorScheme = value; incref colorScheme; } }
875
876    // selectionStart.line, selectionStart.column (With Set)
877    // selection.line1, selection.line2, selection.column1, selection.column2  (Read only)
878
879    // Methods
880 private:
881    Font font;
882    EditBoxBits style;
883    int tabSize;
884    int maxLineSize;
885    int maxLines;
886    OldList lines;
887    int lineCount;
888    // Font Space size
889    Size space, large;
890
891    // Position of Caret (Not necessarily displayed position)
892    int x,y;
893    int col;
894    // Position of beginning of block (Block ends at (x,y))
895    int selX, selY;
896    // line is line at carret, selLine is line at beginning of block
897    EditLine line, selLine, dropLine;
898    // Mouse selection Moving virtual caret
899    int dropX, dropY;
900
901    bool selection;
902
903    // ViewX is x offset in pixels, ViewY is y offset in lines
904    int viewX, viewY;
905    // viewLine is first displayed line
906    EditLine viewLine;
907
908    // start and end of area to redraw
909    int startY, endY;
910
911    // MaxLine is the longest line
912    EditLine maxLine;
913    // MaxLength is the longest line's length
914    int maxLength;
915
916    // MouseSelect is true once button is pressed, overwrite is true if mode is on
917    bool mouseSelect, mouseMove, overwrite, wordSelect;
918    // Timer is used for mouse selection scrolling
919    Timer timer
920    {
921       window = this, delay = 0.1;
922
923       bool DelayExpired()
924       {
925          // LineDown();
926          /*
927          if(this.viewY > 200)
928             GoToLineNum(0);
929          */
930          OnMouseMove(mouseX, mouseY, -1);
931          return true;
932       }
933    };
934
935    // (mouseX,mouseY) is the position of the mouse in the edit box
936    int mouseX, mouseY;
937
938    bool modified;
939
940    void (* FontExtent)(Display display, Font font, char * text, int len, int * width, int * height);
941
942    Color backColor;
943    bool rightButtonDown;
944    bool pasteOperation;
945    int caretX, caretY;
946    UndoBuffer undoBuffer { data = this };
947    int savedAction;
948    ColorAlpha selectionColor, selectionText;
949    SyntaxColorScheme colorScheme { };
950
951    menu = Menu { };
952
953    // Edit Menu
954    Menu editMenu { menu, $"Edit", e };
955    MenuItem itemEditCut
956    {
957       editMenu, $"Cut\tCtrl+X", t, disabled = true;
958
959       bool NotifySelect(MenuItem item, Modifiers mods)
960       {
961          if(!(style.readOnly))
962             Cut();
963          return true;
964       }
965    };
966    MenuItem itemEditCopy
967    {
968       editMenu, $"Copy\tCtrl+C", c, disabled = true;
969
970       bool NotifySelect(MenuItem item, Modifiers mods)
971       {
972          Copy();
973          return true;
974       }
975    };
976    MenuItem itemEditPaste
977    {
978       editMenu, $"Paste\tCtrl+V", p;
979
980       bool NotifySelect(MenuItem item, Modifiers mods)
981       {
982          if(!(style.readOnly))
983             Paste();
984          return true;
985       }
986    };
987    MenuItem itemEditDelete
988    {
989       editMenu, $"Delete\tDel", d, disabled = true;
990
991       bool NotifySelect(MenuItem item, Modifiers mods)
992       {
993          if(!(style.readOnly))
994             DeleteSelection();
995          return true;
996       }
997    };
998    MenuDivider { editMenu };
999    MenuItem itemEditSelectAll
1000    {
1001       editMenu, $"Select All\tCtrl+A", a;
1002
1003       bool NotifySelect(MenuItem item, Modifiers mods)
1004       {
1005          SelectAll();
1006          return true;
1007       }
1008    };
1009    MenuDivider { editMenu };
1010    MenuItem itemEditUndo
1011    {
1012       editMenu, $"Undo\tCtrl+Z", u;
1013       disabled = true;
1014
1015       bool NotifySelect(MenuItem item, Modifiers mods)
1016       {
1017          Undo();
1018          return true;
1019       }
1020    };
1021    MenuItem itemEditRedo
1022    {
1023       editMenu, $"Redo\tCtrl+Y", o;
1024       disabled = true;
1025
1026       bool NotifySelect(MenuItem item, Modifiers mods)
1027       {
1028          Redo();
1029          return true;
1030       }
1031    };
1032    MenuDivider { editMenu };
1033    MenuItem
1034    {
1035       editMenu, $"Find Previous\tShift-F3", e, shiftF3;
1036
1037       bool NotifySelect(MenuItem item, Modifiers mods)
1038       {
1039          if(searchString[0])
1040             Find(searchString, wholeWord, matchCase, false);
1041          else
1042             itemEditFind.NotifySelect(this, item, mods);
1043          return true;
1044       }
1045    };
1046    MenuItem
1047    {
1048       editMenu, $"Find Next\tF3", n, f3;
1049
1050       bool NotifySelect(MenuItem item, Modifiers mods)
1051       {
1052          if(searchString[0])
1053             Find(searchString, wholeWord, matchCase, true);
1054          else
1055             itemEditFind.NotifySelect(this, item, mods);
1056          return true;
1057       }
1058    };
1059    MenuItem itemEditFind
1060    {
1061       editMenu, $"Find...\tCtrl+F", f, ctrlF;
1062
1063       bool NotifySelect(MenuItem item, Modifiers mods)
1064       {
1065          FindDialog dialog
1066          {
1067             editBox = this, master = master, isModal = true, searchString = searchString, matchCase = matchCase, wholeWord = wholeWord,
1068             searchUp = searchUp;
1069
1070             // TODO:
1071             // Fix dialog from above shouldn't be visible
1072             // void NotifyDestroyed(FindDialog dialog, DialogResult result)
1073             void NotifyDestroyed(Window window, DialogResult result)
1074             {
1075                FindDialog dialog = (FindDialog) window;
1076                searchUp = dialog.searchUp;
1077                strcpy(searchString, dialog.searchString);
1078                matchCase = dialog.matchCase;
1079                wholeWord = dialog.wholeWord;
1080             }
1081          };
1082          dialog.Create();
1083          return true;
1084       }
1085    };
1086    MenuItem
1087    {
1088       editMenu, $"Replace...\tCtrl+R", r, ctrlR;
1089
1090       bool NotifySelect(MenuItem item, Modifiers mods)
1091       {
1092          ReplaceDialog dialog
1093          {
1094             master = master,
1095             isModal = true,
1096             searchString = searchString,
1097             replaceString = replaceString,
1098             matchCase = matchCase,
1099             wholeWord = wholeWord,
1100             editBox = this;
1101
1102             // TODO:
1103             // void NotifyDestroyed(ReplaceDialog dialog, DialogResult result)
1104             void NotifyDestroyed(Window window, DialogResult result)
1105             {
1106                ReplaceDialog dialog = (ReplaceDialog)window;
1107                char * replace = dialog.replaceString;
1108                if(replace)
1109                   strcpy(replaceString, replace);
1110                strcpy(searchString, dialog.searchString);
1111                matchCase = dialog.matchCase;
1112                wholeWord = dialog.wholeWord;
1113             }
1114          };
1115          dialog.Create();
1116          return true;
1117       }
1118    };
1119    MenuDivider { editMenu };
1120    MenuItem
1121    {
1122       editMenu, $"Go To...\tCtrl+G", g, ctrlG;
1123
1124       bool NotifySelect(MenuItem item, Modifiers mods)
1125       {
1126          goToDialog.editBox = this;
1127          goToDialog.master = master;
1128          goToDialog.Create();
1129          return true;
1130       }
1131    };
1132    MenuDivider { editMenu };
1133    MenuItem itemEditInsertTab
1134    {
1135       editMenu, $"Insert Tabs", i, checkable = true;
1136
1137       bool NotifySelect(MenuItem item, Modifiers mods)
1138       {
1139          style.useTab = item.checked;
1140          return true;
1141       }
1142    };
1143
1144    borderStyle = deep;
1145    snapVertScroll = true;
1146    snapHorzScroll = true;
1147
1148    EditBox()
1149    {
1150       static bool syntaxInit = false;
1151       if(!syntaxInit)
1152       {
1153          int g,c;
1154          syntaxInit = true;
1155          for(g = 0; g<NUM_KEYWORD_GROUPS; g++)
1156          {
1157             for(c = 0; keyWords[g][c]; c++);
1158             //keyLen[g] = new int[c];
1159             for(c = 0; keyWords[g][c]; c++)
1160             {
1161                keyLen[g][c] = strlen(keyWords[g][c]);
1162             }
1163          }
1164
1165          colorScheme.commentColor = dimGray;
1166          colorScheme.charLiteralColor = crimson;
1167          colorScheme.stringLiteralColor = crimson;
1168          colorScheme.preprocessorColor = green;
1169          colorScheme.numberColor = teal;
1170          colorScheme.keywordColors = [ blue, blue ];
1171       }
1172
1173       FontExtent = Display::FontExtent;
1174       font = fontObject;
1175       lines.offset = (uint)&((EditLine)0).prev;
1176
1177       style = EditBoxBits { hScroll = true };
1178
1179       // cursor = guiApp.GetCursor(IBeam);
1180
1181       // Default Properties
1182       /*maxLines = 65536;
1183       maxLineSize = 1024;*/
1184
1185       maxLines = MAXINT;
1186       maxLineSize = MAXINT;
1187
1188       tabSize = 3;
1189
1190       overwrite = false;
1191       mouseSelect = this.mouseMove = false;
1192       line = null;
1193       lineCount = 0;
1194       x = selX = selY = 0;
1195       col = 0;
1196       y = -1;
1197       line = selLine = null;
1198       viewX = 0;
1199       viewY = 0;
1200       maxLength = 0;
1201       maxLine = null;
1202       startY = 0;
1203       endY = clientSize.h;
1204       wordSelect = false;
1205
1206       // Default space to 8 in case window is not constructed
1207       space = { 8, 8 };
1208
1209       undoBuffer.dontRecord++;
1210       AddCh('\n');
1211       undoBuffer.dontRecord--;
1212
1213       viewLine = lines.first;
1214       style.recomputeSyntax = true;
1215
1216       FindMaxLine();
1217
1218       FixScrollArea();
1219
1220       UpdateCaretPosition(true);
1221       return true;
1222    }
1223
1224    ~EditBox()
1225    {
1226       lines.Free(EditLine::Free);
1227    }
1228
1229    void FlushBuffer(Surface surface, EditLine line, int wc, int * renderStart, int * x, int y, int numSpaces, bool drawSpaces, Box box)
1230    {
1231       int count = wc - *renderStart;
1232       if(count)
1233       {
1234          if(y + space.h >= box.top && y <= box.bottom)
1235          {
1236             int w;
1237
1238             if(!numSpaces)
1239             {
1240                //FontExtent(display, font, line.buffer + *renderStart, count, &w, null);
1241                surface.TextFont(font);
1242                surface.TextExtent(line.buffer + *renderStart, count, &w, null);
1243                if(*x + w + XOFFSET > 0)
1244                   surface.WriteText(XOFFSET + *x,y, line.buffer + *renderStart, count);
1245                *x += w;
1246             }
1247             else
1248             {
1249                w = numSpaces; // * space.w;
1250                if(*x + w + XOFFSET > 0 && (drawSpaces || surface.GetTextOpacity()))
1251                   surface.Area(XOFFSET + *x - 1, y, XOFFSET + *x + w, y + space.h-1);
1252                   // WHATS UP WITH THIS...  surface.Area(XOFFSET + *x, y, XOFFSET + *x + w, y + space.h-1);
1253                *x += w;
1254             }
1255          }
1256          *renderStart = wc;
1257       }
1258    }
1259
1260    int CheckColors(EditLine line, int wc, bool selection, int selX, int editX, bool *selected,
1261                    Color selectionForeground, Color selectionBackground, Color textColor, Color *foreground, Color *background, bool *opacity, bool *overwrite)
1262    {
1263       bool flush = false;
1264
1265       if(selection)
1266       {
1267          if((wc == selX && line == selLine) || (wc == editX && line == this.line))
1268          {
1269             *selected ^= 1;
1270
1271             *foreground = (*selected) ? selectionForeground : textColor;
1272             *background = selectionBackground;
1273             *opacity = *selected;
1274
1275             flush = true;
1276          }
1277       }
1278
1279       // Overwrite Carret
1280       if(this.overwrite && active)
1281       {
1282          if((style.stuckCaret && wc == line.count && !line.next) ||
1283             (!mouseMove && line == this.line && wc == editX))
1284          {
1285             *overwrite = true;
1286             flush = true;
1287          }
1288       }
1289       return flush;
1290    }
1291
1292    void FigureStartSyntaxStates(EditLine firstLine, bool reset)
1293    {
1294       if(style.syntax)
1295       {
1296          bool inMultiLineComment = reset ? false : style.inMultiLineComment;
1297          bool wasInMultiLine = reset ? false : style.wasInMultiLine;
1298          bool inString = false;
1299          bool inQuotes = false;
1300          bool inPrep = reset ? false : style.inPrep;
1301          bool inSingleLineComment = false;
1302          bool escaped = reset ? false : style.escaped;
1303          bool continuedSingleLineComment = reset ? false : style.continuedSingleLineComment;
1304
1305          EditLine line = reset ? lines.first : firstLine;
1306          // int maxBackUp = 1000, c;
1307          // for(c = 0, line = viewLine; c < maxBackUp && line && line.prev; line = line.prev);
1308          for(; line != viewLine; line = line.next)
1309          {
1310             char * text = line.buffer;
1311             int c;
1312             char ch;
1313             bool lastWasStar = false;
1314             bool firstWord = true;
1315             if(!escaped) inPrep = false;
1316             inSingleLineComment = continuedSingleLineComment;
1317             escaped = false;
1318             inString = false;
1319             inQuotes = false;
1320
1321             firstWord = true;
1322             for(c = 0; (ch = text[c]); c++)
1323             {
1324                bool wasEscaped = escaped;
1325                bool backLastWasStar = lastWasStar;
1326                bool backWasInMultiLine = wasInMultiLine;
1327                escaped = false;
1328                lastWasStar = false;
1329                wasInMultiLine = inMultiLineComment;
1330                if(ch == '/')
1331                {
1332                   if(!inSingleLineComment && !inMultiLineComment && !inQuotes && !inString)
1333                   {
1334                      if(text[c+1] == '/')
1335                      {
1336                         inSingleLineComment = true;
1337                      }
1338                      else if(text[c+1] == '*')
1339                      {
1340                         inMultiLineComment = true;
1341                      }
1342                   }
1343                   else if(backLastWasStar)
1344                      inMultiLineComment = false;
1345                }
1346                else if(ch == '*')
1347                {
1348                   if(backWasInMultiLine) lastWasStar = true;
1349                }
1350                else if(ch == '\"' && !inSingleLineComment && !inMultiLineComment && !inQuotes)
1351                {
1352                   if(inString && !wasEscaped)
1353                   {
1354                      inString = false;
1355                   }
1356                   else
1357                   {
1358                      inString = true;
1359                   }
1360                }
1361                else if(ch == '\'' && !inSingleLineComment && !inMultiLineComment && !inString)
1362                {
1363                   if(inQuotes && !wasEscaped)
1364                      inQuotes = false;
1365                   else
1366                   {
1367                      inQuotes = true;
1368                   }
1369                }
1370                else if(ch == '\\')
1371                {
1372                   if(!wasEscaped)
1373                      escaped = true;
1374                }
1375                else if(ch == '#' && !inQuotes && !inString && !inMultiLineComment && !inSingleLineComment)
1376                {
1377                   if(firstWord)
1378                   {
1379                      inPrep = true;
1380                   }
1381                }
1382                else if(ch != ' ' && ch != '\t')
1383                   firstWord = false;
1384             }
1385             continuedSingleLineComment = inSingleLineComment && (line.count && line.text[line.count - 1] == '\\');
1386          }
1387
1388          style.continuedSingleLineComment = continuedSingleLineComment;
1389          style.inMultiLineComment = inMultiLineComment;
1390          style.wasInMultiLine = wasInMultiLine;
1391          style.inPrep = inPrep;
1392          style.escaped = escaped;
1393       }
1394    }
1395
1396    /*void OnDrawOverChildren(Surface surface)
1397    {
1398       if(style.lineNumbers)
1399       {
1400          int currentLineNumber = this.viewY + 1;
1401          int i;
1402          char lineText[256];
1403          for(i = 0; i * space.h < box.height; i++)
1404          {
1405             // ********* LINE NUMBERING *********
1406             surface.SetForeground(Color{60, 60, 60});
1407             //Highlight current line
1408             if(this.caretY / space.h == currentLineNumber - 1)
1409                surface.SetBackground(Color{220, 220, 220});
1410             else
1411                surface.SetBackground(Color{230, 230, 230});
1412             surface.textOpacity = true;
1413
1414             sprintf(lineText,"%5u ", currentLineNumber % 100000);
1415             if(currentLineNumber > this.lineCount)
1416                surface.WriteText(0,i*space.h+1,"      ",6);
1417             else
1418                surface.WriteText(0,i*space.h+1,lineText,6);
1419
1420             currentLineNumber++;
1421          }
1422       }
1423    }*/
1424
1425    void OnRedraw(Surface surface)
1426    {
1427       EditLine line;
1428       int y = YOFFSET;
1429       bool selected = false, selection = true;
1430       int selX, editX;
1431       Color selectionBackground = selectionColor ? selectionColor : SELECTION_COLOR;
1432       Color selectionForeground = selectionText ? selectionText : SELECTION_TEXT;
1433       Color defaultTextColor = property::foreground;
1434       Color textColor;
1435       Box box;
1436       int maxW = clientSize.w;
1437
1438       Color foreground, background;
1439       bool opacity;
1440
1441       // Overwrite Caret Stuff
1442       bool overWrite = false;
1443       int overWriteX, overWriteY;
1444       byte overWriteCh;
1445
1446       // ****** SYNTAX STATES ******
1447       bool inMultiLineComment = style.inMultiLineComment;
1448       bool inString = false;
1449       bool inQuotes = false;
1450       bool inPrep = style.inPrep;
1451       bool inSingleLineComment = false;
1452       bool escaped = style.escaped;
1453       bool continuedSingleLineComment = style.continuedSingleLineComment;
1454       bool wasInMultiLine = style.wasInMultiLine;
1455       // ****** ************* ******
1456
1457       if(!isEnabled)
1458          defaultTextColor = Color { 85, 85, 85 };
1459       textColor = defaultTextColor;
1460
1461       if(
1462          Abs(selectionBackground.r - property::background.r) +
1463          Abs(selectionBackground.g - property::background.g) +
1464          Abs(selectionBackground.b - property::background.b) < 92)
1465       {
1466          selectionBackground = formColor;
1467          selectionForeground = selectionColor ? selectionColor : SELECTION_COLOR;
1468       }
1469
1470       surface.TextFont(this.font);
1471    /*
1472       surface.SetBackground(GDefaultPalette()[RND_Get(1, 15)]);
1473       surface.Area(0,0,MAXINT,MAXINT);
1474    */
1475
1476       if(!isEnabled)
1477       {
1478          surface.SetBackground(formColor);
1479          surface.Area(0,0,clientSize.w, clientSize.h);
1480       }
1481
1482       if(this.selX == this.x && this.selY == this.y)
1483          selection = false;
1484
1485       if(style.freeCaret)
1486       {
1487          editX = this.x;
1488          selX  = this.selX;
1489       }
1490       else
1491       {
1492          editX = Min(this.x,this.line.count);
1493          selX  = Min(this.selX,this.selLine.count);
1494       }
1495
1496       selected = (this.selY < this.viewY) ^ (this.y < this.viewY);
1497
1498       foreground = selected ? selectionForeground : textColor;
1499       background = selectionBackground;
1500       opacity = selected;
1501    /*
1502       opacity = true;
1503       background = WHITE;
1504    */
1505       surface.SetForeground(foreground);
1506       surface.SetBackground(background);
1507       surface.TextOpacity(opacity);
1508
1509       surface.GetBox(box);
1510
1511       for(line = this.viewLine; line; line = line.next)
1512       {
1513          int x = -this.viewX;
1514          int end;
1515          int c;
1516          int start = 0;
1517          Color newTextColor = textColor = defaultTextColor;
1518          bool lineComplete = false;
1519
1520
1521          // ****** SYNTAX HIGHLIGHTING ******
1522          bool lastWasStar = false;
1523          bool trailingSpace = false;
1524          bool firstWord = true;
1525          if(!escaped) inPrep = false;
1526          inSingleLineComment = continuedSingleLineComment;
1527          escaped = false;
1528          inString = false;
1529          inQuotes = false;
1530          // *********************************
1531
1532    /*   === DEBUGGING TOOL FOR MAXLINE ===
1533
1534          if(line == this.maxLine)
1535          {
1536             surface.SetBackground(GREEN|0xFF000000);
1537             surface.TextOpacity(true);
1538          }
1539          else
1540          {
1541             surface.TextOpacity(selected ? true : false);
1542             surface.SetBackground(selected ? SELECTION_COLOR|0xFF000000 : BLACK|0xFF000000);
1543          }
1544    */
1545
1546          if(line == this.selLine && line == this.line)
1547          {
1548             end = Max(line.count, this.x);
1549             end = Max(end, this.selX);
1550          }
1551          else if(line == this.selLine)
1552             end = Max(line.count, this.selX);
1553          else if(line == this.line)
1554             end = Max(line.count, this.x);
1555          else
1556             end = line.count;
1557
1558          for(c=0; c<end; )
1559          {
1560             int bufferLen = 0;
1561             int wordLen = 0;
1562             bool spacing = true;
1563             bool cantHaveWords = false;
1564             int w = 0;
1565
1566             if(lineComplete)
1567             {
1568                x = -viewX;
1569                y += space.h;
1570                lineComplete = false;
1571             }
1572
1573             textColor = newTextColor;
1574             if(!selected)
1575             {
1576                foreground = textColor;
1577                surface.SetForeground(textColor);
1578             }
1579
1580             // Look at words
1581             for(; c<end && !cantHaveWords;)
1582             {
1583                if(wordLen)
1584                {
1585                   bufferLen += wordLen;
1586                   wordLen = 0;
1587                }
1588 #ifdef _DEBUG
1589                /*if(line.count > 4000)
1590                {
1591                   printf("oh");
1592                   CheckMemory();
1593                }*/
1594 #endif
1595                // Parse Words
1596                for(; c<line.count; c++)
1597                {
1598                   unichar ch = line.buffer[c];
1599                   unichar bf = (wordLen == 1) ? line.buffer[c-1] : 0;
1600                   //if(ch == ' ' || ch == '\t' || (wordLen && (ch == '(' || ch == ')' || ch == ';' || ch == ':')) || (wordLen == 1 && line.buffer[c-1] == '('))
1601                   if(CharMatchCategories(ch, separators) || /*ch == ' ' ||*/ ch == '\t' ||
1602                      (wordLen && !CharMatchCategories(ch, numbers|letters|marks|connector) && ch != '#' /*&& ch != '_'*/) ||
1603                      (bf && !CharMatchCategories(bf, numbers|letters|separators|marks|connector) && bf != '#' && bf != '\t' /*&& bf != '_' && bf != ' '*/))
1604                      break;
1605                   wordLen++;
1606                   trailingSpace = false;
1607                }
1608
1609                if(!wordLen)
1610                {
1611
1612                   for(; c<line.count; c++)
1613                   {
1614                      unichar ch = line.buffer[c];
1615                      if(ch == '\t' || ch == ' ')
1616                      {
1617                         cantHaveWords = true;
1618                         if(ch == ' ' && c == line.count-1)
1619                            trailingSpace = true;
1620                         if(bufferLen)
1621                            break;
1622                      }
1623                      if(ch != ' ' && ch != '\t')
1624                         break;
1625                      wordLen++;
1626                   }
1627
1628                   if(c == line.count && c < end)
1629                   {
1630                      if(bufferLen)
1631                      {
1632                         c -= wordLen;
1633                         break;
1634                      }
1635                      wordLen += end - c;
1636                      c = end;
1637                      cantHaveWords = true;
1638                   }
1639
1640                   if(c < end)
1641                      escaped = false;
1642                   lastWasStar = false;
1643                }
1644                else
1645                {
1646                   if(isEnabled)
1647                   {
1648                      bool backEscaped = escaped;
1649                      bool backLastWasStar = lastWasStar;
1650                      bool backInMultiLineComment = inMultiLineComment;
1651                      bool backInString = inString;
1652                      bool backInQuotes = inQuotes;
1653                      bool backInPrep = inPrep;
1654                      bool backInSingleLineComment = inSingleLineComment;
1655                      bool backWasInMultiLine = wasInMultiLine;
1656
1657                      char * word = line.buffer + c - wordLen;
1658                      int g,ccc;
1659                      bool wasEscaped = escaped;
1660                      escaped = false;
1661                      lastWasStar = false;
1662
1663                      wasInMultiLine = inMultiLineComment;
1664
1665                      // Determine Syntax Highlighting
1666                      newTextColor = defaultTextColor;
1667                      if(style.syntax)
1668                      {
1669                         if(inSingleLineComment || inMultiLineComment)
1670                         {
1671                            newTextColor = colorScheme.commentColor;
1672                         }
1673                         else if(inQuotes)
1674                         {
1675                            newTextColor = colorScheme.charLiteralColor;
1676                         }
1677                         else if(inString)
1678                         {
1679                            newTextColor = colorScheme.stringLiteralColor;
1680                         }
1681                         else if(inPrep)
1682                         {
1683                            newTextColor = colorScheme.preprocessorColor;
1684                         }
1685                         if(wordLen == 1 && word[0] == '/')
1686                         {
1687                            if(!inSingleLineComment && !inMultiLineComment && !inQuotes && !inString)
1688                            {
1689                               if(word[1] == '/')
1690                               {
1691                                  inSingleLineComment = true;
1692                                  newTextColor = colorScheme.commentColor;
1693                               }
1694                               else if(word[1] == '*')
1695                               {
1696                                  inMultiLineComment = true;
1697                                  newTextColor = colorScheme.commentColor;
1698                               }
1699                            }
1700                            else if(backLastWasStar)
1701                               inMultiLineComment = false;
1702                         }
1703                         else if(wordLen == 1 && word[0] == '*')
1704                         {
1705                            if(backWasInMultiLine)
1706                               lastWasStar = true;
1707                         }
1708                         else if(!inSingleLineComment && !inMultiLineComment && !inQuotes && wordLen == 1 && word[0] == '\"')
1709                         {
1710                            if(inString && !wasEscaped)
1711                            {
1712                               inString = false;
1713                            }
1714                            else
1715                            {
1716                               inString = true;
1717                               newTextColor = colorScheme.stringLiteralColor;
1718                            }
1719                         }
1720                         else if(!inSingleLineComment && !inMultiLineComment && !inString && wordLen == 1 && word[0] == '\'')
1721                         {
1722                            if(inQuotes && !wasEscaped)
1723                               inQuotes = false;
1724                            else
1725                            {
1726                               inQuotes = true;
1727                               newTextColor = colorScheme.charLiteralColor;
1728                            }
1729                         }
1730                         else if(wordLen == 1 && word[0] == '\\')
1731                         {
1732                            if(!wasEscaped)
1733                               escaped = true;
1734                         }
1735                         else if(!inQuotes && !inString && !inMultiLineComment && !inSingleLineComment && (isdigit(word[0]) || (word[0] == '.' && isdigit(word[1]))))
1736                         {
1737                            char * dot = strchr(word, '.');
1738                            bool isHex = (word[0] == '0' && (word[1] == 'x' || word[1] == 'X'));
1739                            char * exponent;
1740                            bool isReal;
1741                            char * s = null;
1742                            if(isHex)
1743                            {
1744                               exponent = strchr(word, 'p');
1745                               if(!exponent) exponent = strchr(word, 'P');
1746                            }
1747                            else
1748                            {
1749                               exponent = strchr(word, 'e');
1750                               if(!exponent) exponent = strchr(word, 'E');
1751                            }
1752                            if(dot && dot > word + wordLen) dot = null;
1753                            isReal = dot || exponent;
1754                            if(isReal)
1755                               strtod(word, &s);      // strtod() seems to break on hex floats (e.g. 0x23e3p12, 0x1.fp3)
1756                            else
1757                               strtol(word, &s, 0);
1758                            if(s && s != word)
1759                            {
1760                               // Check suffixes
1761                               char ch;
1762                               int i;
1763                               int gotF = 0, gotL = 0, gotU = 0, gotI = 0;
1764                               bool valid = true;
1765
1766                               for(i = 0; valid && i < 5 && (ch = s[i]) && (isalnum(ch) || ch == '_'); i++)
1767                               {
1768                                  switch(ch)
1769                                  {
1770                                     case 'f': case 'F': gotF++; if(gotF > 1 || !isReal) valid = false; break;
1771                                     case 'l': case 'L':
1772                                        gotL++;
1773                                        if(gotL > 2 || isReal || (gotL == 2 && (s[i-1] != ch)))
1774                                        valid = false;
1775                                        break;
1776                                     case 'u': case 'U': gotU++; if(gotU > 1 || isReal) valid = false; break;
1777                                     case 'i': case 'I': case 'j': case 'J': gotI++; if(gotI > 1) valid = false; break;
1778                                     default: valid = false;
1779                                  }
1780                               }
1781
1782                               // Don't highlight numbers with too many decimal points
1783                               if(s[0] == '.' && isdigit(s[1]))
1784                               {
1785                                  int newWordLen;
1786                                  while(s[0] == '.' && isdigit(s[1]))
1787                                  {
1788                                     int newWordLen = s - word;
1789                                     c += newWordLen - wordLen;
1790                                     wordLen = newWordLen;
1791                                     strtod(s, &s);
1792                                  }
1793                                  newWordLen = s - word;
1794                                  c += newWordLen - wordLen;
1795                                  wordLen = newWordLen;
1796                               }
1797                               else if(valid)
1798                               {
1799                                  int newWordLen = s + i - word;
1800                                  newTextColor = colorScheme.numberColor;
1801                                  c += newWordLen - wordLen;
1802                                  wordLen = newWordLen;
1803                               }
1804                               else if(dot && dot > word && dot < s)
1805                                  newTextColor = colorScheme.numberColor;
1806                            }
1807                         }
1808                         else
1809                         {
1810                            if(!inQuotes && !inString && !inMultiLineComment && !inSingleLineComment && word[0] == '#')
1811                            {
1812                               if(firstWord)
1813                               {
1814                                  inPrep = true;
1815                                  newTextColor = colorScheme.preprocessorColor;
1816                               }
1817                            }
1818                            if(!inQuotes && !inString && !inMultiLineComment && !inSingleLineComment)
1819                            {
1820                               for(g = 0; g < ((inPrep && word[0] != '#') ? 2 : 1); g++)
1821                               {
1822                                  char ** keys = keyWords[g];
1823                                  int * len = keyLen[g];
1824                                  for(ccc = 0; keys[ccc]; ccc++)
1825                                  {
1826                                     if(len[ccc] == wordLen && !strncmp(keys[ccc], word, wordLen))
1827                                     {
1828                                        newTextColor = colorScheme.keywordColors[g];
1829                                        break;
1830                                     }
1831                                  }
1832                               }
1833                            }
1834                         }
1835                         firstWord = false;
1836
1837                         // If highlighting for this word is different, break
1838                         if(newTextColor != textColor)
1839                         {
1840                            if(bufferLen)
1841                            {
1842                               // Better solution than going back?
1843                               c -= wordLen;
1844
1845                               // Reset syntax flags
1846                               escaped = backEscaped;
1847                               lastWasStar = backLastWasStar;
1848                               inMultiLineComment = backInMultiLineComment;
1849                               inString = backInString;
1850                               inQuotes = backInQuotes;
1851                               inPrep = backInPrep;
1852                               inSingleLineComment = backInSingleLineComment;
1853                               wasInMultiLine = backWasInMultiLine;
1854                               break;
1855                            }
1856                            else
1857                            {
1858                               textColor = newTextColor;
1859                               if(!selected)
1860                               {
1861                                  foreground = textColor;
1862                                  surface.SetForeground(textColor);
1863                               }
1864                            }
1865                         }
1866                      }
1867                   }
1868
1869                   // If we're not breaking, this can't be rendered as spacing anymore
1870                   spacing = false;
1871
1872                   // Do word wrapping here
1873                   if(style.wrap)
1874                   {
1875                      //if(!numSpaces)
1876                      {
1877                         int tw;
1878                         FontExtent(display, font, line.buffer + start, bufferLen + wordLen, &tw, null);
1879                         w = tw;
1880                      }
1881                      /*else
1882                      {
1883                         w += numSpaces * space.w;
1884                      }*/
1885                      if(x + viewX + w > maxW)
1886                      {
1887                         // Avoid an endless loop until we fix wrapping
1888                         if(c - wordLen > start)
1889                         {
1890                            c -= wordLen;
1891                            lineComplete = true;
1892                            break;
1893                         }
1894                      }
1895                   }
1896                }
1897                bufferLen += wordLen;
1898                wordLen = 0;
1899             }
1900
1901             {
1902                int renderStart = start;
1903                bool flush = false;
1904                bool flagTrailingSpace = false;
1905                int numSpaces = 0;
1906                int wc;
1907
1908                // Render checking if we need to split because of selection or to find where to draw insert caret
1909                for(wc = start; wc < start + bufferLen; wc++)
1910                {
1911                   flush = CheckColors(line, wc, selection, selX, editX, &selected, selectionForeground,
1912                      selectionBackground, textColor, &foreground, &background, &opacity, &overWrite);
1913                   if(overWrite == true)
1914                   {
1915                      overWriteCh = (wc < line.count) ? line.buffer[wc] : ' ';
1916                      if(overWriteCh == '\t') overWriteCh = ' ';
1917                   }
1918
1919                   if(flush)
1920                   {
1921                      flagTrailingSpace = numSpaces && trailingSpace && style.syntax && start + bufferLen == line.count && line != this.line;
1922                      if(flagTrailingSpace) surface.SetBackground(red);
1923                      FlushBuffer(surface, line, wc, &renderStart, &x, y, numSpaces, flagTrailingSpace, box);
1924                      if(overWrite == true)
1925                      {
1926                         overWriteX = x;
1927                         overWriteY = y;
1928                         overWrite = 2;
1929                      }
1930                      numSpaces = 0;
1931
1932                      surface.TextOpacity(opacity);
1933                      surface.SetBackground(background);
1934                      surface.SetForeground(foreground);
1935
1936                      flush = false;
1937                   }
1938
1939                   if(spacing)
1940                   {
1941                      if(wc < line.count && line.buffer[wc] == '\t')
1942                      {
1943                         numSpaces += (tabSize * space.w) - ((x + numSpaces + viewX) % (tabSize * space.w));
1944                      }
1945                      else
1946                      {
1947                         numSpaces += space.w;
1948                      }
1949                   }
1950                }
1951                flagTrailingSpace = numSpaces && trailingSpace && style.syntax && start + bufferLen == line.count && line != this.line;
1952                if(flagTrailingSpace) surface.SetBackground(red);
1953                FlushBuffer(surface, line, wc, &renderStart, &x, y, numSpaces, flagTrailingSpace, box);
1954                start += bufferLen;
1955             }
1956          }
1957
1958          if(CheckColors(line, c, selection, selX, editX, &selected, selectionForeground,
1959                         selectionBackground, textColor, &foreground, &background, &opacity, &overWrite))
1960          {
1961             if(overWrite == true)
1962             {
1963                overWriteX = x;
1964                overWriteY = y;
1965                overWriteCh = ' ';
1966                overWrite = 2;
1967             }
1968             surface.TextOpacity(opacity);
1969             surface.SetBackground(background);
1970             surface.SetForeground(foreground);
1971          }
1972
1973          if(style.freeCaret && selected)
1974          {
1975             surface.SetBackground(selectionBackground);
1976             surface.Area(x + XOFFSET - 1,y,clientSize.w-1,y+this.space.h-1);
1977             // TEST: surface.Area(x + XOFFSET,y,clientSize.w-1,y+this.space.h-1);
1978          }
1979
1980
1981          /*
1982          if(style.freeCaret && selected)
1983          {
1984             surface.SetBackground(selectionBackground);
1985             surface.Area(x + XOFFSET - 1,y,clientSize.w-1,y+this.space.h-1);
1986          }
1987          */
1988
1989          continuedSingleLineComment = inSingleLineComment && (line.count && line.text[line.count - 1] == '\\');
1990
1991          y+=this.space.h;
1992          if(y > box.bottom) // >=clientSize.h)
1993             break;
1994       }
1995
1996       if(overWrite)
1997       {
1998          surface.TextOpacity(true);
1999          surface.SetForeground(black);
2000          surface.SetBackground(Color {255,255,85});
2001          surface.WriteText(XOFFSET + overWriteX,overWriteY, &overWriteCh, 1);
2002       }
2003    }
2004
2005    void FixScrollArea()
2006    {
2007       if(style.hScroll || style.vScroll)
2008       {
2009          int width = maxLength + XOFFSET;
2010          int height = lineCount * space.h;
2011          if(style.freeCaret && line)
2012          {
2013             if(x > selX)
2014             {
2015                if(x > line.count)
2016                   width = Max(line.length + (x - line.count) * space.w, maxLength + XOFFSET);
2017             }
2018             else
2019             {
2020                if(selX > selLine.count)
2021                   width = Max(selLine.length + (selX - selLine.count) * space.w, maxLength + XOFFSET);
2022             }
2023          }
2024
2025          width += space.w;
2026          SetScrollLineStep(8, space.h);
2027          SetScrollArea(width, height, true);
2028       }
2029    }
2030
2031    void ComputeLength(EditLine line)
2032    {
2033       int c;
2034       int tabOccur = 0;
2035       int tabWidth;
2036       int x = 0;
2037
2038       for(c = 0; c < line.count; )
2039       {
2040          int len = 1;
2041          int start = c;
2042          int w;
2043          if(c < line.count)
2044          {
2045             byte ch = 0;
2046             int numBytes;
2047             for(len = 0; c < line.count; c += numBytes)
2048             {
2049                ch = line.buffer[c];
2050                numBytes = UTF8_NUM_BYTES(ch);
2051
2052                if(ch == ' ' || ch == '\t')
2053                {
2054                   if(!len) c++;
2055                   break;
2056                }
2057                len += numBytes;
2058             }
2059             if(!len && ch == ' ')
2060             {
2061                len = 1;
2062                w = space.w;
2063             }
2064             else if(!len && ch == '\t')
2065             {
2066                w = (tabSize * space.w) - (x % (tabSize * space.w));
2067                len = 1;
2068             }
2069             else
2070                FontExtent(display, font, line.buffer + start, len, &w, null);
2071          }
2072          else
2073          {
2074             w = space.w;
2075             c++;
2076          }
2077          x += w;
2078       }
2079       line.length = x;
2080
2081       if(line.length > this.maxLength)
2082       {
2083          this.maxLine = line;
2084          this.maxLength = line.length;
2085
2086          if(style.autoSize) AutoSize();
2087       }
2088    }
2089
2090    void FindMaxLine()
2091    {
2092       EditLine line;
2093
2094       this.maxLength = 0;
2095       this.maxLine = null;
2096
2097       for(line = lines.first; line; line = line.next)
2098       {
2099          if(line.length > this.maxLength)
2100          {
2101             this.maxLength = line.length;
2102             this.maxLine = line;
2103          }
2104       }
2105
2106       if(style.autoSize) AutoSize();
2107    }
2108
2109    void SelDirty()
2110    {
2111       if(this.selY != this.y)
2112          DirtyAll();
2113       else if(this.selX != this.x)  // commented out to erase caret: if(this.selX != this.x)
2114          DirtyLine(this.y);
2115    }
2116
2117    void ComputeColumn()
2118    {
2119       // Adjust new edit X position according to tabs
2120       int c, position = 0;
2121       unichar ch;
2122       int nb;
2123       for(c = 0; c<this.line.count && c<this.x && (ch = UTF8_GET_CHAR(this.line.buffer + c, nb)); c+= nb)
2124       {
2125          // TODO: MIGHT WANT TO RETHINK WHAT COLUMN SHOULD BE REGARDING TABS
2126          if(ch == '\t')
2127             position += this.tabSize - (position % this.tabSize);
2128          else
2129             position ++;
2130       }
2131       position += this.x - c;
2132       this.col = position;
2133    }
2134
2135    int DelCh(EditLine l1, int y1, int c1, EditLine l2, int y2, int c2, bool placeAfter)
2136    {
2137       return _DelCh(l1, y1, c1, l2, y2, c2, placeAfter, null);
2138    }
2139
2140    bool HasCommentOrEscape(EditLine line)
2141    {
2142       bool hadComment = strstr(line.buffer, "/*") || strstr(line.buffer, "*/");
2143       int c;
2144
2145       if(!hadComment)
2146       {
2147          for(c = line.count-1; c >= 0; c--)
2148          {
2149             char ch = line.buffer[c];
2150             if(ch == '\\')
2151             {
2152                hadComment = true;
2153                break;
2154             }
2155             else //if(ch != ' ' && ch != '\t')
2156                break;
2157          }
2158       }
2159       return hadComment;
2160    }
2161
2162    int _DelCh(EditLine l1, int y1, int c1, EditLine l2, int y2, int c2, bool placeAfter, int * addedSpacesPtr)
2163    {
2164       EditLine line = l1, next;
2165       char * buffer;
2166       int oldCount1, oldCount2;
2167       int y, firstViewY, firstY, firstDropY, firstSelY;
2168       int newLineCount;
2169       int extras = 0;
2170       DelTextAction action = null;
2171       bool hadComment = false;
2172       int start = c1;
2173
2174       if(style.syntax)
2175          hadComment = HasCommentOrEscape(line);
2176
2177       if(y2 > y1 || c2 > c1)
2178       {
2179          if(start < l1.count)
2180          {
2181             while(!UTF8_IS_FIRST(l1.buffer[start]) && start)
2182                start--;
2183          }
2184       }
2185       oldCount1 = l1.count;
2186       buffer = l1.buffer;
2187       while(c1 < oldCount1)
2188       {
2189          byte ch = buffer[c1];
2190          if(UTF8_IS_FIRST(ch)) break;
2191          c1--;
2192          extras++;
2193       }
2194       oldCount2 = l2.count;
2195       buffer = l2.buffer;
2196       while(c2 < oldCount2)
2197       {
2198          byte ch = buffer[c2];
2199          if(UTF8_IS_FIRST(ch)) break;
2200          c2++;
2201          extras++;
2202       }
2203
2204       if(!undoBuffer.dontRecord && (y2 > y1 || c2 > c1))
2205       {
2206          int len;
2207          char * string;
2208
2209          len = GetText(null, l1, y1, start, l2, y2, c2, false, false);
2210          string = new char[len];
2211          action = DelTextAction { y1 = y1, x1 = start, y2 = y2, x2 = c2, string = string, placeAfter = placeAfter };
2212          GetText(string, l1, y1, start, l2, y2, c2, false, false);
2213          Record(action);
2214       }
2215
2216       //oldCount1 = l1.count;
2217       //oldCount2 = l2.count;
2218
2219       {
2220          BufferLocation before = { l1,y1,c1}, after = { l2,y2,c2 };
2221          NotifyCharsDeleted(master, this, &before, &after, this.pasteOperation);
2222       }
2223
2224       if(c2 > oldCount2) c2 = oldCount2;
2225       if(!(style.freeCaret))
2226          if(c1 > oldCount1) c1 = oldCount1;
2227       newLineCount = c1 + l2.count-c2;
2228       if(l1 == l2)
2229       {
2230          /*
2231          if(!line.size)
2232             printf("");
2233          buffer = new char[line.size ? line.size : 1];
2234          */
2235          buffer = new char[line.size];
2236          if(!buffer) return;
2237          CopyBytes(buffer,l2.buffer,oldCount1 + 1/*line.count + 1*//*line.size*/);
2238       }
2239       else
2240          buffer = l2.buffer;
2241
2242       if(!line.AdjustBuffer(newLineCount))
2243          return;
2244
2245 #ifdef _DEBUG
2246       /*if(newLineCount > 4000 || newLineCount < 0)
2247          printf("Warning");*/
2248 #endif
2249       line.count = newLineCount;
2250
2251       memmove(l1.buffer+c1, buffer+c2,line.count-c1);
2252       if(c1>oldCount1)
2253       {
2254          if(action)
2255          {
2256             action.addedSpaces = c1-oldCount1;
2257 #ifdef _DEBUG
2258             if(action.addedSpaces > action.x1)
2259             {
2260                printf("bug!");
2261             }
2262 #endif
2263          }
2264          if(addedSpacesPtr) *addedSpacesPtr = c1-oldCount1;
2265          FillBytes(l1.buffer+oldCount1,' ',c1-oldCount1);
2266       }
2267       line.buffer[line.count] = '\0';
2268
2269       if(l1 == l2)
2270       {
2271          delete buffer;
2272          DirtyLine(y1);
2273          /*
2274          this.dropX -= c2-c1;
2275          this.dropX = Max(this.dropX,0);
2276          this.x -= c2-c1-1;
2277          this.x = Max(this.x,0);
2278          */
2279       }
2280       else
2281          DirtyEnd(y1);
2282
2283       y = y1;
2284       firstViewY = this.viewY;
2285       firstY = this.y;
2286       firstDropY = this.dropY;
2287       firstSelY = this.selY;
2288       for(line = l1;line;line = next, y++)
2289       {
2290          next = line.next;
2291          if(line!=l1)
2292          {
2293             this.lineCount--;
2294             delete line.buffer;
2295
2296             if(line == this.viewLine)
2297             {
2298                if(this.viewLine.next)
2299                {
2300                   this.viewLine = this.viewLine.next;
2301                   //this.viewY++;
2302                   style.recomputeSyntax = true;
2303                }
2304                else
2305                {
2306                   this.viewLine = this.viewLine.prev;
2307                   this.viewY--;
2308                   style.recomputeSyntax = true;
2309                }
2310             }
2311             else if(y < firstViewY)
2312                this.viewY--;
2313             if(line == this.line)
2314             {
2315                if(this.line.next)
2316                {
2317                   this.line = this.line.next;
2318                   this.x = this.line.count;
2319                   //this.y++;
2320                }
2321                else
2322                {
2323                   this.line = this.line.prev;
2324                   this.x = this.line.count;
2325                   this.y--;
2326                }
2327                ComputeColumn();
2328             }
2329             else if(y < firstY)
2330                this.y--;
2331             if(line == this.dropLine)
2332             {
2333                if(this.dropLine.next)
2334                {
2335                   this.dropLine = this.dropLine.next;
2336                   this.dropX = this.dropLine.count;
2337                }
2338                else
2339                {
2340                   this.dropLine = this.dropLine.prev;
2341                   this.dropX = this.dropLine.count;
2342                   this.dropY--;
2343                }
2344             }
2345             else if(y < firstDropY)
2346                this.dropY--;
2347             if(line == this.selLine)
2348             {
2349                if(this.selLine.next)
2350                {
2351                   this.selLine = this.selLine.next;
2352                   this.selX = this.selLine.count;
2353                }
2354                else
2355                {
2356                   this.selLine = this.selLine.prev;
2357                   this.selX = this.selLine.count;
2358                   this.selY--;
2359                }
2360             }
2361             else if(y < firstSelY)
2362                this.selY--;
2363             lines.Delete(line);
2364          }
2365          if(line == l2) break;
2366       }
2367       ComputeLength(l1);
2368       FindMaxLine();
2369       if(style.syntax && (hadComment || HasCommentOrEscape(this.line)))
2370       {
2371          DirtyAll();
2372          style.recomputeSyntax = true;
2373       }
2374       return extras;
2375    }
2376
2377    bool DelSel(int * addedSpacesPtr)
2378    {
2379       if(this.line != this.selLine || this.x != this.selX)
2380       {
2381          if(this.selY < this.y)
2382          {
2383             _DelCh(this.selLine, this.selY, this.selX, this.line, this.y, this.x, true, addedSpacesPtr);
2384             this.x = this.selX;
2385             this.y = this.selY;
2386             this.line = this.selLine;
2387          }
2388          else if(this.selY > this.y)
2389          {
2390             _DelCh(this.line, this.y, this.x, this.selLine, this.selY, this.selX, false, addedSpacesPtr);
2391             this.selX = this.x;
2392             this.selY = this.y;
2393             this.selLine = this.line;
2394          }
2395          else if(this.selX < this.x)
2396          {
2397             _DelCh(this.selLine, this.selY, this.selX, this.line, this.y, this.x, true, addedSpacesPtr);
2398             this.x = this.selX;
2399             this.y = this.selY;
2400             this.line = this.selLine;
2401          }
2402          else
2403          {
2404             _DelCh(this.line, this.y, this.x, this.selLine, this.selY, this.selX, false, addedSpacesPtr);
2405             this.selX = this.x;
2406             this.selY = this.y;
2407             this.selLine = this.line;
2408          }
2409          ComputeColumn();
2410          return true;
2411       }
2412       return false;
2413    }
2414
2415    bool AddToLine(char * stringLine, int count, bool LFComing, int * addedSpacesPtr, int * addedTabsPtr)
2416    {
2417       bool hadComment = false;
2418       // Add the line here
2419       EditLine line = this.line;
2420
2421       // The purpose of this is solely to lock a max number of characters if no HSCROLLING is present
2422       if(!style.hScroll && created)
2423       {
2424          int endX = (style.freeCaret) ? this.x : Min(this.x, line.count);
2425          int max;
2426          int x;
2427          int c;
2428
2429          // Lock if no place to display.
2430          if(LFComing)
2431             max = endX;
2432          else if(endX > this.x)
2433             max = Max(this.x, line.count);
2434          else
2435             max = line.count;
2436
2437          for(x = 0, c = 0; c < max+count; )
2438          {
2439             int w;
2440             int numBytes = 1;
2441             char * string;
2442             if(c < Min(this.x, line.count))
2443                string = line.buffer + c;
2444             else if(c < endX)
2445                string = " ";
2446             else if(c < endX + count)
2447                string = stringLine + c - endX;
2448             else
2449                string = line.buffer + c - endX - count;
2450
2451             if(*string == '\t')
2452             {
2453                w = (tabSize * space.w) - (x % (tabSize * space.w));
2454             }
2455             else
2456             {
2457                numBytes = UTF8_NUM_BYTES(*string);
2458                FontExtent(display, this.font, string, numBytes, &w, null);
2459             }
2460             x += w;
2461
2462             if(x >= clientSize.w)
2463             {
2464                count = c - max;
2465                break;
2466             }
2467             c += numBytes; // - 1;
2468          }
2469       }
2470
2471       if(count > 0)
2472       {
2473          int addedSpaces = 0;
2474          int addedTabs = 0;
2475
2476          // Add blank spaces if EES_FREECARET
2477          if(this.x > line.count)
2478          {
2479             if(style.freeCaret)
2480             {
2481                if(style.useTab)
2482                {
2483                   int wantedPosition;
2484                   int position = 0;
2485                   int c;
2486
2487                   for(c = 0; c<line.count; c++)
2488                   {
2489                      if(this.line.buffer[c] == '\t')
2490                         position += this.tabSize - (position % this.tabSize);
2491                      else
2492                         position ++;
2493                   }
2494                   wantedPosition = position + (this.x - line.count);
2495
2496                   // A tab is too much...
2497                   if(position + (this.tabSize - (position % this.tabSize)) > wantedPosition)
2498                      addedSpaces = wantedPosition - position;
2499                   else
2500                   {
2501                      // Put a first tab
2502                      addedTabs = 1;
2503                      position += this.tabSize - (position % this.tabSize);
2504                      // Add as many tabs as needed
2505                      addedTabs += (wantedPosition - position) / this.tabSize;
2506                      position += (addedTabs-1) * this.tabSize;
2507                      // Finish off with spaces
2508                      addedSpaces = wantedPosition - position;
2509                   }
2510                }
2511                else
2512                   addedSpaces = this.x - line.count;
2513             }
2514          }
2515          this.x = Min(this.x, line.count);
2516
2517          if(line.count + count + addedSpaces + addedTabs > this.maxLineSize)
2518             return false;
2519
2520          DirtyLine(this.y);
2521
2522          if(style.syntax)
2523             hadComment = HasCommentOrEscape(line);
2524
2525          // Adjust the size of the line
2526          if(!line.AdjustBuffer(line.count+count+addedTabs+addedSpaces))
2527             return false;
2528
2529          {
2530             BufferLocation before = { this.line, this.y, this.x }, after = { this.line, this.y, this.x };
2531             bool hasComment;
2532
2533             memmove(line.buffer+this.x+count, line.buffer+this.x,line.count-this.x);
2534             CopyBytes(line.buffer + this.x + addedTabs + addedSpaces, stringLine, count);
2535             if(addedTabs)
2536             {
2537                *addedTabsPtr = addedTabs;
2538                FillBytes(line.buffer+line.count,'\t',addedTabs);
2539 #ifdef _DEBUG
2540       if(addedTabs > 4000 || addedTabs < 0)
2541          printf("Warning");
2542 #endif
2543                line.count += addedTabs;
2544             }
2545             else if(addedTabs)
2546                *addedTabsPtr = 0;
2547             if(addedSpaces)
2548             {
2549                FillBytes(line.buffer+line.count,' ',addedSpaces);
2550 #ifdef _DEBUG
2551       if(addedSpaces > 4000 || addedSpaces < 0)
2552          printf("Warning");
2553 #endif
2554                line.count += addedSpaces;
2555                if(addedSpacesPtr) *addedSpacesPtr = addedSpaces;
2556             }
2557             else if(addedSpacesPtr)
2558                *addedSpacesPtr = 0;
2559 #ifdef _DEBUG
2560       if(count > 4000 || count < 0)
2561          printf("Warning");
2562 #endif
2563             line.count += count;
2564             this.x += count + addedTabs + addedSpaces;
2565             this.selX = this.x;
2566             this.selY = this.y;
2567             this.selLine = this.line;
2568
2569             line.buffer[line.count] = '\0';
2570
2571             ComputeLength(line);
2572             ComputeColumn();
2573
2574             after.x = this.x;
2575
2576             hasComment = HasCommentOrEscape(line);
2577             if(!undoBuffer.insideRedo)
2578             {
2579                int backDontRecord = undoBuffer.dontRecord;
2580                undoBuffer.dontRecord = 0;
2581                NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
2582                undoBuffer.dontRecord = backDontRecord;
2583             }
2584             if(style.syntax && (hadComment || hasComment || line != this.line))
2585             {
2586                style.recomputeSyntax = true;
2587                DirtyAll();
2588             }
2589          }
2590       }
2591       return true;
2592    }
2593
2594    void Emptyline(EditLine line, int y)
2595    {
2596       if(line.next)
2597          DelCh(line, y, 0, line.next, y+1, 0, true);
2598       else
2599          DelCh(line, y, 0, line, y, line.count, true);
2600    }
2601
2602    void GoToEnd(bool deselect)
2603    {
2604       if(this.line)
2605       {
2606          this.line = this.lines.last;
2607          if(this.y != this.lineCount-1)
2608             DirtyAll();
2609          else if (this.x != this.line.count)
2610             DirtyLine(this.lineCount-1);
2611          this.y = this.lineCount-1;
2612          this.x = this.line.count;
2613          ComputeColumn();
2614          if(deselect)
2615             _Deselect();
2616       }
2617    }
2618
2619    void GoToHome(bool deselect)
2620    {
2621       if(this.line)
2622       {
2623          this.line = this.lines.first;
2624          if(this.y != 0)
2625             DirtyAll();
2626          else if (this.x !=0)
2627             DirtyLine(this.lineCount-1);
2628          this.y = 0;
2629          this.x = 0;
2630          this.col = 0;
2631          if(deselect)
2632             _Deselect();
2633       }
2634    }
2635
2636    // Returns true if it needs scrolling
2637    bool FindMouse(int px, int py, int * tx, int * ty, EditLine * tline, bool half)
2638    {
2639       int w;
2640       int c;
2641       int x, y;
2642       EditLine line;
2643       bool needHScroll = false;
2644
2645       if(py < 0)
2646       {
2647          if(this.viewY > 0)
2648          {
2649             y = this.viewY-1;
2650             line = this.viewLine ? (void *)this.viewLine.prev : null;
2651          }
2652          else
2653          {
2654             y = 0;
2655             line = (void *)this.lines.first;
2656          }
2657       }
2658       else
2659       {
2660          py = Min(py, clientSize.h);
2661          py /= this.space.h;
2662          py = Min(py, this.lineCount);
2663          y = this.viewY;
2664          for(c = 0, line = this.viewLine; (line != (void *)this.lines.last && c<py); line = line.next, c++)
2665          {
2666             y++;
2667          }
2668       }
2669
2670       if( (px >= clientSize.w || px < clientSize.w/2) && this.viewX)
2671          needHScroll = true;
2672       px = Max(px,0);
2673       px = Min(px,clientSize.w+this.space.w);
2674
2675       if(tx && line)
2676       {
2677          *tx = AdjustXPosition(line, px + viewX, half, null, MAXINT, 0);
2678       }
2679
2680       if(tline) *tline = line;
2681       if(ty) *ty = y;
2682
2683       // Prevent divide by 0 from non valid this.font
2684       if(!this.space.h)
2685          return (y < this.viewY) || needHScroll;
2686       else
2687          return (y < this.viewY || y >= this.viewY + clientSize.h / this.space.h) || needHScroll;
2688       return false;
2689    }
2690
2691    // Minimal Update Management Functions
2692    void DirtyAll()
2693    {
2694       this.startY = 0;
2695       this.endY = clientSize.h-1;
2696    //   ErrorLog("DirtyAll\n");
2697    }
2698
2699    void DirtyEnd(int y)
2700    {
2701       if((y - this.viewY)*this.space.h < this.startY)
2702          this.startY = (y - this.viewY)*this.space.h+ YOFFSET;
2703       this.endY = clientSize.h-1;
2704       //ErrorLog("DirtyEnd %d\n", y);
2705    }
2706
2707    void DirtyLine(int y)
2708    {
2709       if(y >= this.viewY)
2710       {
2711          if((y - this.viewY)*this.space.h < this.startY)
2712             this.startY = (y - this.viewY)*this.space.h + YOFFSET;
2713          if((y - this.viewY+1)*this.space.h > this.endY)
2714             this.endY = (y - this.viewY+1)*this.space.h-1 + YOFFSET;
2715       }
2716       //ErrorLog("DirtyLine %d\n", y);
2717    }
2718
2719    void UpdateDirty()
2720    {
2721       Box box;
2722
2723       if(style.recomputeSyntax)
2724       {
2725          FigureStartSyntaxStates(lines.first, true);
2726          style.recomputeSyntax = false;
2727       }
2728       box.left  = 0;
2729       if(this.startY > this.endY) return;
2730       if(this.startY <= 0 && this.endY >= clientSize.h-1)
2731       {
2732          Update(null);
2733       }
2734       else
2735       {
2736          box.right = clientSize.w-1;
2737          box.top   = this.startY;
2738          box.bottom  = this.endY;
2739          Update(box);
2740       }
2741       this.startY = clientSize.h;
2742       this.endY = 0;
2743    }
2744
2745    bool IsMouseOnSelection()
2746    {
2747       bool mouseOnSelection = false;
2748
2749       int x, y;
2750       int minY = Min(this.selY, this.y);
2751       int maxY = Max(this.selY, this.y);
2752       int minX = Min(this.selX, this.x);
2753       int maxX = Max(this.selX, this.x);
2754
2755       FindMouse(this.mouseX - this.space.w / 2, this.mouseY, &x, &y, null, false);
2756
2757       if(maxX != minX || maxY != minY)
2758       {
2759          if(y > minY && y < maxY)
2760             mouseOnSelection = true;
2761          else if(y == minY && y == maxY)
2762             mouseOnSelection = (x < maxX && x >= minX);
2763          else if(y == minY)
2764          {
2765             if(y == this.selY)
2766                mouseOnSelection = (x >= this.selX);
2767             else if(y == this.y)
2768                mouseOnSelection = (x >= this.x);
2769          }
2770          else if(y == maxY)
2771          {
2772             if(y == this.selY)
2773                mouseOnSelection = (x < this.selX);
2774             else if(y == this.y)
2775                mouseOnSelection = (x < this.x);
2776          }
2777       }
2778       return mouseOnSelection;
2779    }
2780
2781    void UpdateCaretPosition(bool setCaret)
2782    {
2783       if(line)
2784       {
2785          if(mouseMove || (!overwrite && !style.noCaret))
2786          {
2787             int max = this.mouseMove ? this.dropX : this.x;
2788             int y = this.mouseMove ? this.dropY : this.y;
2789             EditLine line = this.mouseMove ? this.dropLine : this.line;
2790             int c, x = 0;
2791             if(!(style.freeCaret))
2792                max = Min(max, line.count);
2793
2794             if(FontExtent && display)
2795             {
2796                for(c = 0; c < max; )
2797                {
2798                   int len = 1;
2799                   int start = c;
2800                   int w;
2801                   if(c < line.count)
2802                   {
2803                      byte ch = 0;
2804                      int numBytes;
2805                      for(len = 0; c < Min(max, line.count); c += numBytes)
2806                      {
2807                         ch = line.buffer[c];
2808                         numBytes = UTF8_NUM_BYTES(ch);
2809
2810                         if(ch == ' ' || ch == '\t')
2811                         {
2812                            if(!len) c++;
2813                            break;
2814                         }
2815                         len += numBytes;
2816                      }
2817                      if(!len && ch == ' ')
2818                      {
2819                         w = space.w;
2820                         len = 1;
2821                      }
2822                      else if(!len && ch == '\t')
2823                      {
2824                         w = (tabSize * space.w) - (x % (tabSize * space.w));
2825                         len = 1;
2826                      }
2827                      else
2828                         FontExtent(display, this.font, line.buffer + start, len, &w, null);
2829                   }
2830                   else
2831                   {
2832                      w = space.w;
2833                      c++;
2834                   }
2835                   x += w;
2836                }
2837             }
2838             if(setCaret)
2839                caretX = x;
2840             caretY = y * this.space.h;
2841             SetCaret(x + XOFFSET-2, y * space.h + YOFFSET, space.h);
2842          }
2843          else
2844             SetCaret(0, 0, 0);
2845
2846          NotifyCaretMove(master, this, y + 1, x + 1);
2847
2848          SelectionEnables();
2849       }
2850    }
2851
2852    void SelectionEnables()
2853    {
2854       if((x != selX || y != selY) && !selection)
2855       {
2856          if(!style.readOnly)
2857          {
2858             itemEditCut.disabled = false;
2859             itemEditDelete.disabled = false;
2860          }
2861          itemEditCopy.disabled = false;
2862
2863          this.selection = true;
2864       }
2865       else if((x == selX && y == selY) && selection)
2866       {
2867          itemEditCut.disabled = true;
2868          itemEditCopy.disabled = true;
2869          itemEditDelete.disabled = true;
2870
2871          this.selection = false;
2872       }
2873    }
2874
2875    void SetSelectCursor()
2876    {
2877       if(!inactive || !style.noSelect)
2878       {
2879          if(this.mouseMove)
2880             cursor = guiApp.GetCursor(arrow);
2881          else if(this.mouseSelect)
2882             cursor = guiApp.GetCursor(iBeam);
2883          else
2884          {
2885             if(IsMouseOnSelection())
2886                cursor = guiApp.GetCursor(arrow);
2887             else
2888                cursor = guiApp.GetCursor(iBeam);
2889          }
2890       }
2891    }
2892
2893    int AdjustXPosition(EditLine line, int position, bool half, int * px, int max, int sc)
2894    {
2895       int c = sc;
2896       int x = px ? *px : 0;
2897       while(true)
2898       {
2899          int start = c;
2900          int numBytes = 1;
2901          int len = 1;
2902          int w;
2903          if(c < Min(max, line.count))
2904          {
2905             byte ch = 0;
2906             int numBytes;
2907             for(len = 0; c < Min(max, line.count); c += numBytes)
2908             {
2909                ch = line.buffer[c];
2910                numBytes = UTF8_NUM_BYTES(ch);
2911
2912                if(ch == ' ' || ch == '\t')
2913                {
2914                   if(!len) c++;
2915                   break;
2916                }
2917                len += numBytes;
2918             }
2919             if(!len && ch == ' ')
2920             {
2921                w = space.w;
2922                len = 1;
2923             }
2924             else if(!len && ch == '\t')
2925             {
2926                w = (tabSize * space.w) - (x % (tabSize * space.w));
2927                len = 1;
2928             }
2929             else
2930                FontExtent(display, font, line.buffer + start, len, &w, null);
2931          }
2932          else
2933          {
2934             if(style.freeCaret && c < max)
2935                w = space.w;
2936             else
2937             {
2938                if(px) *px = x;
2939                return c;
2940             }
2941             c++;
2942          }
2943          if(x + (((half && len == 1) ? (w / 2) : w)) >= position)
2944          {
2945             int lastW;
2946             while(len > 0)
2947             {
2948                int a = start + len;
2949                lastW = w;
2950                if(a <= line.count)
2951                   while(a > 0 && !UTF8_IS_FIRST(line.buffer[--a]));
2952                else
2953                   a--;
2954                if(a > start)
2955                   FontExtent(display, font, line.buffer + start, a - start, &w, null);
2956                else
2957                   w = 0;
2958                if(position > x + (half ? ((w + lastW) / 2) : lastW)) break;
2959                len = a - start;
2960             }
2961             if(px) *px = x + w;
2962             return Min(this.maxLineSize - 1, start + len);
2963          }
2964          x += w;
2965       }
2966    }
2967
2968    void SetCursorToViewX()
2969    {
2970       bool selecting = this.x != selX || y != selY;
2971
2972       // Horizontal Adjustment
2973       int x = 0;
2974       int c = AdjustXPosition(line, viewX, false, &x, MAXINT, 0);
2975       if(this.x < c)
2976          this.x = x;
2977       else
2978       {
2979          c = AdjustXPosition(line, viewX + clientSize.w - 1, false, &x, MAXINT, c);
2980          if(this.x > c)
2981             this.x = c;
2982      }
2983
2984       if(!selecting)
2985       {
2986          selX = this.x;
2987          selY = y;
2988          selLine = line;
2989       }
2990
2991       UpdateCaretPosition(false);
2992
2993       UpdateDirty();
2994       SetSelectCursor();
2995    }
2996
2997    void SetCursorToViewY()
2998    {
2999       int c, numLines;
3000       EditLine oldLine = this.line;
3001
3002       bool selecting = this.x != this.selX || this.y != this.selY;
3003
3004       numLines = clientSize.h / this.space.h;
3005
3006       // Vertical Adjustment
3007       if(this.viewY > this.y)
3008       {
3009          this.y = this.viewY;
3010          this.line = this.viewLine;
3011       }
3012
3013       if(this.viewY + numLines <=  this.y)
3014       {
3015          EditLine line;
3016
3017          this.y = this.viewY-1;
3018          for(c = 0, line = this.viewLine; line && c<numLines; line = line.next, c++)
3019          {
3020             this.line = line;
3021             this.y++;
3022          }
3023       }
3024
3025       if(this.line != oldLine)
3026       {
3027          this.x = AdjustXPosition(this.line, caretX, true, null, MAXINT, 0);
3028          ComputeColumn();
3029       }
3030
3031       if(!selecting)
3032       {
3033          this.selX = this.x;
3034          this.selY = this.y;
3035          this.selLine = this.line;
3036       }
3037
3038       UpdateCaretPosition(false);
3039
3040       UpdateDirty();
3041       SetSelectCursor();
3042    }
3043
3044    /*
3045    bool SaveFile(char * fileName)
3046    {
3047       File f = eFile_Open(fileName, FO_WRITE);
3048       if(f)
3049       {
3050          Save(f, false);
3051          eWindow_SetModified(false);
3052          eInstance_Delete(f);
3053          return true;
3054       }
3055       return false;
3056    }
3057    */
3058
3059    bool OnActivate(bool active, Window previous, bool * goOnWithActivation, bool direct)
3060    {
3061       if(line)
3062       {
3063          if(!style.multiLine)
3064          {
3065             x = (active && this.active && !style.readOnly) ? line.count : 0;
3066             selX = 0;
3067             ComputeColumn();
3068             SetViewToCursor(true);
3069             DirtyLine(0);
3070             UpdateDirty();
3071          }
3072          // Update(null);
3073          if(!active && modified)
3074          {
3075             modified = false;
3076             if(!NotifyModified(master, this))
3077             {
3078                modified = true;
3079                *goOnWithActivation = false;
3080             }
3081          }
3082       }
3083       if(!active)
3084       {
3085          ReleaseCapture();
3086          if(timer) timer.Stop();
3087
3088          mouseSelect = false;
3089          wordSelect = false;
3090       }
3091       return true;
3092    }
3093
3094    void AutoSize()
3095    {
3096       int aw = maxLength + 12, ah = Max(lineCount, 1) * space.h + 2;
3097       int nw = minClientSize.w, nh = minClientSize.h, xw = maxClientSize.w, xh = maxClientSize.h;
3098       clientSize = { nw && aw < nw ? nw : xw && aw > xw ? xw : aw, nh && ah < nh ? nh : xh && ah > xh ? xh : ah };
3099    }
3100
3101    bool OnResizing(int *w, int *h)
3102    {
3103       if(!*h)
3104          *h = space.h + 2;
3105       if(!*w)
3106          //*w = 80;
3107          *w = space.h * 80 / 14;
3108       return true;
3109    }
3110
3111    void OnResize(int w, int h)
3112    {
3113    /*
3114       if(!hasHorzScroll && !hasVertScroll && viewLine)
3115          SetViewToCursor(true);
3116    */
3117       //if(!hasHorzScroll && !hasVertScroll && viewLine)
3118       if(viewLine)
3119          SetViewToCursor(true);
3120       //else
3121    //     FixScrollArea();
3122    }
3123
3124    bool OnMiddleButtonDown(int x, int y, Modifiers mods)
3125    {
3126       if(style.readOnly) return true;
3127       // We really shouldn't be pasting here, Unix middle button paste is for the selection (not the clipboard), good for the terminal
3128       // Middle button already serves as a dragger as well.
3129       // Paste();
3130       return true;
3131    }
3132
3133    bool OnRightButtonDown(int x, int y, Modifiers mods)
3134    {
3135       this.rightButtonDown = true;
3136       // Copy();
3137       return true;
3138    }
3139
3140    bool OnRightButtonUp(int x, int y, Modifiers mods)
3141    {
3142       // Context Menu
3143       if(!parent.inactive && rightButtonDown)
3144       {
3145          PopupMenu popup;
3146          Menu contextMenu { };
3147
3148          MenuItem { contextMenu, $"Cut\tCtrl+X", t, NotifySelect = itemEditCut.NotifySelect, disabled = !selection || style.readOnly };
3149          MenuItem { contextMenu, $"Copy\tCtrl+C", c, NotifySelect = itemEditCopy.NotifySelect, disabled = !selection };
3150          MenuItem { contextMenu, $"Paste\tCtrl+V", p, NotifySelect = itemEditPaste.NotifySelect, disabled = style.readOnly };
3151          MenuItem { contextMenu, $"Delete\tDel", d, NotifySelect = itemEditDelete.NotifySelect, disabled = !selection || style.readOnly };
3152          MenuDivider { contextMenu };
3153          MenuItem { contextMenu, $"Select All\tCtrl+A", a, NotifySelect = itemEditSelectAll.NotifySelect };
3154
3155          popup = PopupMenu { master = this, menu = contextMenu,
3156    /*
3157             nonClient = true, interim = false, parent = parent,
3158             position = { x + clientStart.x + parent.clientStart.x + position.x, y + cientStart.y + parent.sy + clientStart.y + position.y };
3159    */
3160             position = { x + clientStart.x + absPosition.x - guiApp.desktop.position.x, y + clientStart.y + absPosition.y - guiApp.desktop.position.y }
3161          };
3162          popup.Create();
3163       }
3164       rightButtonDown = false;
3165       return true;
3166    }
3167
3168    bool OnLeftButtonDown(int mx, int my, Modifiers mods)
3169    {
3170       int x,y;
3171       EditLine line;
3172
3173       if(style.noSelect) return true;
3174
3175       // Should we have a separate 'selectOnActivate' style?
3176       if(!mods.isActivate || (style.readOnly && style.multiLine))
3177       {
3178          Capture();
3179          mouseSelect = true;
3180       }
3181
3182       mouseX = mx - XOFFSET;
3183       mouseY = my;
3184
3185       FindMouse(mouseX, mouseY, &x, &y, &line, true);
3186 #ifdef _DEBUG
3187       //PrintLn("OnLeftButtonDown: ", x, ", ", y);
3188 #endif
3189       if(!style.readOnly)
3190       {
3191          if(wordSelect)
3192             mouseMove = false;
3193          else if(IsMouseOnSelection() && !mods.isActivate)
3194          {
3195             DirtyLine(this.y);
3196             mouseMove = true;
3197             dropX = x;
3198             dropY = y;
3199             dropLine = line;
3200          }
3201       }
3202
3203       if(!mouseMove && !wordSelect && (!mods.isActivate || style.multiLine))
3204       {
3205          if(mods.shift && !mods.isActivate)
3206          {
3207             this.x = x;
3208             this.y = y;
3209             this.line = line;
3210             DirtyAll();
3211          }
3212          else
3213          {
3214             SelDirty();
3215             DirtyLine(this.y);
3216             this.x = x;
3217             this.y = y;
3218             this.line = line;
3219             DirtyLine(this.y);
3220             _Deselect();
3221          }
3222          ComputeColumn();
3223       }
3224
3225       UpdateDirty();
3226       UpdateCaretPosition(true);
3227       return true;
3228    }
3229
3230    bool OnLeftButtonUp(int x, int y, Modifiers mods)
3231    {
3232       timer.Stop();
3233
3234       mouseSelect = false;
3235       wordSelect = false;
3236
3237       x -= XOFFSET;
3238
3239       ReleaseCapture();
3240       if(!style.readOnly)
3241       {
3242          if(mouseMove)
3243          {
3244             EditLine line;
3245             FindMouse(mouseX, mouseY, &x, &y, &line, true);
3246 #ifdef _DEBUG
3247             //PrintLn("MouseMove: ", x, ", ", y);
3248 #endif
3249             dropX = x;
3250             dropY = y;
3251             dropLine = line;
3252
3253             mouseMove = IsMouseOnSelection();
3254
3255             if(!mouseMove)
3256             {
3257                int size = SelSize();
3258                if(size)
3259                {
3260                   char * text = new char[size+1];
3261                   if(text)
3262                   {
3263                      int moveX = 0;
3264                      GetSel(text, false);
3265
3266                      if(Max(selY, this.y) == dropY)
3267                      {
3268                         if(this.x > selX)
3269                         {
3270                            if(this.dropX > this.selX)
3271                               moveX = this.x - this.selX;
3272                         }
3273                         else
3274                         {
3275                            if(this.dropX > this.x)
3276                               moveX = this.selX - this.x;
3277                         }
3278                      }
3279                      DelSel(null);
3280                      this.dropX -= moveX;
3281                      this.selX = this.x = this.dropX;
3282                      this.selY = this.y = this.dropY;
3283                      this.selLine = this.line = this.dropLine;
3284                      AddS(text);
3285                      SetViewToCursor(true);
3286                      delete text;
3287                      Modified();
3288 #ifdef _DEBUG
3289                    /*  {
3290                         byte * c = ((EditBox)this).multiLineContents, * c2;
3291                         int l1 = c ? strlen(c) : 0, l2;
3292                         Undo();
3293                         Redo();
3294                         c2 = ((EditBox)this).multiLineContents;
3295                         l2 = c2 ? strlen(c2) : 0;
3296                         if(l1 != l2 || (l1 && strcmp(c, c2)))
3297                         {
3298                            PrintLn("Fail!");
3299                         }
3300                         delete c;
3301                      }
3302                      */
3303 #endif
3304                   }
3305                }
3306             }
3307             else
3308             {
3309                SelDirty();
3310                DirtyLine(this.y);
3311                this.x = x;
3312                this.y = y;
3313                this.line = line;
3314                ComputeColumn();
3315                DirtyLine(this.y);
3316                _Deselect();
3317                UpdateDirty();
3318             }
3319          }
3320          else
3321          {
3322             EditLine line;
3323             mouseX = x;
3324             mouseY = y;
3325
3326             FindMouse(mouseX, mouseY, &x, &y, &line, true);
3327 #ifdef _DEBUG
3328             //PrintLn("Dropped: ", x, ", ", y);
3329 #endif
3330             NotifyDropped(master, this, x, y);
3331          }
3332       }
3333       mouseMove = false;
3334       return true;
3335    }
3336
3337    bool OnMouseMove(int mx, int my, Modifiers mods)
3338    {
3339       int x,y;
3340       EditLine line;
3341       bool needScroll;
3342
3343       if(mods != -1 && mods.isSideEffect)
3344       {
3345          SetSelectCursor();
3346          return true;
3347       }
3348       if(style.noSelect) return true;
3349       if(wordSelect) return true;
3350       mouseX = mx - XOFFSET;
3351       mouseY = my;
3352
3353       needScroll = FindMouse(this.mouseX, this.mouseY, &x, &y, &line, true);
3354
3355       if(this.mouseMove || this.mouseSelect)
3356       {
3357          if(!needScroll)
3358             timer.Stop();
3359          else
3360          {
3361             if(needScroll)
3362                timer.Start();
3363             if(mods != -1 &&
3364                ((style.hScroll) || (style.vScroll)))
3365                return true;
3366          }
3367       }
3368
3369       if(this.mouseMove)
3370       {
3371          DirtyLine(this.dropY);
3372          this.dropX = x;
3373          this.dropY = y;
3374          DirtyLine(this.dropY);
3375          this.dropLine = line;
3376          SetViewToCursor(true);
3377 #ifdef _DEBUG
3378          //PrintLn("MouseMove: ", "dropX = ", x, ", dropY = ", y);
3379 #endif
3380       }
3381       else if(this.mouseSelect)
3382       {
3383          DirtyLine(this.selY);
3384          DirtyLine(this.y);
3385          this.x = x;
3386          this.y = y;
3387          ComputeColumn();
3388          DirtyLine(this.y);
3389          this.line = line;
3390          SetViewToCursor(true);
3391          UpdateDirty();
3392 #ifdef _DEBUG
3393          //PrintLn("MouseSelect: ", "x = ", x, ", y = ", y);
3394 #endif
3395       }
3396       SetSelectCursor();
3397       return true;
3398    }
3399
3400    bool OnLeftDoubleClick(int mx, int my, Modifiers mods)
3401    {
3402       int x,y;
3403       EditLine line;
3404 #ifdef _DEBUG
3405       //PrintLn("OnLeftDoubleClick: ", mx, ", ", my, ", mods = ", mods);
3406 #endif
3407       mx -= XOFFSET;
3408
3409       if(style.noSelect) return true;
3410       FindMouse(mx, my, &x, &y, &line, false);
3411       if(!NotifyDoubleClick(master, this, line, mods))
3412          return false;
3413       if(x < line.count)
3414       {
3415          int c;
3416          int start = -1;
3417          int numBytes;
3418          for(c = x; c >= 0; c--)
3419          {
3420             unichar ch;
3421             while(c > 0 && !UTF8_IS_FIRST(line.buffer[c])) c--;
3422             ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
3423             if(!IS_ALUNDER(ch))
3424                break;
3425             start = c;
3426          }
3427          if(start != -1)
3428          {
3429             for(c = start; c<line.count; c += numBytes)
3430             {
3431                unichar ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
3432                if(!ch || !IS_ALUNDER(ch))
3433                   break;
3434             }
3435             SelDirty();
3436             DirtyLine(this.y);
3437             this.y = y;
3438             DirtyLine(this.y);
3439             this.selX = start;
3440             this.x = c;
3441             ComputeColumn();
3442             this.line = this.selLine = line;
3443             this.wordSelect = (c != start);
3444             UpdateDirty();
3445          }
3446       }
3447       return true;
3448    }
3449
3450    bool OnPostCreate()
3451    {
3452       /*if(isDocument)
3453       {
3454          Menu fileMenu { menu, "File", F };
3455          saveDialog = fileDialog;
3456          MenuItem { fileMenu, $"Save\tCtrl+S", S, CtrlS, NotifySelect = MenuFileSave };
3457          MenuItem { fileMenu, $"Save As...", A, NotifySelect = MenuFileSaveAs };
3458       }*/
3459       if(style.autoSize) AutoSize();
3460       return true;
3461    }
3462
3463    void ComputeFont()
3464    {
3465       if(FontExtent)
3466       {
3467          FontExtent(display, font, " ", 1, (int *)&space.w, (int *)&space.h);
3468          FontExtent(display, font, "W", 1, (int *)&large.w, (int *)&large.h);
3469
3470          space.w = Max(space.w, 1);
3471          large.w = Max(large.w, 1);
3472          space.h = Max(space.h, 1);
3473          large.h = Max(large.h, 1);
3474
3475          {
3476             EditLine line;
3477             for(line = lines.first; line; line = line.next)
3478                ComputeLength(line);
3479             FindMaxLine();
3480          }
3481
3482          if(viewLine)
3483             SetViewToCursor(true);
3484
3485          FixScrollArea();
3486
3487          Update(null);
3488       }
3489    }
3490
3491    bool OnLoadGraphics()
3492    {
3493       FontExtent = Display::FontExtent;
3494       font = fontObject;
3495       ComputeFont();
3496       // UpdateCaretPosition(true);
3497       return true;
3498    }
3499
3500    void OnUnloadGraphics()
3501    {
3502       this.font = null;
3503    }
3504
3505    bool OnKeyHit(Key key, unichar ch)
3506    {
3507       bool shift = (key.shift) ? true : false;
3508 #ifdef _DEBUG
3509       //PrintLn("OnKeyHit: code = ", key.code, ", mods = ", key.modifiers, ", ch = ", (int)ch);
3510 #endif
3511       if(!ch && !key.alt && !key.ctrl)
3512       {
3513          key.code = (SmartKey)key.code;
3514       }
3515       else if(!ch && key.alt)
3516          key.code = 0;
3517
3518       switch(key.code) //(ch || key.alt || key.ctrl) ? key.code : (Key)(SmartKey)key.code)
3519       {
3520          case backSpace:
3521             if(style.readOnly) break;
3522             if(style.stuckCaret) GoToEnd(true);
3523             if(!(style.freeCaret))
3524             {
3525                this.x = Min(this.x, this.line.count);
3526             }
3527             if(key.ctrl)
3528             {
3529                int y;
3530                bool done = false;
3531                EditLine line = this.line;
3532                int c;
3533                for(y = this.y; y>= 0; y--)
3534                {
3535                   c = (y == this.y) ? (this.x-1) : line.count-1;
3536
3537                   // Slow down when going on lines...
3538                   if(y != this.y) break;
3539
3540                   for(; c>=0; c--)
3541                   {
3542                      //if(this.line.buffer[c] != '\t' && this.line.buffer[c] != ' ')
3543                      if(IS_ALUNDER(line.buffer[c]))
3544                         break;
3545                   }
3546                   for(; c>=0; c--)
3547                   {
3548                      if(!IS_ALUNDER(line.buffer[c]))
3549                      {
3550                      //if(this.line.buffer[c] == '\t' || this.line.buffer[c] == ' ')
3551                         done = true;
3552                         break;
3553                      }
3554                   }
3555                   if(done)
3556                      break;
3557                   if(line.prev)
3558                      line = line.prev;
3559                   else
3560                      break;
3561                }
3562                //if(this.x != 0)
3563                {
3564                   DelCh(line,y,c+1,this.line,this.y,this.x, true);
3565                   this.x = this.selX = Min(c+1, line.count);
3566                   this.y = this.selY = y;
3567                   this.line = this.selLine = line;
3568                   SetViewToCursor(true);
3569                }
3570                ComputeColumn();
3571             }
3572             else
3573             {
3574                BackSpace();
3575             }
3576             return false;
3577             //break;
3578          case del:
3579             if(style.readOnly) break;
3580             if(style.stuckCaret) break;
3581
3582             if(shift)
3583             {
3584                Cut();
3585             }
3586             else
3587             {
3588                // Delete selection
3589                if(this.line != this.selLine || this.x != this.selX)
3590                {
3591                   DelSel(null);
3592                   SetViewToCursor(true);
3593                   Modified();
3594                }
3595                // Delete word
3596                else if(key.ctrl)
3597                {
3598                   if(this.x < this.line.count)
3599                   {
3600                      int i;
3601                      int length;
3602                      for(i = this.x; i < this.line.count; i++)
3603                      {
3604                         if(!IS_ALUNDER(this.line.buffer[i]))
3605                            break;
3606                      }
3607
3608                      for(; i < this.line.count; i++)
3609                      {
3610                         //Delete trailing whitespace
3611                         if(IS_ALUNDER(this.line.buffer[i]))
3612                            break;
3613                      }
3614                      DelCh(this.line, this.y, this.x, this.line, this.y, i, false);
3615                      SetViewToCursor(true);
3616                      Modified();
3617                   }
3618                   else if(this.line.next)
3619                   {
3620                      DelCh(this.line, this.y, this.x, this.line.next, this.y+1, 0, false);
3621                      SetViewToCursor(true);
3622                      Modified();
3623                   }
3624                }
3625                else
3626                {
3627                   if(!(style.freeCaret))
3628                   {
3629                      this.selX = this.x = Min(this.x, this.line.count);
3630                      ComputeColumn();
3631                   }
3632                   if(this.x < this.line.count)
3633                   {
3634                      DelCh(this.line, this.y, this.x, this.line, this.y, this.x+1, false);
3635                   }
3636                   else if(this.line.next)
3637                   {
3638                      DelCh(this.line, this.y, this.x, this.line.next, this.y+1, 0, false);
3639                   }
3640                   SetViewToCursor(true);
3641                   Modified();
3642                }
3643             }
3644             return false;
3645             //break;
3646          case enter:
3647          case keyPadEnter:
3648          {
3649             if(!key.alt && !key.ctrl)
3650             {
3651                int c;
3652                int position = 0;
3653                bool stuffAfter = false;
3654                char * addString;
3655                int len = 0;
3656
3657                if(style.stuckCaret) GoToEnd(true);
3658                if(style.readOnly) break;
3659                if(!(style.multiLine)) break;
3660
3661                for(c = 0; c<this.line.count && c<this.x; c++)
3662                {
3663                   if(this.line.buffer[c] == '\t')
3664                      position += this.tabSize - (position % this.tabSize);
3665                   else if(this.line.buffer[c] == ' ')
3666                      position++;
3667                   else
3668                      break;
3669                }
3670                if(!line.count)
3671                   position = x;
3672
3673                if(this.x < this.line.count)
3674                   stuffAfter = true;
3675
3676                //If last character is a { indent one tab
3677                if(this.line.buffer[this.x - 1] == '{')
3678                {
3679                   //Except if the next non space character is a }
3680                   bool indent = false;
3681                   int i;
3682                   for(i = this.x; i < this.line.size; i++)
3683                      if(this.line.buffer[i] != ' ' && this.line.buffer[i] != '\t')
3684                      {
3685                         if(this.line.buffer[i] != '}')
3686                            indent = true;
3687                         break;
3688                      }
3689                   if(indent)
3690                      position += this.tabSize;
3691                }
3692
3693                addString = new char[position + 2];
3694                addString[len++] = '\n';
3695                addString[len] = '\0';
3696
3697                if(stuffAfter || !style.freeCaret)
3698                {
3699                   for(c = 0; c<position; )
3700                   {
3701                      if(style.useTab && c + this.tabSize <= position)
3702                      {
3703                         addString[len++] = '\t';
3704                         c += this.tabSize;
3705                      }
3706                      else
3707                      {
3708                         addString[len++] = ' ';
3709                         c++;
3710                      }
3711                   }
3712                   addString[len] = '\0';
3713                }
3714                if(AddS(addString))
3715                {
3716                   if(!stuffAfter && style.freeCaret)
3717                   {
3718                      this.x = this.selX = position;
3719                      ComputeColumn();
3720                   }
3721                   FindMaxLine();
3722                   SetViewToCursor(true);
3723                   Modified();
3724                }
3725                delete addString;
3726                return false;
3727             }
3728             break;
3729          }
3730          case left:
3731          {
3732             if(style.stuckCaret) break;
3733             if(!(style.freeCaret))
3734             {
3735                this.x = Min(this.x, this.line.count);
3736                this.selX = Min(this.selX, this.selLine.count);
3737                ComputeColumn();
3738             }
3739             if(!shift) SelDirty();
3740             if(key.ctrl)
3741             {
3742                bool foundAlpha = false;
3743                bool found = false;
3744                int y = this.y;
3745                EditLine line, lastLine;
3746                int lastC, lastY;
3747
3748                for(line = this.line; (line && !found); line = line.prev, y--)
3749                {
3750                   int start;
3751                   int c;
3752
3753                   if(this.x == 0 && line != this.line)
3754                   {
3755                      foundAlpha = true;
3756                      lastC = line.count;
3757                      lastY = y;
3758                      lastLine = line;
3759                      break;
3760                   }
3761
3762                   if(line == this.line) start = this.x -1; else start = line.count-1;
3763                   start = Min(start, line.count-1);
3764
3765                   for(c = start; c >= 0;)
3766                   {
3767                      int numBytes;
3768                      unichar ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
3769                      if(IS_ALUNDER(ch))
3770                      {
3771                         foundAlpha = true;
3772                         lastC = c;
3773                         lastY = y;
3774                         lastLine = line;
3775                      }
3776                      else
3777                      {
3778                         if(foundAlpha)
3779                         {
3780                            found = true;
3781                            break;
3782                         }
3783                      }
3784                      while(--c)
3785                      {
3786                         byte ch = line.buffer[c];
3787                         if(UTF8_IS_FIRST(ch)) break;
3788                      }
3789
3790                   }
3791                   // No next word found,
3792                   if(!found && ( this.x > 0 || (!line.count && this.x)))
3793                   {
3794                      foundAlpha = true;
3795                      lastC = 0;
3796                      lastY = y;
3797                      lastLine = line;
3798                      break;
3799                   }
3800                }
3801                if(foundAlpha)
3802                {
3803                   DirtyLine(this.y);
3804                   this.x = lastC;
3805                   this.y = lastY;
3806                   this.line = lastLine;
3807                   DirtyLine(this.y);
3808                   ComputeColumn();
3809                }
3810             }
3811             else
3812             {
3813                if(x > 0)
3814                {
3815                   if(x <= line.count)
3816                   {
3817                      byte * buffer = line.buffer;
3818                      while(--x)
3819                      {
3820                         byte ch = buffer[x];
3821                         if(UTF8_IS_FIRST(ch)) break;
3822                      }
3823                   }
3824                   else
3825                      x--;
3826                   DirtyLine(y);
3827                }
3828                else if(line.prev)
3829                {
3830                   line = line.prev;
3831                   DirtyLine(y);
3832                   x = line.count;
3833                   y--;
3834                   DirtyLine(y);
3835                }
3836                ComputeColumn();
3837             }
3838             if(!shift) _Deselect();
3839             SetViewToCursor(true);
3840             //break;
3841             return false;
3842          }
3843          case right:
3844          {
3845             if(style.stuckCaret) break;
3846             if(!(style.freeCaret))
3847             {
3848                this.x = Min(this.x, this.line.count);
3849                this.selX = Min(this.selX, this.selLine.count);
3850                ComputeColumn();
3851             }
3852             if(!shift) SelDirty();
3853             if(!shift && (this.x != this.selX || this.y != this.selY));
3854             else if(key.ctrl)
3855             {
3856                bool onAChar = false;
3857                if(this.selX != this.x || this.selY != this.y)
3858                   onAChar = true;
3859                if(this.x<this.line.count)
3860                   if(this.line.buffer[this.x] != '\t' && this.line.buffer[this.x] !=' ')
3861                      onAChar = true;
3862                if(key.shift && onAChar &&
3863                   ((this.y > this.selY)||((this.selY == this.y)&&(this.x >= this.selX))))
3864                {
3865                   bool foundAlpha = false;
3866                   bool found = false;
3867                   EditLine line, lastLine;
3868                   int y = this.y;
3869                   int lastC, lastY, lastNumBytes;
3870
3871                   for(line = this.line; (line && !found); line = line.next, y++)
3872                   {
3873                      int start = (line == this.line) ? this.x : 0;
3874                      int c;
3875                      int numBytes;
3876                      unichar ch;
3877                      for(c = start; c < line.count && (ch = UTF8_GET_CHAR(line.buffer + c, numBytes)); c += numBytes)
3878                      {
3879                         if(IS_ALUNDER(ch))
3880                         {
3881                            foundAlpha = true;
3882                            lastC = c;
3883                            lastNumBytes = numBytes;
3884                            lastY = y;
3885                            lastLine = line;
3886                         }
3887                         else if(foundAlpha)
3888                         {
3889                            found = true;
3890                            break;
3891                         }
3892                      }
3893                      if(!found && (c != this.x || line != this.line))
3894                      {
3895                         found = true;
3896                         lastLine = line;
3897                         lastC = line.count;
3898                         lastNumBytes = 0;
3899                         lastY = y;
3900                         break;
3901                      }
3902                   }
3903                   if(found)
3904                   {
3905                      DirtyLine(this.y);
3906                      this.x = lastC + lastNumBytes;
3907                      this.y = lastY;
3908                      this.line = lastLine;
3909                      DirtyLine(this.y);
3910                      ComputeColumn();
3911                   }
3912                }
3913                else
3914                {
3915                   bool foundAlpha = false;
3916                   bool found = false;
3917                   EditLine line;
3918                   int y = this.y;
3919
3920                   for(line = this.line; (line && !found); line = line.next, y++)
3921                   {
3922                      int start = (line == this.line) ? this.x : 0;
3923                      int c;
3924                      int numBytes;
3925                      unichar ch;
3926                      for(c = start; c < line.count && (ch = UTF8_GET_CHAR(line.buffer + c, numBytes)); c += numBytes)
3927                      {
3928                         if(!IS_ALUNDER(ch))
3929                            foundAlpha = true;
3930                         else if(foundAlpha)
3931                         {
3932                            found = true;
3933                            DirtyLine(this.y);
3934                            this.x = c;
3935                            this.y = y;
3936                            this.line = line;
3937                            DirtyLine(this.y);
3938                            ComputeColumn();
3939                            break;
3940                         }
3941                      }
3942                      // No next word found,
3943                      if(!found && (c != this.x || line != this.line))
3944                      {
3945                         found = true;
3946                         DirtyLine(this.y);
3947                         this.x = line.count;
3948                         this.y = y;
3949                         this.line = line;
3950                         DirtyLine(this.y);
3951                         ComputeColumn();
3952                      }
3953                      foundAlpha = true;
3954                   }
3955                }
3956             }
3957             else
3958             {
3959                if(x < line.count || (style.freeCaret && line.count < maxLineSize))
3960                {
3961                   if(x < line.count)
3962                   {
3963                      byte * buffer = line.buffer;
3964                      while(++x)
3965                      {
3966                         byte ch = buffer[x];
3967                         if(UTF8_IS_FIRST(ch)) break;
3968                      }
3969                   }
3970                   else
3971                      x++;
3972                   ComputeColumn();
3973                   DirtyLine(y);
3974                }
3975                else
3976                {
3977                   if(line.next)
3978                   {
3979                      DirtyLine(y);
3980                      line = line.next;
3981                      y++;
3982                      x = 0;
3983                      col = 0;
3984                      DirtyLine(y);
3985                   }
3986                }
3987             }
3988             if(!shift) _Deselect();
3989             SetViewToCursor(true);
3990             // break;
3991             return false;
3992          }
3993          case up:
3994             if(key.ctrl)
3995             {
3996                if(!style.vScroll || hasVertScroll) break;
3997                LineUp();
3998                return false;
3999             }
4000             else
4001             {
4002                if(style.stuckCaret) break;
4003
4004                if(!shift) SelDirty();
4005                DirtyLine(this.y);
4006
4007                if(style.wrap)
4008                {
4009                }
4010                else if(line.prev)
4011                {
4012                   line = line.prev;
4013                   this.y--;
4014                   this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
4015                }
4016
4017                DirtyLine(this.y);
4018                if(!shift) _Deselect();
4019                ComputeColumn();
4020                SetViewToCursor(false);
4021
4022                /*
4023                if(caretY == this.y * space.h)
4024                {
4025                   if(line.prev)
4026                   {
4027                      line = line.prev;
4028                      this.y--;
4029                      if(!style.freeCaret)
4030                         this.x = Min(this.x, line.count);
4031                      caretY = MAXINT;
4032                   }
4033                   else
4034                      return false;
4035                }
4036
4037                {
4038                   int th = space.h;
4039                   int textPos = 0;
4040                   int sx = 0, sy = this.y * space.h;
4041                   char * text = line.text;
4042                   int maxW = clientSize.w - sx;
4043                   display.FontExtent(font, " ", 1, null, &th);
4044
4045                   do
4046                   {
4047                      int startPos = textPos;
4048                      int width = 0;
4049                      int x = 0;
4050                      bool lineComplete = false;
4051
4052                      if(!style.wrap && caretY == MAXINT)
4053                      {
4054                         caretY = sy + th;
4055                         //textPos = line.count;
4056                         //lineComplete = true;
4057                      }
4058
4059                      for(; (style.freeCaret || textPos < line.count) && !lineComplete;)
4060                      {
4061                         int w = 0;
4062                         int len;
4063                         char * nextSpace = (textPos < line.count) ? strchr(text + textPos, ' ') : (text + textPos);
4064
4065                         if(nextSpace)
4066                            len = (nextSpace - (text + textPos));
4067                         else
4068                            len = line.count - textPos;
4069
4070                         if(textPos < line.count)
4071                         {
4072                            display.FontExtent(font, text + textPos, len, &w, null);
4073                         }
4074                         if(nextSpace) { w += space.w; len++; }
4075
4076                         if(style.wrap && x + width + w > maxW && x > 0)
4077                         {
4078                            lineComplete = true;
4079                            break;
4080                         }
4081                         textPos += len;
4082                         width += w;
4083                         if(nextSpace)
4084                         {
4085                            x += width;
4086                            width = 0;
4087                         }
4088                         if((!style.freeCaret && textPos >= line.count) || (sy == caretY - th && caretX <= x + width + sx))
4089                         {
4090                            x += width;
4091                            this.x = textPos;
4092                            while(this.x > 0 && x + sx > caretX && this.x > startPos)
4093                            {
4094                               int len;
4095                               if(this.x > line.count)
4096                                  this.x--;
4097                               else
4098                                  while(this.x > 0 && !UTF8_IS_FIRST(text[--this.x]));
4099                               len = this.x - startPos;
4100                               display.FontExtent(font, text + startPos, len, &x, null);
4101                            }
4102
4103                            DirtyLine(this.y);
4104                            if(!shift) _Deselect();
4105                            ComputeColumn();
4106                            SetViewToCursor(false);
4107                            return false;
4108                         }
4109                      }
4110                      if(sy == caretY - th || textPos >= line.count)
4111                      {
4112                         if(textPos >= line.count)
4113                         {
4114                            int c = textPos - 1;
4115                            while(c > 0 && text[c] == ' ') c--;
4116                            this.x = c + 1;
4117                         }
4118                         else
4119                            this.x = line.count;
4120
4121                         DirtyLine(this.y);
4122                         if(!shift) _Deselect();
4123                         ComputeColumn();
4124                         SetViewToCursor(false);
4125                         return false;
4126                      }
4127                      sy += th;
4128                      sx = 0;
4129                   } while(textPos < line.count);
4130
4131                   DirtyLine(this.y);
4132                   if(!shift) _Deselect();
4133                   ComputeColumn();
4134                   SetViewToCursor(false);
4135                   return false;
4136                }
4137                */
4138
4139                // PREVIOUS CODE
4140                /*
4141                if(this.line.prev)
4142                {
4143                   int x = AdjustXPosition(this.line, this.line.prev, true, null, MAXINT, 0);
4144                   if(!shift) SelDirty();
4145                   this.line = this.line.prev;
4146                   DirtyLine(this.y);
4147                   this.y--;
4148
4149                   DirtyLine(this.y);
4150                   this.x = x;
4151                   if(!shift) _Deselect();
4152
4153                   ComputeColumn();
4154
4155                   SetViewToCursor(false);
4156                }
4157                */
4158
4159             }
4160             // break;
4161             return style.multiLine ? false : true;
4162          case down:
4163             if(key.ctrl)
4164             {
4165                if(!style.vScroll || hasVertScroll)
4166                   break;
4167                LineDown();
4168                return false;
4169             }
4170             else
4171             {
4172                if(style.stuckCaret) break;
4173                {
4174                   int th = space.h;
4175                   int textPos = 0;
4176                   int sx = 0, sy = this.y * this.space.h;
4177                   int maxW = clientSize.w - sx;
4178                   char * text = line.buffer;
4179
4180                   if(!shift) SelDirty();
4181                   DirtyLine(this.y);
4182
4183                   if(style.wrap)
4184                   {
4185                      /*
4186                      if(AdjustXPosition(line, maxW, this.x, line.count, true, null, MAXINT, 0) <= line.count)
4187                      {
4188
4189                      }
4190                      */
4191                   }
4192                   else if(line.next)
4193                   {
4194                      line = line.next;
4195                      this.y++;
4196                      this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
4197                   }
4198
4199                   DirtyLine(this.y);
4200                   if(!shift) _Deselect();
4201                   ComputeColumn();
4202                   SetViewToCursor(false);
4203
4204                   /*
4205                   while(!textPos || (style.freeCaret || textPos<line.count))
4206                   {
4207                      int startPos = textPos;
4208                      int width = 0;
4209                      int x = 0;
4210                      bool lineComplete = false;
4211                      if(!style.wrap && sy <= caretY)
4212                      {
4213                         textPos = line.count;
4214                         lineComplete = true;
4215                      }
4216                      for(; (style.freeCaret || textPos<line.count) && !lineComplete;)
4217                      {
4218                         int w = 0;
4219                         int len;
4220                         char * nextSpace = (textPos < line.count) ? strchr(text + textPos, ' ') : (text + textPos);
4221
4222                         if(nextSpace)
4223                            len = (nextSpace - (text + textPos));
4224                         else
4225                            len = line.count - textPos;
4226
4227                         if(textPos < line.count)
4228                         {
4229                            display.FontExtent(font, text + textPos, len, &w, &th);
4230                         }
4231                         if(nextSpace) { w += space.w; len++; }
4232                         if(style.wrap && x + width + w > maxW && x > 0)
4233                         {
4234                            lineComplete = true;
4235                            break;
4236                         }
4237                         textPos += len;
4238                         width += w;
4239                         if(nextSpace)
4240                         {
4241                            x += width;
4242                            width = 0;
4243                         }
4244                         if(sy > caretY && ((!style.freeCaret && textPos >= line.count) || caretX <= x + width + sx))
4245                         {
4246                            this.x = textPos;
4247                            x += width;
4248                            while(this.x > 0 && x + sx > caretX && textPos > startPos)
4249                            {
4250                               int len;
4251                               if(this.x > line.count)
4252                                  this.x--;
4253                               else
4254                                  while(this.x > 0 && !UTF8_IS_FIRST(text[--this.x]));
4255
4256                               len = this.x - startPos;
4257                               display.FontExtent(font, text + startPos, len, &x, null);
4258                            }
4259
4260                            if(!shift) _Deselect();
4261                            ComputeColumn();
4262
4263                            SetViewToCursor(false);
4264                            return false;
4265                         }
4266                      }
4267                      if(sy > caretY)
4268                      {
4269                         this.x = line.count;
4270
4271                         DirtyLine(this.y);
4272                         if(!shift) _Deselect();
4273                         ComputeColumn();
4274
4275                         SetViewToCursor(false);
4276                         return false;
4277                      }
4278                      else if(textPos >= line.count && line.next)
4279                      {
4280                         startPos = 0;
4281                         textPos = 0;
4282                         line = line.next;
4283                         this.y++;
4284                         sy = this.y * this.space.h;
4285                         sx = 0; //textBlock.startX;
4286                         text = line.buffer;
4287                      }
4288                      else
4289                      {
4290                         sy += th;
4291                         sx = 0; //textBlock.startX;
4292                      }
4293                   }
4294                   */
4295                   /*
4296                   if(line.next)
4297                   {
4298                      line = line.next;
4299                      this.x = Min(this.x, line.count);
4300                      //int x = AdjustXPosition(this.line, this.line.next, true, null, MAXINT, 0);
4301                      this.y++;
4302                      if(!shift) _Deselect();
4303                      ComputeColumn();
4304
4305                      if(this.selX != this.x || this.selY != this.y)
4306                         DirtyLine(this.y);
4307
4308                      SetViewToCursor(false);
4309                   }
4310                   */
4311                }
4312                /* PREVIOUS CODE
4313                if(this.line.next)
4314                {
4315                   int x = AdjustXPosition(this.line, this.line.next, true, null, MAXINT, 0);
4316                   if(!shift) SelDirty();
4317                   this.line = this.line.next;
4318                   DirtyLine(this.y);
4319                   this.y++;
4320                   this.x = x;
4321                   if(!shift) _Deselect();
4322                   ComputeColumn();
4323
4324                   if(this.selX != this.x || this.selY != this.y)
4325                      DirtyLine(this.y);
4326
4327                   SetViewToCursor();
4328                }
4329                */
4330             }
4331             // break;
4332             return style.multiLine ? false : true;
4333          case home:
4334          {
4335             if(style.stuckCaret) break;
4336             if(!style.multiLine && key.ctrl) break;
4337             if(!(style.freeCaret))
4338                this.selX = Min(this.selX, this.selLine.count);
4339
4340             if(!shift) SelDirty();
4341             if(key.ctrl)
4342             {
4343                this.line = this.lines.first;
4344                if(this.y != 0 || this.x != 0)
4345                   DirtyAll();
4346                this.y = 0;
4347                this.x = 0;
4348                this.col = 0;
4349             }
4350             else
4351             {
4352                if(style.smartHome)
4353                {
4354                   EditLine line = this.line;
4355                   int c;
4356                   for(c=0; line.buffer[c]; c++)
4357                      if(line.buffer[c] != ' ' && line.buffer[c] != '\t')
4358                         break;
4359                   if(shift && (c != 0 || this.x))
4360                      DirtyLine(this.y);
4361                   if(this.x != c)
4362                      this.x = c;
4363                   else
4364                      this.x = 0;
4365                }
4366                else
4367                {
4368                   if(shift && this.x != 0)
4369                      DirtyLine(this.y);
4370                   this.x = 0;
4371                }
4372                ComputeColumn();
4373             }
4374             if(!shift) _Deselect();
4375             SetViewToCursor(true);
4376             //break;
4377             return false;
4378          }
4379          case end:
4380          {
4381             if(style.stuckCaret) break;
4382             if(!style.multiLine && key.ctrl) break;
4383             if(!style.freeCaret)
4384                this.selX = Min(this.selX, this.selLine.count);
4385
4386             if(!shift) SelDirty();
4387             if(key.ctrl)
4388             {
4389                GoToEnd(false);
4390             }
4391             else if(this.x != this.line.count)
4392             {
4393                this.x = this.line.count;
4394                if(shift)
4395                   DirtyLine(this.y);
4396                ComputeColumn();
4397             }
4398             if(!shift) _Deselect();
4399             SetViewToCursor(true);
4400             //break;
4401             return false;
4402          }
4403          case tab:
4404             if(style.tabKey && !key.ctrl)
4405             {
4406                if(this.selY != this.y && style.tabSel)
4407                {
4408                   EditLine firstLine, lastLine;
4409                   EditLine line;
4410                   int y, x;
4411
4412                   // Do multi line selection tabbing here
4413                   if(this.selY < this.y)
4414                   {
4415                      firstLine = this.selLine;
4416                      lastLine = this.line;
4417                      y = this.selY;
4418                      x = this.x;
4419                   }
4420                   else
4421                   {
4422                      // Selecting going up
4423                      firstLine = this.line;
4424                      lastLine = this.selLine;
4425                      y = this.y;
4426                      x = this.selX;
4427                   }
4428                   ComputeColumn();
4429                   if(shift)
4430                   {
4431                      for(line = firstLine; line; line = line.next, y++)
4432                      {
4433                         if(line != lastLine || x)
4434                         {
4435                            int c;
4436                            int lastC = 0;
4437                            BufferLocation before = { line, y, 0 }, after = { line, y, 0 };
4438
4439                            for(c=0; c<line.count && lastC < this.tabSize; c++, lastC++)
4440                            {
4441                               if(line.buffer[c] == '\t')
4442                               {
4443                                  lastC++;
4444                                  break;
4445                               }
4446                               else if(line.buffer[c] != ' ')
4447                                  break;
4448                            }
4449                            after.x = lastC;
4450
4451                            NotifyCharsDeleted(master, this, &before, &after, this.pasteOperation);
4452                            if(lastC)
4453                            {
4454                               int len = GetText(null, line, y, 0, line, y, lastC, false, false);
4455                               char * string = new char[len];
4456                               DelTextAction action { y1 = y, x1 = 0, y2 = y, x2 = lastC, string = string, placeAfter = true };
4457                               GetText(string, line, y, 0, line, y, lastC, false, false);
4458                               Record(action);
4459                            }
4460                            memmove(line.buffer,line.buffer+lastC,line.size-lastC);
4461                            if(!line.AdjustBuffer(line.count-lastC))
4462                               break;
4463                            line.count-=lastC;
4464                            DirtyLine(y);
4465                         }
4466
4467                         if(line == lastLine) break;
4468                      }
4469                   }
4470                   else
4471                   {
4472                      for(line = firstLine; line; line = line.next, y++)
4473                      {
4474                         if(line.count)
4475                         {
4476                            if(line != lastLine || x)
4477                            {
4478                               if(style.useTab)
4479                               {
4480                                  if(line.count + 1 <= this.maxLineSize)
4481                                  {
4482                                     BufferLocation before = { line, y, 0 }, after = { line, y, 1 };
4483
4484                                     if(!line.AdjustBuffer(line.count+1))
4485                                        break;
4486
4487                                     NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
4488                                     {
4489                                        AddCharAction action { ch = '\t', x = 0, y = y };
4490                                        Record(action);
4491                                     }
4492
4493                                     memmove(line.buffer+1,line.buffer,line.size-1);
4494                                     line.count++;
4495                                     line.buffer[0] = '\t';
4496                                  }
4497                               }
4498                               else
4499                               {
4500                                  if(line.count + this.tabSize <= this.maxLineSize)
4501                                  {
4502                                     int c;
4503                                     BufferLocation before = { line, y, 0 }, after = { line, y, this.tabSize };
4504                                     NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
4505
4506                                     if(!line.AdjustBuffer(line.count+this.tabSize))
4507                                        break;
4508
4509                                     {
4510                                        char * string = new char[this.tabSize + 1];
4511                                        memset(string, ' ', this.tabSize);
4512                                        string[this.tabSize] = '\0';
4513                                        Record(AddTextAction { string = string, x1 = 0, y1 = y, x2 = this.tabSize, y2 = y });
4514                                     }
4515
4516                                     memmove(line.buffer+this.tabSize,line.buffer,line.size-(this.tabSize));
4517                                     line.count+=this.tabSize;
4518                                     for(c=0; c<this.tabSize; c++)
4519                                        line.buffer[c] = ' ';
4520                                  }
4521                               }
4522                               DirtyLine(y);
4523                            }
4524                         }
4525                         if(line == lastLine) break;
4526                      }
4527                   }
4528                   ComputeLength(maxLine);
4529                }
4530                else
4531                {
4532                   if(style.useTab)
4533                   {
4534                      // Insert character
4535                      AddCh('\t');
4536                   }
4537                   else
4538                   {
4539                      int start, c;
4540                      char * addString = new char[this.tabSize + 1];
4541                      int len = 0;
4542                      if(!(style.freeCaret))
4543                      {
4544                         this.x = Min(this.x, this.line.count);
4545                         ComputeColumn();
4546                      }
4547                      // Insert spaces
4548                      start = Min(this.x, this.selX);
4549                      for(c=start; ((c == start) || ((c) % this.tabSize)); c++)
4550                      {
4551                         addString[len++] = ' ';
4552                      }
4553                      addString[len] = 0;
4554                      AddS(addString);
4555                      delete addString;
4556                   }
4557                }
4558                Modified();
4559                SetViewToCursor(true);
4560                return false;
4561             }
4562             break;
4563          case pageDown:
4564             if(style.multiLine)
4565             {
4566                if(key.ctrl)
4567                {
4568                   if(!(style.hScroll) || hasHorzScroll) break;
4569                   if(this.viewX < this.maxLength)
4570                   {
4571                      //this.viewX+=this.space.w*this.tabSize;
4572                      //DirtyAll();
4573                      SetScrollPosition((this.viewX + this.space.w*this.tabSize), this.viewY * this.space.h);
4574                   }
4575                }
4576                else
4577                {
4578                   PageDown();
4579                   DirtyAll();
4580                   if(!shift) _Deselect();
4581                   SetCursorToViewX();
4582                   SetCursorToViewY();
4583                }
4584                return false;
4585             }
4586             break;
4587          case pageUp:
4588             if(style.multiLine)
4589             {
4590                if(key.ctrl)
4591                {
4592                   if(!(style.hScroll) || hasHorzScroll) break;
4593                   if(this.viewX > 0)
4594                   {
4595                      //this.viewX-=this.space.w*this.tabSize;
4596                      //this.viewX = Max(this.viewX,0);
4597                      //DirtyAll();
4598                      SetScrollPosition((this.viewX-this.space.w*this.tabSize), this.viewY * this.space.h);
4599                      // SetCursorToView();
4600                   }
4601                }
4602                else
4603                {
4604                   PageUp();
4605                   DirtyAll();
4606                   if(!shift) _Deselect();
4607                   SetCursorToViewX();
4608                   SetCursorToViewY();
4609                }
4610                return false;
4611             }
4612             break;
4613          case insert:
4614             if(key.ctrl)
4615             {
4616                Copy();
4617                return false;
4618             }
4619             else if(key.shift)
4620             {
4621                if(!(style.readOnly))
4622                   Paste();
4623                return false;
4624             }
4625             else
4626             {
4627                this.overwrite ^= 1;
4628                UpdateCaretPosition(true);
4629                if(this.overwrite)
4630                   SetCaret(0,0,0);
4631                DirtyLine(this.y);
4632                UpdateDirty();
4633                NotifyOvrToggle(master, this, this.overwrite);
4634             }
4635             break;
4636          case hotKey:
4637             break;
4638          default:
4639
4640             switch(key)
4641             {
4642                case ctrlA:
4643                   // Select All
4644                   if(style.noSelect) break;
4645
4646                   {
4647                      //Save current view position
4648                      int tempX = this.viewX;
4649                      int tempY = this.viewY;
4650
4651                      this.selX = 0;
4652                      this.selY = 0;
4653                      this.selLine = this.lines.first;
4654                      this.y = this.lineCount-1;
4655                      this.line = this.lines.last;
4656                      this.x = this.line.count;
4657                      ComputeColumn();
4658                      DirtyAll();
4659                      SetViewToCursor(true);
4660
4661                      //Restore previous view position
4662                      SetScrollPosition(tempX, tempY * this.space.h);
4663
4664                      UpdateDirty();
4665                      SetSelectCursor();
4666                      SelectionEnables();
4667                   }
4668                   // TOCHECK: Was there any good reason why we weren't returning false here?
4669                   return false; // break;
4670                case ctrlC:
4671                   Copy();
4672                   return false;
4673                case ctrlV:
4674                   if(!(style.readOnly))
4675                   {
4676                      Paste();
4677                      return false;
4678                   }
4679                   break;
4680                case ctrlX:
4681                {
4682                   if(style.readOnly) break;
4683                   Cut();
4684                   return false;
4685                }
4686                case ctrlL:
4687                {
4688                   if(style.readOnly) break;
4689                   ClearLine();
4690                   return false;
4691                }
4692                case ctrlZ:
4693                   if(style.readOnly) break;
4694                   Undo();
4695                   return false;
4696                case ctrlY:
4697                   if(style.readOnly) break;
4698                   Redo();
4699                   return false;
4700                default:
4701                   if(style.readOnly) break;
4702                   if(key.shift && key.code == rightBracket)
4703                   {
4704                      //Only indent back if you are exactly at one tab.
4705                      {
4706                         bool whitespace = true;
4707                         int i;
4708                         char * newline;
4709                         int putsize;
4710
4711                         int indentwidth;
4712                         EditLine line = this.line;
4713
4714                         //Only remove one tab if there is nothing else on the line.
4715                         for(i = 0; i < this.line.count; i++)
4716                         {
4717                            if(this.line.buffer[i] != ' ' && this.line.buffer[i] != '\t')
4718                            {
4719                               PutCh(ch);
4720                               return false;
4721                            }
4722                         }
4723
4724                         //Find opening {
4725                         i = 1;
4726                         while(i && line)
4727                         {
4728                            int pos;
4729
4730                            indentwidth = 0;
4731                            for(pos = line.count - 1; pos >= 0; pos--)
4732                            {
4733                               char c = line.buffer[pos];
4734                               if(c == ' ')
4735                                  indentwidth++;
4736                               else if(c == '\t')
4737                                  indentwidth += this.tabSize;
4738                               else
4739                                  //Counting backwards, so when you find a character, indentation is reset
4740                                  indentwidth = 0;
4741                               if(i && c == '}')
4742                                  i++;
4743                               if(i && c == '{')
4744                                  i--;
4745                            }
4746                            line = line.prev;
4747                         }
4748
4749                         //Place the } to get an undo:
4750                         PutCh(ch);
4751
4752                         this.x = this.line.count;
4753                         this.selX = 0;
4754
4755                         if(!style.useTab)
4756                            putsize = indentwidth;
4757                         else
4758                            putsize = indentwidth / this.tabSize + indentwidth % this.tabSize;
4759
4760                         newline = new char[putsize+2];
4761                         newline[putsize] = '}';
4762                         newline[putsize+1] = '\0';
4763
4764                         i = 0;
4765                         if(style.useTab)
4766                            for(; i < indentwidth / this.tabSize; i++)
4767                               newline[i] = '\t';
4768                         for(;i < putsize; i++)
4769                            newline[i] = ' ';
4770
4771                         AddS(newline);
4772
4773                         delete newline;
4774                      }
4775                      return false;
4776                   }
4777                   else if(!key.ctrl && !key.alt && ch != 128 && ch >= 32)
4778                   {
4779                      PutCh(ch);
4780                      return false;
4781                   }
4782                   break;
4783             }
4784             break;
4785       }
4786       return true;
4787    }
4788
4789    void OnHScroll(ScrollBarAction action, int position, Key key)
4790    {
4791 #ifdef _DEBUG
4792       //PrintLn("OnHScroll: ", action, ", pos = ", position, ", key = ", key);
4793 #endif
4794       this.viewX = position;
4795       if(action != setRange)
4796       {
4797          if(!this.mouseMove && style.cursorFollowsView)
4798             SetCursorToViewX();
4799       }
4800       DirtyAll();
4801       UpdateDirty();
4802    }
4803
4804    void OnVScroll(ScrollBarAction action, int position, Key key)
4805    {
4806       int oldViewY = this.viewY;
4807
4808 #ifdef _DEBUG
4809       //PrintLn("OnVScroll: ", action, ", pos = ", position, ", key = ", key);
4810 #endif
4811       position /= this.space.h;
4812
4813       if(position < this.viewY)
4814       {
4815          for(; position < this.viewY && this.viewLine.prev; this.viewLine = this.viewLine.prev, this.viewY--);
4816          style.recomputeSyntax = true;
4817       }
4818       else if(position > this.viewY)
4819       {
4820          EditLine oldViewLine = viewLine;
4821          for(; position > this.viewY && this.viewLine.next; this.viewLine = this.viewLine.next, this.viewY++);
4822          FigureStartSyntaxStates(oldViewLine, false);
4823       }
4824
4825       if(action != setRange)
4826       {
4827          if(!this.mouseMove && style.cursorFollowsView && !SelSize()) SetCursorToViewY();
4828       }
4829
4830       if(this.x != this.selX || this.y != this.selY)
4831          DirtyLine(this.y);
4832
4833       {
4834          Scroll(0, (this.viewY - oldViewY) * this.space.h);
4835
4836          /*
4837          int numLines = clientSize.h / this.space.h;
4838          int y;
4839          if(Abs(this.viewY - oldViewY) < numLines)
4840
4841
4842          if(this.viewY > oldViewY)
4843          {
4844             for(y = oldViewY; y <this.viewY; y++)
4845                DirtyLine(y + numLines);
4846          }
4847          else
4848          {
4849             for(y = this.viewY; y <oldViewY; y++)
4850                DirtyLine(y + numLines);
4851          }*/
4852       }
4853       //DirtyAll();
4854
4855       // Fix dirt of stuff before first line...
4856       if(this.viewY - oldViewY > 0)
4857       {
4858          Box box { 0,0, clientSize.w-1, YOFFSET-1 };
4859          Update(box);
4860       }
4861
4862       UpdateDirty();
4863    }
4864
4865    bool _AddCh(unichar ch, int * addedSpacesPtr, int * addedTabsPtr)
4866    {
4867       EditLine line;
4868       int length, endX;
4869       bool result;
4870       ReplaceTextAction replaceAction = null;
4871       AddCharAction addCharAction = null;
4872       int addedSpaces = 0, addedTabs = 0;
4873
4874       if(ch == '\r') return true;
4875       if(style.stuckCaret /*|EES_READONLY)*/ )
4876          GoToEnd(true);
4877
4878       if(ch == '\n' && !(style.multiLine) && this.line) return false;
4879
4880       if(!undoBuffer.dontRecord)
4881       {
4882          if(selX != x || selY != y)
4883          {
4884             char buffer[5];
4885             char * newString;
4886             char * oldString;
4887             int len = GetText(null, selLine, selY, selX, this.line, y, x, false, false);
4888             oldString = new char[len];
4889             UTF32toUTF8Len(&ch, 1, buffer, 4);
4890             newString = CopyString(buffer);
4891             GetText(oldString, selLine, selY, selX, this.line, y, x, false, false);
4892
4893             replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = false };
4894             if(selY < y || (selY == y && selX < x))
4895             {
4896                replaceAction.x1 = selX;
4897                replaceAction.y1 = selY;
4898                replaceAction.x2 = x;
4899                replaceAction.y2 = y;
4900             }
4901             else
4902             {
4903                replaceAction.x1 = x;
4904                replaceAction.y1 = y;
4905                replaceAction.x2 = selX;
4906                replaceAction.y2 = selY;
4907             }
4908             Record(replaceAction);
4909             undoBuffer.dontRecord++;
4910          }
4911          else
4912          {
4913             addCharAction = AddCharAction { y = y, x = x, ch = ch };
4914             Record(addCharAction);
4915          }
4916       }
4917
4918       if(ch == '\n')
4919       {
4920          DelSel(&addedSpaces);
4921          if(this.lineCount+1 > this.maxLines)
4922          {
4923             if(style.autoEmpty)
4924                Emptyline(this.lines.first,0);
4925             else
4926                return false;
4927          }
4928          if(!(style.autoSize && (!maxClientSize.h || maxClientSize.h > clientSize.h + this.space.h)) && !(style.vScroll))
4929          {
4930             // Make sure it fits, but we need a default line is this.font is too big for window
4931             if(this.space.h * (this.lineCount+1) > clientSize.h && this.line)
4932                return false;
4933          }
4934          if((this.y >= 0) && this.y < this.viewY)
4935          {
4936             this.viewY++;
4937             DirtyAll();
4938          }
4939          line = EditLine { };
4940          if(!line)
4941             return false;
4942          lines.Insert(this.line, line);
4943          line.editBox = this;
4944          line.buffer = null;
4945          line.count = 0;
4946          line.size = 0;
4947          length = 0;
4948
4949          // If we're displacing the lines from a current line ...
4950          if(this.line)
4951          {
4952             if(this.line.buffer)
4953             {
4954                endX = this.x;
4955                if(this.line.count < endX) endX = this.line.count;
4956                length = this.line.count - endX;
4957             }
4958          }
4959          if(!line.AdjustBuffer(length))
4960             return false;
4961          if(this.line)
4962          {
4963             if(this.line.buffer)
4964             {
4965                CopyBytes(line.buffer,this.line.buffer+endX, length+1);
4966 #ifdef _DEBUG
4967       if(endX > 4000 || endX < 0)
4968          printf("Warning");
4969 #endif
4970                this.line.count = endX;
4971                this.line.buffer[this.line.count] = '\0';
4972                this.line.AdjustBuffer(this.line.count);
4973                ComputeLength(this.line);
4974             }
4975          }
4976          {
4977             BufferLocation before = { this.line, this.y, this.x }, after;
4978
4979             this.line = line;
4980             this.x = 0;
4981             this.col = 0;
4982             line.count = length;
4983
4984 #ifdef _DEBUG
4985       if(length > 4000 || length < 0)
4986          printf("Warning");
4987 #endif
4988             ComputeLength(this.line);
4989
4990             DirtyEnd(this.y);
4991             this.y++;
4992             this.lineCount++;
4993             line.buffer[line.count] = '\0';
4994             result = true;
4995
4996             after.line = this.line, after.y = this.y, after.x = this.x;
4997
4998             NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
4999          }
5000       }
5001       else
5002       {
5003          char string[5];
5004          int count = UTF32toUTF8Len(&ch, 1, string, 5);
5005          DelSel(&addedSpaces);
5006          result = AddToLine(string, count, false, addedSpaces ? null : &addedSpaces, &addedTabs);
5007          if(addedSpacesPtr) *addedSpacesPtr = addedSpaces;
5008          if(addedTabsPtr) *addedTabsPtr = addedTabs;
5009       }
5010       this.selX = this.x;
5011       this.selY = this.y;
5012       this.selLine = this.line;
5013
5014       if(replaceAction)
5015       {
5016          replaceAction.x3 = x;
5017          replaceAction.y3 = y;
5018          replaceAction.addedSpaces = addedSpaces;
5019          replaceAction.addedTabs = addedTabs;
5020          undoBuffer.dontRecord--;
5021       }
5022       if(addCharAction)
5023       {
5024          addCharAction.x -= addedTabs * (tabSize-1);
5025          addCharAction.addedSpaces = addedSpaces;
5026          addCharAction.addedTabs = addedTabs;
5027       }
5028       return result;
5029    }
5030
5031 public:
5032
5033    /****************************************************************************
5034                                  EDIT BOX METHODS
5035    ****************************************************************************/
5036
5037
5038    bool AddCh(unichar ch)
5039    {
5040       return _AddCh(ch, null, null);
5041    }
5042
5043    void Modified()
5044    {
5045       this.modified = true;
5046       NotifyUpdate(master, this);
5047       modifiedDocument = true;
5048    }
5049
5050    void Delete(EditLine line1, int y1, int x1, EditLine line2, int y2, int x2)
5051    {
5052       Deselect();
5053       DelCh(line1, y1, x1, line2, y2, x2, false);
5054       SetViewToCursor(true);
5055       UpdateDirty();
5056       Modified();
5057    }
5058
5059    void Undo()
5060    {
5061       undoBuffer.Undo();
5062       itemEditUndo.disabled = undoBuffer.curAction == 0;
5063       itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
5064       if(savedAction == undoBuffer.curAction)
5065       {
5066          modifiedDocument = false;
5067          SetModified(false);
5068          NotifyUnsetModified(master, this);
5069       }
5070    }
5071
5072    void Redo()
5073    {
5074       undoBuffer.Redo();
5075       itemEditUndo.disabled = undoBuffer.curAction == 0;
5076       itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
5077       if(savedAction == undoBuffer.curAction)
5078       {
5079          modifiedDocument = false;
5080          SetModified(false);
5081          NotifyUnsetModified(master, this);
5082       }
5083    }
5084
5085    void Record(UndoAction action)
5086    {
5087       if(!undoBuffer.dontRecord)
5088       {
5089          undoBuffer.Record(action);
5090          itemEditUndo.disabled = undoBuffer.curAction == 0;
5091          itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
5092       }
5093    }
5094
5095    void SelectAll()
5096    {
5097       Select(
5098          this.lines.first, 0,0,
5099          this.lines.last, this.lines.count-1, strlen(((EditLine)this.lines.last).buffer));
5100    }
5101
5102    void Select(EditLine line1, int y1, int x1, EditLine line2, int y2, int x2)
5103    {
5104       SelDirty();
5105       this.selX = x1;
5106       this.selY = y1;
5107       this.selLine = line1 ? (EditLine)line1 : this.lines.first;
5108       this.x = line2 ? x2 : ((EditLine)this.lines.last).count;
5109       this.y = line2 ? y2 : (this.lineCount-1);
5110       this.line = line2 ? (EditLine)line2 : this.lines.last;
5111       ComputeColumn();
5112       SelDirty();
5113       SetViewToCursor(true);
5114       UpdateDirty();
5115    }
5116
5117    // TODO: Fix this vs modifiedDocument window property
5118    void SetModified(bool flag)
5119    {
5120       if(this)
5121       {
5122          this.modified = false;
5123          if(flag && !NotifyModified(master, this))
5124             this.modified = true;
5125       }
5126    }
5127
5128    // BASIC OUTPUT
5129    bool AddS(char * string)
5130    {
5131       if(this)
5132       {
5133          bool ret = true;
5134          char * line;
5135          int c, count;
5136          int addedSpaces = 0, addedTabs = 0;
5137          AddTextAction action = null;
5138          ReplaceTextAction replaceAction = null;
5139
5140          this.pasteOperation = true;
5141
5142          if(style.stuckCaret /*|EES_READONLY)*/ )
5143             GoToEnd(true);
5144
5145          if(!undoBuffer.dontRecord)
5146          {
5147             char * placeString = CopyString(string);
5148             if(!style.multiLine)
5149             {
5150                int i;
5151                char ch;
5152                for(i = 0; (ch = placeString[i]); i++)
5153                   if(ch == '\n')
5154                   {
5155                      placeString[i] = '\0';
5156                      placeString = renew placeString byte[i+1];
5157                      break;
5158                   }
5159             }
5160
5161             if(selX != x || selY != y)
5162             {
5163                char * newString;
5164                char * oldString;
5165                int len = GetText(null, selLine, selY, selX, this.line, y, x, false, false);
5166                oldString = new char[len];
5167                newString = placeString;
5168                GetText(oldString, selLine, selY, selX, this.line, y, x, false, false);
5169
5170                replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = false };
5171                if(selY < y || (selY == y && selX < x))
5172                {
5173                   replaceAction.x1 = selX;
5174                   replaceAction.y1 = selY;
5175                   replaceAction.x2 = x;
5176                   replaceAction.y2 = y;
5177                }
5178                else
5179                {
5180                   replaceAction.x1 = x;
5181                   replaceAction.y1 = y;
5182                   replaceAction.x2 = selX;
5183                   replaceAction.y2 = selY;
5184                }
5185                Record(replaceAction);
5186             }
5187             else if(string[0])
5188             {
5189                if(string[0] == '\n')
5190                   action = AddTextAction { y1 = y, x1 = Min(this.line.count, x), string = placeString };
5191                else
5192                   action = AddTextAction { y1 = y, x1 = x, string = placeString };
5193
5194                Record(action);
5195             }
5196             else
5197                delete placeString;
5198          }
5199
5200          undoBuffer.dontRecord++;
5201          DelSel(&addedSpaces);
5202
5203          count = 0;
5204          line = string;
5205          for(c = 0; string[c]; c++)
5206          {
5207             if(string[c] == '\n' || string[c] == '\r')
5208             {
5209                if(!AddToLine(line,count, true, addedSpaces ? null : &addedSpaces, addedTabs ? null : &addedTabs))
5210                {
5211                   ret = false;
5212                   break;
5213                }
5214                if(string[c] == '\n')
5215                {
5216                   if(!AddCh('\n'))
5217                   {
5218                      count = 0;
5219                      ret = false;
5220                      break;
5221                   }
5222                }
5223                // Reset for next line
5224                count = 0;
5225                line = string+c+1;
5226                /*
5227                if(string[c] == '\r' && *line == '\n')
5228                {
5229                   line++;
5230                   c++;
5231                }*/
5232             }
5233             else
5234             {
5235                count++;
5236             }
5237          }
5238
5239          // Why was this here?
5240          // FindMaxLine();
5241
5242          // Add the line here
5243          if(ret && count)
5244             if(!AddToLine(line,count,false, addedSpaces ? null : &addedSpaces, addedTabs ? null : &addedTabs))
5245             {
5246                ret = false;
5247             }
5248          FindMaxLine();
5249
5250          undoBuffer.dontRecord--;
5251          if(action)
5252          {
5253             action.y2 = y;
5254             action.x2 = x;
5255             action.addedSpaces = addedSpaces;
5256             action.addedTabs = addedTabs;
5257          }
5258          else if(replaceAction)
5259          {
5260             replaceAction.y3 = y;
5261             replaceAction.x3 = x;
5262             replaceAction.addedSpaces = addedSpaces;
5263             replaceAction.addedTabs = addedTabs;
5264          }
5265
5266          UpdateCaretPosition(true);
5267
5268          this.pasteOperation = false;
5269
5270          return ret;
5271       }
5272       return false;
5273    }
5274
5275    // CONSOLE OUTPUT
5276    void Clear()
5277    {
5278       if(this)
5279       {
5280          Deselect();
5281          DelCh(this.lines.first, 0, 0, this.lines.last, this.lineCount-1, ((EditLine)(this.lines.last)).count, true);
5282          SetViewToCursor(true);
5283          UpdateDirty();
5284          Modified();
5285       }
5286    }
5287
5288    void PutCh(unichar ch)
5289    {
5290       bool result;
5291
5292       if((ch >= 32 /*&& ch <=126*/) || ch == '\n')
5293       //if((ch >= 32) || ch == '\n')
5294       {
5295          int addedSpaces = 0, addedTabs = 0;
5296          ReplaceTextAction replaceAction = null;
5297          AddCharAction addCharAction = null;
5298
5299          if(style.allCaps)
5300             ch = (ch < 128) ? toupper(ch) : ch;     // TODO: UNICODE TO UPPER
5301
5302          if(this.overwrite && selX == x && selY == y && this.x < this.line.count)
5303          {
5304             char buffer[5];
5305             char * newString;
5306             char * oldString;
5307             int len = GetText(null, line, y, x, line, y, x+1, false, false);
5308             oldString = new char[len];
5309             UTF32toUTF8Len(&ch, 1, buffer, 4);
5310             newString = CopyString(buffer);
5311             GetText(oldString, line, y, x, line, y, x+1, false, false);
5312             replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = false };
5313             replaceAction.x1 = x;
5314             replaceAction.y1 = y;
5315             replaceAction.x2 = x+1;
5316             replaceAction.y2 = y;
5317             Record(replaceAction);
5318
5319             undoBuffer.dontRecord++;
5320             DelCh(this.line, this.y, this.x, this.line, this.y, this.x+1, true);
5321             undoBuffer.dontRecord--;
5322          }
5323          else if(!undoBuffer.dontRecord)
5324          {
5325             if(selX != x || selY != y)
5326             {
5327                char buffer[5];
5328                char * newString;
5329                char * oldString;
5330                int len = GetText(null, line, y, x, selLine, selY, selX, false, false);
5331                oldString = new char[len];
5332                UTF32toUTF8Len(&ch, 1, buffer, 4);
5333                newString = CopyString(buffer);
5334                GetText(oldString, line, y, x, selLine, selY, selX, false, false);
5335                replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = true };
5336                if(selY < y || (selY == y && selX < x))
5337                {
5338                   replaceAction.x1 = selX;
5339                   replaceAction.y1 = selY;
5340                   replaceAction.x2 = x;
5341                   replaceAction.y2 = y;
5342                }
5343                else
5344                {
5345                   replaceAction.x1 = x;
5346                   replaceAction.y1 = y;
5347                   replaceAction.x2 = selX;
5348                   replaceAction.y2 = selY;
5349                }
5350                Record(replaceAction);
5351             }
5352             else
5353             {
5354                addCharAction = AddCharAction { y = y, x = x, ch = ch };
5355                Record(addCharAction);
5356             }
5357          }
5358          undoBuffer.dontRecord++;
5359          result = _AddCh(ch, &addedSpaces, &addedTabs);
5360          if(replaceAction)
5361          {
5362             replaceAction.x3 = x;
5363             replaceAction.y3 = y;
5364             replaceAction.addedSpaces = addedSpaces;
5365             replaceAction.addedTabs = addedTabs;
5366          }
5367          if(addCharAction)
5368          {
5369             addCharAction.x -= addedTabs * (tabSize-1);
5370             addCharAction.addedSpaces = addedSpaces;
5371             addCharAction.addedTabs = addedTabs;
5372          }
5373          undoBuffer.dontRecord--;
5374          if(ch == '\n')
5375             FindMaxLine();
5376          Modified();
5377          if(result) SetViewToCursor(true);
5378       }
5379    }
5380
5381    void PutS(char * string)
5382    {
5383       if(this)
5384       {
5385          AddS(string);
5386          SetViewToCursor(true);
5387          Modified();
5388       }
5389    }
5390
5391    void Printf(char * format, ...)
5392    {
5393       if(this)
5394       {
5395          char temp[MAX_F_STRING];
5396          va_list args;
5397          va_start(args, format);
5398          vsnprintf(temp, sizeof(temp), format, args);
5399          temp[sizeof(temp)-1] = 0;
5400          va_end(args);
5401          PutS(temp);
5402       }
5403    }
5404
5405    void SetContents(char * format, ...)
5406    {
5407       if(this)
5408       {
5409          undoBuffer.dontRecord++;
5410          Deselect();
5411          DelCh(this.lines.first, 0, 0, this.lines.last, this.lineCount-1, ((EditLine)(this.lines.last)).count, true);
5412          if(format)
5413          {
5414             char temp[MAX_F_STRING];
5415             va_list args;
5416             va_start(args, format);
5417             vsnprintf(temp, sizeof(temp), format, args);
5418             temp[sizeof(temp)-1] = 0;
5419             va_end(args);
5420
5421             AddS(temp);
5422          }
5423          UpdateDirty();
5424          Home();
5425          undoBuffer.dontRecord--;
5426       }
5427    }
5428
5429    void BackSpace()
5430    {
5431       if(!DelSel(null))
5432       {
5433          if(x > 0)
5434          {
5435             x -= 1 + DelCh(line, y, x-1, line, y, x, true);
5436             Modified();
5437          }
5438          else if(this.line.prev)
5439          {
5440             EditLine line = this.line.prev;
5441             int x = line.count;
5442             int y = this.y;
5443
5444             DelCh(line, this.y-1, x, this.line, this.y, this.x, true);
5445             this.line = line;
5446             this.y = y-1;
5447             this.x = x;
5448             Modified();
5449          }
5450          this.selX = this.x;
5451          this.selY = this.y;
5452          this.selLine = this.line;
5453          ComputeColumn();
5454       }
5455       else
5456          Modified();
5457       SetViewToCursor(true);
5458    }
5459
5460    void ClearLine()
5461    {
5462       Emptyline(this.line,this.y);
5463       this.selX = this.x = 0;
5464       this.col = 0;
5465       this.selY = this.y;
5466       this.selLine = this.line;
5467
5468       SetViewToCursor(true);
5469       Modified();
5470    }
5471
5472    // CARET CONTROL
5473    void End()
5474    {
5475       if(this)
5476       {
5477          GoToEnd(true);
5478          SetViewToCursor(true);
5479       }
5480    }
5481    void Home()
5482    {
5483       if(this)
5484       {
5485          GoToHome(true);
5486          SetViewToCursor(true);
5487       }
5488    }
5489
5490    bool GoToLineNum(int lineNum)
5491    {
5492       if(this.line)
5493       {
5494          int c;
5495          EditLine line = this.lines.first;
5496          for(c = 0; c < lineNum && line; c++, line = line.next);
5497          if(line)
5498          {
5499             if(this.y != c)
5500                DirtyAll();
5501             else
5502                DirtyLine(c);
5503             this.y = c;
5504             this.line = line;
5505             _Deselect();
5506             SetViewToCursor(true);
5507             return true;
5508          }
5509       }
5510       return false;
5511    }
5512
5513    bool GoToPosition(EditLine line, int y, int x)
5514    {
5515       /*
5516       if(!line)
5517       {
5518          line = this.line;
5519          y = this.y;
5520       }
5521       */
5522       if(!line)
5523       {
5524          int c;
5525          for(line = this.lines.first, c = 0; c<y && line; c++, line = line.next);
5526       }
5527
5528       if(line)
5529       {
5530          if(this.y != y)
5531             DirtyAll();
5532          else
5533             DirtyLine(y);
5534          this.x = x;
5535          this.y = y;
5536          this.line = line;
5537          ComputeColumn();
5538          _Deselect();
5539          SetViewToCursor(true);
5540          return true;
5541       }
5542       return false;
5543    }
5544
5545    // VIEW POSITIONING
5546    void SetViewToCursor(bool setCaret)
5547    {
5548       if(created)
5549       {
5550          int w;
5551          int c, numLines;
5552          EditLine line;
5553          int x;
5554          int checkX, checkY;
5555          EditLine checkLine;
5556          bool dontScroll = false;
5557          bool selected;
5558          int viewX, viewY;
5559
5560          FixScrollArea();
5561
5562          selected = selX != this.x || selY != y;
5563
5564          viewX = this.viewX;
5565          viewY = this.viewY;
5566
5567          if(mouseMove)
5568          {
5569             checkLine = dropLine;
5570             checkX = dropX;
5571             checkY = dropY;
5572          }
5573          else
5574          {
5575             checkLine = this.line;
5576             checkX = this.x;
5577             checkY = y;
5578          }
5579
5580          numLines = clientSize.h / space.h;
5581
5582          // This is broken. The EditBox now doesn't do anything different when adding to it,
5583          // regardless of the previous scrolling position. It should be read and then set again
5584          // if one wishes to preserve it.
5585          /*  // Don't scroll view to cursor if we're in a EES_NOCARET box
5586          if(style.noCaret && this.viewY < lineCount - numLines - 1)
5587             dontScroll = true;
5588          */
5589
5590          // Horizontal Adjustment
5591          if(!dontScroll && checkLine)
5592          {
5593             x = 0;
5594             if(mouseMove)
5595                dropX = AdjustXPosition(this.line, MAXINT, true, &x, checkX, 0);
5596             else
5597             {
5598                this.x = AdjustXPosition(this.line, MAXINT, true, &x, checkX, 0);
5599                ComputeColumn();
5600             }
5601
5602             if(style.hScroll)
5603             {
5604                if(x + space.w >= this.viewX + clientSize.w && clientSize.w >= space.w)
5605                   viewX = x - clientSize.w+space.w;
5606                if(x < this.viewX + clientSize.w/2 - space.w)
5607                   viewX = Max(0, x - clientSize.w/2 + space.w);
5608             }
5609          }
5610
5611          if(!dontScroll)
5612          {
5613             if(style.vScroll)
5614             {
5615                // Vertical Adjustment
5616                if(viewY > checkY) viewY = checkY;
5617                if(viewY + numLines <= checkY)
5618                {
5619                   if(clientSize.h >= space.h)
5620                   {
5621                      for(line = viewLine; line && (viewY + numLines <= checkY); line = line.next)
5622                         viewY++;
5623                   }
5624                   else
5625                      viewY = checkY;
5626                }
5627             }
5628             else
5629             {
5630                if(mouseMove)
5631                   for(;dropLine && dropLine.prev && dropY >= numLines;)
5632                   {
5633                      dropLine = dropLine.prev;
5634                      dropY--;
5635                   }
5636                else
5637                   for(;this.line && this.line.prev && this.y >= numLines;)
5638                   {
5639                      this.line = this.line.prev;
5640                      y--;
5641                   }
5642             }
5643
5644             SetScrollPosition(viewX, viewY * this.space.h);
5645
5646             UpdateCaretPosition(setCaret);
5647
5648             if(!selected)
5649             {
5650                selX = this.x;
5651                selY = this.y;
5652                selLine = this.line;
5653             }
5654          }
5655
5656          UpdateDirty();
5657          SetSelectCursor();
5658          SelectionEnables();
5659       }
5660    }
5661
5662    void CenterOnCursor()
5663    {
5664       int numLines = clientSize.h / this.space.h;
5665       int y = this.y - numLines / 2;
5666       int viewY;
5667       bool figureSyntax = false;
5668       EditLine oldViewLine = viewLine;
5669       if(y > this.lineCount - numLines) y = this.lineCount-numLines;
5670       if(y < 0) y = 0;
5671
5672       viewY = y;
5673
5674       for(;y < this.viewY; y++)
5675       {
5676          this.viewLine = this.viewLine.prev;
5677          style.recomputeSyntax = true;
5678       }
5679       for(;y > this.viewY; y--)
5680       {
5681          this.viewLine = this.viewLine.next;
5682          figureSyntax = true;
5683       }
5684       if(figureSyntax)
5685          FigureStartSyntaxStates(oldViewLine, false);
5686
5687       this.viewY = viewY;
5688
5689       SetScrollPosition(this.viewX, viewY * this.space.h);
5690       UpdateCaretPosition(true);
5691       UpdateDirty();
5692    }
5693
5694    void SetCursorToView()
5695    {
5696       SetCursorToViewX();
5697       SetCursorToViewY();
5698    }
5699
5700    void PageDown()
5701    {
5702       int c, numLines;
5703       EditLine line;
5704
5705       numLines = clientSize.h / this.space.h;
5706
5707       if(style.noCaret)
5708       {
5709          for(c=0, line = this.viewLine.next; line && c<numLines && (this.viewY < this.lineCount - numLines); line = line.next, c++);
5710          SetScrollPosition(this.viewX, (this.viewY + c) * this.space.h);
5711       }
5712       else
5713       {
5714          EditLine oldLine = this.line;
5715          bool lastOne = false;
5716          EditLine oldViewLine = this.viewLine;
5717          bool figureSyntax = false;
5718
5719          if(this.y >= this.lineCount-1) return;
5720
5721          for(c=0, line = this.line.next; line && c<numLines; line = line.next, c++)
5722          {
5723             if(this.viewY + numLines < this.lines.count)
5724             {
5725                this.viewLine = this.viewLine.next;
5726                this.viewY++;
5727                figureSyntax = true;
5728             }
5729             else if(c && !lastOne)
5730                break;
5731             else
5732                lastOne = true;
5733
5734             this.line = line;
5735             this.y++;
5736          }
5737          if(figureSyntax)
5738             FigureStartSyntaxStates(oldViewLine, false);
5739          this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
5740          ComputeColumn();
5741
5742          SetViewToCursor(false);
5743       }
5744    }
5745
5746    void PageUp()
5747    {
5748       int c, numLines;
5749       EditLine line;
5750
5751       if(this.y == 0) return;
5752
5753       numLines = clientSize.h / this.space.h;
5754
5755       if(style.noCaret)
5756       {
5757          for(c=0, line = this.viewLine.prev; line && c<numLines; line = line.prev, c++);
5758          SetScrollPosition(this.viewX, (this.viewY - c) * this.space.h);
5759       }
5760       else
5761       {
5762          EditLine oldLine = this.line;
5763
5764          for(c=0, line = this.line.prev; line && c<numLines; line = line.prev, c++)
5765          {
5766             this.line = line;
5767             this.y--;
5768
5769             if(this.viewLine.prev)
5770             {
5771                this.viewLine = this.viewLine.prev;
5772                this.viewY--;
5773                style.recomputeSyntax = true;
5774             }
5775          }
5776
5777          this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
5778          ComputeColumn();
5779
5780          SetViewToCursor(false);
5781       }
5782    }
5783
5784    void LineUp()
5785    {
5786
5787       if(this.viewLine.prev)
5788       {
5789          //DirtyAll();
5790          // this.viewLine = this.viewLine.prev;
5791          //this.viewY--;
5792
5793          SetScrollPosition(this.viewX, (this.viewY - 1) * this.space.h);
5794       }
5795    }
5796
5797
5798    void LineDown()
5799    {
5800       if(this.viewLine.next)
5801       {
5802          //DirtyAll();
5803          // this.viewLine = this.viewLine.next;
5804          // this.viewY++;
5805
5806          SetScrollPosition(this.viewX, (this.viewY + 1) * this.space.h);
5807       }
5808    }
5809
5810    // Selection
5811    uint SelSize()
5812    {
5813       EditLine l1, l2, line;
5814       int x1, x2, nx1, nx2;
5815       int count;
5816       int start, end;
5817       int size;
5818
5819       if(!this.selLine) return 0;
5820       if(this.selLine == this.line && this.selX == this.x) return 0;
5821       if(this.selY < this.y)
5822       {
5823          l1 = this.selLine;
5824          x1 = this.selX;
5825          l2 = this.line;
5826          x2 = this.x;
5827       }
5828       else if(this.selY > this.y)
5829       {
5830          l1 = this.line;
5831          x1 = this.x;
5832          l2 = this.selLine;
5833          x2 = this.selX;
5834       }
5835       else if(this.selX < this.x)
5836       {
5837          l1 = l2 = this.line;
5838          x1 = this.selX;
5839          x2 = this.x;
5840       }
5841       else
5842       {
5843          l1 = l2 = this.line;
5844          x1 = this.x;
5845          x2 = this.selX;
5846       }
5847       nx1 = Min(x1,l1.count);
5848       nx2 = Min(x2,l2.count);
5849
5850       // Find Number of Bytes Needed
5851       size = 0;
5852       for(line = l1; line; line = line.next)
5853       {
5854          if(line == l1) start = nx1; else start = 0;
5855          if(line == l2) end = nx2; else end = line.count;
5856          size += end-start;
5857          if(style.freeCaret && line == l2)
5858          {
5859             if(l1 == l2)
5860                count = Max(x2-Max(x1,l1.count),0);
5861             else
5862                count = Max(x2-l2.count,0);
5863             size+=count;
5864          }
5865
5866          if(line == l2) break;
5867          // Add Carriage Return / line Feed
5868          size++;
5869          size++;
5870       }
5871       return size;
5872    }
5873
5874    void GetSelPos(EditLine * l1, int *y1, int *x1, EditLine * l2, int * y2, int * x2, bool reorder)
5875    {
5876       if(this)
5877       {
5878          if(!reorder || this.selY < this.y)
5879          {
5880             if(l1) *l1 = this.selLine;
5881             if(y1) *y1 = this.selY;
5882             if(x1) *x1 = this.selX;
5883             if(l2) *l2 = this.line;
5884             if(y2) *y2 = this.y;
5885             if(x2) *x2 = this.x;
5886          }
5887          else if(this.selY > this.y)
5888          {
5889             if(l1) *l1 = this.line;
5890             if(y1) *y1 = this.y;
5891             if(x1) *x1 = this.x;
5892             if(l2) *l2 = this.selLine;
5893             if(y2) *y2 = this.selY;
5894             if(x2) *x2 = this.selX;
5895          }
5896          else if(this.selX < this.x)
5897          {
5898             if(l1) *l1 = this.line;
5899             if(y1) *y1 = this.selY;
5900             if(x1) *x1 = this.selX;
5901             if(l2) *l2 = this.line;
5902             if(y2) *y2 = this.y;
5903             if(x2) *x2 = this.x;
5904          }
5905          else
5906          {
5907             if(l1) *l1 = this.line;
5908             if(y1) *y1 = this.y;
5909             if(x1) *x1 = this.x;
5910             if(l2) *l2 = this.line;
5911             if(y2) *y2 = this.selY;
5912             if(x2) *x2 = this.selX;
5913          }
5914       }
5915    }
5916
5917    void SetSelPos(EditLine l1, int y1, int x1, EditLine l2, int y2, int x2)
5918    {
5919       if(this && (this.selY != y1 || this.selX != x1 || this.y != y2 || this.x != x2))
5920       {
5921          this.selLine = (EditLine)l1;
5922          this.selY = y1;
5923          this.selX = x1;
5924          this.line = (EditLine)l2;
5925          this.y = y2;
5926          this.x = x2;
5927          ComputeColumn();
5928          SetViewToCursor(true);
5929       }
5930    }
5931
5932    int GetText(char * text, EditLine _l1, int _y1, int _x1, EditLine _l2, int _y2, int _x2, bool addCr, bool addSpaces)
5933    {
5934       EditLine l1, l2, line;
5935       int x1, x2, nx1, nx2;
5936       int count;
5937       int start, end;
5938       int numChars = 0;
5939
5940       if(_y2 < _y1)
5941       {
5942          l1 = _l2;
5943          x1 = _x2;
5944          l2 = _l1;
5945          x2 = _x1;
5946       }
5947       else if(_y2 > _y1)
5948       {
5949          l1 = _l1;
5950          x1 = _x1;
5951          l2 = _l2;
5952          x2 = _x2;
5953       }
5954       else if(_x2 < _x1)
5955       {
5956          l1 = l2 = _l1;
5957          x1 = _x2;
5958          x2 = _x1;
5959       }
5960       else
5961       {
5962          l1 = l2 = _l1;
5963          x1 = _x1;
5964          x2 = _x2;
5965       }
5966       nx1 = Min(x1,l1.count);
5967       nx2 = Min(x2,l2.count);
5968
5969       // Copy text
5970       for(line = l1; line; line = line.next)
5971       {
5972          if(line == l1) start = nx1; else start = 0;
5973          if(line == l2) end = nx2; else end = line.count;
5974          if(text)
5975          {
5976             CopyBytes(text, line.buffer + start, end - start);
5977             text += end-start;
5978          }
5979          numChars += end-start;
5980
5981          if(style.freeCaret && line == l2 && addSpaces)
5982          {
5983             if(l1 == l2)
5984                count = Max(x2-Max(x1,l1.count),0);
5985             else
5986                count = Max(x2-l2.count,0);
5987             if(text)
5988             {
5989                FillBytes(text,' ',count);
5990                text += count;
5991             }
5992             else
5993                numChars += count;
5994          }
5995          if(line == l2) break;
5996          // Add line Feed
5997          if(addCr)
5998          {
5999             if(text)
6000                *(text++) = '\r';
6001             numChars++;
6002          }
6003          if(text)
6004             *(text++) = '\n';
6005          numChars++;
6006       }
6007       // '\0' terminate Terminate
6008       if(text)
6009          *(text) = '\0';
6010       numChars++;
6011       return numChars;
6012    }
6013
6014    void GetSel(char * text, bool addCr)
6015    {
6016       GetText(text, line, y, x, selLine, selY, selX, addCr, true);
6017    }
6018
6019    private void _Deselect()   // This assumes marking lines as dirty is handled by the caller
6020    {
6021       selLine = line;
6022       selX = x;
6023       selY = y;
6024    }
6025
6026    void Deselect()
6027    {
6028       SelDirty();
6029       _Deselect();
6030    }
6031
6032    // CLIPBOARD
6033    void Copy()
6034    {
6035       if(this)
6036       {
6037          int size = SelSize();
6038          if(size)
6039          {
6040             // Try to allocate memory
6041             ClipBoard clipBoard { };
6042             if(clipBoard.Allocate(size+1))
6043             {
6044                GetSel(clipBoard.memory, true);
6045                // Save clipboard
6046                clipBoard.Save();
6047             }
6048             delete clipBoard;
6049          }
6050       }
6051    }
6052
6053    void Paste()
6054    {
6055       if(this)
6056       {
6057          ClipBoard clipBoard { };
6058          if(clipBoard.Load())
6059             PutS(clipBoard.memory);
6060          delete clipBoard;
6061       }
6062    }
6063
6064    void Cut()
6065    {
6066       if(this)
6067       {
6068          Copy();
6069          if(DelSel(null))
6070          {
6071             SetViewToCursor(true);
6072             Modified();
6073          }
6074       }
6075    }
6076
6077    void DeleteSelection()
6078    {
6079       if(this)
6080       {
6081          DelSel(null);
6082          SetViewToCursor(true);
6083          Modified();
6084       }
6085    }
6086
6087    // FILE INTERFACE
6088    void Save(File f, bool cr)
6089    {
6090       EditLine line;
6091       savedAction = undoBuffer.curAction;
6092
6093       for(line = this.lines.first; line; line = line.next)
6094       {
6095          if(style.syntax && line.count && isspace(line.buffer[line.count-1]))
6096          {
6097             int c = 0;
6098             for(c=line.count-2; c>=-1; c--)
6099             {
6100                if(c == -1 || !isspace(line.buffer[c]))
6101                {
6102                   c++;
6103                   line.buffer[c] = '\0';
6104                   line.count -= (line.count - c);
6105                   break;
6106                }
6107             }
6108          }
6109          f.Write(line.buffer, line.count,1);
6110          if(line.next)
6111          {
6112             if(cr) f.Putc('\r');
6113             f.Putc('\n');
6114          }
6115       }
6116    }
6117
6118    #define BUFFER_SIZE  16384
6119    void Load(File f)
6120    {
6121       undoBuffer.dontRecord++;
6122       if(f)
6123       {
6124          char buffer[BUFFER_SIZE];
6125
6126          for(;;)
6127          {
6128             int count = f.Read(buffer, 1, BUFFER_SIZE-1);
6129             buffer[count] = '\0';
6130             AddS(buffer);
6131             if(!count) break;
6132          }
6133          Home();
6134       }
6135       undoBuffer.dontRecord--;
6136       undoBuffer.count = 0;
6137       undoBuffer.curAction = 0;
6138       itemEditUndo.disabled = undoBuffer.curAction == 0;
6139       itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
6140    }
6141
6142    EditBoxFindResult Find(char * text, bool matchWord, bool matchCase, bool isSearchDown)
6143    {
6144       EditLine line;
6145       int num;
6146       bool firstPass = true;
6147       EditBoxFindResult result = found;
6148
6149       if(!this.line) return notFound;
6150       num = this.y;
6151
6152       for(line = this.line;;)
6153       {
6154          char * string;
6155
6156          if(!line)
6157          {
6158             if(isSearchDown)
6159             {
6160                line = this.lines.first;
6161                num = 0;
6162                result = wrapped;
6163             }
6164             else
6165             {
6166                line = this.lines.last;
6167                num = this.lineCount - 1;
6168                result = wrapped;
6169             }
6170          }
6171
6172          if(isSearchDown)
6173             string = SearchString(line.buffer, firstPass ? Min(this.x,line.count) : 0,text,matchCase,matchWord);
6174          else
6175             string = RSearchString(line.buffer,text,firstPass ? (Min(this.x,line.count)-1) : line.count,matchCase,matchWord);
6176
6177          if(string)
6178          {
6179             Select((void *)line,num,string - line.buffer,(void *)line,num,string - line.buffer + strlen(text));
6180             return result;
6181          }
6182          if(line == this.line && !firstPass) break;
6183
6184          if(isSearchDown)
6185          {
6186             line = line.next;
6187             num++;
6188          }
6189          else
6190          {
6191             line = line.prev;
6192             num--;
6193          }
6194
6195          firstPass = false;
6196       }
6197       return notFound;
6198    }
6199
6200    EditBoxFindResult FindInSelection(char * text, bool matchWord, bool matchCase, EditLine l2, int y2, int x2)
6201    {
6202       EditLine line;
6203       int y;
6204       int searchLen = strlen(text);
6205       for(line = (EditLine)this.line, y = this.y; y <= y2; line = line.next, y++)
6206       {
6207          char * string = SearchString(line.buffer, (y == this.y) ? Min(this.x,line.count) : 0,text,matchCase,matchWord);
6208          if(string && (y < y2 || (string - line.buffer) + searchLen <= x2))
6209          {
6210             Select((void *)line,y,string - line.buffer,(void *)line,y,string - line.buffer + strlen(text));
6211             return found;
6212          }
6213       }
6214       return notFound;
6215    }
6216
6217    bool OnKeyDown(Key key, unichar ch)
6218    {
6219 #ifdef _DEBUG
6220       //PrintLn("OnKeyDown: code = ", key.code, ", mods = ", key.modifiers, ", ch = ", (int)ch);
6221 #endif
6222       if(!NotifyKeyDown(master, this, key, ch))
6223          return false;
6224       else
6225       {
6226          switch(key)
6227          {
6228             case escape:
6229             {
6230                if(this.mouseMove)
6231                {
6232                   this.mouseMove = false;
6233                   OnLeftButtonUp(0,0,0);
6234                   SetViewToCursor(true);
6235                   return false;
6236                }
6237             }
6238          }
6239       }
6240       return true;
6241    }
6242 };
6243
6244 public class EditBoxStream : File
6245 {
6246    EditBox editBox;
6247    BufferLocation start, sel;
6248    uint pos;
6249    byte utf8Bytes[5];
6250    int numBytes;
6251
6252    ~EditBoxStream()
6253    {
6254       EditBox editBox = this.editBox;
6255
6256       editBox.x = start.x;
6257       editBox.y = start.y;
6258       editBox.line = start.line;
6259
6260       editBox.selX = sel.x;
6261       editBox.selY = sel.y;
6262       editBox.selLine = sel.line;
6263
6264       editBox.SetViewToCursor(true);
6265       //editBox.ComputeColumn();
6266    }
6267
6268 public:
6269    property EditBox editBox
6270    {
6271       set
6272       {
6273          editBox = value;
6274
6275          start.x = value.x;
6276          start.y = value.y;
6277          start.line = value.line;
6278
6279          sel.x = value.selX;
6280          sel.y = value.selY;
6281          sel.line = value.selLine;
6282          numBytes = 0;
6283
6284          value.GoToHome(true);
6285       }
6286
6287       get { return editBox; }
6288    }
6289
6290    uint Read(byte * buffer, uint size, uint count)
6291    {
6292       uint read = 0;
6293       EditBox editBox = this.editBox;
6294       EditLine line = editBox.line;
6295       int x = editBox.x;
6296       int y = editBox.y;
6297
6298       count *= size;
6299
6300       for(;read < count && line; line = (*&line.next))
6301       {
6302          int numBytes = Min(count - read, (*&line.count) - x);
6303          if(numBytes > 0)
6304          {
6305             memcpy(buffer + read, (*&line.buffer) + x, numBytes);
6306             read += numBytes;
6307             x += numBytes;
6308          }
6309          /*
6310          for(;read < count && x < (*&line.count);)
6311          {
6312             buffer[read++] = (*&line.buffer)[x++];
6313          }
6314          */
6315          if((*&line.next))
6316          {
6317             if(read < count)
6318             {
6319                buffer[read++] = '\n';
6320             }
6321             else
6322                break;
6323          }
6324          if(x == (*&line.count) && (*&line.next))
6325          {
6326             x = 0;
6327             y++;
6328          }
6329          else
6330             break;
6331       }
6332
6333       editBox.line = editBox.selLine = line;
6334       editBox.x = editBox.selX = x;
6335       editBox.y = editBox.selY = y;
6336       pos += read;
6337       return read / size;
6338    }
6339
6340    bool Seek(int pos, FileSeekMode mode)
6341    {
6342       bool result = true;
6343       EditBox editBox = this.editBox;
6344       EditLine line = editBox.line;
6345       numBytes = 0;
6346       if(mode == FileSeekMode::start)
6347       {
6348          pos = pos - this.pos;
6349          mode = current;
6350       }
6351       if(mode == current)
6352       {
6353          uint read = 0;
6354          int x = editBox.x;
6355          int y = editBox.y;
6356          if(pos > 0)
6357          {
6358             for(;read < pos && line; line = (*&line.next))
6359             {
6360                int numBytes = Min(pos - read, (*&line.count) - x);
6361                if(numBytes > 0)
6362                {
6363                   read += numBytes; x += numBytes;
6364                }
6365
6366                /*for(;read < pos && x < (*&line.count);)
6367                {
6368                   read++; x++;
6369                }
6370                */
6371                if((*&line.next))
6372                {
6373                   if(read < pos)
6374                   {
6375                      read++;
6376                      x = 0;
6377                   }
6378                   else
6379                      break;
6380                }
6381                else
6382                {
6383                   if(read<pos)
6384                      result = false;
6385                   break;
6386                }
6387                y++;
6388             }
6389             this.pos += read;
6390          }
6391          else if(pos < 0)
6392          {
6393             pos = -pos;
6394             for(;read < pos && line; line = (*&line.prev))
6395             {
6396                int numBytes = Min(pos - read, x);
6397                if(numBytes > 0)
6398                {
6399                   read += numBytes;
6400                   x -= numBytes;
6401                }
6402                /*for(;read < pos && x > 0;)
6403                {
6404                   read++; x--;
6405                }
6406                */
6407                if((*&line.prev))
6408                {
6409                   if(read < pos)
6410                   {
6411                      read++;
6412                      x = (*&(*&line.prev).count);
6413                   }
6414                   else
6415                      break;
6416                }
6417                else
6418                {
6419                   if(read<pos)
6420                      result = false;
6421                   break;
6422                }
6423                y--;
6424             }
6425             this.pos -= read;
6426          }
6427
6428          editBox.line = editBox.selLine = line;
6429          editBox.x = editBox.selX = x;
6430          editBox.y = editBox.selY = y;
6431       }
6432       return result;
6433    }
6434
6435    bool Puts(char * string)
6436    {
6437       EditBox editBox = this.editBox;
6438       BufferLocation start { editBox.line, editBox.y, editBox.x };
6439       BufferLocation pos;
6440
6441       numBytes = 0;
6442       editBox.AddS(string);
6443
6444       pos.line = editBox.line;
6445       pos.y = editBox.y;
6446       pos.x = editBox.x;
6447
6448       this.start.AdjustAdd(start, pos);
6449       sel.AdjustAdd(start, pos);
6450
6451       return true;
6452    }
6453
6454    // NOTE: BYTE, NOT UNICODE CHARACTER!
6455    bool Putc(char ch)
6456    {
6457       EditBox editBox = this.editBox;
6458       BufferLocation start = { editBox.line, editBox.y, editBox.x };
6459       BufferLocation pos;
6460
6461       if(numBytes < 4)
6462       {
6463          utf8Bytes[numBytes++] = ch;
6464          utf8Bytes[numBytes] = 0;
6465          if(UTF8Validate(utf8Bytes))
6466          {
6467             editBox.AddCh(UTF8_GET_CHAR(utf8Bytes, numBytes));
6468             numBytes = 0;
6469             pos.line = editBox.line;
6470             pos.y = editBox.y;
6471             pos.x = editBox.x;
6472             this.start.AdjustAdd(start, pos);
6473             sel.AdjustAdd(start, pos);
6474          }
6475          return true;
6476       }
6477       return false;
6478    }
6479
6480    bool Getc(char * ch)
6481    {
6482       return Read(ch, 1, 1) ? true : false;
6483    }
6484
6485    void DeleteBytes(uint count)
6486    {
6487       EditBox editBox = this.editBox;
6488       if(count)
6489       {
6490          BufferLocation pos { editBox.line, editBox.y, editBox.x };
6491          BufferLocation end = pos;
6492
6493          uint c = 0;
6494          for(;;)
6495          {
6496             for(;c < count && end.line && end.x < end.line.count;)
6497             {
6498                end.x++;
6499                c++;
6500             }
6501             if(c < count && end.line && end.line.next)
6502             {
6503                end.line = end.line.next;
6504                end.y++;
6505                c++;
6506                end.x = 0;
6507             }
6508             else
6509                break;
6510          }
6511
6512          start.AdjustDelete(pos, end);
6513          sel.AdjustDelete(pos, end);
6514
6515          editBox.DelCh(pos.line, pos.y, pos.x, end.line, end.y, end.x, true);
6516       }
6517    }
6518 };