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