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