ecere;gui;controls;EditBox; (#605) show trailing space with big red area. nuke traili...
[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                            char * s = null;
1739                            if(dot && dot > word + wordLen) dot = null;
1740                            if(dot)
1741                               strtod((dot == word + wordLen) ? (dot+1) : word, &s);
1742                            else
1743                               strtol(word, &s, 0);
1744                            if(s && s != word)
1745                            {
1746                               if((dot && *s == 'f' && !isalnum(s[1]) && s[1] != '_') || (!isalpha(*s) && *s != '_'))
1747                               {
1748                                  int newWordLen = s + ((*s == 'f') ? 1 : 0) - word;
1749                                  newTextColor = colorScheme.numberColor;
1750                                  c += newWordLen - wordLen;
1751                                  wordLen = newWordLen;
1752                               }
1753                               else if(dot && dot > word && (isalpha(dot[1]) || dot[1] == '_'))
1754                                  newTextColor = colorScheme.numberColor;
1755                            }
1756                         }
1757                         else
1758                         {
1759                            if(!inQuotes && !inString && !inMultiLineComment && !inSingleLineComment && word[0] == '#')
1760                            {
1761                               if(firstWord)
1762                               {
1763                                  inPrep = true;
1764                                  newTextColor = colorScheme.preprocessorColor;
1765                               }
1766                            }
1767                            if(!inQuotes && !inString && !inMultiLineComment && !inSingleLineComment)
1768                            {
1769                               for(g = 0; g < ((inPrep && word[0] != '#') ? 2 : 1); g++)
1770                               {
1771                                  char ** keys = keyWords[g];
1772                                  int * len = keyLen[g];
1773                                  for(ccc = 0; keys[ccc]; ccc++)
1774                                  {
1775                                     if(len[ccc] == wordLen && !strncmp(keys[ccc], word, wordLen))
1776                                     {
1777                                        newTextColor = colorScheme.keywordColors[g];
1778                                        break;
1779                                     }
1780                                  }
1781                               }
1782                            }
1783                         }
1784                         firstWord = false;
1785
1786                         // If highlighting for this word is different, break
1787                         if(newTextColor != textColor)
1788                         {
1789                            if(bufferLen)
1790                            {
1791                               // Better solution than going back?
1792                               c -= wordLen;
1793
1794                               // Reset syntax flags
1795                               escaped = backEscaped;
1796                               lastWasStar = backLastWasStar;
1797                               inMultiLineComment = backInMultiLineComment;
1798                               inString = backInString;
1799                               inQuotes = backInQuotes;
1800                               inPrep = backInPrep;
1801                               inSingleLineComment = backInSingleLineComment;
1802                               wasInMultiLine = backWasInMultiLine;
1803                               break;
1804                            }
1805                            else
1806                            {
1807                               textColor = newTextColor;
1808                               if(!selected)
1809                               {
1810                                  foreground = textColor;
1811                                  surface.SetForeground(textColor);
1812                               }
1813                            }
1814                         }
1815                      }
1816                   }
1817
1818                   // If we're not breaking, this can't be rendered as spacing anymore
1819                   spacing = false;
1820
1821                   // Do word wrapping here
1822                   if(style.wrap)
1823                   {
1824                      //if(!numSpaces)
1825                      {
1826                         int tw;
1827                         FontExtent(display, font, line.buffer + start, bufferLen + wordLen, &tw, null);
1828                         w = tw;
1829                      }
1830                      /*else
1831                      {
1832                         w += numSpaces * space.w;
1833                      }*/
1834                      if(x + viewX + w > maxW)
1835                      {
1836                         // Avoid an endless loop until we fix wrapping
1837                         if(c - wordLen > start)
1838                         {
1839                            c -= wordLen;
1840                            lineComplete = true;
1841                            break;
1842                         }
1843                      }
1844                   }
1845                }
1846                bufferLen += wordLen;
1847                wordLen = 0;
1848             }
1849
1850             {
1851                int renderStart = start;
1852                bool flush = false;
1853                bool flagTrailingSpace = false;
1854                int numSpaces = 0;
1855                int wc;
1856
1857                // Render checking if we need to split because of selection or to find where to draw insert caret
1858                for(wc = start; wc < start + bufferLen; wc++)
1859                {
1860                   flush = CheckColors(line, wc, selection, selX, editX, &selected, selectionForeground,
1861                      selectionBackground, textColor, &foreground, &background, &opacity, &overWrite);
1862                   if(overWrite == true)
1863                   {
1864                      overWriteCh = (wc < line.count) ? line.buffer[wc] : ' ';
1865                      if(overWriteCh == '\t') overWriteCh = ' ';
1866                   }
1867
1868                   if(flush)
1869                   {
1870                      flagTrailingSpace = numSpaces && trailingSpace && style.syntax && start + bufferLen == line.count && line != this.line;
1871                      if(flagTrailingSpace) surface.SetBackground(red);
1872                      FlushBuffer(surface, line, wc, &renderStart, &x, y, numSpaces, flagTrailingSpace, box);
1873                      if(overWrite == true)
1874                      {
1875                         overWriteX = x;
1876                         overWriteY = y;
1877                         overWrite = 2;
1878                      }
1879                      numSpaces = 0;
1880
1881                      surface.TextOpacity(opacity);
1882                      surface.SetBackground(background);
1883                      surface.SetForeground(foreground);
1884
1885                      flush = false;
1886                   }
1887
1888                   if(spacing)
1889                   {
1890                      if(wc < line.count && line.buffer[wc] == '\t')
1891                      {
1892                         numSpaces += (tabSize * space.w) - ((x + numSpaces + viewX) % (tabSize * space.w));
1893                      }
1894                      else
1895                      {
1896                         numSpaces += space.w;
1897                      }
1898                   }
1899                }
1900                flagTrailingSpace = numSpaces && trailingSpace && style.syntax && start + bufferLen == line.count && line != this.line;
1901                if(flagTrailingSpace) surface.SetBackground(red);
1902                FlushBuffer(surface, line, wc, &renderStart, &x, y, numSpaces, flagTrailingSpace, box);
1903                start += bufferLen;
1904             }
1905          }
1906
1907          if(CheckColors(line, c, selection, selX, editX, &selected, selectionForeground,
1908                         selectionBackground, textColor, &foreground, &background, &opacity, &overWrite))
1909          {
1910             if(overWrite == true)
1911             {
1912                overWriteX = x;
1913                overWriteY = y;
1914                overWriteCh = ' ';
1915                overWrite = 2;
1916             }
1917             surface.TextOpacity(opacity);
1918             surface.SetBackground(background);
1919             surface.SetForeground(foreground);
1920          }
1921
1922          if(style.freeCaret && selected)
1923          {
1924             surface.SetBackground(selectionBackground);
1925             surface.Area(x + XOFFSET - 1,y,clientSize.w-1,y+this.space.h-1);
1926             // TEST: surface.Area(x + XOFFSET,y,clientSize.w-1,y+this.space.h-1);
1927          }
1928
1929
1930          /*
1931          if(style.freeCaret && selected)
1932          {
1933             surface.SetBackground(selectionBackground);
1934             surface.Area(x + XOFFSET - 1,y,clientSize.w-1,y+this.space.h-1);
1935          }
1936          */
1937
1938          continuedSingleLineComment = inSingleLineComment && (line.count && line.text[line.count - 1] == '\\');
1939
1940          y+=this.space.h;
1941          if(y > box.bottom) // >=clientSize.h)
1942             break;
1943       }
1944
1945       if(overWrite)
1946       {
1947          surface.TextOpacity(true);
1948          surface.SetForeground(black);
1949          surface.SetBackground(Color {255,255,85});
1950          surface.WriteText(XOFFSET + overWriteX,overWriteY, &overWriteCh, 1);
1951       }
1952    }
1953
1954    void FixScrollArea()
1955    {
1956       if(style.hScroll || style.vScroll)
1957       {
1958          int width = maxLength + XOFFSET;
1959          int height = lineCount * space.h;
1960          if(style.freeCaret && line)
1961          {
1962             if(x > selX)
1963             {
1964                if(x > line.count)
1965                   width = Max(line.length + (x - line.count) * space.w, maxLength + XOFFSET);
1966             }
1967             else
1968             {
1969                if(selX > selLine.count)
1970                   width = Max(selLine.length + (selX - selLine.count) * space.w, maxLength + XOFFSET);
1971             }
1972          }
1973
1974          width += space.w;
1975          SetScrollLineStep(8, space.h);
1976          SetScrollArea(width, height, true);
1977       }
1978    }
1979
1980    void ComputeLength(EditLine line)
1981    {
1982       int c;
1983       int tabOccur = 0;
1984       int tabWidth;
1985       int x = 0;
1986
1987       for(c = 0; c < line.count; )
1988       {
1989          int len = 1;
1990          int start = c;
1991          int w;
1992          if(c < line.count)
1993          {
1994             byte ch = 0;
1995             int numBytes;
1996             for(len = 0; c < line.count; c += numBytes)
1997             {
1998                ch = line.buffer[c];
1999                numBytes = UTF8_NUM_BYTES(ch);
2000
2001                if(ch == ' ' || ch == '\t')
2002                {
2003                   if(!len) c++;
2004                   break;
2005                }
2006                len += numBytes;
2007             }
2008             if(!len && ch == ' ')
2009             {
2010                len = 1;
2011                w = space.w;
2012             }
2013             else if(!len && ch == '\t')
2014             {
2015                w = (tabSize * space.w) - (x % (tabSize * space.w));
2016                len = 1;
2017             }
2018             else
2019                FontExtent(display, font, line.buffer + start, len, &w, null);
2020          }
2021          else
2022          {
2023             w = space.w;
2024             c++;
2025          }
2026          x += w;
2027       }
2028       line.length = x;
2029
2030       if(line.length > this.maxLength)
2031       {
2032          this.maxLine = line;
2033          this.maxLength = line.length;
2034
2035          if(style.autoSize) AutoSize();
2036       }
2037    }
2038
2039    void FindMaxLine()
2040    {
2041       EditLine line;
2042
2043       this.maxLength = 0;
2044       this.maxLine = null;
2045
2046       for(line = lines.first; line; line = line.next)
2047       {
2048          if(line.length > this.maxLength)
2049          {
2050             this.maxLength = line.length;
2051             this.maxLine = line;
2052          }
2053       }
2054
2055       if(style.autoSize) AutoSize();
2056    }
2057
2058    void SelDirty()
2059    {
2060       if(this.selY != this.y)
2061          DirtyAll();
2062       else if(this.selX != this.x)  // commented out to erase caret: if(this.selX != this.x)
2063          DirtyLine(this.y);
2064    }
2065
2066    void ComputeColumn()
2067    {
2068       // Adjust new edit X position according to tabs
2069       int c, position = 0;
2070       unichar ch;
2071       int nb;
2072       for(c = 0; c<this.line.count && c<this.x && (ch = UTF8_GET_CHAR(this.line.buffer + c, nb)); c+= nb)
2073       {
2074          // TODO: MIGHT WANT TO RETHINK WHAT COLUMN SHOULD BE REGARDING TABS
2075          if(ch == '\t')
2076             position += this.tabSize - (position % this.tabSize);
2077          else
2078             position ++;
2079       }
2080       position += this.x - c;
2081       this.col = position;
2082    }
2083
2084    int DelCh(EditLine l1, int y1, int c1, EditLine l2, int y2, int c2, bool placeAfter)
2085    {
2086       return _DelCh(l1, y1, c1, l2, y2, c2, placeAfter, null);
2087    }
2088
2089    bool HasCommentOrEscape(EditLine line)
2090    {
2091       bool hadComment = strstr(line.buffer, "/*") || strstr(line.buffer, "*/");
2092       int c;
2093
2094       if(!hadComment)
2095       {
2096          for(c = line.count-1; c >= 0; c--)
2097          {
2098             char ch = line.buffer[c];
2099             if(ch == '\\')
2100             {
2101                hadComment = true;
2102                break;
2103             }
2104             else //if(ch != ' ' && ch != '\t')
2105                break;
2106          }
2107       }
2108       return hadComment;
2109    }
2110
2111    int _DelCh(EditLine l1, int y1, int c1, EditLine l2, int y2, int c2, bool placeAfter, int * addedSpacesPtr)
2112    {
2113       EditLine line = l1, next;
2114       char * buffer;
2115       int oldCount1, oldCount2;
2116       int y, firstViewY, firstY, firstDropY, firstSelY;
2117       int newLineCount;
2118       int extras = 0;
2119       DelTextAction action = null;
2120       bool hadComment = false;
2121       int start = c1;
2122
2123       if(style.syntax)
2124          hadComment = HasCommentOrEscape(line);
2125
2126       if(y2 > y1 || c2 > c1)
2127       {
2128          if(start < l1.count)
2129          {
2130             while(!UTF8_IS_FIRST(l1.buffer[start]) && start)
2131                start--;
2132          }
2133       }
2134       oldCount1 = l1.count;
2135       buffer = l1.buffer;
2136       while(c1 < oldCount1)
2137       {
2138          byte ch = buffer[c1];
2139          if(UTF8_IS_FIRST(ch)) break;
2140          c1--;
2141          extras++;
2142       }
2143       oldCount2 = l2.count;
2144       buffer = l2.buffer;
2145       while(c2 < oldCount2)
2146       {
2147          byte ch = buffer[c2];
2148          if(UTF8_IS_FIRST(ch)) break;
2149          c2++;
2150          extras++;
2151       }
2152
2153       if(!undoBuffer.dontRecord && (y2 > y1 || c2 > c1))
2154       {
2155          int len;
2156          char * string;
2157
2158          len = GetText(null, l1, y1, start, l2, y2, c2, false, false);
2159          string = new char[len];
2160          action = DelTextAction { y1 = y1, x1 = start, y2 = y2, x2 = c2, string = string, placeAfter = placeAfter };
2161          GetText(string, l1, y1, start, l2, y2, c2, false, false);
2162          Record(action);
2163       }
2164
2165       //oldCount1 = l1.count;
2166       //oldCount2 = l2.count;
2167
2168       {
2169          BufferLocation before = { l1,y1,c1}, after = { l2,y2,c2 };
2170          NotifyCharsDeleted(master, this, &before, &after, this.pasteOperation);
2171       }
2172
2173       if(c2 > oldCount2) c2 = oldCount2;
2174       if(!(style.freeCaret))
2175          if(c1 > oldCount1) c1 = oldCount1;
2176       newLineCount = c1 + l2.count-c2;
2177       if(l1 == l2)
2178       {
2179          /*
2180          if(!line.size)
2181             printf("");
2182          buffer = new char[line.size ? line.size : 1];
2183          */
2184          buffer = new char[line.size];
2185          if(!buffer) return;
2186          CopyBytes(buffer,l2.buffer,oldCount1 + 1/*line.count + 1*//*line.size*/);
2187       }
2188       else
2189          buffer = l2.buffer;
2190
2191       if(!line.AdjustBuffer(newLineCount))
2192          return;
2193
2194 #ifdef _DEBUG
2195       /*if(newLineCount > 4000 || newLineCount < 0)
2196          printf("Warning");*/
2197 #endif
2198       line.count = newLineCount;
2199
2200       memmove(l1.buffer+c1, buffer+c2,line.count-c1);
2201       if(c1>oldCount1)
2202       {
2203          if(action)
2204          {
2205             action.addedSpaces = c1-oldCount1;
2206 #ifdef _DEBUG
2207             if(action.addedSpaces > action.x1)
2208             {
2209                printf("bug!");
2210             }
2211 #endif
2212          }
2213          if(addedSpacesPtr) *addedSpacesPtr = c1-oldCount1;
2214          FillBytes(l1.buffer+oldCount1,' ',c1-oldCount1);
2215       }
2216       line.buffer[line.count] = '\0';
2217
2218       if(l1 == l2)
2219       {
2220          delete buffer;
2221          DirtyLine(y1);
2222          /*
2223          this.dropX -= c2-c1;
2224          this.dropX = Max(this.dropX,0);
2225          this.x -= c2-c1-1;
2226          this.x = Max(this.x,0);
2227          */
2228       }
2229       else
2230          DirtyEnd(y1);
2231
2232       y = y1;
2233       firstViewY = this.viewY;
2234       firstY = this.y;
2235       firstDropY = this.dropY;
2236       firstSelY = this.selY;
2237       for(line = l1;line;line = next, y++)
2238       {
2239          next = line.next;
2240          if(line!=l1)
2241          {
2242             this.lineCount--;
2243             delete line.buffer;
2244
2245             if(line == this.viewLine)
2246             {
2247                if(this.viewLine.next)
2248                {
2249                   this.viewLine = this.viewLine.next;
2250                   //this.viewY++;
2251                   style.recomputeSyntax = true;
2252                }
2253                else
2254                {
2255                   this.viewLine = this.viewLine.prev;
2256                   this.viewY--;
2257                   style.recomputeSyntax = true;
2258                }
2259             }
2260             else if(y < firstViewY)
2261                this.viewY--;
2262             if(line == this.line)
2263             {
2264                if(this.line.next)
2265                {
2266                   this.line = this.line.next;
2267                   this.x = this.line.count;
2268                   //this.y++;
2269                }
2270                else
2271                {
2272                   this.line = this.line.prev;
2273                   this.x = this.line.count;
2274                   this.y--;
2275                }
2276                ComputeColumn();
2277             }
2278             else if(y < firstY)
2279                this.y--;
2280             if(line == this.dropLine)
2281             {
2282                if(this.dropLine.next)
2283                {
2284                   this.dropLine = this.dropLine.next;
2285                   this.dropX = this.dropLine.count;
2286                }
2287                else
2288                {
2289                   this.dropLine = this.dropLine.prev;
2290                   this.dropX = this.dropLine.count;
2291                   this.dropY--;
2292                }
2293             }
2294             else if(y < firstDropY)
2295                this.dropY--;
2296             if(line == this.selLine)
2297             {
2298                if(this.selLine.next)
2299                {
2300                   this.selLine = this.selLine.next;
2301                   this.selX = this.selLine.count;
2302                }
2303                else
2304                {
2305                   this.selLine = this.selLine.prev;
2306                   this.selX = this.selLine.count;
2307                   this.selY--;
2308                }
2309             }
2310             else if(y < firstSelY)
2311                this.selY--;
2312             lines.Delete(line);
2313          }
2314          if(line == l2) break;
2315       }
2316       ComputeLength(l1);
2317       FindMaxLine();
2318       if(style.syntax && (hadComment || HasCommentOrEscape(this.line)))
2319       {
2320          DirtyAll();
2321          style.recomputeSyntax = true;
2322       }
2323       return extras;
2324    }
2325
2326    bool DelSel(int * addedSpacesPtr)
2327    {
2328       if(this.line != this.selLine || this.x != this.selX)
2329       {
2330          if(this.selY < this.y)
2331          {
2332             _DelCh(this.selLine, this.selY, this.selX, this.line, this.y, this.x, true, addedSpacesPtr);
2333             this.x = this.selX;
2334             this.y = this.selY;
2335             this.line = this.selLine;
2336          }
2337          else if(this.selY > this.y)
2338          {
2339             _DelCh(this.line, this.y, this.x, this.selLine, this.selY, this.selX, false, addedSpacesPtr);
2340             this.selX = this.x;
2341             this.selY = this.y;
2342             this.selLine = this.line;
2343          }
2344          else if(this.selX < this.x)
2345          {
2346             _DelCh(this.selLine, this.selY, this.selX, this.line, this.y, this.x, true, addedSpacesPtr);
2347             this.x = this.selX;
2348             this.y = this.selY;
2349             this.line = this.selLine;
2350          }
2351          else
2352          {
2353             _DelCh(this.line, this.y, this.x, this.selLine, this.selY, this.selX, false, addedSpacesPtr);
2354             this.selX = this.x;
2355             this.selY = this.y;
2356             this.selLine = this.line;
2357          }
2358          ComputeColumn();
2359          return true;
2360       }
2361       return false;
2362    }
2363
2364    bool AddToLine(char * stringLine, int count, bool LFComing, int * addedSpacesPtr, int * addedTabsPtr)
2365    {
2366       bool hadComment = false;
2367       // Add the line here
2368       EditLine line = this.line;
2369
2370       // The purpose of this is solely to lock a max number of characters if no HSCROLLING is present
2371       if(!style.hScroll && created)
2372       {
2373          int endX = (style.freeCaret) ? this.x : Min(this.x, line.count);
2374          int max;
2375          int x;
2376          int c;
2377
2378          // Lock if no place to display.
2379          if(LFComing)
2380             max = endX;
2381          else if(endX > this.x)
2382             max = Max(this.x, line.count);
2383          else
2384             max = line.count;
2385
2386          for(x = 0, c = 0; c < max+count; )
2387          {
2388             int w;
2389             int numBytes = 1;
2390             char * string;
2391             if(c < Min(this.x, line.count))
2392                string = line.buffer + c;
2393             else if(c < endX)
2394                string = " ";
2395             else if(c < endX + count)
2396                string = stringLine + c - endX;
2397             else
2398                string = line.buffer + c - endX - count;
2399
2400             if(*string == '\t')
2401             {
2402                w = (tabSize * space.w) - (x % (tabSize * space.w));
2403             }
2404             else
2405             {
2406                numBytes = UTF8_NUM_BYTES(*string);
2407                FontExtent(display, this.font, string, numBytes, &w, null);
2408             }
2409             x += w;
2410
2411             if(x >= clientSize.w)
2412             {
2413                count = c - max;
2414                break;
2415             }
2416             c += numBytes; // - 1;
2417          }
2418       }
2419
2420       if(count > 0)
2421       {
2422          int addedSpaces = 0;
2423          int addedTabs = 0;
2424
2425          // Add blank spaces if EES_FREECARET
2426          if(this.x > line.count)
2427          {
2428             if(style.freeCaret)
2429             {
2430                if(style.useTab)
2431                {
2432                   int wantedPosition;
2433                   int position = 0;
2434                   int c;
2435
2436                   for(c = 0; c<line.count; c++)
2437                   {
2438                      if(this.line.buffer[c] == '\t')
2439                         position += this.tabSize - (position % this.tabSize);
2440                      else
2441                         position ++;
2442                   }
2443                   wantedPosition = position + (this.x - line.count);
2444
2445                   // A tab is too much...
2446                   if(position + (this.tabSize - (position % this.tabSize)) > wantedPosition)
2447                      addedSpaces = wantedPosition - position;
2448                   else
2449                   {
2450                      // Put a first tab
2451                      addedTabs = 1;
2452                      position += this.tabSize - (position % this.tabSize);
2453                      // Add as many tabs as needed
2454                      addedTabs += (wantedPosition - position) / this.tabSize;
2455                      position += (addedTabs-1) * this.tabSize;
2456                      // Finish off with spaces
2457                      addedSpaces = wantedPosition - position;
2458                   }
2459                }
2460                else
2461                   addedSpaces = this.x - line.count;
2462             }
2463          }
2464          this.x = Min(this.x, line.count);
2465
2466          if(line.count + count + addedSpaces + addedTabs > this.maxLineSize)
2467             return false;
2468
2469          DirtyLine(this.y);
2470
2471          if(style.syntax)
2472             hadComment = HasCommentOrEscape(line);
2473
2474          // Adjust the size of the line
2475          if(!line.AdjustBuffer(line.count+count+addedTabs+addedSpaces))
2476             return false;
2477
2478          {
2479             BufferLocation before = { this.line, this.y, this.x }, after = { this.line, this.y, this.x };
2480             bool hasComment;
2481
2482             memmove(line.buffer+this.x+count, line.buffer+this.x,line.count-this.x);
2483             CopyBytes(line.buffer + this.x + addedTabs + addedSpaces, stringLine, count);
2484             if(addedTabs)
2485             {
2486                *addedTabsPtr = addedTabs;
2487                FillBytes(line.buffer+line.count,'\t',addedTabs);
2488 #ifdef _DEBUG
2489       if(addedTabs > 4000 || addedTabs < 0)
2490          printf("Warning");
2491 #endif
2492                line.count += addedTabs;
2493             }
2494             else if(addedTabs)
2495                *addedTabsPtr = 0;
2496             if(addedSpaces)
2497             {
2498                FillBytes(line.buffer+line.count,' ',addedSpaces);
2499 #ifdef _DEBUG
2500       if(addedSpaces > 4000 || addedSpaces < 0)
2501          printf("Warning");
2502 #endif
2503                line.count += addedSpaces;
2504                if(addedSpacesPtr) *addedSpacesPtr = addedSpaces;
2505             }
2506             else if(addedSpacesPtr)
2507                *addedSpacesPtr = 0;
2508 #ifdef _DEBUG
2509       if(count > 4000 || count < 0)
2510          printf("Warning");
2511 #endif
2512             line.count += count;
2513             this.x += count + addedTabs + addedSpaces;
2514             this.selX = this.x;
2515             this.selY = this.y;
2516             this.selLine = this.line;
2517
2518             line.buffer[line.count] = '\0';
2519
2520             ComputeLength(line);
2521             ComputeColumn();
2522
2523             after.x = this.x;
2524
2525             hasComment = HasCommentOrEscape(line);
2526             if(!undoBuffer.insideRedo)
2527             {
2528                int backDontRecord = undoBuffer.dontRecord;
2529                undoBuffer.dontRecord = 0;
2530                NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
2531                undoBuffer.dontRecord = backDontRecord;
2532             }
2533             if(style.syntax && (hadComment || hasComment || line != this.line))
2534             {
2535                style.recomputeSyntax = true;
2536                DirtyAll();
2537             }
2538          }
2539       }
2540       return true;
2541    }
2542
2543    void Emptyline(EditLine line, int y)
2544    {
2545       if(line.next)
2546          DelCh(line, y, 0, line.next, y+1, 0, true);
2547       else
2548          DelCh(line, y, 0, line, y, line.count, true);
2549    }
2550
2551    void GoToEnd(bool deselect)
2552    {
2553       if(this.line)
2554       {
2555          this.line = this.lines.last;
2556          if(this.y != this.lineCount-1)
2557             DirtyAll();
2558          else if (this.x != this.line.count)
2559             DirtyLine(this.lineCount-1);
2560          this.y = this.lineCount-1;
2561          this.x = this.line.count;
2562          ComputeColumn();
2563          if(deselect)
2564             _Deselect();
2565       }
2566    }
2567
2568    void GoToHome(bool deselect)
2569    {
2570       if(this.line)
2571       {
2572          this.line = this.lines.first;
2573          if(this.y != 0)
2574             DirtyAll();
2575          else if (this.x !=0)
2576             DirtyLine(this.lineCount-1);
2577          this.y = 0;
2578          this.x = 0;
2579          this.col = 0;
2580          if(deselect)
2581             _Deselect();
2582       }
2583    }
2584
2585    // Returns true if it needs scrolling
2586    bool FindMouse(int px, int py, int * tx, int * ty, EditLine * tline, bool half)
2587    {
2588       int w;
2589       int c;
2590       int x, y;
2591       EditLine line;
2592       bool needHScroll = false;
2593
2594       if(py < 0)
2595       {
2596          if(this.viewY > 0)
2597          {
2598             y = this.viewY-1;
2599             line = this.viewLine ? (void *)this.viewLine.prev : null;
2600          }
2601          else
2602          {
2603             y = 0;
2604             line = (void *)this.lines.first;
2605          }
2606       }
2607       else
2608       {
2609          py = Min(py, clientSize.h);
2610          py /= this.space.h;
2611          py = Min(py, this.lineCount);
2612          y = this.viewY;
2613          for(c = 0, line = this.viewLine; (line != (void *)this.lines.last && c<py); line = line.next, c++)
2614          {
2615             y++;
2616          }
2617       }
2618
2619       if( (px >= clientSize.w || px < clientSize.w/2) && this.viewX)
2620          needHScroll = true;
2621       px = Max(px,0);
2622       px = Min(px,clientSize.w+this.space.w);
2623
2624       if(tx && line)
2625       {
2626          *tx = AdjustXPosition(line, px + viewX, half, null, MAXINT, 0);
2627       }
2628
2629       if(tline) *tline = line;
2630       if(ty) *ty = y;
2631
2632       // Prevent divide by 0 from non valid this.font
2633       if(!this.space.h)
2634          return (y < this.viewY) || needHScroll;
2635       else
2636          return (y < this.viewY || y >= this.viewY + clientSize.h / this.space.h) || needHScroll;
2637       return false;
2638    }
2639
2640    // Minimal Update Management Functions
2641    void DirtyAll()
2642    {
2643       this.startY = 0;
2644       this.endY = clientSize.h-1;
2645    //   ErrorLog("DirtyAll\n");
2646    }
2647
2648    void DirtyEnd(int y)
2649    {
2650       if((y - this.viewY)*this.space.h < this.startY)
2651          this.startY = (y - this.viewY)*this.space.h+ YOFFSET;
2652       this.endY = clientSize.h-1;
2653       //ErrorLog("DirtyEnd %d\n", y);
2654    }
2655
2656    void DirtyLine(int y)
2657    {
2658       if(y >= this.viewY)
2659       {
2660          if((y - this.viewY)*this.space.h < this.startY)
2661             this.startY = (y - this.viewY)*this.space.h + YOFFSET;
2662          if((y - this.viewY+1)*this.space.h > this.endY)
2663             this.endY = (y - this.viewY+1)*this.space.h-1 + YOFFSET;
2664       }
2665       //ErrorLog("DirtyLine %d\n", y);
2666    }
2667
2668    void UpdateDirty()
2669    {
2670       Box box;
2671
2672       if(style.recomputeSyntax)
2673       {
2674          FigureStartSyntaxStates(lines.first, true);
2675          style.recomputeSyntax = false;
2676       }
2677       box.left  = 0;
2678       if(this.startY > this.endY) return;
2679       if(this.startY <= 0 && this.endY >= clientSize.h-1)
2680       {
2681          Update(null);
2682       }
2683       else
2684       {
2685          box.right = clientSize.w-1;
2686          box.top   = this.startY;
2687          box.bottom  = this.endY;
2688          Update(box);
2689       }
2690       this.startY = clientSize.h;
2691       this.endY = 0;
2692    }
2693
2694    bool IsMouseOnSelection()
2695    {
2696       bool mouseOnSelection = false;
2697
2698       int x, y;
2699       int minY = Min(this.selY, this.y);
2700       int maxY = Max(this.selY, this.y);
2701       int minX = Min(this.selX, this.x);
2702       int maxX = Max(this.selX, this.x);
2703
2704       FindMouse(this.mouseX - this.space.w / 2, this.mouseY, &x, &y, null, false);
2705
2706       if(maxX != minX || maxY != minY)
2707       {
2708          if(y > minY && y < maxY)
2709             mouseOnSelection = true;
2710          else if(y == minY && y == maxY)
2711             mouseOnSelection = (x < maxX && x >= minX);
2712          else if(y == minY)
2713          {
2714             if(y == this.selY)
2715                mouseOnSelection = (x >= this.selX);
2716             else if(y == this.y)
2717                mouseOnSelection = (x >= this.x);
2718          }
2719          else if(y == maxY)
2720          {
2721             if(y == this.selY)
2722                mouseOnSelection = (x < this.selX);
2723             else if(y == this.y)
2724                mouseOnSelection = (x < this.x);
2725          }
2726       }
2727       return mouseOnSelection;
2728    }
2729
2730    void UpdateCaretPosition(bool setCaret)
2731    {
2732       if(line)
2733       {
2734          if(mouseMove || (!overwrite && !style.noCaret))
2735          {
2736             int max = this.mouseMove ? this.dropX : this.x;
2737             int y = this.mouseMove ? this.dropY : this.y;
2738             EditLine line = this.mouseMove ? this.dropLine : this.line;
2739             int c, x = 0;
2740             if(!(style.freeCaret))
2741                max = Min(max, line.count);
2742
2743             if(FontExtent && display)
2744             {
2745                for(c = 0; c < max; )
2746                {
2747                   int len = 1;
2748                   int start = c;
2749                   int w;
2750                   if(c < line.count)
2751                   {
2752                      byte ch = 0;
2753                      int numBytes;
2754                      for(len = 0; c < Min(max, line.count); c += numBytes)
2755                      {
2756                         ch = line.buffer[c];
2757                         numBytes = UTF8_NUM_BYTES(ch);
2758
2759                         if(ch == ' ' || ch == '\t')
2760                         {
2761                            if(!len) c++;
2762                            break;
2763                         }
2764                         len += numBytes;
2765                      }
2766                      if(!len && ch == ' ')
2767                      {
2768                         w = space.w;
2769                         len = 1;
2770                      }
2771                      else if(!len && ch == '\t')
2772                      {
2773                         w = (tabSize * space.w) - (x % (tabSize * space.w));
2774                         len = 1;
2775                      }
2776                      else
2777                         FontExtent(display, this.font, line.buffer + start, len, &w, null);
2778                   }
2779                   else
2780                   {
2781                      w = space.w;
2782                      c++;
2783                   }
2784                   x += w;
2785                }
2786             }
2787             if(setCaret)
2788                caretX = x;
2789             caretY = y * this.space.h;
2790             SetCaret(x + XOFFSET-2, y * space.h + YOFFSET, space.h);
2791          }
2792          else
2793             SetCaret(0, 0, 0);
2794
2795          NotifyCaretMove(master, this, y + 1, x + 1);
2796
2797          SelectionEnables();
2798       }
2799    }
2800
2801    void SelectionEnables()
2802    {
2803       if((x != selX || y != selY) && !selection)
2804       {
2805          if(!style.readOnly)
2806          {
2807             itemEditCut.disabled = false;
2808             itemEditDelete.disabled = false;
2809          }
2810          itemEditCopy.disabled = false;
2811
2812          this.selection = true;
2813       }
2814       else if((x == selX && y == selY) && selection)
2815       {
2816          itemEditCut.disabled = true;
2817          itemEditCopy.disabled = true;
2818          itemEditDelete.disabled = true;
2819
2820          this.selection = false;
2821       }
2822    }
2823
2824    void SetSelectCursor()
2825    {
2826       if(!inactive || !style.noSelect)
2827       {
2828          if(this.mouseMove)
2829             cursor = guiApp.GetCursor(arrow);
2830          else if(this.mouseSelect)
2831             cursor = guiApp.GetCursor(iBeam);
2832          else
2833          {
2834             if(IsMouseOnSelection())
2835                cursor = guiApp.GetCursor(arrow);
2836             else
2837                cursor = guiApp.GetCursor(iBeam);
2838          }
2839       }
2840    }
2841
2842    int AdjustXPosition(EditLine line, int position, bool half, int * px, int max, int sc)
2843    {
2844       int c = sc;
2845       int x = px ? *px : 0;
2846       while(true)
2847       {
2848          int start = c;
2849          int numBytes = 1;
2850          int len = 1;
2851          int w;
2852          if(c < Min(max, line.count))
2853          {
2854             byte ch = 0;
2855             int numBytes;
2856             for(len = 0; c < Min(max, line.count); c += numBytes)
2857             {
2858                ch = line.buffer[c];
2859                numBytes = UTF8_NUM_BYTES(ch);
2860
2861                if(ch == ' ' || ch == '\t')
2862                {
2863                   if(!len) c++;
2864                   break;
2865                }
2866                len += numBytes;
2867             }
2868             if(!len && ch == ' ')
2869             {
2870                w = space.w;
2871                len = 1;
2872             }
2873             else if(!len && ch == '\t')
2874             {
2875                w = (tabSize * space.w) - (x % (tabSize * space.w));
2876                len = 1;
2877             }
2878             else
2879                FontExtent(display, font, line.buffer + start, len, &w, null);
2880          }
2881          else
2882          {
2883             if(style.freeCaret && c < max)
2884                w = space.w;
2885             else
2886             {
2887                if(px) *px = x;
2888                return c;
2889             }
2890             c++;
2891          }
2892          if(x + (((half && len == 1) ? (w / 2) : w)) >= position)
2893          {
2894             int lastW;
2895             while(len > 0)
2896             {
2897                int a = start + len;
2898                lastW = w;
2899                if(a <= line.count)
2900                   while(a > 0 && !UTF8_IS_FIRST(line.buffer[--a]));
2901                else
2902                   a--;
2903                if(a > start)
2904                   FontExtent(display, font, line.buffer + start, a - start, &w, null);
2905                else
2906                   w = 0;
2907                if(position > x + (half ? ((w + lastW) / 2) : lastW)) break;
2908                len = a - start;
2909             }
2910             if(px) *px = x + w;
2911             return Min(this.maxLineSize - 1, start + len);
2912          }
2913          x += w;
2914       }
2915    }
2916
2917    void SetCursorToViewX()
2918    {
2919       bool selecting = this.x != selX || y != selY;
2920
2921       // Horizontal Adjustment
2922       int x = 0;
2923       int c = AdjustXPosition(line, viewX, false, &x, MAXINT, 0);
2924       if(this.x < c)
2925          this.x = x;
2926       else
2927       {
2928          c = AdjustXPosition(line, viewX + clientSize.w - 1, false, &x, MAXINT, c);
2929          if(this.x > c)
2930             this.x = c;
2931      }
2932
2933       if(!selecting)
2934       {
2935          selX = this.x;
2936          selY = y;
2937          selLine = line;
2938       }
2939
2940       UpdateCaretPosition(false);
2941
2942       UpdateDirty();
2943       SetSelectCursor();
2944    }
2945
2946    void SetCursorToViewY()
2947    {
2948       int c, numLines;
2949       EditLine oldLine = this.line;
2950
2951       bool selecting = this.x != this.selX || this.y != this.selY;
2952
2953       numLines = clientSize.h / this.space.h;
2954
2955       // Vertical Adjustment
2956       if(this.viewY > this.y)
2957       {
2958          this.y = this.viewY;
2959          this.line = this.viewLine;
2960       }
2961
2962       if(this.viewY + numLines <=  this.y)
2963       {
2964          EditLine line;
2965
2966          this.y = this.viewY-1;
2967          for(c = 0, line = this.viewLine; line && c<numLines; line = line.next, c++)
2968          {
2969             this.line = line;
2970             this.y++;
2971          }
2972       }
2973
2974       if(this.line != oldLine)
2975       {
2976          this.x = AdjustXPosition(this.line, caretX, true, null, MAXINT, 0);
2977          ComputeColumn();
2978       }
2979
2980       if(!selecting)
2981       {
2982          this.selX = this.x;
2983          this.selY = this.y;
2984          this.selLine = this.line;
2985       }
2986
2987       UpdateCaretPosition(false);
2988
2989       UpdateDirty();
2990       SetSelectCursor();
2991    }
2992
2993    /*
2994    bool SaveFile(char * fileName)
2995    {
2996       File f = eFile_Open(fileName, FO_WRITE);
2997       if(f)
2998       {
2999          Save(f, false);
3000          eWindow_SetModified(false);
3001          eInstance_Delete(f);
3002          return true;
3003       }
3004       return false;
3005    }
3006    */
3007
3008    bool OnActivate(bool active, Window previous, bool * goOnWithActivation, bool direct)
3009    {
3010       if(line)
3011       {
3012          if(!style.multiLine)
3013          {
3014             x = (active && this.active && !style.readOnly) ? line.count : 0;
3015             selX = 0;
3016             ComputeColumn();
3017             SetViewToCursor(true);
3018             DirtyLine(0);
3019             UpdateDirty();
3020          }
3021          // Update(null);
3022          if(!active && modified)
3023          {
3024             modified = false;
3025             if(!NotifyModified(master, this))
3026             {
3027                modified = true;
3028                *goOnWithActivation = false;
3029             }
3030          }
3031       }
3032       if(!active)
3033       {
3034          ReleaseCapture();
3035          if(timer) timer.Stop();
3036
3037          mouseSelect = false;
3038          wordSelect = false;
3039       }
3040       return true;
3041    }
3042
3043    void AutoSize()
3044    {
3045       int aw = maxLength + 12, ah = Max(lineCount, 1) * space.h + 2;
3046       int nw = minClientSize.w, nh = minClientSize.h, xw = maxClientSize.w, xh = maxClientSize.h;
3047       clientSize = { nw && aw < nw ? nw : xw && aw > xw ? xw : aw, nh && ah < nh ? nh : xh && ah > xh ? xh : ah };
3048    }
3049
3050    bool OnResizing(int *w, int *h)
3051    {
3052       if(!*h)
3053          *h = space.h + 2;
3054       if(!*w)
3055          //*w = 80;
3056          *w = space.h * 80 / 14;
3057       return true;
3058    }
3059
3060    void OnResize(int w, int h)
3061    {
3062    /*
3063       if(!hasHorzScroll && !hasVertScroll && viewLine)
3064          SetViewToCursor(true);
3065    */
3066       //if(!hasHorzScroll && !hasVertScroll && viewLine)
3067       if(viewLine)
3068          SetViewToCursor(true);
3069       //else
3070    //     FixScrollArea();
3071    }
3072
3073    bool OnMiddleButtonDown(int x, int y, Modifiers mods)
3074    {
3075       if(style.readOnly) return true;
3076       // We really shouldn't be pasting here, Unix middle button paste is for the selection (not the clipboard), good for the terminal
3077       // Middle button already serves as a dragger as well.
3078       // Paste();
3079       return true;
3080    }
3081
3082    bool OnRightButtonDown(int x, int y, Modifiers mods)
3083    {
3084       this.rightButtonDown = true;
3085       // Copy();
3086       return true;
3087    }
3088
3089    bool OnRightButtonUp(int x, int y, Modifiers mods)
3090    {
3091       // Context Menu
3092       if(!parent.inactive && rightButtonDown)
3093       {
3094          PopupMenu popup;
3095          Menu contextMenu { };
3096
3097          MenuItem { contextMenu, $"Cut\tCtrl+X", t, NotifySelect = itemEditCut.NotifySelect, disabled = !selection || style.readOnly };
3098          MenuItem { contextMenu, $"Copy\tCtrl+C", c, NotifySelect = itemEditCopy.NotifySelect, disabled = !selection };
3099          MenuItem { contextMenu, $"Paste\tCtrl+V", p, NotifySelect = itemEditPaste.NotifySelect, disabled = style.readOnly };
3100          MenuItem { contextMenu, $"Delete\tDel", d, NotifySelect = itemEditDelete.NotifySelect, disabled = !selection || style.readOnly };
3101          MenuDivider { contextMenu };
3102          MenuItem { contextMenu, $"Select All\tCtrl+A", a, NotifySelect = itemEditSelectAll.NotifySelect };
3103
3104          popup = PopupMenu { master = this, menu = contextMenu,
3105    /*
3106             nonClient = true, interim = false, parent = parent,
3107             position = { x + clientStart.x + parent.clientStart.x + position.x, y + cientStart.y + parent.sy + clientStart.y + position.y };
3108    */
3109             position = { x + clientStart.x + absPosition.x - guiApp.desktop.position.x, y + clientStart.y + absPosition.y - guiApp.desktop.position.y }
3110          };
3111          popup.Create();
3112       }
3113       rightButtonDown = false;
3114       return true;
3115    }
3116
3117    bool OnLeftButtonDown(int mx, int my, Modifiers mods)
3118    {
3119       int x,y;
3120       EditLine line;
3121
3122       if(style.noSelect) return true;
3123
3124       // Should we have a separate 'selectOnActivate' style?
3125       if(!mods.isActivate || (style.readOnly && style.multiLine))
3126       {
3127          Capture();
3128          mouseSelect = true;
3129       }
3130
3131       mouseX = mx - XOFFSET;
3132       mouseY = my;
3133
3134       FindMouse(mouseX, mouseY, &x, &y, &line, true);
3135 #ifdef _DEBUG
3136       //PrintLn("OnLeftButtonDown: ", x, ", ", y);
3137 #endif
3138       if(!style.readOnly)
3139       {
3140          if(wordSelect)
3141             mouseMove = false;
3142          else if(IsMouseOnSelection() && !mods.isActivate)
3143          {
3144             DirtyLine(this.y);
3145             mouseMove = true;
3146             dropX = x;
3147             dropY = y;
3148             dropLine = line;
3149          }
3150       }
3151
3152       if(!mouseMove && !wordSelect && (!mods.isActivate || style.multiLine))
3153       {
3154          if(mods.shift && !mods.isActivate)
3155          {
3156             this.x = x;
3157             this.y = y;
3158             this.line = line;
3159             DirtyAll();
3160          }
3161          else
3162          {
3163             SelDirty();
3164             DirtyLine(this.y);
3165             this.x = x;
3166             this.y = y;
3167             this.line = line;
3168             DirtyLine(this.y);
3169             _Deselect();
3170          }
3171          ComputeColumn();
3172       }
3173
3174       UpdateDirty();
3175       UpdateCaretPosition(true);
3176       return true;
3177    }
3178
3179    bool OnLeftButtonUp(int x, int y, Modifiers mods)
3180    {
3181       timer.Stop();
3182
3183       mouseSelect = false;
3184       wordSelect = false;
3185
3186       x -= XOFFSET;
3187
3188       ReleaseCapture();
3189       if(!style.readOnly)
3190       {
3191          if(mouseMove)
3192          {
3193             EditLine line;
3194             FindMouse(mouseX, mouseY, &x, &y, &line, true);
3195 #ifdef _DEBUG
3196             //PrintLn("MouseMove: ", x, ", ", y);
3197 #endif
3198             dropX = x;
3199             dropY = y;
3200             dropLine = line;
3201
3202             mouseMove = IsMouseOnSelection();
3203
3204             if(!mouseMove)
3205             {
3206                int size = SelSize();
3207                if(size)
3208                {
3209                   char * text = new char[size+1];
3210                   if(text)
3211                   {
3212                      int moveX = 0;
3213                      GetSel(text, false);
3214
3215                      if(Max(selY, this.y) == dropY)
3216                      {
3217                         if(this.x > selX)
3218                         {
3219                            if(this.dropX > this.selX)
3220                               moveX = this.x - this.selX;
3221                         }
3222                         else
3223                         {
3224                            if(this.dropX > this.x)
3225                               moveX = this.selX - this.x;
3226                         }
3227                      }
3228                      DelSel(null);
3229                      this.dropX -= moveX;
3230                      this.selX = this.x = this.dropX;
3231                      this.selY = this.y = this.dropY;
3232                      this.selLine = this.line = this.dropLine;
3233                      AddS(text);
3234                      SetViewToCursor(true);
3235                      delete text;
3236                      Modified();
3237 #ifdef _DEBUG
3238                    /*  {
3239                         byte * c = ((EditBox)this).multiLineContents, * c2;
3240                         int l1 = c ? strlen(c) : 0, l2;
3241                         Undo();
3242                         Redo();
3243                         c2 = ((EditBox)this).multiLineContents;
3244                         l2 = c2 ? strlen(c2) : 0;
3245                         if(l1 != l2 || (l1 && strcmp(c, c2)))
3246                         {
3247                            PrintLn("Fail!");
3248                         }
3249                         delete c;
3250                      }
3251                      */
3252 #endif
3253                   }
3254                }
3255             }
3256             else
3257             {
3258                SelDirty();
3259                DirtyLine(this.y);
3260                this.x = x;
3261                this.y = y;
3262                this.line = line;
3263                ComputeColumn();
3264                DirtyLine(this.y);
3265                _Deselect();
3266                UpdateDirty();
3267             }
3268          }
3269          else
3270          {
3271             EditLine line;
3272             mouseX = x;
3273             mouseY = y;
3274
3275             FindMouse(mouseX, mouseY, &x, &y, &line, true);
3276 #ifdef _DEBUG
3277             //PrintLn("Dropped: ", x, ", ", y);
3278 #endif
3279             NotifyDropped(master, this, x, y);
3280          }
3281       }
3282       mouseMove = false;
3283       return true;
3284    }
3285
3286    bool OnMouseMove(int mx, int my, Modifiers mods)
3287    {
3288       int x,y;
3289       EditLine line;
3290       bool needScroll;
3291
3292       if(mods != -1 && mods.isSideEffect)
3293       {
3294          SetSelectCursor();
3295          return true;
3296       }
3297       if(style.noSelect) return true;
3298       if(wordSelect) return true;
3299       mouseX = mx - XOFFSET;
3300       mouseY = my;
3301
3302       needScroll = FindMouse(this.mouseX, this.mouseY, &x, &y, &line, true);
3303
3304       if(this.mouseMove || this.mouseSelect)
3305       {
3306          if(!needScroll)
3307             timer.Stop();
3308          else
3309          {
3310             if(needScroll)
3311                timer.Start();
3312             if(mods != -1 &&
3313                ((style.hScroll) || (style.vScroll)))
3314                return true;
3315          }
3316       }
3317
3318       if(this.mouseMove)
3319       {
3320          DirtyLine(this.dropY);
3321          this.dropX = x;
3322          this.dropY = y;
3323          DirtyLine(this.dropY);
3324          this.dropLine = line;
3325          SetViewToCursor(true);
3326 #ifdef _DEBUG
3327          //PrintLn("MouseMove: ", "dropX = ", x, ", dropY = ", y);
3328 #endif
3329       }
3330       else if(this.mouseSelect)
3331       {
3332          DirtyLine(this.selY);
3333          DirtyLine(this.y);
3334          this.x = x;
3335          this.y = y;
3336          ComputeColumn();
3337          DirtyLine(this.y);
3338          this.line = line;
3339          SetViewToCursor(true);
3340          UpdateDirty();
3341 #ifdef _DEBUG
3342          //PrintLn("MouseSelect: ", "x = ", x, ", y = ", y);
3343 #endif
3344       }
3345       SetSelectCursor();
3346       return true;
3347    }
3348
3349    bool OnLeftDoubleClick(int mx, int my, Modifiers mods)
3350    {
3351       int x,y;
3352       EditLine line;
3353 #ifdef _DEBUG
3354       //PrintLn("OnLeftDoubleClick: ", mx, ", ", my, ", mods = ", mods);
3355 #endif
3356       mx -= XOFFSET;
3357
3358       if(style.noSelect) return true;
3359       FindMouse(mx, my, &x, &y, &line, false);
3360       if(!NotifyDoubleClick(master, this, line, mods))
3361          return false;
3362       if(x < line.count)
3363       {
3364          int c;
3365          int start = -1;
3366          int numBytes;
3367          for(c = x; c >= 0; c--)
3368          {
3369             unichar ch;
3370             while(c > 0 && !UTF8_IS_FIRST(line.buffer[c])) c--;
3371             ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
3372             if(!IS_ALUNDER(ch))
3373                break;
3374             start = c;
3375          }
3376          if(start != -1)
3377          {
3378             for(c = start; c<line.count; c += numBytes)
3379             {
3380                unichar ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
3381                if(!ch || !IS_ALUNDER(ch))
3382                   break;
3383             }
3384             SelDirty();
3385             DirtyLine(this.y);
3386             this.y = y;
3387             DirtyLine(this.y);
3388             this.selX = start;
3389             this.x = c;
3390             ComputeColumn();
3391             this.line = this.selLine = line;
3392             this.wordSelect = (c != start);
3393             UpdateDirty();
3394          }
3395       }
3396       return true;
3397    }
3398
3399    bool OnPostCreate()
3400    {
3401       /*if(isDocument)
3402       {
3403          Menu fileMenu { menu, "File", F };
3404          saveDialog = fileDialog;
3405          MenuItem { fileMenu, $"Save\tCtrl+S", S, CtrlS, NotifySelect = MenuFileSave };
3406          MenuItem { fileMenu, $"Save As...", A, NotifySelect = MenuFileSaveAs };
3407       }*/
3408       if(style.autoSize) AutoSize();
3409       return true;
3410    }
3411
3412    void ComputeFont()
3413    {
3414       if(FontExtent)
3415       {
3416          FontExtent(display, font, " ", 1, (int *)&space.w, (int *)&space.h);
3417          FontExtent(display, font, "W", 1, (int *)&large.w, (int *)&large.h);
3418
3419          space.w = Max(space.w, 1);
3420          large.w = Max(large.w, 1);
3421          space.h = Max(space.h, 1);
3422          large.h = Max(large.h, 1);
3423
3424          {
3425             EditLine line;
3426             for(line = lines.first; line; line = line.next)
3427                ComputeLength(line);
3428             FindMaxLine();
3429          }
3430
3431          if(viewLine)
3432             SetViewToCursor(true);
3433
3434          FixScrollArea();
3435
3436          Update(null);
3437       }
3438    }
3439
3440    bool OnLoadGraphics()
3441    {
3442       FontExtent = Display::FontExtent;
3443       font = fontObject;
3444       ComputeFont();
3445       // UpdateCaretPosition(true);
3446       return true;
3447    }
3448
3449    void OnUnloadGraphics()
3450    {
3451       this.font = null;
3452    }
3453
3454    bool OnKeyHit(Key key, unichar ch)
3455    {
3456       bool shift = (key.shift) ? true : false;
3457 #ifdef _DEBUG
3458       //PrintLn("OnKeyHit: code = ", key.code, ", mods = ", key.modifiers, ", ch = ", (int)ch);
3459 #endif
3460       if(!ch && !key.alt && !key.ctrl)
3461       {
3462          key.code = (SmartKey)key.code;
3463       }
3464       else if(!ch && key.alt)
3465          key.code = 0;
3466
3467       switch(key.code) //(ch || key.alt || key.ctrl) ? key.code : (Key)(SmartKey)key.code)
3468       {
3469          case backSpace:
3470             if(style.readOnly) break;
3471             if(style.stuckCaret) GoToEnd(true);
3472             if(!(style.freeCaret))
3473             {
3474                this.x = Min(this.x, this.line.count);
3475             }
3476             if(key.ctrl)
3477             {
3478                int y;
3479                bool done = false;
3480                EditLine line = this.line;
3481                int c;
3482                for(y = this.y; y>= 0; y--)
3483                {
3484                   c = (y == this.y) ? (this.x-1) : line.count-1;
3485
3486                   // Slow down when going on lines...
3487                   if(y != this.y) break;
3488
3489                   for(; c>=0; c--)
3490                   {
3491                      //if(this.line.buffer[c] != '\t' && this.line.buffer[c] != ' ')
3492                      if(IS_ALUNDER(line.buffer[c]))
3493                         break;
3494                   }
3495                   for(; c>=0; c--)
3496                   {
3497                      if(!IS_ALUNDER(line.buffer[c]))
3498                      {
3499                      //if(this.line.buffer[c] == '\t' || this.line.buffer[c] == ' ')
3500                         done = true;
3501                         break;
3502                      }
3503                   }
3504                   if(done)
3505                      break;
3506                   if(line.prev)
3507                      line = line.prev;
3508                   else
3509                      break;
3510                }
3511                //if(this.x != 0)
3512                {
3513                   DelCh(line,y,c+1,this.line,this.y,this.x, true);
3514                   this.x = this.selX = Min(c+1, line.count);
3515                   this.y = this.selY = y;
3516                   this.line = this.selLine = line;
3517                   SetViewToCursor(true);
3518                }
3519                ComputeColumn();
3520             }
3521             else
3522             {
3523                BackSpace();
3524             }
3525             return false;
3526             //break;
3527          case del:
3528             if(style.readOnly) break;
3529             if(style.stuckCaret) break;
3530
3531             if(shift)
3532             {
3533                Cut();
3534             }
3535             else
3536             {
3537                // Delete selection
3538                if(this.line != this.selLine || this.x != this.selX)
3539                {
3540                   DelSel(null);
3541                   SetViewToCursor(true);
3542                   Modified();
3543                }
3544                // Delete word
3545                else if(key.ctrl)
3546                {
3547                   if(this.x < this.line.count)
3548                   {
3549                      int i;
3550                      int length;
3551                      for(i = this.x; i < this.line.count; i++)
3552                      {
3553                         if(!IS_ALUNDER(this.line.buffer[i]))
3554                            break;
3555                      }
3556
3557                      for(; i < this.line.count; i++)
3558                      {
3559                         //Delete trailing whitespace
3560                         if(IS_ALUNDER(this.line.buffer[i]))
3561                            break;
3562                      }
3563                      DelCh(this.line, this.y, this.x, this.line, this.y, i, false);
3564                      SetViewToCursor(true);
3565                      Modified();
3566                   }
3567                   else if(this.line.next)
3568                   {
3569                      DelCh(this.line, this.y, this.x, this.line.next, this.y+1, 0, false);
3570                      SetViewToCursor(true);
3571                      Modified();
3572                   }
3573                }
3574                else
3575                {
3576                   if(!(style.freeCaret))
3577                   {
3578                      this.selX = this.x = Min(this.x, this.line.count);
3579                      ComputeColumn();
3580                   }
3581                   if(this.x < this.line.count)
3582                   {
3583                      DelCh(this.line, this.y, this.x, this.line, this.y, this.x+1, false);
3584                   }
3585                   else if(this.line.next)
3586                   {
3587                      DelCh(this.line, this.y, this.x, this.line.next, this.y+1, 0, false);
3588                   }
3589                   SetViewToCursor(true);
3590                   Modified();
3591                }
3592             }
3593             return false;
3594             //break;
3595          case enter:
3596          case keyPadEnter:
3597          {
3598             if(!key.alt && !key.ctrl)
3599             {
3600                int c;
3601                int position = 0;
3602                bool stuffAfter = false;
3603                char * addString;
3604                int len = 0;
3605
3606                if(style.stuckCaret) GoToEnd(true);
3607                if(style.readOnly) break;
3608                if(!(style.multiLine)) break;
3609
3610                for(c = 0; c<this.line.count && c<this.x; c++)
3611                {
3612                   if(this.line.buffer[c] == '\t')
3613                      position += this.tabSize - (position % this.tabSize);
3614                   else if(this.line.buffer[c] == ' ')
3615                      position++;
3616                   else
3617                      break;
3618                }
3619                if(!line.count)
3620                   position = x;
3621
3622                if(this.x < this.line.count)
3623                   stuffAfter = true;
3624
3625                //If last character is a { indent one tab
3626                if(this.line.buffer[this.x - 1] == '{')
3627                {
3628                   //Except if the next non space character is a }
3629                   bool indent = false;
3630                   int i;
3631                   for(i = this.x; i < this.line.size; i++)
3632                      if(this.line.buffer[i] != ' ' && this.line.buffer[i] != '\t')
3633                      {
3634                         if(this.line.buffer[i] != '}')
3635                            indent = true;
3636                         break;
3637                      }
3638                   if(indent)
3639                      position += this.tabSize;
3640                }
3641
3642                addString = new char[position + 2];
3643                addString[len++] = '\n';
3644                addString[len] = '\0';
3645
3646                if(stuffAfter || !style.freeCaret)
3647                {
3648                   for(c = 0; c<position; )
3649                   {
3650                      if(style.useTab && c + this.tabSize <= position)
3651                      {
3652                         addString[len++] = '\t';
3653                         c += this.tabSize;
3654                      }
3655                      else
3656                      {
3657                         addString[len++] = ' ';
3658                         c++;
3659                      }
3660                   }
3661                   addString[len] = '\0';
3662                }
3663                if(AddS(addString))
3664                {
3665                   if(!stuffAfter && style.freeCaret)
3666                   {
3667                      this.x = this.selX = position;
3668                      ComputeColumn();
3669                   }
3670                   FindMaxLine();
3671                   SetViewToCursor(true);
3672                   Modified();
3673                }
3674                delete addString;
3675                return false;
3676             }
3677             break;
3678          }
3679          case left:
3680          {
3681             if(style.stuckCaret) break;
3682             if(!(style.freeCaret))
3683             {
3684                this.x = Min(this.x, this.line.count);
3685                this.selX = Min(this.selX, this.selLine.count);
3686                ComputeColumn();
3687             }
3688             if(!shift) SelDirty();
3689             if(key.ctrl)
3690             {
3691                bool foundAlpha = false;
3692                bool found = false;
3693                int y = this.y;
3694                EditLine line, lastLine;
3695                int lastC, lastY;
3696
3697                for(line = this.line; (line && !found); line = line.prev, y--)
3698                {
3699                   int start;
3700                   int c;
3701
3702                   if(this.x == 0 && line != this.line)
3703                   {
3704                      foundAlpha = true;
3705                      lastC = line.count;
3706                      lastY = y;
3707                      lastLine = line;
3708                      break;
3709                   }
3710
3711                   if(line == this.line) start = this.x -1; else start = line.count-1;
3712                   start = Min(start, line.count-1);
3713
3714                   for(c = start; c >= 0;)
3715                   {
3716                      int numBytes;
3717                      unichar ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
3718                      if(IS_ALUNDER(ch))
3719                      {
3720                         foundAlpha = true;
3721                         lastC = c;
3722                         lastY = y;
3723                         lastLine = line;
3724                      }
3725                      else
3726                      {
3727                         if(foundAlpha)
3728                         {
3729                            found = true;
3730                            break;
3731                         }
3732                      }
3733                      while(--c)
3734                      {
3735                         byte ch = line.buffer[c];
3736                         if(UTF8_IS_FIRST(ch)) break;
3737                      }
3738
3739                   }
3740                   // No next word found,
3741                   if(!found && ( this.x > 0 || (!line.count && this.x)))
3742                   {
3743                      foundAlpha = true;
3744                      lastC = 0;
3745                      lastY = y;
3746                      lastLine = line;
3747                      break;
3748                   }
3749                }
3750                if(foundAlpha)
3751                {
3752                   DirtyLine(this.y);
3753                   this.x = lastC;
3754                   this.y = lastY;
3755                   this.line = lastLine;
3756                   DirtyLine(this.y);
3757                   ComputeColumn();
3758                }
3759             }
3760             else
3761             {
3762                if(x > 0)
3763                {
3764                   if(x <= line.count)
3765                   {
3766                      byte * buffer = line.buffer;
3767                      while(--x)
3768                      {
3769                         byte ch = buffer[x];
3770                         if(UTF8_IS_FIRST(ch)) break;
3771                      }
3772                   }
3773                   else
3774                      x--;
3775                   DirtyLine(y);
3776                }
3777                else if(line.prev)
3778                {
3779                   line = line.prev;
3780                   DirtyLine(y);
3781                   x = line.count;
3782                   y--;
3783                   DirtyLine(y);
3784                }
3785                ComputeColumn();
3786             }
3787             if(!shift) _Deselect();
3788             SetViewToCursor(true);
3789             //break;
3790             return false;
3791          }
3792          case right:
3793          {
3794             if(style.stuckCaret) break;
3795             if(!(style.freeCaret))
3796             {
3797                this.x = Min(this.x, this.line.count);
3798                this.selX = Min(this.selX, this.selLine.count);
3799                ComputeColumn();
3800             }
3801             if(!shift) SelDirty();
3802             if(!shift && (this.x != this.selX || this.y != this.selY));
3803             else if(key.ctrl)
3804             {
3805                bool onAChar = false;
3806                if(this.selX != this.x || this.selY != this.y)
3807                   onAChar = true;
3808                if(this.x<this.line.count)
3809                   if(this.line.buffer[this.x] != '\t' && this.line.buffer[this.x] !=' ')
3810                      onAChar = true;
3811                if(key.shift && onAChar &&
3812                   ((this.y > this.selY)||((this.selY == this.y)&&(this.x >= this.selX))))
3813                {
3814                   bool foundAlpha = false;
3815                   bool found = false;
3816                   EditLine line, lastLine;
3817                   int y = this.y;
3818                   int lastC, lastY, lastNumBytes;
3819
3820                   for(line = this.line; (line && !found); line = line.next, y++)
3821                   {
3822                      int start = (line == this.line) ? this.x : 0;
3823                      int c;
3824                      int numBytes;
3825                      unichar ch;
3826                      for(c = start; c < line.count && (ch = UTF8_GET_CHAR(line.buffer + c, numBytes)); c += numBytes)
3827                      {
3828                         if(IS_ALUNDER(ch))
3829                         {
3830                            foundAlpha = true;
3831                            lastC = c;
3832                            lastNumBytes = numBytes;
3833                            lastY = y;
3834                            lastLine = line;
3835                         }
3836                         else if(foundAlpha)
3837                         {
3838                            found = true;
3839                            break;
3840                         }
3841                      }
3842                      if(!found && (c != this.x || line != this.line))
3843                      {
3844                         found = true;
3845                         lastLine = line;
3846                         lastC = line.count;
3847                         lastNumBytes = 0;
3848                         lastY = y;
3849                         break;
3850                      }
3851                   }
3852                   if(found)
3853                   {
3854                      DirtyLine(this.y);
3855                      this.x = lastC + lastNumBytes;
3856                      this.y = lastY;
3857                      this.line = lastLine;
3858                      DirtyLine(this.y);
3859                      ComputeColumn();
3860                   }
3861                }
3862                else
3863                {
3864                   bool foundAlpha = false;
3865                   bool found = false;
3866                   EditLine line;
3867                   int y = this.y;
3868
3869                   for(line = this.line; (line && !found); line = line.next, y++)
3870                   {
3871                      int start = (line == this.line) ? this.x : 0;
3872                      int c;
3873                      int numBytes;
3874                      unichar ch;
3875                      for(c = start; c < line.count && (ch = UTF8_GET_CHAR(line.buffer + c, numBytes)); c += numBytes)
3876                      {
3877                         if(!IS_ALUNDER(ch))
3878                            foundAlpha = true;
3879                         else if(foundAlpha)
3880                         {
3881                            found = true;
3882                            DirtyLine(this.y);
3883                            this.x = c;
3884                            this.y = y;
3885                            this.line = line;
3886                            DirtyLine(this.y);
3887                            ComputeColumn();
3888                            break;
3889                         }
3890                      }
3891                      // No next word found,
3892                      if(!found && (c != this.x || line != this.line))
3893                      {
3894                         found = true;
3895                         DirtyLine(this.y);
3896                         this.x = line.count;
3897                         this.y = y;
3898                         this.line = line;
3899                         DirtyLine(this.y);
3900                         ComputeColumn();
3901                      }
3902                      foundAlpha = true;
3903                   }
3904                }
3905             }
3906             else
3907             {
3908                if(x < line.count || (style.freeCaret && line.count < maxLineSize))
3909                {
3910                   if(x < line.count)
3911                   {
3912                      byte * buffer = line.buffer;
3913                      while(++x)
3914                      {
3915                         byte ch = buffer[x];
3916                         if(UTF8_IS_FIRST(ch)) break;
3917                      }
3918                   }
3919                   else
3920                      x++;
3921                   ComputeColumn();
3922                   DirtyLine(y);
3923                }
3924                else
3925                {
3926                   if(line.next)
3927                   {
3928                      DirtyLine(y);
3929                      line = line.next;
3930                      y++;
3931                      x = 0;
3932                      col = 0;
3933                      DirtyLine(y);
3934                   }
3935                }
3936             }
3937             if(!shift) _Deselect();
3938             SetViewToCursor(true);
3939             // break;
3940             return false;
3941          }
3942          case up:
3943             if(key.ctrl)
3944             {
3945                if(!style.vScroll || hasVertScroll) break;
3946                LineUp();
3947                return false;
3948             }
3949             else
3950             {
3951                if(style.stuckCaret) break;
3952
3953                if(!shift) SelDirty();
3954                DirtyLine(this.y);
3955
3956                if(style.wrap)
3957                {
3958                }
3959                else if(line.prev)
3960                {
3961                   line = line.prev;
3962                   this.y--;
3963                   this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
3964                }
3965
3966                DirtyLine(this.y);
3967                if(!shift) _Deselect();
3968                ComputeColumn();
3969                SetViewToCursor(false);
3970
3971                /*
3972                if(caretY == this.y * space.h)
3973                {
3974                   if(line.prev)
3975                   {
3976                      line = line.prev;
3977                      this.y--;
3978                      if(!style.freeCaret)
3979                         this.x = Min(this.x, line.count);
3980                      caretY = MAXINT;
3981                   }
3982                   else
3983                      return false;
3984                }
3985
3986                {
3987                   int th = space.h;
3988                   int textPos = 0;
3989                   int sx = 0, sy = this.y * space.h;
3990                   char * text = line.text;
3991                   int maxW = clientSize.w - sx;
3992                   display.FontExtent(font, " ", 1, null, &th);
3993
3994                   do
3995                   {
3996                      int startPos = textPos;
3997                      int width = 0;
3998                      int x = 0;
3999                      bool lineComplete = false;
4000
4001                      if(!style.wrap && caretY == MAXINT)
4002                      {
4003                         caretY = sy + th;
4004                         //textPos = line.count;
4005                         //lineComplete = true;
4006                      }
4007
4008                      for(; (style.freeCaret || textPos < line.count) && !lineComplete;)
4009                      {
4010                         int w = 0;
4011                         int len;
4012                         char * nextSpace = (textPos < line.count) ? strchr(text + textPos, ' ') : (text + textPos);
4013
4014                         if(nextSpace)
4015                            len = (nextSpace - (text + textPos));
4016                         else
4017                            len = line.count - textPos;
4018
4019                         if(textPos < line.count)
4020                         {
4021                            display.FontExtent(font, text + textPos, len, &w, null);
4022                         }
4023                         if(nextSpace) { w += space.w; len++; }
4024
4025                         if(style.wrap && x + width + w > maxW && x > 0)
4026                         {
4027                            lineComplete = true;
4028                            break;
4029                         }
4030                         textPos += len;
4031                         width += w;
4032                         if(nextSpace)
4033                         {
4034                            x += width;
4035                            width = 0;
4036                         }
4037                         if((!style.freeCaret && textPos >= line.count) || (sy == caretY - th && caretX <= x + width + sx))
4038                         {
4039                            x += width;
4040                            this.x = textPos;
4041                            while(this.x > 0 && x + sx > caretX && this.x > startPos)
4042                            {
4043                               int len;
4044                               if(this.x > line.count)
4045                                  this.x--;
4046                               else
4047                                  while(this.x > 0 && !UTF8_IS_FIRST(text[--this.x]));
4048                               len = this.x - startPos;
4049                               display.FontExtent(font, text + startPos, len, &x, null);
4050                            }
4051
4052                            DirtyLine(this.y);
4053                            if(!shift) _Deselect();
4054                            ComputeColumn();
4055                            SetViewToCursor(false);
4056                            return false;
4057                         }
4058                      }
4059                      if(sy == caretY - th || textPos >= line.count)
4060                      {
4061                         if(textPos >= line.count)
4062                         {
4063                            int c = textPos - 1;
4064                            while(c > 0 && text[c] == ' ') c--;
4065                            this.x = c + 1;
4066                         }
4067                         else
4068                            this.x = line.count;
4069
4070                         DirtyLine(this.y);
4071                         if(!shift) _Deselect();
4072                         ComputeColumn();
4073                         SetViewToCursor(false);
4074                         return false;
4075                      }
4076                      sy += th;
4077                      sx = 0;
4078                   } while(textPos < line.count);
4079
4080                   DirtyLine(this.y);
4081                   if(!shift) _Deselect();
4082                   ComputeColumn();
4083                   SetViewToCursor(false);
4084                   return false;
4085                }
4086                */
4087
4088                // PREVIOUS CODE
4089                /*
4090                if(this.line.prev)
4091                {
4092                   int x = AdjustXPosition(this.line, this.line.prev, true, null, MAXINT, 0);
4093                   if(!shift) SelDirty();
4094                   this.line = this.line.prev;
4095                   DirtyLine(this.y);
4096                   this.y--;
4097
4098                   DirtyLine(this.y);
4099                   this.x = x;
4100                   if(!shift) _Deselect();
4101
4102                   ComputeColumn();
4103
4104                   SetViewToCursor(false);
4105                }
4106                */
4107
4108             }
4109             // break;
4110             return style.multiLine ? false : true;
4111          case down:
4112             if(key.ctrl)
4113             {
4114                if(!style.vScroll || hasVertScroll)
4115                   break;
4116                LineDown();
4117                return false;
4118             }
4119             else
4120             {
4121                if(style.stuckCaret) break;
4122                {
4123                   int th = space.h;
4124                   int textPos = 0;
4125                   int sx = 0, sy = this.y * this.space.h;
4126                   int maxW = clientSize.w - sx;
4127                   char * text = line.buffer;
4128
4129                   if(!shift) SelDirty();
4130                   DirtyLine(this.y);
4131
4132                   if(style.wrap)
4133                   {
4134                      /*
4135                      if(AdjustXPosition(line, maxW, this.x, line.count, true, null, MAXINT, 0) <= line.count)
4136                      {
4137
4138                      }
4139                      */
4140                   }
4141                   else if(line.next)
4142                   {
4143                      line = line.next;
4144                      this.y++;
4145                      this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
4146                   }
4147
4148                   DirtyLine(this.y);
4149                   if(!shift) _Deselect();
4150                   ComputeColumn();
4151                   SetViewToCursor(false);
4152
4153                   /*
4154                   while(!textPos || (style.freeCaret || textPos<line.count))
4155                   {
4156                      int startPos = textPos;
4157                      int width = 0;
4158                      int x = 0;
4159                      bool lineComplete = false;
4160                      if(!style.wrap && sy <= caretY)
4161                      {
4162                         textPos = line.count;
4163                         lineComplete = true;
4164                      }
4165                      for(; (style.freeCaret || textPos<line.count) && !lineComplete;)
4166                      {
4167                         int w = 0;
4168                         int len;
4169                         char * nextSpace = (textPos < line.count) ? strchr(text + textPos, ' ') : (text + textPos);
4170
4171                         if(nextSpace)
4172                            len = (nextSpace - (text + textPos));
4173                         else
4174                            len = line.count - textPos;
4175
4176                         if(textPos < line.count)
4177                         {
4178                            display.FontExtent(font, text + textPos, len, &w, &th);
4179                         }
4180                         if(nextSpace) { w += space.w; len++; }
4181                         if(style.wrap && x + width + w > maxW && x > 0)
4182                         {
4183                            lineComplete = true;
4184                            break;
4185                         }
4186                         textPos += len;
4187                         width += w;
4188                         if(nextSpace)
4189                         {
4190                            x += width;
4191                            width = 0;
4192                         }
4193                         if(sy > caretY && ((!style.freeCaret && textPos >= line.count) || caretX <= x + width + sx))
4194                         {
4195                            this.x = textPos;
4196                            x += width;
4197                            while(this.x > 0 && x + sx > caretX && textPos > startPos)
4198                            {
4199                               int len;
4200                               if(this.x > line.count)
4201                                  this.x--;
4202                               else
4203                                  while(this.x > 0 && !UTF8_IS_FIRST(text[--this.x]));
4204
4205                               len = this.x - startPos;
4206                               display.FontExtent(font, text + startPos, len, &x, null);
4207                            }
4208
4209                            if(!shift) _Deselect();
4210                            ComputeColumn();
4211
4212                            SetViewToCursor(false);
4213                            return false;
4214                         }
4215                      }
4216                      if(sy > caretY)
4217                      {
4218                         this.x = line.count;
4219
4220                         DirtyLine(this.y);
4221                         if(!shift) _Deselect();
4222                         ComputeColumn();
4223
4224                         SetViewToCursor(false);
4225                         return false;
4226                      }
4227                      else if(textPos >= line.count && line.next)
4228                      {
4229                         startPos = 0;
4230                         textPos = 0;
4231                         line = line.next;
4232                         this.y++;
4233                         sy = this.y * this.space.h;
4234                         sx = 0; //textBlock.startX;
4235                         text = line.buffer;
4236                      }
4237                      else
4238                      {
4239                         sy += th;
4240                         sx = 0; //textBlock.startX;
4241                      }
4242                   }
4243                   */
4244                   /*
4245                   if(line.next)
4246                   {
4247                      line = line.next;
4248                      this.x = Min(this.x, line.count);
4249                      //int x = AdjustXPosition(this.line, this.line.next, true, null, MAXINT, 0);
4250                      this.y++;
4251                      if(!shift) _Deselect();
4252                      ComputeColumn();
4253
4254                      if(this.selX != this.x || this.selY != this.y)
4255                         DirtyLine(this.y);
4256
4257                      SetViewToCursor(false);
4258                   }
4259                   */
4260                }
4261                /* PREVIOUS CODE
4262                if(this.line.next)
4263                {
4264                   int x = AdjustXPosition(this.line, this.line.next, true, null, MAXINT, 0);
4265                   if(!shift) SelDirty();
4266                   this.line = this.line.next;
4267                   DirtyLine(this.y);
4268                   this.y++;
4269                   this.x = x;
4270                   if(!shift) _Deselect();
4271                   ComputeColumn();
4272
4273                   if(this.selX != this.x || this.selY != this.y)
4274                      DirtyLine(this.y);
4275
4276                   SetViewToCursor();
4277                }
4278                */
4279             }
4280             // break;
4281             return style.multiLine ? false : true;
4282          case home:
4283          {
4284             if(style.stuckCaret) break;
4285             if(!style.multiLine && key.ctrl) break;
4286             if(!(style.freeCaret))
4287                this.selX = Min(this.selX, this.selLine.count);
4288
4289             if(!shift) SelDirty();
4290             if(key.ctrl)
4291             {
4292                this.line = this.lines.first;
4293                if(this.y != 0 || this.x != 0)
4294                   DirtyAll();
4295                this.y = 0;
4296                this.x = 0;
4297                this.col = 0;
4298             }
4299             else
4300             {
4301                if(style.smartHome)
4302                {
4303                   EditLine line = this.line;
4304                   int c;
4305                   for(c=0; line.buffer[c]; c++)
4306                      if(line.buffer[c] != ' ' && line.buffer[c] != '\t')
4307                         break;
4308                   if(shift && (c != 0 || this.x))
4309                      DirtyLine(this.y);
4310                   if(this.x != c)
4311                      this.x = c;
4312                   else
4313                      this.x = 0;
4314                }
4315                else
4316                {
4317                   if(shift && this.x != 0)
4318                      DirtyLine(this.y);
4319                   this.x = 0;
4320                }
4321                ComputeColumn();
4322             }
4323             if(!shift) _Deselect();
4324             SetViewToCursor(true);
4325             //break;
4326             return false;
4327          }
4328          case end:
4329          {
4330             if(style.stuckCaret) break;
4331             if(!style.multiLine && key.ctrl) break;
4332             if(!style.freeCaret)
4333                this.selX = Min(this.selX, this.selLine.count);
4334
4335             if(!shift) SelDirty();
4336             if(key.ctrl)
4337             {
4338                GoToEnd(false);
4339             }
4340             else if(this.x != this.line.count)
4341             {
4342                this.x = this.line.count;
4343                if(shift)
4344                   DirtyLine(this.y);
4345                ComputeColumn();
4346             }
4347             if(!shift) _Deselect();
4348             SetViewToCursor(true);
4349             //break;
4350             return false;
4351          }
4352          case tab:
4353             if(style.tabKey && !key.ctrl)
4354             {
4355                if(this.selY != this.y && style.tabSel)
4356                {
4357                   EditLine firstLine, lastLine;
4358                   EditLine line;
4359                   int y, x;
4360
4361                   // Do multi line selection tabbing here
4362                   if(this.selY < this.y)
4363                   {
4364                      firstLine = this.selLine;
4365                      lastLine = this.line;
4366                      y = this.selY;
4367                      x = this.x;
4368                   }
4369                   else
4370                   {
4371                      // Selecting going up
4372                      firstLine = this.line;
4373                      lastLine = this.selLine;
4374                      y = this.y;
4375                      x = this.selX;
4376                   }
4377                   ComputeColumn();
4378                   if(shift)
4379                   {
4380                      for(line = firstLine; line; line = line.next, y++)
4381                      {
4382                         if(line != lastLine || x)
4383                         {
4384                            int c;
4385                            int lastC = 0;
4386                            BufferLocation before = { line, y, 0 }, after = { line, y, 0 };
4387
4388                            for(c=0; c<line.count && lastC < this.tabSize; c++, lastC++)
4389                            {
4390                               if(line.buffer[c] == '\t')
4391                               {
4392                                  lastC++;
4393                                  break;
4394                               }
4395                               else if(line.buffer[c] != ' ')
4396                                  break;
4397                            }
4398                            after.x = lastC;
4399
4400                            NotifyCharsDeleted(master, this, &before, &after, this.pasteOperation);
4401                            if(lastC)
4402                            {
4403                               int len = GetText(null, line, y, 0, line, y, lastC, false, false);
4404                               char * string = new char[len];
4405                               DelTextAction action { y1 = y, x1 = 0, y2 = y, x2 = lastC, string = string, placeAfter = true };
4406                               GetText(string, line, y, 0, line, y, lastC, false, false);
4407                               Record(action);
4408                            }
4409                            memmove(line.buffer,line.buffer+lastC,line.size-lastC);
4410                            if(!line.AdjustBuffer(line.count-lastC))
4411                               break;
4412                            line.count-=lastC;
4413                            DirtyLine(y);
4414                         }
4415
4416                         if(line == lastLine) break;
4417                      }
4418                   }
4419                   else
4420                   {
4421                      for(line = firstLine; line; line = line.next, y++)
4422                      {
4423                         if(line.count)
4424                         {
4425                            if(line != lastLine || x)
4426                            {
4427                               if(style.useTab)
4428                               {
4429                                  if(line.count + 1 <= this.maxLineSize)
4430                                  {
4431                                     BufferLocation before = { line, y, 0 }, after = { line, y, 1 };
4432
4433                                     if(!line.AdjustBuffer(line.count+1))
4434                                        break;
4435
4436                                     NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
4437                                     {
4438                                        AddCharAction action { ch = '\t', x = 0, y = y };
4439                                        Record(action);
4440                                     }
4441
4442                                     memmove(line.buffer+1,line.buffer,line.size-1);
4443                                     line.count++;
4444                                     line.buffer[0] = '\t';
4445                                  }
4446                               }
4447                               else
4448                               {
4449                                  if(line.count + this.tabSize <= this.maxLineSize)
4450                                  {
4451                                     int c;
4452                                     BufferLocation before = { line, y, 0 }, after = { line, y, this.tabSize };
4453                                     NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
4454
4455                                     if(!line.AdjustBuffer(line.count+this.tabSize))
4456                                        break;
4457
4458                                     {
4459                                        char * string = new char[this.tabSize + 1];
4460                                        memset(string, ' ', this.tabSize);
4461                                        string[this.tabSize] = '\0';
4462                                        Record(AddTextAction { string = string, x1 = 0, y1 = y, x2 = this.tabSize, y2 = y });
4463                                     }
4464
4465                                     memmove(line.buffer+this.tabSize,line.buffer,line.size-(this.tabSize));
4466                                     line.count+=this.tabSize;
4467                                     for(c=0; c<this.tabSize; c++)
4468                                        line.buffer[c] = ' ';
4469                                  }
4470                               }
4471                               DirtyLine(y);
4472                            }
4473                         }
4474                         if(line == lastLine) break;
4475                      }
4476                   }
4477                   ComputeLength(maxLine);
4478                }
4479                else
4480                {
4481                   if(style.useTab)
4482                   {
4483                      // Insert character
4484                      AddCh('\t');
4485                   }
4486                   else
4487                   {
4488                      int start, c;
4489                      char * addString = new char[this.tabSize + 1];
4490                      int len = 0;
4491                      if(!(style.freeCaret))
4492                      {
4493                         this.x = Min(this.x, this.line.count);
4494                         ComputeColumn();
4495                      }
4496                      // Insert spaces
4497                      start = Min(this.x, this.selX);
4498                      for(c=start; ((c == start) || ((c) % this.tabSize)); c++)
4499                      {
4500                         addString[len++] = ' ';
4501                      }
4502                      addString[len] = 0;
4503                      AddS(addString);
4504                      delete addString;
4505                   }
4506                }
4507                Modified();
4508                SetViewToCursor(true);
4509                return false;
4510             }
4511             break;
4512          case pageDown:
4513             if(style.multiLine)
4514             {
4515                if(key.ctrl)
4516                {
4517                   if(!(style.hScroll) || hasHorzScroll) break;
4518                   if(this.viewX < this.maxLength)
4519                   {
4520                      //this.viewX+=this.space.w*this.tabSize;
4521                      //DirtyAll();
4522                      SetScrollPosition((this.viewX + this.space.w*this.tabSize), this.viewY * this.space.h);
4523                   }
4524                }
4525                else
4526                {
4527                   PageDown();
4528                   DirtyAll();
4529                   if(!shift) _Deselect();
4530                   SetCursorToViewX();
4531                   SetCursorToViewY();
4532                }
4533                return false;
4534             }
4535             break;
4536          case pageUp:
4537             if(style.multiLine)
4538             {
4539                if(key.ctrl)
4540                {
4541                   if(!(style.hScroll) || hasHorzScroll) break;
4542                   if(this.viewX > 0)
4543                   {
4544                      //this.viewX-=this.space.w*this.tabSize;
4545                      //this.viewX = Max(this.viewX,0);
4546                      //DirtyAll();
4547                      SetScrollPosition((this.viewX-this.space.w*this.tabSize), this.viewY * this.space.h);
4548                      // SetCursorToView();
4549                   }
4550                }
4551                else
4552                {
4553                   PageUp();
4554                   DirtyAll();
4555                   if(!shift) _Deselect();
4556                   SetCursorToViewX();
4557                   SetCursorToViewY();
4558                }
4559                return false;
4560             }
4561             break;
4562          case insert:
4563             if(key.ctrl)
4564             {
4565                Copy();
4566                return false;
4567             }
4568             else if(key.shift)
4569             {
4570                if(!(style.readOnly))
4571                   Paste();
4572                return false;
4573             }
4574             else
4575             {
4576                this.overwrite ^= 1;
4577                UpdateCaretPosition(true);
4578                if(this.overwrite)
4579                   SetCaret(0,0,0);
4580                DirtyLine(this.y);
4581                UpdateDirty();
4582                NotifyOvrToggle(master, this, this.overwrite);
4583             }
4584             break;
4585          case hotKey:
4586             break;
4587          default:
4588
4589             switch(key)
4590             {
4591                case ctrlA:
4592                   // Select All
4593                   if(style.noSelect) break;
4594
4595                   {
4596                      //Save current view position
4597                      int tempX = this.viewX;
4598                      int tempY = this.viewY;
4599
4600                      this.selX = 0;
4601                      this.selY = 0;
4602                      this.selLine = this.lines.first;
4603                      this.y = this.lineCount-1;
4604                      this.line = this.lines.last;
4605                      this.x = this.line.count;
4606                      ComputeColumn();
4607                      DirtyAll();
4608                      SetViewToCursor(true);
4609
4610                      //Restore previous view position
4611                      SetScrollPosition(tempX, tempY * this.space.h);
4612
4613                      UpdateDirty();
4614                      SetSelectCursor();
4615                      SelectionEnables();
4616                   }
4617                   // TOCHECK: Was there any good reason why we weren't returning false here?
4618                   return false; // break;
4619                case ctrlC:
4620                   Copy();
4621                   return false;
4622                case ctrlV:
4623                   if(!(style.readOnly))
4624                   {
4625                      Paste();
4626                      return false;
4627                   }
4628                   break;
4629                case ctrlX:
4630                {
4631                   if(style.readOnly) break;
4632                   Cut();
4633                   return false;
4634                }
4635                case ctrlL:
4636                {
4637                   if(style.readOnly) break;
4638                   ClearLine();
4639                   return false;
4640                }
4641                case ctrlZ:
4642                   if(style.readOnly) break;
4643                   Undo();
4644                   return false;
4645                case ctrlY:
4646                   if(style.readOnly) break;
4647                   Redo();
4648                   return false;
4649                default:
4650                   if(style.readOnly) break;
4651                   if(key.shift && key.code == rightBracket)
4652                   {
4653                      //Only indent back if you are exactly at one tab.
4654                      {
4655                         bool whitespace = true;
4656                         int i;
4657                         char * newline;
4658                         int putsize;
4659
4660                         int indentwidth;
4661                         EditLine line = this.line;
4662
4663                         //Only remove one tab if there is nothing else on the line.
4664                         for(i = 0; i < this.line.count; i++)
4665                         {
4666                            if(this.line.buffer[i] != ' ' && this.line.buffer[i] != '\t')
4667                            {
4668                               PutCh(ch);
4669                               return false;
4670                            }
4671                         }
4672
4673                         //Find opening {
4674                         i = 1;
4675                         while(i && line)
4676                         {
4677                            int pos;
4678
4679                            indentwidth = 0;
4680                            for(pos = line.count - 1; pos >= 0; pos--)
4681                            {
4682                               char c = line.buffer[pos];
4683                               if(c == ' ')
4684                                  indentwidth++;
4685                               else if(c == '\t')
4686                                  indentwidth += this.tabSize;
4687                               else
4688                                  //Counting backwards, so when you find a character, indentation is reset
4689                                  indentwidth = 0;
4690                               if(i && c == '}')
4691                                  i++;
4692                               if(i && c == '{')
4693                                  i--;
4694                            }
4695                            line = line.prev;
4696                         }
4697
4698                         //Place the } to get an undo:
4699                         PutCh(ch);
4700
4701                         this.x = this.line.count;
4702                         this.selX = 0;
4703
4704                         if(!style.useTab)
4705                            putsize = indentwidth;
4706                         else
4707                            putsize = indentwidth / this.tabSize + indentwidth % this.tabSize;
4708
4709                         newline = new char[putsize+2];
4710                         newline[putsize] = '}';
4711                         newline[putsize+1] = '\0';
4712
4713                         i = 0;
4714                         if(style.useTab)
4715                            for(; i < indentwidth / this.tabSize; i++)
4716                               newline[i] = '\t';
4717                         for(;i < putsize; i++)
4718                            newline[i] = ' ';
4719
4720                         AddS(newline);
4721
4722                         delete newline;
4723                      }
4724                      return false;
4725                   }
4726                   else if(!key.ctrl && !key.alt && ch != 128 && ch >= 32)
4727                   {
4728                      PutCh(ch);
4729                      return false;
4730                   }
4731                   break;
4732             }
4733             break;
4734       }
4735       return true;
4736    }
4737
4738    void OnHScroll(ScrollBarAction action, int position, Key key)
4739    {
4740 #ifdef _DEBUG
4741       //PrintLn("OnHScroll: ", action, ", pos = ", position, ", key = ", key);
4742 #endif
4743       this.viewX = position;
4744       if(action != setRange)
4745       {
4746          if(!this.mouseMove && style.cursorFollowsView)
4747             SetCursorToViewX();
4748       }
4749       DirtyAll();
4750       UpdateDirty();
4751    }
4752
4753    void OnVScroll(ScrollBarAction action, int position, Key key)
4754    {
4755       int oldViewY = this.viewY;
4756
4757 #ifdef _DEBUG
4758       //PrintLn("OnVScroll: ", action, ", pos = ", position, ", key = ", key);
4759 #endif
4760       position /= this.space.h;
4761
4762       if(position < this.viewY)
4763       {
4764          for(; position < this.viewY && this.viewLine.prev; this.viewLine = this.viewLine.prev, this.viewY--);
4765          style.recomputeSyntax = true;
4766       }
4767       else if(position > this.viewY)
4768       {
4769          EditLine oldViewLine = viewLine;
4770          for(; position > this.viewY && this.viewLine.next; this.viewLine = this.viewLine.next, this.viewY++);
4771          FigureStartSyntaxStates(oldViewLine, false);
4772       }
4773
4774       if(action != setRange)
4775       {
4776          if(!this.mouseMove && style.cursorFollowsView && !SelSize()) SetCursorToViewY();
4777       }
4778
4779       if(this.x != this.selX || this.y != this.selY)
4780          DirtyLine(this.y);
4781
4782       {
4783          Scroll(0, (this.viewY - oldViewY) * this.space.h);
4784
4785          /*
4786          int numLines = clientSize.h / this.space.h;
4787          int y;
4788          if(Abs(this.viewY - oldViewY) < numLines)
4789
4790
4791          if(this.viewY > oldViewY)
4792          {
4793             for(y = oldViewY; y <this.viewY; y++)
4794                DirtyLine(y + numLines);
4795          }
4796          else
4797          {
4798             for(y = this.viewY; y <oldViewY; y++)
4799                DirtyLine(y + numLines);
4800          }*/
4801       }
4802       //DirtyAll();
4803
4804       // Fix dirt of stuff before first line...
4805       if(this.viewY - oldViewY > 0)
4806       {
4807          Box box { 0,0, clientSize.w-1, YOFFSET-1 };
4808          Update(box);
4809       }
4810
4811       UpdateDirty();
4812    }
4813
4814    bool _AddCh(unichar ch, int * addedSpacesPtr, int * addedTabsPtr)
4815    {
4816       EditLine line;
4817       int length, endX;
4818       bool result;
4819       ReplaceTextAction replaceAction = null;
4820       AddCharAction addCharAction = null;
4821       int addedSpaces = 0, addedTabs = 0;
4822
4823       if(ch == '\r') return true;
4824       if(style.stuckCaret /*|EES_READONLY)*/ )
4825          GoToEnd(true);
4826
4827       if(ch == '\n' && !(style.multiLine) && this.line) return false;
4828
4829       if(!undoBuffer.dontRecord)
4830       {
4831          if(selX != x || selY != y)
4832          {
4833             char buffer[5];
4834             char * newString;
4835             char * oldString;
4836             int len = GetText(null, selLine, selY, selX, this.line, y, x, false, false);
4837             oldString = new char[len];
4838             UTF32toUTF8Len(&ch, 1, buffer, 4);
4839             newString = CopyString(buffer);
4840             GetText(oldString, selLine, selY, selX, this.line, y, x, false, false);
4841
4842             replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = false };
4843             if(selY < y || (selY == y && selX < x))
4844             {
4845                replaceAction.x1 = selX;
4846                replaceAction.y1 = selY;
4847                replaceAction.x2 = x;
4848                replaceAction.y2 = y;
4849             }
4850             else
4851             {
4852                replaceAction.x1 = x;
4853                replaceAction.y1 = y;
4854                replaceAction.x2 = selX;
4855                replaceAction.y2 = selY;
4856             }
4857             Record(replaceAction);
4858             undoBuffer.dontRecord++;
4859          }
4860          else
4861          {
4862             addCharAction = AddCharAction { y = y, x = x, ch = ch };
4863             Record(addCharAction);
4864          }
4865       }
4866
4867       if(ch == '\n')
4868       {
4869          DelSel(&addedSpaces);
4870          if(this.lineCount+1 > this.maxLines)
4871          {
4872             if(style.autoEmpty)
4873                Emptyline(this.lines.first,0);
4874             else
4875                return false;
4876          }
4877          if(!(style.autoSize && (!maxClientSize.h || maxClientSize.h > clientSize.h + this.space.h)) && !(style.vScroll))
4878          {
4879             // Make sure it fits, but we need a default line is this.font is too big for window
4880             if(this.space.h * (this.lineCount+1) > clientSize.h && this.line)
4881                return false;
4882          }
4883          if((this.y >= 0) && this.y < this.viewY)
4884          {
4885             this.viewY++;
4886             DirtyAll();
4887          }
4888          line = EditLine { };
4889          if(!line)
4890             return false;
4891          lines.Insert(this.line, line);
4892          line.editBox = this;
4893          line.buffer = null;
4894          line.count = 0;
4895          line.size = 0;
4896          length = 0;
4897
4898          // If we're displacing the lines from a current line ...
4899          if(this.line)
4900          {
4901             if(this.line.buffer)
4902             {
4903                endX = this.x;
4904                if(this.line.count < endX) endX = this.line.count;
4905                length = this.line.count - endX;
4906             }
4907          }
4908          if(!line.AdjustBuffer(length))
4909             return false;
4910          if(this.line)
4911          {
4912             if(this.line.buffer)
4913             {
4914                CopyBytes(line.buffer,this.line.buffer+endX, length+1);
4915 #ifdef _DEBUG
4916       if(endX > 4000 || endX < 0)
4917          printf("Warning");
4918 #endif
4919                this.line.count = endX;
4920                this.line.buffer[this.line.count] = '\0';
4921                this.line.AdjustBuffer(this.line.count);
4922                ComputeLength(this.line);
4923             }
4924          }
4925          {
4926             BufferLocation before = { this.line, this.y, this.x }, after;
4927
4928             this.line = line;
4929             this.x = 0;
4930             this.col = 0;
4931             line.count = length;
4932
4933 #ifdef _DEBUG
4934       if(length > 4000 || length < 0)
4935          printf("Warning");
4936 #endif
4937             ComputeLength(this.line);
4938
4939             DirtyEnd(this.y);
4940             this.y++;
4941             this.lineCount++;
4942             line.buffer[line.count] = '\0';
4943             result = true;
4944
4945             after.line = this.line, after.y = this.y, after.x = this.x;
4946
4947             NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
4948          }
4949       }
4950       else
4951       {
4952          char string[5];
4953          int count = UTF32toUTF8Len(&ch, 1, string, 5);
4954          DelSel(&addedSpaces);
4955          result = AddToLine(string, count, false, addedSpaces ? null : &addedSpaces, &addedTabs);
4956          if(addedSpacesPtr) *addedSpacesPtr = addedSpaces;
4957          if(addedTabsPtr) *addedTabsPtr = addedTabs;
4958       }
4959       this.selX = this.x;
4960       this.selY = this.y;
4961       this.selLine = this.line;
4962
4963       if(replaceAction)
4964       {
4965          replaceAction.x3 = x;
4966          replaceAction.y3 = y;
4967          replaceAction.addedSpaces = addedSpaces;
4968          replaceAction.addedTabs = addedTabs;
4969          undoBuffer.dontRecord--;
4970       }
4971       if(addCharAction)
4972       {
4973          addCharAction.x -= addedTabs * (tabSize-1);
4974          addCharAction.addedSpaces = addedSpaces;
4975          addCharAction.addedTabs = addedTabs;
4976       }
4977       return result;
4978    }
4979
4980 public:
4981
4982    /****************************************************************************
4983                                  EDIT BOX METHODS
4984    ****************************************************************************/
4985
4986
4987    bool AddCh(unichar ch)
4988    {
4989       return _AddCh(ch, null, null);
4990    }
4991
4992    void Modified()
4993    {
4994       this.modified = true;
4995       NotifyUpdate(master, this);
4996       modifiedDocument = true;
4997    }
4998
4999    void Delete(EditLine line1, int y1, int x1, EditLine line2, int y2, int x2)
5000    {
5001       Deselect();
5002       DelCh(line1, y1, x1, line2, y2, x2, false);
5003       SetViewToCursor(true);
5004       UpdateDirty();
5005       Modified();
5006    }
5007
5008    void Undo()
5009    {
5010       undoBuffer.Undo();
5011       itemEditUndo.disabled = undoBuffer.curAction == 0;
5012       itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
5013       if(savedAction == undoBuffer.curAction)
5014       {
5015          modifiedDocument = false;
5016          SetModified(false);
5017          NotifyUnsetModified(master, this);
5018       }
5019    }
5020
5021    void Redo()
5022    {
5023       undoBuffer.Redo();
5024       itemEditUndo.disabled = undoBuffer.curAction == 0;
5025       itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
5026       if(savedAction == undoBuffer.curAction)
5027       {
5028          modifiedDocument = false;
5029          SetModified(false);
5030          NotifyUnsetModified(master, this);
5031       }
5032    }
5033
5034    void Record(UndoAction action)
5035    {
5036       if(!undoBuffer.dontRecord)
5037       {
5038          undoBuffer.Record(action);
5039          itemEditUndo.disabled = undoBuffer.curAction == 0;
5040          itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
5041       }
5042    }
5043
5044    void SelectAll()
5045    {
5046       Select(
5047          this.lines.first, 0,0,
5048          this.lines.last, this.lines.count-1, strlen(((EditLine)this.lines.last).buffer));
5049    }
5050
5051    void Select(EditLine line1, int y1, int x1, EditLine line2, int y2, int x2)
5052    {
5053       SelDirty();
5054       this.selX = x1;
5055       this.selY = y1;
5056       this.selLine = line1 ? (EditLine)line1 : this.lines.first;
5057       this.x = line2 ? x2 : ((EditLine)this.lines.last).count;
5058       this.y = line2 ? y2 : (this.lineCount-1);
5059       this.line = line2 ? (EditLine)line2 : this.lines.last;
5060       ComputeColumn();
5061       SelDirty();
5062       SetViewToCursor(true);
5063       UpdateDirty();
5064    }
5065
5066    // TODO: Fix this vs modifiedDocument window property
5067    void SetModified(bool flag)
5068    {
5069       if(this)
5070       {
5071          this.modified = false;
5072          if(flag && !NotifyModified(master, this))
5073             this.modified = true;
5074       }
5075    }
5076
5077    // BASIC OUTPUT
5078    bool AddS(char * string)
5079    {
5080       if(this)
5081       {
5082          bool ret = true;
5083          char * line;
5084          int c, count;
5085          int addedSpaces = 0, addedTabs = 0;
5086          AddTextAction action = null;
5087          ReplaceTextAction replaceAction = null;
5088
5089          this.pasteOperation = true;
5090
5091          if(style.stuckCaret /*|EES_READONLY)*/ )
5092             GoToEnd(true);
5093
5094          if(!undoBuffer.dontRecord)
5095          {
5096             char * placeString = CopyString(string);
5097             if(!style.multiLine)
5098             {
5099                int i;
5100                char ch;
5101                for(i = 0; (ch = placeString[i]); i++)
5102                   if(ch == '\n')
5103                   {
5104                      placeString[i] = '\0';
5105                      placeString = renew placeString byte[i+1];
5106                      break;
5107                   }
5108             }
5109
5110             if(selX != x || selY != y)
5111             {
5112                char * newString;
5113                char * oldString;
5114                int len = GetText(null, selLine, selY, selX, this.line, y, x, false, false);
5115                oldString = new char[len];
5116                newString = placeString;
5117                GetText(oldString, selLine, selY, selX, this.line, y, x, false, false);
5118
5119                replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = false };
5120                if(selY < y || (selY == y && selX < x))
5121                {
5122                   replaceAction.x1 = selX;
5123                   replaceAction.y1 = selY;
5124                   replaceAction.x2 = x;
5125                   replaceAction.y2 = y;
5126                }
5127                else
5128                {
5129                   replaceAction.x1 = x;
5130                   replaceAction.y1 = y;
5131                   replaceAction.x2 = selX;
5132                   replaceAction.y2 = selY;
5133                }
5134                Record(replaceAction);
5135             }
5136             else if(string[0])
5137             {
5138                if(string[0] == '\n')
5139                   action = AddTextAction { y1 = y, x1 = Min(this.line.count, x), string = placeString };
5140                else
5141                   action = AddTextAction { y1 = y, x1 = x, string = placeString };
5142
5143                Record(action);
5144             }
5145             else
5146                delete placeString;
5147          }
5148
5149          undoBuffer.dontRecord++;
5150          DelSel(&addedSpaces);
5151
5152          count = 0;
5153          line = string;
5154          for(c = 0; string[c]; c++)
5155          {
5156             if(string[c] == '\n' || string[c] == '\r')
5157             {
5158                if(!AddToLine(line,count, true, addedSpaces ? null : &addedSpaces, addedTabs ? null : &addedTabs))
5159                {
5160                   ret = false;
5161                   break;
5162                }
5163                if(string[c] == '\n')
5164                {
5165                   if(!AddCh('\n'))
5166                   {
5167                      count = 0;
5168                      ret = false;
5169                      break;
5170                   }
5171                }
5172                // Reset for next line
5173                count = 0;
5174                line = string+c+1;
5175                /*
5176                if(string[c] == '\r' && *line == '\n')
5177                {
5178                   line++;
5179                   c++;
5180                }*/
5181             }
5182             else
5183             {
5184                count++;
5185             }
5186          }
5187
5188          // Why was this here?
5189          // FindMaxLine();
5190
5191          // Add the line here
5192          if(ret && count)
5193             if(!AddToLine(line,count,false, addedSpaces ? null : &addedSpaces, addedTabs ? null : &addedTabs))
5194             {
5195                ret = false;
5196             }
5197          FindMaxLine();
5198
5199          undoBuffer.dontRecord--;
5200          if(action)
5201          {
5202             action.y2 = y;
5203             action.x2 = x;
5204             action.addedSpaces = addedSpaces;
5205             action.addedTabs = addedTabs;
5206          }
5207          else if(replaceAction)
5208          {
5209             replaceAction.y3 = y;
5210             replaceAction.x3 = x;
5211             replaceAction.addedSpaces = addedSpaces;
5212             replaceAction.addedTabs = addedTabs;
5213          }
5214
5215          UpdateCaretPosition(true);
5216
5217          this.pasteOperation = false;
5218
5219          return ret;
5220       }
5221       return false;
5222    }
5223
5224    // CONSOLE OUTPUT
5225    void Clear()
5226    {
5227       if(this)
5228       {
5229          Deselect();
5230          DelCh(this.lines.first, 0, 0, this.lines.last, this.lineCount-1, ((EditLine)(this.lines.last)).count, true);
5231          SetViewToCursor(true);
5232          UpdateDirty();
5233          Modified();
5234       }
5235    }
5236
5237    void PutCh(unichar ch)
5238    {
5239       bool result;
5240
5241       if((ch >= 32 /*&& ch <=126*/) || ch == '\n')
5242       //if((ch >= 32) || ch == '\n')
5243       {
5244          int addedSpaces = 0, addedTabs = 0;
5245          ReplaceTextAction replaceAction = null;
5246          AddCharAction addCharAction = null;
5247
5248          if(style.allCaps)
5249             ch = (ch < 128) ? toupper(ch) : ch;     // TODO: UNICODE TO UPPER
5250
5251          if(this.overwrite && selX == x && selY == y && this.x < this.line.count)
5252          {
5253             char buffer[5];
5254             char * newString;
5255             char * oldString;
5256             int len = GetText(null, line, y, x, line, y, x+1, false, false);
5257             oldString = new char[len];
5258             UTF32toUTF8Len(&ch, 1, buffer, 4);
5259             newString = CopyString(buffer);
5260             GetText(oldString, line, y, x, line, y, x+1, false, false);
5261             replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = false };
5262             replaceAction.x1 = x;
5263             replaceAction.y1 = y;
5264             replaceAction.x2 = x+1;
5265             replaceAction.y2 = y;
5266             Record(replaceAction);
5267
5268             undoBuffer.dontRecord++;
5269             DelCh(this.line, this.y, this.x, this.line, this.y, this.x+1, true);
5270             undoBuffer.dontRecord--;
5271          }
5272          else if(!undoBuffer.dontRecord)
5273          {
5274             if(selX != x || selY != y)
5275             {
5276                char buffer[5];
5277                char * newString;
5278                char * oldString;
5279                int len = GetText(null, line, y, x, selLine, selY, selX, false, false);
5280                oldString = new char[len];
5281                UTF32toUTF8Len(&ch, 1, buffer, 4);
5282                newString = CopyString(buffer);
5283                GetText(oldString, line, y, x, selLine, selY, selX, false, false);
5284                replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = true };
5285                if(selY < y || (selY == y && selX < x))
5286                {
5287                   replaceAction.x1 = selX;
5288                   replaceAction.y1 = selY;
5289                   replaceAction.x2 = x;
5290                   replaceAction.y2 = y;
5291                }
5292                else
5293                {
5294                   replaceAction.x1 = x;
5295                   replaceAction.y1 = y;
5296                   replaceAction.x2 = selX;
5297                   replaceAction.y2 = selY;
5298                }
5299                Record(replaceAction);
5300             }
5301             else
5302             {
5303                addCharAction = AddCharAction { y = y, x = x, ch = ch };
5304                Record(addCharAction);
5305             }
5306          }
5307          undoBuffer.dontRecord++;
5308          result = _AddCh(ch, &addedSpaces, &addedTabs);
5309          if(replaceAction)
5310          {
5311             replaceAction.x3 = x;
5312             replaceAction.y3 = y;
5313             replaceAction.addedSpaces = addedSpaces;
5314             replaceAction.addedTabs = addedTabs;
5315          }
5316          if(addCharAction)
5317          {
5318             addCharAction.x -= addedTabs * (tabSize-1);
5319             addCharAction.addedSpaces = addedSpaces;
5320             addCharAction.addedTabs = addedTabs;
5321          }
5322          undoBuffer.dontRecord--;
5323          if(ch == '\n')
5324             FindMaxLine();
5325          Modified();
5326          if(result) SetViewToCursor(true);
5327       }
5328    }
5329
5330    void PutS(char * string)
5331    {
5332       if(this)
5333       {
5334          AddS(string);
5335          SetViewToCursor(true);
5336          Modified();
5337       }
5338    }
5339
5340    void Printf(char * format, ...)
5341    {
5342       if(this)
5343       {
5344          char temp[MAX_F_STRING];
5345          va_list args;
5346          va_start(args, format);
5347          vsnprintf(temp, sizeof(temp), format, args);
5348          temp[sizeof(temp)-1] = 0;
5349          va_end(args);
5350          PutS(temp);
5351       }
5352    }
5353
5354    void SetContents(char * format, ...)
5355    {
5356       if(this)
5357       {
5358          undoBuffer.dontRecord++;
5359          Deselect();
5360          DelCh(this.lines.first, 0, 0, this.lines.last, this.lineCount-1, ((EditLine)(this.lines.last)).count, true);
5361          if(format)
5362          {
5363             char temp[MAX_F_STRING];
5364             va_list args;
5365             va_start(args, format);
5366             vsnprintf(temp, sizeof(temp), format, args);
5367             temp[sizeof(temp)-1] = 0;
5368             va_end(args);
5369
5370             AddS(temp);
5371          }
5372          UpdateDirty();
5373          Home();
5374          undoBuffer.dontRecord--;
5375       }
5376    }
5377
5378    void BackSpace()
5379    {
5380       if(!DelSel(null))
5381       {
5382          if(x > 0)
5383          {
5384             x -= 1 + DelCh(line, y, x-1, line, y, x, true);
5385             Modified();
5386          }
5387          else if(this.line.prev)
5388          {
5389             EditLine line = this.line.prev;
5390             int x = line.count;
5391             int y = this.y;
5392
5393             DelCh(line, this.y-1, x, this.line, this.y, this.x, true);
5394             this.line = line;
5395             this.y = y-1;
5396             this.x = x;
5397             Modified();
5398          }
5399          this.selX = this.x;
5400          this.selY = this.y;
5401          this.selLine = this.line;
5402          ComputeColumn();
5403       }
5404       else
5405          Modified();
5406       SetViewToCursor(true);
5407    }
5408
5409    void ClearLine()
5410    {
5411       Emptyline(this.line,this.y);
5412       this.selX = this.x = 0;
5413       this.col = 0;
5414       this.selY = this.y;
5415       this.selLine = this.line;
5416
5417       SetViewToCursor(true);
5418       Modified();
5419    }
5420
5421    // CARET CONTROL
5422    void End()
5423    {
5424       if(this)
5425       {
5426          GoToEnd(true);
5427          SetViewToCursor(true);
5428       }
5429    }
5430    void Home()
5431    {
5432       if(this)
5433       {
5434          GoToHome(true);
5435          SetViewToCursor(true);
5436       }
5437    }
5438
5439    bool GoToLineNum(int lineNum)
5440    {
5441       if(this.line)
5442       {
5443          int c;
5444          EditLine line = this.lines.first;
5445          for(c = 0; c < lineNum && line; c++, line = line.next);
5446          if(line)
5447          {
5448             if(this.y != c)
5449                DirtyAll();
5450             else
5451                DirtyLine(c);
5452             this.y = c;
5453             this.line = line;
5454             _Deselect();
5455             SetViewToCursor(true);
5456             return true;
5457          }
5458       }
5459       return false;
5460    }
5461
5462    bool GoToPosition(EditLine line, int y, int x)
5463    {
5464       /*
5465       if(!line)
5466       {
5467          line = this.line;
5468          y = this.y;
5469       }
5470       */
5471       if(!line)
5472       {
5473          int c;
5474          for(line = this.lines.first, c = 0; c<y && line; c++, line = line.next);
5475       }
5476
5477       if(line)
5478       {
5479          if(this.y != y)
5480             DirtyAll();
5481          else
5482             DirtyLine(y);
5483          this.x = x;
5484          this.y = y;
5485          this.line = line;
5486          ComputeColumn();
5487          _Deselect();
5488          SetViewToCursor(true);
5489          return true;
5490       }
5491       return false;
5492    }
5493
5494    // VIEW POSITIONING
5495    void SetViewToCursor(bool setCaret)
5496    {
5497       if(created)
5498       {
5499          int w;
5500          int c, numLines;
5501          EditLine line;
5502          int x;
5503          int checkX, checkY;
5504          EditLine checkLine;
5505          bool dontScroll = false;
5506          bool selected;
5507          int viewX, viewY;
5508
5509          FixScrollArea();
5510
5511          selected = selX != this.x || selY != y;
5512
5513          viewX = this.viewX;
5514          viewY = this.viewY;
5515
5516          if(mouseMove)
5517          {
5518             checkLine = dropLine;
5519             checkX = dropX;
5520             checkY = dropY;
5521          }
5522          else
5523          {
5524             checkLine = this.line;
5525             checkX = this.x;
5526             checkY = y;
5527          }
5528
5529          numLines = clientSize.h / space.h;
5530
5531          // This is broken. The EditBox now doesn't do anything different when adding to it,
5532          // regardless of the previous scrolling position. It should be read and then set again
5533          // if one wishes to preserve it.
5534          /*  // Don't scroll view to cursor if we're in a EES_NOCARET box
5535          if(style.noCaret && this.viewY < lineCount - numLines - 1)
5536             dontScroll = true;
5537          */
5538
5539          // Horizontal Adjustment
5540          if(!dontScroll && checkLine)
5541          {
5542             x = 0;
5543             if(mouseMove)
5544                dropX = AdjustXPosition(this.line, MAXINT, true, &x, checkX, 0);
5545             else
5546             {
5547                this.x = AdjustXPosition(this.line, MAXINT, true, &x, checkX, 0);
5548                ComputeColumn();
5549             }
5550
5551             if(style.hScroll)
5552             {
5553                if(x + space.w >= this.viewX + clientSize.w && clientSize.w >= space.w)
5554                   viewX = x - clientSize.w+space.w;
5555                if(x < this.viewX + clientSize.w/2 - space.w)
5556                   viewX = Max(0, x - clientSize.w/2 + space.w);
5557             }
5558          }
5559
5560          if(!dontScroll)
5561          {
5562             if(style.vScroll)
5563             {
5564                // Vertical Adjustment
5565                if(viewY > checkY) viewY = checkY;
5566                if(viewY + numLines <= checkY)
5567                {
5568                   if(clientSize.h >= space.h)
5569                   {
5570                      for(line = viewLine; line && (viewY + numLines <= checkY); line = line.next)
5571                         viewY++;
5572                   }
5573                   else
5574                      viewY = checkY;
5575                }
5576             }
5577             else
5578             {
5579                if(mouseMove)
5580                   for(;dropLine && dropLine.prev && dropY >= numLines;)
5581                   {
5582                      dropLine = dropLine.prev;
5583                      dropY--;
5584                   }
5585                else
5586                   for(;this.line && this.line.prev && this.y >= numLines;)
5587                   {
5588                      this.line = this.line.prev;
5589                      y--;
5590                   }
5591             }
5592
5593             SetScrollPosition(viewX, viewY * this.space.h);
5594
5595             UpdateCaretPosition(setCaret);
5596
5597             if(!selected)
5598             {
5599                selX = this.x;
5600                selY = this.y;
5601                selLine = this.line;
5602             }
5603          }
5604
5605          UpdateDirty();
5606          SetSelectCursor();
5607          SelectionEnables();
5608       }
5609    }
5610
5611    void CenterOnCursor()
5612    {
5613       int numLines = clientSize.h / this.space.h;
5614       int y = this.y - numLines / 2;
5615       int viewY;
5616       bool figureSyntax = false;
5617       EditLine oldViewLine = viewLine;
5618       if(y > this.lineCount - numLines) y = this.lineCount-numLines;
5619       if(y < 0) y = 0;
5620
5621       viewY = y;
5622
5623       for(;y < this.viewY; y++)
5624       {
5625          this.viewLine = this.viewLine.prev;
5626          style.recomputeSyntax = true;
5627       }
5628       for(;y > this.viewY; y--)
5629       {
5630          this.viewLine = this.viewLine.next;
5631          figureSyntax = true;
5632       }
5633       if(figureSyntax)
5634          FigureStartSyntaxStates(oldViewLine, false);
5635
5636       this.viewY = viewY;
5637
5638       SetScrollPosition(this.viewX, viewY * this.space.h);
5639       UpdateCaretPosition(true);
5640       UpdateDirty();
5641    }
5642
5643    void SetCursorToView()
5644    {
5645       SetCursorToViewX();
5646       SetCursorToViewY();
5647    }
5648
5649    void PageDown()
5650    {
5651       int c, numLines;
5652       EditLine line;
5653
5654       numLines = clientSize.h / this.space.h;
5655
5656       if(style.noCaret)
5657       {
5658          for(c=0, line = this.viewLine.next; line && c<numLines && (this.viewY < this.lineCount - numLines); line = line.next, c++);
5659          SetScrollPosition(this.viewX, (this.viewY + c) * this.space.h);
5660       }
5661       else
5662       {
5663          EditLine oldLine = this.line;
5664          bool lastOne = false;
5665          EditLine oldViewLine = this.viewLine;
5666          bool figureSyntax = false;
5667
5668          if(this.y >= this.lineCount-1) return;
5669
5670          for(c=0, line = this.line.next; line && c<numLines; line = line.next, c++)
5671          {
5672             if(this.viewY + numLines < this.lines.count)
5673             {
5674                this.viewLine = this.viewLine.next;
5675                this.viewY++;
5676                figureSyntax = true;
5677             }
5678             else if(c && !lastOne)
5679                break;
5680             else
5681                lastOne = true;
5682
5683             this.line = line;
5684             this.y++;
5685          }
5686          if(figureSyntax)
5687             FigureStartSyntaxStates(oldViewLine, false);
5688          this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
5689          ComputeColumn();
5690
5691          SetViewToCursor(false);
5692       }
5693    }
5694
5695    void PageUp()
5696    {
5697       int c, numLines;
5698       EditLine line;
5699
5700       if(this.y == 0) return;
5701
5702       numLines = clientSize.h / this.space.h;
5703
5704       if(style.noCaret)
5705       {
5706          for(c=0, line = this.viewLine.prev; line && c<numLines; line = line.prev, c++);
5707          SetScrollPosition(this.viewX, (this.viewY - c) * this.space.h);
5708       }
5709       else
5710       {
5711          EditLine oldLine = this.line;
5712
5713          for(c=0, line = this.line.prev; line && c<numLines; line = line.prev, c++)
5714          {
5715             this.line = line;
5716             this.y--;
5717
5718             if(this.viewLine.prev)
5719             {
5720                this.viewLine = this.viewLine.prev;
5721                this.viewY--;
5722                style.recomputeSyntax = true;
5723             }
5724          }
5725
5726          this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
5727          ComputeColumn();
5728
5729          SetViewToCursor(false);
5730       }
5731    }
5732
5733    void LineUp()
5734    {
5735
5736       if(this.viewLine.prev)
5737       {
5738          //DirtyAll();
5739          // this.viewLine = this.viewLine.prev;
5740          //this.viewY--;
5741
5742          SetScrollPosition(this.viewX, (this.viewY - 1) * this.space.h);
5743       }
5744    }
5745
5746
5747    void LineDown()
5748    {
5749       if(this.viewLine.next)
5750       {
5751          //DirtyAll();
5752          // this.viewLine = this.viewLine.next;
5753          // this.viewY++;
5754
5755          SetScrollPosition(this.viewX, (this.viewY + 1) * this.space.h);
5756       }
5757    }
5758
5759    // Selection
5760    uint SelSize()
5761    {
5762       EditLine l1, l2, line;
5763       int x1, x2, nx1, nx2;
5764       int count;
5765       int start, end;
5766       int size;
5767
5768       if(!this.selLine) return 0;
5769       if(this.selLine == this.line && this.selX == this.x) return 0;
5770       if(this.selY < this.y)
5771       {
5772          l1 = this.selLine;
5773          x1 = this.selX;
5774          l2 = this.line;
5775          x2 = this.x;
5776       }
5777       else if(this.selY > this.y)
5778       {
5779          l1 = this.line;
5780          x1 = this.x;
5781          l2 = this.selLine;
5782          x2 = this.selX;
5783       }
5784       else if(this.selX < this.x)
5785       {
5786          l1 = l2 = this.line;
5787          x1 = this.selX;
5788          x2 = this.x;
5789       }
5790       else
5791       {
5792          l1 = l2 = this.line;
5793          x1 = this.x;
5794          x2 = this.selX;
5795       }
5796       nx1 = Min(x1,l1.count);
5797       nx2 = Min(x2,l2.count);
5798
5799       // Find Number of Bytes Needed
5800       size = 0;
5801       for(line = l1; line; line = line.next)
5802       {
5803          if(line == l1) start = nx1; else start = 0;
5804          if(line == l2) end = nx2; else end = line.count;
5805          size += end-start;
5806          if(style.freeCaret && line == l2)
5807          {
5808             if(l1 == l2)
5809                count = Max(x2-Max(x1,l1.count),0);
5810             else
5811                count = Max(x2-l2.count,0);
5812             size+=count;
5813          }
5814
5815          if(line == l2) break;
5816          // Add Carriage Return / line Feed
5817          size++;
5818          size++;
5819       }
5820       return size;
5821    }
5822
5823    void GetSelPos(EditLine * l1, int *y1, int *x1, EditLine * l2, int * y2, int * x2, bool reorder)
5824    {
5825       if(this)
5826       {
5827          if(!reorder || this.selY < this.y)
5828          {
5829             if(l1) *l1 = this.selLine;
5830             if(y1) *y1 = this.selY;
5831             if(x1) *x1 = this.selX;
5832             if(l2) *l2 = this.line;
5833             if(y2) *y2 = this.y;
5834             if(x2) *x2 = this.x;
5835          }
5836          else if(this.selY > this.y)
5837          {
5838             if(l1) *l1 = this.line;
5839             if(y1) *y1 = this.y;
5840             if(x1) *x1 = this.x;
5841             if(l2) *l2 = this.selLine;
5842             if(y2) *y2 = this.selY;
5843             if(x2) *x2 = this.selX;
5844          }
5845          else if(this.selX < this.x)
5846          {
5847             if(l1) *l1 = this.line;
5848             if(y1) *y1 = this.selY;
5849             if(x1) *x1 = this.selX;
5850             if(l2) *l2 = this.line;
5851             if(y2) *y2 = this.y;
5852             if(x2) *x2 = this.x;
5853          }
5854          else
5855          {
5856             if(l1) *l1 = this.line;
5857             if(y1) *y1 = this.y;
5858             if(x1) *x1 = this.x;
5859             if(l2) *l2 = this.line;
5860             if(y2) *y2 = this.selY;
5861             if(x2) *x2 = this.selX;
5862          }
5863       }
5864    }
5865
5866    void SetSelPos(EditLine l1, int y1, int x1, EditLine l2, int y2, int x2)
5867    {
5868       if(this && (this.selY != y1 || this.selX != x1 || this.y != y2 || this.x != x2))
5869       {
5870          this.selLine = (EditLine)l1;
5871          this.selY = y1;
5872          this.selX = x1;
5873          this.line = (EditLine)l2;
5874          this.y = y2;
5875          this.x = x2;
5876          ComputeColumn();
5877          SetViewToCursor(true);
5878       }
5879    }
5880
5881    int GetText(char * text, EditLine _l1, int _y1, int _x1, EditLine _l2, int _y2, int _x2, bool addCr, bool addSpaces)
5882    {
5883       EditLine l1, l2, line;
5884       int x1, x2, nx1, nx2;
5885       int count;
5886       int start, end;
5887       int numChars = 0;
5888
5889       if(_y2 < _y1)
5890       {
5891          l1 = _l2;
5892          x1 = _x2;
5893          l2 = _l1;
5894          x2 = _x1;
5895       }
5896       else if(_y2 > _y1)
5897       {
5898          l1 = _l1;
5899          x1 = _x1;
5900          l2 = _l2;
5901          x2 = _x2;
5902       }
5903       else if(_x2 < _x1)
5904       {
5905          l1 = l2 = _l1;
5906          x1 = _x2;
5907          x2 = _x1;
5908       }
5909       else
5910       {
5911          l1 = l2 = _l1;
5912          x1 = _x1;
5913          x2 = _x2;
5914       }
5915       nx1 = Min(x1,l1.count);
5916       nx2 = Min(x2,l2.count);
5917
5918       // Copy text
5919       for(line = l1; line; line = line.next)
5920       {
5921          if(line == l1) start = nx1; else start = 0;
5922          if(line == l2) end = nx2; else end = line.count;
5923          if(text)
5924          {
5925             CopyBytes(text, line.buffer + start, end - start);
5926             text += end-start;
5927          }
5928          numChars += end-start;
5929
5930          if(style.freeCaret && line == l2 && addSpaces)
5931          {
5932             if(l1 == l2)
5933                count = Max(x2-Max(x1,l1.count),0);
5934             else
5935                count = Max(x2-l2.count,0);
5936             if(text)
5937             {
5938                FillBytes(text,' ',count);
5939                text += count;
5940             }
5941             else
5942                numChars += count;
5943          }
5944          if(line == l2) break;
5945          // Add line Feed
5946          if(addCr)
5947          {
5948             if(text)
5949                *(text++) = '\r';
5950             numChars++;
5951          }
5952          if(text)
5953             *(text++) = '\n';
5954          numChars++;
5955       }
5956       // '\0' terminate Terminate
5957       if(text)
5958          *(text) = '\0';
5959       numChars++;
5960       return numChars;
5961    }
5962
5963    void GetSel(char * text, bool addCr)
5964    {
5965       GetText(text, line, y, x, selLine, selY, selX, addCr, true);
5966    }
5967
5968    private void _Deselect()   // This assumes marking lines as dirty is handled by the caller
5969    {
5970       selLine = line;
5971       selX = x;
5972       selY = y;
5973    }
5974
5975    void Deselect()
5976    {
5977       SelDirty();
5978       _Deselect();
5979    }
5980
5981    // CLIPBOARD
5982    void Copy()
5983    {
5984       if(this)
5985       {
5986          int size = SelSize();
5987          if(size)
5988          {
5989             // Try to allocate memory
5990             ClipBoard clipBoard { };
5991             if(clipBoard.Allocate(size+1))
5992             {
5993                GetSel(clipBoard.memory, true);
5994                // Save clipboard
5995                clipBoard.Save();
5996             }
5997             delete clipBoard;
5998          }
5999       }
6000    }
6001
6002    void Paste()
6003    {
6004       if(this)
6005       {
6006          ClipBoard clipBoard { };
6007          if(clipBoard.Load())
6008             PutS(clipBoard.memory);
6009          delete clipBoard;
6010       }
6011    }
6012
6013    void Cut()
6014    {
6015       if(this)
6016       {
6017          Copy();
6018          if(DelSel(null))
6019          {
6020             SetViewToCursor(true);
6021             Modified();
6022          }
6023       }
6024    }
6025
6026    void DeleteSelection()
6027    {
6028       if(this)
6029       {
6030          DelSel(null);
6031          SetViewToCursor(true);
6032          Modified();
6033       }
6034    }
6035
6036    // FILE INTERFACE
6037    void Save(File f, bool cr)
6038    {
6039       EditLine line;
6040       savedAction = undoBuffer.curAction;
6041
6042       for(line = this.lines.first; line; line = line.next)
6043       {
6044          if(style.syntax && line.count && isspace(line.buffer[line.count-1]))
6045          {
6046             int c = 0;
6047             for(c=line.count-2; c>=-1; c--)
6048             {
6049                if(c == -1 || !isspace(line.buffer[c]))
6050                {
6051                   c++;
6052                   line.buffer[c] = '\0';
6053                   line.count -= (line.count - c);
6054                   break;
6055                }
6056             }
6057          }
6058          f.Write(line.buffer, line.count,1);
6059          if(line.next)
6060          {
6061             if(cr) f.Putc('\r');
6062             f.Putc('\n');
6063          }
6064       }
6065    }
6066
6067    #define BUFFER_SIZE  16384
6068    void Load(File f)
6069    {
6070       undoBuffer.dontRecord++;
6071       if(f)
6072       {
6073          char buffer[BUFFER_SIZE];
6074
6075          for(;;)
6076          {
6077             int count = f.Read(buffer, 1, BUFFER_SIZE-1);
6078             buffer[count] = '\0';
6079             AddS(buffer);
6080             if(!count) break;
6081          }
6082          Home();
6083       }
6084       undoBuffer.dontRecord--;
6085       undoBuffer.count = 0;
6086       undoBuffer.curAction = 0;
6087       itemEditUndo.disabled = undoBuffer.curAction == 0;
6088       itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
6089    }
6090
6091    EditBoxFindResult Find(char * text, bool matchWord, bool matchCase, bool isSearchDown)
6092    {
6093       EditLine line;
6094       int num;
6095       bool firstPass = true;
6096       EditBoxFindResult result = found;
6097
6098       if(!this.line) return notFound;
6099       num = this.y;
6100
6101       for(line = this.line;;)
6102       {
6103          char * string;
6104
6105          if(!line)
6106          {
6107             if(isSearchDown)
6108             {
6109                line = this.lines.first;
6110                num = 0;
6111                result = wrapped;
6112             }
6113             else
6114             {
6115                line = this.lines.last;
6116                num = this.lineCount - 1;
6117                result = wrapped;
6118             }
6119          }
6120
6121          if(isSearchDown)
6122             string = SearchString(line.buffer, firstPass ? Min(this.x,line.count) : 0,text,matchCase,matchWord);
6123          else
6124             string = RSearchString(line.buffer,text,firstPass ? (Min(this.x,line.count)-1) : line.count,matchCase,matchWord);
6125
6126          if(string)
6127          {
6128             Select((void *)line,num,string - line.buffer,(void *)line,num,string - line.buffer + strlen(text));
6129             return result;
6130          }
6131          if(line == this.line && !firstPass) break;
6132
6133          if(isSearchDown)
6134          {
6135             line = line.next;
6136             num++;
6137          }
6138          else
6139          {
6140             line = line.prev;
6141             num--;
6142          }
6143
6144          firstPass = false;
6145       }
6146       return notFound;
6147    }
6148
6149    EditBoxFindResult FindInSelection(char * text, bool matchWord, bool matchCase, EditLine l2, int y2, int x2)
6150    {
6151       EditLine line;
6152       int y;
6153       int searchLen = strlen(text);
6154       for(line = (EditLine)this.line, y = this.y; y <= y2; line = line.next, y++)
6155       {
6156          char * string = SearchString(line.buffer, (y == this.y) ? Min(this.x,line.count) : 0,text,matchCase,matchWord);
6157          if(string && (y < y2 || (string - line.buffer) + searchLen <= x2))
6158          {
6159             Select((void *)line,y,string - line.buffer,(void *)line,y,string - line.buffer + strlen(text));
6160             return found;
6161          }
6162       }
6163       return notFound;
6164    }
6165
6166    bool OnKeyDown(Key key, unichar ch)
6167    {
6168 #ifdef _DEBUG
6169       //PrintLn("OnKeyDown: code = ", key.code, ", mods = ", key.modifiers, ", ch = ", (int)ch);
6170 #endif
6171       if(!NotifyKeyDown(master, this, key, ch))
6172          return false;
6173       else
6174       {
6175          switch(key)
6176          {
6177             case escape:
6178             {
6179                if(this.mouseMove)
6180                {
6181                   this.mouseMove = false;
6182                   OnLeftButtonUp(0,0,0);
6183                   SetViewToCursor(true);
6184                   return false;
6185                }
6186             }
6187          }
6188       }
6189       return true;
6190    }
6191 };
6192
6193 public class EditBoxStream : File
6194 {
6195    EditBox editBox;
6196    BufferLocation start, sel;
6197    uint pos;
6198    byte utf8Bytes[5];
6199    int numBytes;
6200
6201    ~EditBoxStream()
6202    {
6203       EditBox editBox = this.editBox;
6204
6205       editBox.x = start.x;
6206       editBox.y = start.y;
6207       editBox.line = start.line;
6208
6209       editBox.selX = sel.x;
6210       editBox.selY = sel.y;
6211       editBox.selLine = sel.line;
6212
6213       editBox.SetViewToCursor(true);
6214       //editBox.ComputeColumn();
6215    }
6216
6217 public:
6218    property EditBox editBox
6219    {
6220       set
6221       {
6222          editBox = value;
6223
6224          start.x = value.x;
6225          start.y = value.y;
6226          start.line = value.line;
6227
6228          sel.x = value.selX;
6229          sel.y = value.selY;
6230          sel.line = value.selLine;
6231          numBytes = 0;
6232
6233          value.GoToHome(true);
6234       }
6235
6236       get { return editBox; }
6237    }
6238
6239    uint Read(byte * buffer, uint size, uint count)
6240    {
6241       uint read = 0;
6242       EditBox editBox = this.editBox;
6243       EditLine line = editBox.line;
6244       int x = editBox.x;
6245       int y = editBox.y;
6246
6247       count *= size;
6248
6249       for(;read < count && line; line = (*&line.next))
6250       {
6251          int numBytes = Min(count - read, (*&line.count) - x);
6252          if(numBytes > 0)
6253          {
6254             memcpy(buffer + read, (*&line.buffer) + x, numBytes);
6255             read += numBytes;
6256             x += numBytes;
6257          }
6258          /*
6259          for(;read < count && x < (*&line.count);)
6260          {
6261             buffer[read++] = (*&line.buffer)[x++];
6262          }
6263          */
6264          if((*&line.next))
6265          {
6266             if(read < count)
6267             {
6268                buffer[read++] = '\n';
6269             }
6270             else
6271                break;
6272          }
6273          if(x == (*&line.count) && (*&line.next))
6274          {
6275             x = 0;
6276             y++;
6277          }
6278          else
6279             break;
6280       }
6281
6282       editBox.line = editBox.selLine = line;
6283       editBox.x = editBox.selX = x;
6284       editBox.y = editBox.selY = y;
6285       pos += read;
6286       return read / size;
6287    }
6288
6289    bool Seek(int pos, FileSeekMode mode)
6290    {
6291       bool result = true;
6292       EditBox editBox = this.editBox;
6293       EditLine line = editBox.line;
6294       numBytes = 0;
6295       if(mode == FileSeekMode::start)
6296       {
6297          pos = pos - this.pos;
6298          mode = current;
6299       }
6300       if(mode == current)
6301       {
6302          uint read = 0;
6303          int x = editBox.x;
6304          int y = editBox.y;
6305          if(pos > 0)
6306          {
6307             for(;read < pos && line; line = (*&line.next))
6308             {
6309                int numBytes = Min(pos - read, (*&line.count) - x);
6310                if(numBytes > 0)
6311                {
6312                   read += numBytes; x += numBytes;
6313                }
6314
6315                /*for(;read < pos && x < (*&line.count);)
6316                {
6317                   read++; x++;
6318                }
6319                */
6320                if((*&line.next))
6321                {
6322                   if(read < pos)
6323                   {
6324                      read++;
6325                      x = 0;
6326                   }
6327                   else
6328                      break;
6329                }
6330                else
6331                {
6332                   if(read<pos)
6333                      result = false;
6334                   break;
6335                }
6336                y++;
6337             }
6338             this.pos += read;
6339          }
6340          else if(pos < 0)
6341          {
6342             pos = -pos;
6343             for(;read < pos && line; line = (*&line.prev))
6344             {
6345                int numBytes = Min(pos - read, x);
6346                if(numBytes > 0)
6347                {
6348                   read += numBytes;
6349                   x -= numBytes;
6350                }
6351                /*for(;read < pos && x > 0;)
6352                {
6353                   read++; x--;
6354                }
6355                */
6356                if((*&line.prev))
6357                {
6358                   if(read < pos)
6359                   {
6360                      read++;
6361                      x = (*&(*&line.prev).count);
6362                   }
6363                   else
6364                      break;
6365                }
6366                else
6367                {
6368                   if(read<pos)
6369                      result = false;
6370                   break;
6371                }
6372                y--;
6373             }
6374             this.pos -= read;
6375          }
6376
6377          editBox.line = editBox.selLine = line;
6378          editBox.x = editBox.selX = x;
6379          editBox.y = editBox.selY = y;
6380       }
6381       return result;
6382    }
6383
6384    bool Puts(char * string)
6385    {
6386       EditBox editBox = this.editBox;
6387       BufferLocation start { editBox.line, editBox.y, editBox.x };
6388       BufferLocation pos;
6389
6390       numBytes = 0;
6391       editBox.AddS(string);
6392
6393       pos.line = editBox.line;
6394       pos.y = editBox.y;
6395       pos.x = editBox.x;
6396
6397       this.start.AdjustAdd(start, pos);
6398       sel.AdjustAdd(start, pos);
6399
6400       return true;
6401    }
6402
6403    // NOTE: BYTE, NOT UNICODE CHARACTER!
6404    bool Putc(char ch)
6405    {
6406       EditBox editBox = this.editBox;
6407       BufferLocation start = { editBox.line, editBox.y, editBox.x };
6408       BufferLocation pos;
6409
6410       if(numBytes < 4)
6411       {
6412          utf8Bytes[numBytes++] = ch;
6413          utf8Bytes[numBytes] = 0;
6414          if(UTF8Validate(utf8Bytes))
6415          {
6416             editBox.AddCh(UTF8_GET_CHAR(utf8Bytes, numBytes));
6417             numBytes = 0;
6418             pos.line = editBox.line;
6419             pos.y = editBox.y;
6420             pos.x = editBox.x;
6421             this.start.AdjustAdd(start, pos);
6422             sel.AdjustAdd(start, pos);
6423          }
6424          return true;
6425       }
6426       return false;
6427    }
6428
6429    bool Getc(char * ch)
6430    {
6431       return Read(ch, 1, 1) ? true : false;
6432    }
6433
6434    void DeleteBytes(uint count)
6435    {
6436       EditBox editBox = this.editBox;
6437       if(count)
6438       {
6439          BufferLocation pos { editBox.line, editBox.y, editBox.x };
6440          BufferLocation end = pos;
6441
6442          uint c = 0;
6443          for(;;)
6444          {
6445             for(;c < count && end.line && end.x < end.line.count;)
6446             {
6447                end.x++;
6448                c++;
6449             }
6450             if(c < count && end.line && end.line.next)
6451             {
6452                end.line = end.line.next;
6453                end.y++;
6454                c++;
6455                end.x = 0;
6456             }
6457             else
6458                break;
6459          }
6460
6461          start.AdjustDelete(pos, end);
6462          sel.AdjustDelete(pos, end);
6463
6464          editBox.DelCh(pos.line, pos.y, pos.x, end.line, end.y, end.x, true);
6465       }
6466    }
6467 };