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