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