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